@behindthemusictree/app-kit-ui 33.1.0 → 33.2.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.
@@ -17,11 +17,6 @@ import {
17
17
  GenreTreeViewSkeleton,
18
18
  useGenreNodeRelations
19
19
  } from "@behindthemusictree/genre-tree-view";
20
- import {
21
- useArtistSearch,
22
- useSongSearch,
23
- isSongSearchResultPlayable
24
- } from "@behindthemusictree/app-kit/genre-tree";
25
20
  import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
26
21
  import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
27
22
 
@@ -465,11 +460,148 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
465
460
  }
466
461
 
467
462
  // src/genre-tree/GenreSearch.tsx
468
- import { useState as useState2 } from "react";
469
- import { ArrowLeft, LoaderCircle, Search, X } from "lucide-react";
470
- import { Combobox } from "@behindthemusictree/ui";
463
+ import { ArrowLeft } from "lucide-react";
464
+ import { SEARCH_LEADING_BUTTON_CLASS } from "@behindthemusictree/ui";
471
465
  import { useGenreSearch } from "@behindthemusictree/app-kit/genre-tree";
472
- import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
466
+
467
+ // src/genre-tree/search/SearchBox.tsx
468
+ import { useState as useState2 } from "react";
469
+ import { SearchCombobox } from "@behindthemusictree/ui";
470
+ import { jsx as jsx5 } from "react/jsx-runtime";
471
+ function SearchBox({
472
+ label,
473
+ placeholder,
474
+ value,
475
+ onValueChange,
476
+ groups,
477
+ onPicked,
478
+ onClear,
479
+ leading,
480
+ className: className2,
481
+ onFocus
482
+ }) {
483
+ const [open, setOpen] = useState2(false);
484
+ const loading = groups.some((group) => group.loading);
485
+ const status = groups.map((group) => group.status).filter(Boolean).join(". ");
486
+ const handlePick = (groupKey, itemKey) => {
487
+ setOpen(false);
488
+ groups.find((group) => group.key === groupKey)?.items.find((item) => item.key === itemKey)?.onSelect();
489
+ onPicked?.(groupKey, itemKey);
490
+ };
491
+ return /* @__PURE__ */ jsx5("div", { className: className2, onFocusCapture: onFocus, children: /* @__PURE__ */ jsx5(
492
+ SearchCombobox,
493
+ {
494
+ ariaLabel: label,
495
+ listLabel: `${label} results`,
496
+ value,
497
+ placeholder: placeholder ?? `${label}\u2026`,
498
+ groups: groups.map((group) => ({
499
+ key: group.key,
500
+ label: group.label,
501
+ options: group.items.map((item) => ({ key: item.key, content: item.content }))
502
+ })),
503
+ open,
504
+ onOpenChange: setOpen,
505
+ onValueChange,
506
+ onPick: handlePick,
507
+ status,
508
+ loading,
509
+ onClear,
510
+ leading,
511
+ inputClassName: "focus-visible:ring-0"
512
+ }
513
+ ) });
514
+ }
515
+
516
+ // src/genre-tree/search/SearchGroup.ts
517
+ var SEARCH_FAILED_STATUS = "Search failed";
518
+ var searchStatus = (state, count, noun) => state.isError ? SEARCH_FAILED_STATUS : state.settled && count === 0 ? `No ${noun} found` : "";
519
+
520
+ // src/genre-tree/search/useArtistSearchGroup.tsx
521
+ import { useArtistSearch } from "@behindthemusictree/app-kit/genre-tree";
522
+ import { Fragment, jsxs as jsxs2 } from "react/jsx-runtime";
523
+ function useArtistSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
524
+ const search = useArtistSearch(getBaseUrl, enabled ? text : "");
525
+ if (!enabled) return void 0;
526
+ return {
527
+ key: "artists",
528
+ label: "Artists",
529
+ items: search.artists.map((artist) => ({
530
+ key: artist.uuid,
531
+ content: /* @__PURE__ */ jsxs2(Fragment, { children: [
532
+ /* @__PURE__ */ jsxs2("span", { className: "block truncate font-medium", children: [
533
+ artist.name,
534
+ artist.disambiguation && /* @__PURE__ */ jsxs2("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
535
+ " \xB7 ",
536
+ artist.disambiguation
537
+ ] })
538
+ ] }),
539
+ artist.matchedAlias && /* @__PURE__ */ jsxs2("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
540
+ "also known as ",
541
+ artist.matchedAlias
542
+ ] })
543
+ ] }),
544
+ onSelect: () => onSelect(artist)
545
+ })),
546
+ status: searchStatus(search, search.artists.length, "artists"),
547
+ loading: search.pending
548
+ };
549
+ }
550
+
551
+ // src/genre-tree/search/useSongSearchGroup.tsx
552
+ import {
553
+ isSongSearchResultPlayable,
554
+ useSongSearch
555
+ } from "@behindthemusictree/app-kit/genre-tree";
556
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
557
+ function useSongSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
558
+ const search = useSongSearch(getBaseUrl, enabled ? text : "");
559
+ if (!enabled) return void 0;
560
+ return {
561
+ key: "songs",
562
+ label: "Songs",
563
+ items: search.songs.map((song) => ({
564
+ key: song.uuid,
565
+ content: /* @__PURE__ */ jsxs3("span", { className: isSongSearchResultPlayable(song) ? void 0 : "opacity-60", children: [
566
+ /* @__PURE__ */ jsx6("span", { className: "block truncate font-medium", children: song.title }),
567
+ /* @__PURE__ */ jsx6("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 ") })
568
+ ] }),
569
+ onSelect: () => onSelect(song)
570
+ })),
571
+ status: searchStatus(search, search.songs.length, "songs"),
572
+ loading: search.pending
573
+ };
574
+ }
575
+
576
+ // src/genre-tree/search/useGenreRemoteSearchGroup.tsx
577
+ import {
578
+ useGenreRemoteSearch
579
+ } from "@behindthemusictree/app-kit/genre-tree";
580
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
581
+ function useGenreRemoteSearchGroup(getBaseUrl, text, onSelect, { key = "genres", label = "Genres", filter, describe, ...options } = {}) {
582
+ const search = useGenreRemoteSearch(getBaseUrl, text, options);
583
+ const genres = filter ? search.genres.filter(filter) : search.genres;
584
+ return {
585
+ key,
586
+ label,
587
+ items: genres.map((genre) => ({
588
+ key: genre.uuid,
589
+ content: /* @__PURE__ */ jsxs4(Fragment2, { children: [
590
+ /* @__PURE__ */ jsxs4("span", { className: "block truncate font-medium", children: [
591
+ genre.name,
592
+ genre.isUmbrella && /* @__PURE__ */ jsx7("span", { className: "ml-2 text-xs font-normal italic text-[var(--color-text-muted)]", children: "umbrella" })
593
+ ] }),
594
+ /* @__PURE__ */ jsx7("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: describe?.(genre) ?? `${genre.treeName} \xB7 ${genre.root.name}` })
595
+ ] }),
596
+ onSelect: () => onSelect(genre)
597
+ })),
598
+ status: searchStatus(search, genres.length, "genres"),
599
+ loading: search.pending
600
+ };
601
+ }
602
+
603
+ // src/genre-tree/GenreSearch.tsx
604
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
473
605
  function GenreSearch({
474
606
  genrePlaylists,
475
607
  onSelect,
@@ -478,7 +610,8 @@ function GenreSearch({
478
610
  onPrevious,
479
611
  placeholder,
480
612
  onQueryChange,
481
- extraGroups = []
613
+ extraGroups = [],
614
+ onFocus
482
615
  }) {
483
616
  const { query, setQuery, results, canClear, clear, select } = useGenreSearch({
484
617
  genrePlaylists,
@@ -486,112 +619,49 @@ function GenreSearch({
486
619
  selectedName,
487
620
  onClear
488
621
  });
489
- const [open, setOpen] = useState2(false);
490
622
  const active = query !== "" && query !== selectedName;
491
623
  const hasExtras = extraGroups.length > 0;
492
624
  const label = hasExtras ? "Search" : "Search a genre";
493
- const loading = active && extraGroups.some((group) => group.loading);
494
- const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
495
625
  const groups = [
496
626
  {
497
627
  key: "genres",
498
628
  label: hasExtras ? "Genres" : void 0,
499
- options: results.map((genrePlaylist) => ({
629
+ items: results.map((genrePlaylist) => ({
500
630
  key: genrePlaylist.uuid,
501
- content: /* @__PURE__ */ jsxs2(Fragment, { children: [
631
+ content: /* @__PURE__ */ jsxs5(Fragment3, { children: [
502
632
  genrePlaylist.name,
503
- genrePlaylist.isUmbrella && /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
504
- ] })
633
+ genrePlaylist.isUmbrella && /* @__PURE__ */ jsx8("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
634
+ ] }),
635
+ onSelect: () => select(genrePlaylist)
505
636
  }))
506
637
  },
507
- ...extraGroups.map((group) => ({
508
- key: group.key,
509
- label: group.label,
510
- options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
511
- }))
638
+ ...extraGroups.map(
639
+ (group) => active ? group : { ...group, items: [], status: "", loading: false }
640
+ )
512
641
  ];
513
- const handlePick = (groupKey, optionKey) => {
514
- setOpen(false);
515
- if (groupKey === "genres") {
516
- const genrePlaylist = results.find((result) => result.uuid === optionKey);
517
- if (!genrePlaylist) return;
518
- select(genrePlaylist);
519
- onQueryChange?.("");
520
- return;
642
+ return /* @__PURE__ */ jsx8("div", { className: "genre-search w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: /* @__PURE__ */ jsx8(
643
+ SearchBox,
644
+ {
645
+ label,
646
+ placeholder,
647
+ value: query,
648
+ onValueChange: (value) => {
649
+ setQuery(value);
650
+ onQueryChange?.(value);
651
+ },
652
+ groups,
653
+ onPicked: (groupKey) => {
654
+ if (groupKey !== "genres") setQuery("");
655
+ onQueryChange?.("");
656
+ },
657
+ onClear: canClear ? () => {
658
+ clear();
659
+ onQueryChange?.("");
660
+ } : void 0,
661
+ leading: onPrevious && /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Previous", title: "Previous", className: SEARCH_LEADING_BUTTON_CLASS, onClick: onPrevious, children: /* @__PURE__ */ jsx8(ArrowLeft, { "aria-hidden": "true", className: "h-4 w-4" }) }),
662
+ onFocus
521
663
  }
522
- const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
523
- if (!item) return;
524
- setQuery("");
525
- onQueryChange?.("");
526
- item.onSelect();
527
- };
528
- return /* @__PURE__ */ jsxs2("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
529
- onPrevious && /* @__PURE__ */ jsx5(
530
- "button",
531
- {
532
- type: "button",
533
- "aria-label": "Previous",
534
- title: "Previous",
535
- 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)]",
536
- onClick: onPrevious,
537
- children: /* @__PURE__ */ jsx5(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
538
- }
539
- ),
540
- /* @__PURE__ */ jsx5(
541
- Combobox,
542
- {
543
- ariaLabel: label,
544
- listLabel: `${label} results`,
545
- value: query,
546
- placeholder: placeholder ?? `${label}\u2026`,
547
- groups,
548
- open,
549
- onOpenChange: setOpen,
550
- onValueChange: (value) => {
551
- setQuery(value);
552
- onQueryChange?.(value);
553
- },
554
- onPick: handlePick,
555
- status,
556
- loading: loading ? "Searching\u2026" : void 0,
557
- inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "pl-6"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md focus-visible:ring-0`,
558
- className: "genre-search-results z-40 rounded-xl shadow-md",
559
- listClassName: "max-h-[min(20rem,50dvh)]",
560
- optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
561
- }
562
- ),
563
- /* @__PURE__ */ jsxs2("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
564
- /* @__PURE__ */ jsxs2("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
565
- /* @__PURE__ */ jsx5(
566
- Search,
567
- {
568
- "aria-hidden": "true",
569
- className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
570
- }
571
- ),
572
- /* @__PURE__ */ jsx5(
573
- LoaderCircle,
574
- {
575
- "aria-hidden": "true",
576
- className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
577
- }
578
- )
579
- ] }),
580
- canClear && /* @__PURE__ */ jsx5(
581
- "button",
582
- {
583
- type: "button",
584
- "aria-label": "Clear search",
585
- className: "flex items-center justify-center p-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
586
- onClick: () => {
587
- clear();
588
- onQueryChange?.("");
589
- },
590
- children: /* @__PURE__ */ jsx5(X, { "aria-hidden": "true", className: "w-6 h-6" })
591
- }
592
- )
593
- ] })
594
- ] });
664
+ ) });
595
665
  }
596
666
 
597
667
  // src/genre-tree/GenreInfoPanelBody.tsx
@@ -611,12 +681,12 @@ import { FaWhatsapp } from "react-icons/fa";
611
681
  import { SiGmail } from "react-icons/si";
612
682
 
613
683
  // src/genre-tree/ShareDestinationButton.tsx
614
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
684
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
615
685
  var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
616
686
  function Contents({ icon, label }) {
617
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
618
- /* @__PURE__ */ jsx6("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
619
- /* @__PURE__ */ jsx6("span", { className: "max-w-full truncate", children: label })
687
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
688
+ /* @__PURE__ */ jsx9("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
689
+ /* @__PURE__ */ jsx9("span", { className: "max-w-full truncate", children: label })
620
690
  ] });
621
691
  }
622
692
  function ShareDestinationButton({
@@ -628,29 +698,29 @@ function ShareDestinationButton({
628
698
  ariaLabel
629
699
  }) {
630
700
  if (href) {
631
- return /* @__PURE__ */ jsx6("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx6(Contents, { icon, label }) });
701
+ return /* @__PURE__ */ jsx9("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx9(Contents, { icon, label }) });
632
702
  }
633
- return /* @__PURE__ */ jsx6("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx6(Contents, { icon, label }) });
703
+ return /* @__PURE__ */ jsx9("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx9(Contents, { icon, label }) });
634
704
  }
635
705
 
636
706
  // src/genre-tree/SharePreviewCard.tsx
637
707
  import { Music, Music2, UserRound } from "lucide-react";
638
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
708
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
639
709
  function SharePreviewCard({ target, url }) {
640
710
  const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];
641
711
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
642
- return /* @__PURE__ */ jsx7("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs4("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
643
- /* @__PURE__ */ jsx7("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx7(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
644
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
645
- /* @__PURE__ */ jsx7("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
646
- /* @__PURE__ */ jsx7("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
647
- /* @__PURE__ */ jsx7("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
712
+ return /* @__PURE__ */ jsx10("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs7("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
713
+ /* @__PURE__ */ jsx10("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx10(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
714
+ /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
715
+ /* @__PURE__ */ jsx10("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
716
+ /* @__PURE__ */ jsx10("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
717
+ /* @__PURE__ */ jsx10("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
648
718
  ] })
649
719
  ] }) });
650
720
  }
651
721
 
652
722
  // src/genre-tree/SharePopup.tsx
653
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
723
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
654
724
  function resolveAbsoluteUrl(url) {
655
725
  try {
656
726
  const parsed = new URL(url, window.location.href);
@@ -725,8 +795,8 @@ function SharePopup({ target, url, onClose }) {
725
795
  setShareError("Could not open the share options.");
726
796
  }
727
797
  };
728
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
729
- /* @__PURE__ */ jsx8(
798
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
799
+ /* @__PURE__ */ jsx11(
730
800
  BasePopup_default,
731
801
  {
732
802
  title: "Share",
@@ -741,67 +811,67 @@ function SharePopup({ target, url, onClose }) {
741
811
  titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
742
812
  closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
743
813
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
744
- children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4", children: [
745
- /* @__PURE__ */ jsx8(SharePreviewCard, { target, url: absoluteUrl || url }),
746
- /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
747
- /* @__PURE__ */ jsx8(
814
+ children: /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-4", children: [
815
+ /* @__PURE__ */ jsx11(SharePreviewCard, { target, url: absoluteUrl || url }),
816
+ /* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
817
+ /* @__PURE__ */ jsx11(
748
818
  ShareDestinationButton,
749
819
  {
750
820
  label: "Copy Link",
751
- icon: /* @__PURE__ */ jsx8(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
821
+ icon: /* @__PURE__ */ jsx11(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
752
822
  onClick: () => void handleCopy(),
753
823
  ariaLabel: "Copy link",
754
824
  disabled: !absoluteUrl
755
825
  }
756
826
  ),
757
- /* @__PURE__ */ jsx8(
827
+ /* @__PURE__ */ jsx11(
758
828
  ShareDestinationButton,
759
829
  {
760
830
  label: "WhatsApp",
761
- icon: /* @__PURE__ */ jsx8(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
831
+ icon: /* @__PURE__ */ jsx11(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
762
832
  href: destinationUrls?.whatsapp,
763
833
  ariaLabel: `Share ${target.name} via WhatsApp`,
764
834
  disabled: !absoluteUrl
765
835
  }
766
836
  ),
767
- /* @__PURE__ */ jsx8(
837
+ /* @__PURE__ */ jsx11(
768
838
  ShareDestinationButton,
769
839
  {
770
840
  label: "Gmail",
771
- icon: /* @__PURE__ */ jsx8(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
841
+ icon: /* @__PURE__ */ jsx11(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
772
842
  href: destinationUrls?.gmail,
773
843
  ariaLabel: `Email ${target.name} with Gmail`,
774
844
  disabled: !absoluteUrl
775
845
  }
776
846
  ),
777
- canUseNativeShare && /* @__PURE__ */ jsx8(
847
+ canUseNativeShare && /* @__PURE__ */ jsx11(
778
848
  ShareDestinationButton,
779
849
  {
780
850
  label: "More",
781
- icon: /* @__PURE__ */ jsx8(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
851
+ icon: /* @__PURE__ */ jsx11(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
782
852
  onClick: () => void handleNativeShare(),
783
853
  ariaLabel: "More sharing options",
784
854
  disabled: !absoluteUrl
785
855
  }
786
856
  )
787
857
  ] }),
788
- copyStatus === "failed" && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
789
- shareError && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
858
+ copyStatus === "failed" && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
859
+ shareError && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
790
860
  ] })
791
861
  }
792
862
  ),
793
- toastId > 0 && /* @__PURE__ */ jsx8(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
863
+ toastId > 0 && /* @__PURE__ */ jsx11(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
794
864
  ] });
795
865
  }
796
866
 
797
867
  // src/genre-tree/ShareActionButton.tsx
798
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
868
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
799
869
  function ShareActionButton({ target, url }) {
800
870
  const { showPopup, hidePopup } = usePopup3();
801
871
  const openSharePopup = () => {
802
- showPopup(/* @__PURE__ */ jsx9(SharePopup, { target, url, onClose: hidePopup }));
872
+ showPopup(/* @__PURE__ */ jsx12(SharePopup, { target, url, onClose: hidePopup }));
803
873
  };
804
- return /* @__PURE__ */ jsxs6(
874
+ return /* @__PURE__ */ jsxs9(
805
875
  "button",
806
876
  {
807
877
  type: "button",
@@ -810,7 +880,7 @@ function ShareActionButton({ target, url }) {
810
880
  "aria-label": `Share ${target.name}`,
811
881
  title: `Share ${target.name}`,
812
882
  children: [
813
- /* @__PURE__ */ jsx9("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: /* @__PURE__ */ jsx9(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
883
+ /* @__PURE__ */ jsx12("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: /* @__PURE__ */ jsx12(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
814
884
  "Share"
815
885
  ]
816
886
  }
@@ -818,7 +888,7 @@ function ShareActionButton({ target, url }) {
818
888
  }
819
889
 
820
890
  // src/genre-tree/GenreActionBand.tsx
821
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
891
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
822
892
  function GenreActionBand({
823
893
  node,
824
894
  onPlayPause,
@@ -840,7 +910,7 @@ function GenreActionBand({
840
910
  items.push({
841
911
  key: "play-pause",
842
912
  label: isPlaying ? "Pause" : "Play",
843
- icon: isPlaying ? /* @__PURE__ */ jsx10(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx10(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
913
+ icon: isPlaying ? /* @__PURE__ */ jsx13(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx13(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
844
914
  onClick: onPlayPause
845
915
  });
846
916
  }
@@ -848,24 +918,24 @@ function GenreActionBand({
848
918
  items.push({
849
919
  key: "zoom-in",
850
920
  label: "Focus",
851
- icon: /* @__PURE__ */ jsx10(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
921
+ icon: /* @__PURE__ */ jsx13(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
852
922
  onClick: onZoomIn
853
923
  });
854
924
  }
855
925
  if (onAddChild) {
856
- adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx10(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
926
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx13(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
857
927
  }
858
928
  if (onRename) {
859
- adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx10(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
929
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx13(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
860
930
  }
861
931
  if (onChangeParent) {
862
- adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx10(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
932
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx13(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
863
933
  }
864
934
  if (onToggleSide) {
865
935
  adminItems.push({
866
936
  key: "toggle-side",
867
937
  label: side === "pop" ? "Make core" : "Make pop",
868
- icon: /* @__PURE__ */ jsx10(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
938
+ icon: /* @__PURE__ */ jsx13(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
869
939
  onClick: onToggleSide,
870
940
  disabled: !!toggleSideDisabledReason,
871
941
  title: toggleSideDisabledReason
@@ -881,7 +951,7 @@ function GenreActionBand({
881
951
  });
882
952
  }
883
953
  if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
884
- const renderItems = (list) => list.map((item) => /* @__PURE__ */ jsxs7(
954
+ const renderItems = (list) => list.map((item) => /* @__PURE__ */ jsxs10(
885
955
  "button",
886
956
  {
887
957
  type: "button",
@@ -890,25 +960,25 @@ function GenreActionBand({
890
960
  onClick: item.onClick,
891
961
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
892
962
  children: [
893
- /* @__PURE__ */ jsx10("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
963
+ /* @__PURE__ */ jsx13("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
894
964
  item.label
895
965
  ]
896
966
  },
897
967
  item.key
898
968
  ));
899
- return /* @__PURE__ */ jsxs7("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
900
- (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
969
+ return /* @__PURE__ */ jsxs10("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
970
+ (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
901
971
  renderItems(items),
902
- shareTarget && shareUrl && /* @__PURE__ */ jsx10(ShareActionButton, { target: shareTarget, url: shareUrl })
972
+ shareTarget && shareUrl && /* @__PURE__ */ jsx13(ShareActionButton, { target: shareTarget, url: shareUrl })
903
973
  ] }),
904
- adminItems.length > 0 && /* @__PURE__ */ jsxs7(
974
+ adminItems.length > 0 && /* @__PURE__ */ jsxs10(
905
975
  "section",
906
976
  {
907
977
  "aria-label": "Admin actions",
908
978
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
909
979
  children: [
910
- /* @__PURE__ */ jsx10("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
911
- /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
980
+ /* @__PURE__ */ jsx13("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
981
+ /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
912
982
  ]
913
983
  }
914
984
  )
@@ -921,7 +991,7 @@ import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
921
991
 
922
992
  // src/genre-tree/ClampedText.tsx
923
993
  import { useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
924
- import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
994
+ import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
925
995
  function ClampedText({ children, className: className2 = "" }) {
926
996
  const ref = useRef2(null);
927
997
  const [expanded, setExpanded] = useState4(false);
@@ -931,9 +1001,9 @@ function ClampedText({ children, className: className2 = "" }) {
931
1001
  if (!element || expanded) return;
932
1002
  setOverflows(element.scrollHeight > element.clientHeight);
933
1003
  }, [children, expanded]);
934
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
935
- /* @__PURE__ */ jsx11("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
936
- (overflows || expanded) && /* @__PURE__ */ jsx11(
1004
+ return /* @__PURE__ */ jsxs11(Fragment6, { children: [
1005
+ /* @__PURE__ */ jsx14("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1006
+ (overflows || expanded) && /* @__PURE__ */ jsx14(
937
1007
  "button",
938
1008
  {
939
1009
  type: "button",
@@ -967,15 +1037,15 @@ import { useTrackList as useTrackList2 } from "@behindthemusictree/app-kit/genre
967
1037
 
968
1038
  // src/genre-tree/MusicBrainzRecordingLink.tsx
969
1039
  import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
970
- import { Fragment as Fragment5, jsx as jsx12 } from "react/jsx-runtime";
1040
+ import { Fragment as Fragment7, jsx as jsx15 } from "react/jsx-runtime";
971
1041
  function MusicBrainzRecordingLink({
972
1042
  song,
973
1043
  children,
974
1044
  className: className2 = ""
975
1045
  }) {
976
1046
  const url = musicBrainzRecordingUrl(song);
977
- if (!url) return /* @__PURE__ */ jsx12(Fragment5, { children });
978
- return /* @__PURE__ */ jsx12(
1047
+ if (!url) return /* @__PURE__ */ jsx15(Fragment7, { children });
1048
+ return /* @__PURE__ */ jsx15(
979
1049
  "a",
980
1050
  {
981
1051
  href: url,
@@ -992,7 +1062,7 @@ function MusicBrainzRecordingLink({
992
1062
  // src/genre-tree/GenrePlaylistTracks.tsx
993
1063
  import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
994
1064
  import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
995
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1065
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
996
1066
  var CATALOGUE_PAGE_SIZE = 100;
997
1067
  var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
998
1068
  overallTotal: z.number().nullable(),
@@ -1065,35 +1135,35 @@ function GenrePlaylistTracks({
1065
1135
  const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1066
1136
  started.catch((error) => {
1067
1137
  console.error("Failed to play genre playlist:", error);
1068
- showPopup(/* @__PURE__ */ jsx13(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
1138
+ showPopup(/* @__PURE__ */ jsx16(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
1069
1139
  });
1070
1140
  };
1071
- return /* @__PURE__ */ jsx13(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs9("div", { children: [
1141
+ return /* @__PURE__ */ jsx16(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs12("div", { children: [
1072
1142
  renderTracksToolbar?.(),
1073
- isPending ? /* @__PURE__ */ jsx13(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs9("p", { role: "alert", children: [
1143
+ isPending ? /* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
1074
1144
  "Couldn't load ",
1075
1145
  trackNoun.plural,
1076
1146
  ".",
1077
1147
  " ",
1078
- /* @__PURE__ */ jsx13("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1079
- ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx13("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs9("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1148
+ /* @__PURE__ */ jsx16("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1149
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx16("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1080
1150
  tracks.map((track, index) => {
1081
1151
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1082
1152
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1083
1153
  const isPlaying = isCurrent && playState === PlayStates.PLAYING;
1084
- const text = /* @__PURE__ */ jsxs9("span", { className: TEXT_CELL, children: [
1085
- /* @__PURE__ */ jsx13("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx13(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1086
- track.artists?.length ? /* @__PURE__ */ jsx13("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1087
- /* @__PURE__ */ jsx13("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx13(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1088
- track.album && /* @__PURE__ */ jsx13("span", { className: ALBUM_CELL, children: track.album.name })
1154
+ const text = /* @__PURE__ */ jsxs12("span", { className: TEXT_CELL, children: [
1155
+ /* @__PURE__ */ jsx16("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx16(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1156
+ track.artists?.length ? /* @__PURE__ */ jsx16("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1157
+ /* @__PURE__ */ jsx16("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx16(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1158
+ track.album && /* @__PURE__ */ jsx16("span", { className: ALBUM_CELL, children: track.album.name })
1089
1159
  ] });
1090
1160
  const leading = renderSongLeading?.(track, index);
1091
1161
  const actions = renderSongActions?.(track);
1092
1162
  const isUnplayable = isSong(track) && songUnplayableReason(track) !== null;
1093
1163
  if (isUnplayable) {
1094
- return /* @__PURE__ */ jsxs9("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1164
+ return /* @__PURE__ */ jsxs12("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1095
1165
  leading,
1096
- /* @__PURE__ */ jsx13(
1166
+ /* @__PURE__ */ jsx16(
1097
1167
  "button",
1098
1168
  {
1099
1169
  type: "button",
@@ -1101,27 +1171,27 @@ function GenrePlaylistTracks({
1101
1171
  "aria-disabled": "true",
1102
1172
  "aria-label": label,
1103
1173
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1104
- children: /* @__PURE__ */ jsx13(FaPlay, { size: 10 })
1174
+ children: /* @__PURE__ */ jsx16(FaPlay, { size: 10 })
1105
1175
  }
1106
1176
  ),
1107
1177
  text,
1108
1178
  actions
1109
1179
  ] }, track.uuid);
1110
1180
  }
1111
- return /* @__PURE__ */ jsxs9(
1181
+ return /* @__PURE__ */ jsxs12(
1112
1182
  "li",
1113
1183
  {
1114
1184
  className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
1115
1185
  children: [
1116
1186
  leading,
1117
- /* @__PURE__ */ jsx13(
1187
+ /* @__PURE__ */ jsx16(
1118
1188
  "button",
1119
1189
  {
1120
1190
  type: "button",
1121
1191
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1122
1192
  className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
1123
1193
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1124
- children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx13(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx13(FaPause, { size: 10 }) : /* @__PURE__ */ jsx13(FaPlay, { size: 10 })
1194
+ children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx16(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx16(FaPause, { size: 10 }) : /* @__PURE__ */ jsx16(FaPlay, { size: 10 })
1125
1195
  }
1126
1196
  ),
1127
1197
  text,
@@ -1131,7 +1201,7 @@ function GenrePlaylistTracks({
1131
1201
  track.uuid
1132
1202
  );
1133
1203
  }),
1134
- hasNextPage && /* @__PURE__ */ jsx13("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1204
+ hasNextPage && /* @__PURE__ */ jsx16("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1135
1205
  ] })
1136
1206
  ] }) });
1137
1207
  }
@@ -1139,7 +1209,7 @@ function GenrePlaylistTracks({
1139
1209
  // src/genre-tree/GenreDetailExtras.tsx
1140
1210
  import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
1141
1211
  import { SiWikidata, SiWikipedia } from "react-icons/si";
1142
- import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1212
+ import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1143
1213
  function GenreDetailExtras({
1144
1214
  genreUuid,
1145
1215
  genrePlaylist,
@@ -1163,17 +1233,17 @@ function GenreDetailExtras({
1163
1233
  criteriaOverviewSchema
1164
1234
  );
1165
1235
  if (isPending) {
1166
- return /* @__PURE__ */ jsxs10(Fragment6, { children: [
1167
- /* @__PURE__ */ jsx14(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx14("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx14(Skeleton2, { className: "h-4 w-3/4" }) }) }),
1236
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
1237
+ /* @__PURE__ */ jsx17(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx17("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx17(Skeleton2, { className: "h-4 w-3/4" }) }) }),
1168
1238
  family
1169
1239
  ] });
1170
1240
  }
1171
- if (!overview) return /* @__PURE__ */ jsx14(Fragment6, { children: family });
1241
+ if (!overview) return /* @__PURE__ */ jsx17(Fragment8, { children: family });
1172
1242
  const { summary, summarySourceUrl, essentialSongs } = overview;
1173
1243
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1174
- return /* @__PURE__ */ jsxs10(Fragment6, { children: [
1175
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx14(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1176
- summarySourceUrl && /* @__PURE__ */ jsxs10(
1244
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
1245
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx17(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1246
+ summarySourceUrl && /* @__PURE__ */ jsxs13(
1177
1247
  "a",
1178
1248
  {
1179
1249
  href: summarySourceUrl,
@@ -1181,12 +1251,12 @@ function GenreDetailExtras({
1181
1251
  rel: "noopener noreferrer",
1182
1252
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1183
1253
  children: [
1184
- /* @__PURE__ */ jsx14(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1254
+ /* @__PURE__ */ jsx17(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1185
1255
  "Wikipedia"
1186
1256
  ]
1187
1257
  }
1188
1258
  ),
1189
- wikidataUrl && /* @__PURE__ */ jsxs10(
1259
+ wikidataUrl && /* @__PURE__ */ jsxs13(
1190
1260
  "a",
1191
1261
  {
1192
1262
  href: wikidataUrl,
@@ -1194,17 +1264,17 @@ function GenreDetailExtras({
1194
1264
  rel: "noopener noreferrer",
1195
1265
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1196
1266
  children: [
1197
- /* @__PURE__ */ jsx14(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1267
+ /* @__PURE__ */ jsx17(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1198
1268
  "Wikidata"
1199
1269
  ]
1200
1270
  }
1201
1271
  )
1202
1272
  ] }) }),
1203
- /* @__PURE__ */ jsx14(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx14(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1273
+ /* @__PURE__ */ jsx17(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx17(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1204
1274
  family,
1205
1275
  renderGenreDetailExtras?.(overview),
1206
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx14(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx14("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx14("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx14("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1207
- /* @__PURE__ */ jsx14(
1276
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx17(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx17("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx17("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx17("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1277
+ /* @__PURE__ */ jsx17(
1208
1278
  GenrePlaylistTracks,
1209
1279
  {
1210
1280
  genreUuid,
@@ -1220,7 +1290,7 @@ function GenreDetailExtras({
1220
1290
  }
1221
1291
 
1222
1292
  // src/genre-tree/GenreInfoPanelBody.tsx
1223
- import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1293
+ import { Fragment as Fragment9, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1224
1294
  import { createElement } from "react";
1225
1295
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1226
1296
  const { isPlaying, setIsPlaying } = usePlayer5();
@@ -1234,7 +1304,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1234
1304
  console.error("Failed to load genre playlist tracks:", error);
1235
1305
  });
1236
1306
  };
1237
- return /* @__PURE__ */ jsx15(
1307
+ return /* @__PURE__ */ jsx18(
1238
1308
  GenreActionBand,
1239
1309
  {
1240
1310
  ...bandProps,
@@ -1255,9 +1325,9 @@ function GenreInfoPanelBody({
1255
1325
  band,
1256
1326
  extras
1257
1327
  }) {
1258
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
1328
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1259
1329
  toolbar,
1260
- /* @__PURE__ */ jsx15(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1330
+ /* @__PURE__ */ jsx18(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1261
1331
  /* @__PURE__ */ createElement(
1262
1332
  GenreDetailExtras,
1263
1333
  {
@@ -1280,7 +1350,7 @@ import {
1280
1350
  getReparentCandidates,
1281
1351
  useUpdateGenre
1282
1352
  } from "@behindthemusictree/app-kit/genre-tree";
1283
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1353
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1284
1354
  function ChangeParentPopup({
1285
1355
  genrePlaylist,
1286
1356
  genrePlaylists,
@@ -1315,12 +1385,12 @@ function ChangeParentPopup({
1315
1385
  window.addEventListener("keydown", handleKeyDown);
1316
1386
  return () => window.removeEventListener("keydown", handleKeyDown);
1317
1387
  }, [onClose]);
1318
- return /* @__PURE__ */ jsxs12(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1319
- /* @__PURE__ */ jsxs12("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1388
+ return /* @__PURE__ */ jsxs15(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1389
+ /* @__PURE__ */ jsxs15("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1320
1390
  "Current parent: ",
1321
1391
  genrePlaylist.parent?.name ?? "none"
1322
1392
  ] }),
1323
- /* @__PURE__ */ jsx16("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx16(
1393
+ /* @__PURE__ */ jsx19("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx19(
1324
1394
  GenreSearch,
1325
1395
  {
1326
1396
  genrePlaylists: candidates,
@@ -1328,7 +1398,7 @@ function ChangeParentPopup({
1328
1398
  placeholder: "Search a new parent\u2026"
1329
1399
  }
1330
1400
  ) }),
1331
- formErrors.length > 0 && /* @__PURE__ */ jsx16("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1401
+ formErrors.length > 0 && /* @__PURE__ */ jsx19("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1332
1402
  ] });
1333
1403
  }
1334
1404
 
@@ -1352,7 +1422,7 @@ import {
1352
1422
  SiYoutube
1353
1423
  } from "react-icons/si";
1354
1424
  import { Chip } from "@behindthemusictree/ui";
1355
- import { jsx as jsx17 } from "react/jsx-runtime";
1425
+ import { jsx as jsx20 } from "react/jsx-runtime";
1356
1426
  var LINK_KINDS = {
1357
1427
  official: { label: "Official website", Icon: Globe },
1358
1428
  wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
@@ -1389,7 +1459,7 @@ function artistLinks(artist) {
1389
1459
  return links;
1390
1460
  }
1391
1461
  function ArtistLinks({ links }) {
1392
- return /* @__PURE__ */ jsx17("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx17("li", { children: /* @__PURE__ */ jsx17(Chip, { href: url, icon: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1462
+ return /* @__PURE__ */ jsx20("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx20("li", { children: /* @__PURE__ */ jsx20(Chip, { href: url, icon: /* @__PURE__ */ jsx20(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1393
1463
  }
1394
1464
 
1395
1465
  // src/genre-tree/artist/formatYears.ts
@@ -1400,7 +1470,7 @@ function formatYears(beginYear, endYear, ended) {
1400
1470
  }
1401
1471
 
1402
1472
  // src/genre-tree/artist/ArtistHeader.tsx
1403
- import { jsx as jsx18 } from "react/jsx-runtime";
1473
+ import { jsx as jsx21 } from "react/jsx-runtime";
1404
1474
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1405
1475
  function ArtistHeader({ artist }) {
1406
1476
  const fields = [];
@@ -1410,35 +1480,35 @@ function ArtistHeader({ artist }) {
1410
1480
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1411
1481
  if (years) fields.push({ label: "Active", value: years });
1412
1482
  const links = artistLinks(artist);
1413
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx18(ArtistLinks, { links }) });
1483
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx21(ArtistLinks, { links }) });
1414
1484
  if (fields.length === 0) return null;
1415
- return /* @__PURE__ */ jsx18(FieldList, { items: fields });
1485
+ return /* @__PURE__ */ jsx21(FieldList, { items: fields });
1416
1486
  }
1417
1487
 
1418
1488
  // src/genre-tree/artist/ArtistPhoto.tsx
1419
- import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1489
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1420
1490
  function ExternalLink({ href, children }) {
1421
- return /* @__PURE__ */ jsx19("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1491
+ return /* @__PURE__ */ jsx22("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1422
1492
  }
1423
1493
  function ArtistPhoto({ image, name }) {
1424
- return /* @__PURE__ */ jsxs13("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1425
- /* @__PURE__ */ jsx19("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1426
- /* @__PURE__ */ jsxs13("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1494
+ return /* @__PURE__ */ jsxs16("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1495
+ /* @__PURE__ */ jsx22("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1496
+ /* @__PURE__ */ jsxs16("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1427
1497
  "Photo: ",
1428
- /* @__PURE__ */ jsx19(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1498
+ /* @__PURE__ */ jsx22(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1429
1499
  ",",
1430
1500
  " ",
1431
- image.licenseUrl ? /* @__PURE__ */ jsx19(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1501
+ image.licenseUrl ? /* @__PURE__ */ jsx22(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1432
1502
  ] })
1433
1503
  ] });
1434
1504
  }
1435
1505
 
1436
1506
  // src/genre-tree/artist/ArtistMembers.tsx
1437
1507
  import { PanelGroup, PanelSection as PanelSection3 } from "@behindthemusictree/ui";
1438
- import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
1508
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1439
1509
  function MembershipList({ title, memberships, renderArtistLink }) {
1440
1510
  if (memberships.length === 0) return null;
1441
- return /* @__PURE__ */ jsx20(PanelGroup, { title, children: /* @__PURE__ */ jsx20("ul", { children: memberships.map((membership, index) => {
1511
+ return /* @__PURE__ */ jsx23(PanelGroup, { title, children: /* @__PURE__ */ jsx23("ul", { children: memberships.map((membership, index) => {
1442
1512
  const details = [
1443
1513
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1444
1514
  ...membership.roles
@@ -1446,9 +1516,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1446
1516
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1447
1517
  return (
1448
1518
  // A member can have several periods in the same group.
1449
- /* @__PURE__ */ jsxs14("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1450
- /* @__PURE__ */ jsx20("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1451
- detailsText && /* @__PURE__ */ jsxs14("span", { className: "text-[var(--color-text-muted)]", children: [
1519
+ /* @__PURE__ */ jsxs17("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1520
+ /* @__PURE__ */ jsx23("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1521
+ detailsText && /* @__PURE__ */ jsxs17("span", { className: "text-[var(--color-text-muted)]", children: [
1452
1522
  " \u2014 ",
1453
1523
  detailsText
1454
1524
  ] })
@@ -1459,9 +1529,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1459
1529
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1460
1530
  if (memberships.length === 0) return null;
1461
1531
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1462
- return /* @__PURE__ */ jsxs14(PanelSection3, { title, children: [
1463
- /* @__PURE__ */ jsx20(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1464
- /* @__PURE__ */ jsx20(
1532
+ return /* @__PURE__ */ jsxs17(PanelSection3, { title, children: [
1533
+ /* @__PURE__ */ jsx23(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1534
+ /* @__PURE__ */ jsx23(
1465
1535
  MembershipList,
1466
1536
  {
1467
1537
  title: "Past",
@@ -1474,7 +1544,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1474
1544
 
1475
1545
  // src/genre-tree/artist/ArtistDiscography.tsx
1476
1546
  import { PanelGroup as PanelGroup2, PanelSection as PanelSection4 } from "@behindthemusictree/ui";
1477
- import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1547
+ import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
1478
1548
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1479
1549
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1480
1550
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1494,12 +1564,12 @@ function byYear(a, b) {
1494
1564
  function ArtistDiscography({ albums }) {
1495
1565
  if (albums.length === 0) return null;
1496
1566
  const sorted = [...albums].sort(byYear);
1497
- return /* @__PURE__ */ jsx21(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1567
+ return /* @__PURE__ */ jsx24(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1498
1568
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1499
1569
  if (groupAlbums.length === 0) return null;
1500
- return /* @__PURE__ */ jsx21(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx21("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs15("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1501
- /* @__PURE__ */ jsx21("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1502
- /* @__PURE__ */ jsx21("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1570
+ return /* @__PURE__ */ jsx24(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx24("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs18("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1571
+ /* @__PURE__ */ jsx24("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1572
+ /* @__PURE__ */ jsx24("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1503
1573
  ] }, album.uuid)) }) }, group);
1504
1574
  }) });
1505
1575
  }
@@ -1526,7 +1596,7 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
1526
1596
  import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
1527
1597
  import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
1528
1598
  import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
1529
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1599
+ import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
1530
1600
  function TrackPositionPlayPause({
1531
1601
  position,
1532
1602
  uuid,
@@ -1540,14 +1610,14 @@ function TrackPositionPlayPause({
1540
1610
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PLAYING;
1541
1611
  const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PAUSED;
1542
1612
  const isCurrentTrackActive = isPlaying || isPaused;
1543
- return /* @__PURE__ */ jsxs16(
1613
+ return /* @__PURE__ */ jsxs19(
1544
1614
  "div",
1545
1615
  {
1546
1616
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1547
1617
  style: { minWidth: "4rem", maxWidth: "4rem" },
1548
1618
  onClick: handlePlayPauseClick,
1549
1619
  children: [
1550
- /* @__PURE__ */ jsx22("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs16(
1620
+ /* @__PURE__ */ jsx25("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs19(
1551
1621
  "span",
1552
1622
  {
1553
1623
  className: "track-position-play-pause__bars",
@@ -1555,18 +1625,18 @@ function TrackPositionPlayPause({
1555
1625
  role: "img",
1556
1626
  "aria-label": isPlaying ? "Playing" : "Paused",
1557
1627
  children: [
1558
- /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" }),
1559
- /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" }),
1560
- /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" })
1628
+ /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__bar" }),
1629
+ /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__bar" }),
1630
+ /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__bar" })
1561
1631
  ]
1562
1632
  }
1563
1633
  ) : position }),
1564
- /* @__PURE__ */ jsx22(
1634
+ /* @__PURE__ */ jsx25(
1565
1635
  "div",
1566
1636
  {
1567
1637
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1568
1638
  "aria-hidden": "true",
1569
- children: isAwaitingPlayback ? /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx22(FaPause2, {}) : /* @__PURE__ */ jsx22(FaPlay2, {})
1639
+ children: isAwaitingPlayback ? /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx25(FaPause2, {}) : /* @__PURE__ */ jsx25(FaPlay2, {})
1570
1640
  }
1571
1641
  )
1572
1642
  ]
@@ -1577,7 +1647,7 @@ function TrackPositionPlayPause({
1577
1647
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1578
1648
  import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
1579
1649
  import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
1580
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1650
+ import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1581
1651
  function TrackItem({
1582
1652
  track,
1583
1653
  position,
@@ -1601,12 +1671,12 @@ function TrackItem({
1601
1671
  loadTrackForPlayer(track.uuid);
1602
1672
  }
1603
1673
  };
1604
- return /* @__PURE__ */ jsxs17(
1674
+ return /* @__PURE__ */ jsxs20(
1605
1675
  "div",
1606
1676
  {
1607
1677
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1608
1678
  children: [
1609
- isUnplayable ? /* @__PURE__ */ jsx23(
1679
+ isUnplayable ? /* @__PURE__ */ jsx26(
1610
1680
  "div",
1611
1681
  {
1612
1682
  className: "flex items-center justify-center text-lg w-16",
@@ -1614,7 +1684,7 @@ function TrackItem({
1614
1684
  "aria-disabled": "true",
1615
1685
  children: position
1616
1686
  }
1617
- ) : /* @__PURE__ */ jsx23(
1687
+ ) : /* @__PURE__ */ jsx26(
1618
1688
  TrackPositionPlayPause,
1619
1689
  {
1620
1690
  position,
@@ -1623,12 +1693,12 @@ function TrackItem({
1623
1693
  isSelected: selectedTrack?.uuid === track.uuid
1624
1694
  }
1625
1695
  ),
1626
- /* @__PURE__ */ jsxs17("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1627
- /* @__PURE__ */ jsx23("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx23("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx23(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1628
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx23("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx23(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
1696
+ /* @__PURE__ */ jsxs20("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1697
+ /* @__PURE__ */ jsx26("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx26("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx26(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1698
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx26("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx26(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
1629
1699
  ] }),
1630
- /* @__PURE__ */ jsx23("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx23("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1631
- renderDuration && /* @__PURE__ */ jsx23(
1700
+ /* @__PURE__ */ jsx26("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx26("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1701
+ renderDuration && /* @__PURE__ */ jsx26(
1632
1702
  "div",
1633
1703
  {
1634
1704
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1636,14 +1706,14 @@ function TrackItem({
1636
1706
  children: renderDuration(track)
1637
1707
  }
1638
1708
  ),
1639
- renderActions && /* @__PURE__ */ jsx23("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1709
+ renderActions && /* @__PURE__ */ jsx26("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1640
1710
  ]
1641
1711
  }
1642
1712
  );
1643
1713
  }
1644
1714
 
1645
1715
  // src/genre-tree/artist/ArtistSongs.tsx
1646
- import { Fragment as Fragment8, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
1716
+ import { Fragment as Fragment10, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
1647
1717
  var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
1648
1718
  var SORTS = [
1649
1719
  { ordering: "title", label: "Title" },
@@ -1676,21 +1746,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1676
1746
  event.stopPropagation();
1677
1747
  void play(song.uuid);
1678
1748
  };
1679
- return /* @__PURE__ */ jsx24(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx24(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx24("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx24("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs18(Fragment8, { children: [
1680
- /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1681
- /* @__PURE__ */ jsxs18(
1749
+ return /* @__PURE__ */ jsx27(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx27(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx27("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx27("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs21(Fragment10, { children: [
1750
+ /* @__PURE__ */ jsxs21("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1751
+ /* @__PURE__ */ jsxs21(
1682
1752
  "button",
1683
1753
  {
1684
1754
  type: "button",
1685
1755
  onClick: () => void play(),
1686
1756
  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",
1687
1757
  children: [
1688
- /* @__PURE__ */ jsx24(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1758
+ /* @__PURE__ */ jsx27(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1689
1759
  "Play all"
1690
1760
  ]
1691
1761
  }
1692
1762
  ),
1693
- /* @__PURE__ */ jsx24("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx24(
1763
+ /* @__PURE__ */ jsx27("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx27(
1694
1764
  "button",
1695
1765
  {
1696
1766
  type: "button",
@@ -1705,7 +1775,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1705
1775
  sort.ordering
1706
1776
  )) })
1707
1777
  ] }),
1708
- /* @__PURE__ */ jsx24("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx24("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx24(
1778
+ /* @__PURE__ */ jsx27("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx27("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx27(
1709
1779
  TrackItem,
1710
1780
  {
1711
1781
  track: song,
@@ -1714,12 +1784,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1714
1784
  renderSongLink
1715
1785
  }
1716
1786
  ) }, song.uuid)) }),
1717
- /* @__PURE__ */ jsx24(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1787
+ /* @__PURE__ */ jsx27(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1718
1788
  ] }) });
1719
1789
  }
1720
1790
 
1721
1791
  // src/genre-tree/artist/useArtistPanel.tsx
1722
- import { Fragment as Fragment9, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
1792
+ import { Fragment as Fragment11, jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
1723
1793
  function useArtistPanel({
1724
1794
  uuid,
1725
1795
  scope,
@@ -1740,26 +1810,26 @@ function useArtistPanel({
1740
1810
  return {
1741
1811
  ...panel,
1742
1812
  title: "Artist",
1743
- content: /* @__PURE__ */ jsxs19("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1744
- /* @__PURE__ */ jsx25(Skeleton4, { className: "h-3 w-16" }),
1745
- /* @__PURE__ */ jsx25(Skeleton4, { className: "h-4 w-32" }),
1746
- /* @__PURE__ */ jsx25(Skeleton4, { className: "h-3 w-16" }),
1747
- /* @__PURE__ */ jsx25(Skeleton4, { className: "h-4 w-24" }),
1748
- /* @__PURE__ */ jsx25(Skeleton4, { className: "h-16 mt-2.5" }),
1749
- /* @__PURE__ */ jsx25(Skeleton4, { className: "h-16" })
1813
+ content: /* @__PURE__ */ jsxs22("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1814
+ /* @__PURE__ */ jsx28(Skeleton4, { className: "h-3 w-16" }),
1815
+ /* @__PURE__ */ jsx28(Skeleton4, { className: "h-4 w-32" }),
1816
+ /* @__PURE__ */ jsx28(Skeleton4, { className: "h-3 w-16" }),
1817
+ /* @__PURE__ */ jsx28(Skeleton4, { className: "h-4 w-24" }),
1818
+ /* @__PURE__ */ jsx28(Skeleton4, { className: "h-16 mt-2.5" }),
1819
+ /* @__PURE__ */ jsx28(Skeleton4, { className: "h-16" })
1750
1820
  ] })
1751
1821
  };
1752
1822
  case "not-found":
1753
1823
  return {
1754
1824
  ...panel,
1755
1825
  title: "Artist not found",
1756
- content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1826
+ content: /* @__PURE__ */ jsx28("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1757
1827
  };
1758
1828
  case "error":
1759
1829
  return {
1760
1830
  ...panel,
1761
1831
  title: "Artist",
1762
- content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
1832
+ content: /* @__PURE__ */ jsx28("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
1763
1833
  };
1764
1834
  case "success": {
1765
1835
  const { artist } = result;
@@ -1768,13 +1838,13 @@ function useArtistPanel({
1768
1838
  return {
1769
1839
  ...panel,
1770
1840
  title: artist.name,
1771
- content: /* @__PURE__ */ jsxs19(Fragment9, { children: [
1772
- shareUrl && /* @__PURE__ */ jsx25("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx25("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx25(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
1773
- artist.image && /* @__PURE__ */ jsx25(ArtistPhoto, { image: artist.image, name: artist.name }),
1774
- /* @__PURE__ */ jsx25(ArtistHeader, { artist }),
1775
- /* @__PURE__ */ jsx25(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
1776
- /* @__PURE__ */ jsx25(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
1777
- artist.tracksCount > 0 && /* @__PURE__ */ jsx25(
1841
+ content: /* @__PURE__ */ jsxs22(Fragment11, { children: [
1842
+ shareUrl && /* @__PURE__ */ jsx28("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx28("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx28(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
1843
+ artist.image && /* @__PURE__ */ jsx28(ArtistPhoto, { image: artist.image, name: artist.name }),
1844
+ /* @__PURE__ */ jsx28(ArtistHeader, { artist }),
1845
+ /* @__PURE__ */ jsx28(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
1846
+ /* @__PURE__ */ jsx28(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
1847
+ artist.tracksCount > 0 && /* @__PURE__ */ jsx28(
1778
1848
  ArtistSongs,
1779
1849
  {
1780
1850
  artist: { uuid: artist.uuid, name: artist.name },
@@ -1784,7 +1854,7 @@ function useArtistPanel({
1784
1854
  }
1785
1855
  )
1786
1856
  ] }),
1787
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx25(ArtistDiscography, { albums: artist.albums }) : void 0
1857
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx28(ArtistDiscography, { albums: artist.albums }) : void 0
1788
1858
  };
1789
1859
  }
1790
1860
  }
@@ -1808,7 +1878,7 @@ import {
1808
1878
  useSongHistory,
1809
1879
  useTrackList as useTrackList5
1810
1880
  } from "@behindthemusictree/app-kit/genre-tree";
1811
- import { Fragment as Fragment10, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1881
+ import { Fragment as Fragment12, jsx as jsx29, jsxs as jsxs23 } from "react/jsx-runtime";
1812
1882
  var numberFormat = new Intl.NumberFormat("en");
1813
1883
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
1814
1884
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
@@ -1816,32 +1886,32 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
1816
1886
  const [open, setOpen] = useState7(false);
1817
1887
  const history = useSongHistory(uuid, getBackendBaseUrl, open);
1818
1888
  let body = null;
1819
- if (history.status === "loading") body = /* @__PURE__ */ jsx26(Skeleton5, { className: "h-10", "aria-busy": "true" });
1820
- else if (history.status === "error") body = /* @__PURE__ */ jsx26("p", { className: "text-red-600", children: "Could not load the history." });
1889
+ if (history.status === "loading") body = /* @__PURE__ */ jsx29(Skeleton5, { className: "h-10", "aria-busy": "true" });
1890
+ else if (history.status === "error") body = /* @__PURE__ */ jsx29("p", { className: "text-red-600", children: "Could not load the history." });
1821
1891
  else if (history.status === "success")
1822
- body = history.entries.length === 0 ? /* @__PURE__ */ jsx26("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx26("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs20("li", { className: "py-1.5 text-sm", children: [
1823
- /* @__PURE__ */ jsx26("span", { className: "font-medium", children: entry.action }),
1824
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs20("span", { className: "text-[var(--color-text-muted)]", children: [
1892
+ body = history.entries.length === 0 ? /* @__PURE__ */ jsx29("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx29("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs23("li", { className: "py-1.5 text-sm", children: [
1893
+ /* @__PURE__ */ jsx29("span", { className: "font-medium", children: entry.action }),
1894
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs23("span", { className: "text-[var(--color-text-muted)]", children: [
1825
1895
  " ",
1826
1896
  "\u2014 ",
1827
1897
  entry.oldValue ?? "none",
1828
1898
  " \u2192 ",
1829
1899
  entry.newValue ?? "none"
1830
1900
  ] }) : null,
1831
- /* @__PURE__ */ jsxs20("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
1901
+ /* @__PURE__ */ jsxs23("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
1832
1902
  entry.actorPseudo ?? "Anonymous",
1833
1903
  " \xB7 ",
1834
1904
  dateFormat.format(new Date(entry.createdOn))
1835
1905
  ] })
1836
1906
  ] }, entry.uuid)) });
1837
- return /* @__PURE__ */ jsxs20(
1907
+ return /* @__PURE__ */ jsxs23(
1838
1908
  "details",
1839
1909
  {
1840
1910
  onToggle: (event) => setOpen(event.currentTarget.open),
1841
1911
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
1842
1912
  children: [
1843
- /* @__PURE__ */ jsx26("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
1844
- /* @__PURE__ */ jsx26("div", { className: "mt-1.5", children: body })
1913
+ /* @__PURE__ */ jsx29("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
1914
+ /* @__PURE__ */ jsx29("div", { className: "mt-1.5", children: body })
1845
1915
  ]
1846
1916
  }
1847
1917
  );
@@ -1867,13 +1937,13 @@ function SongPanelContent({
1867
1937
  const path = genrePath.length > 0 ? genrePath : null;
1868
1938
  fields.push({
1869
1939
  label: "Genre",
1870
- value: path ? /* @__PURE__ */ jsx26("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs20("span", { children: [
1871
- index > 0 && /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: "\u203A " }),
1872
- /* @__PURE__ */ jsx26("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
1940
+ value: path ? /* @__PURE__ */ jsx29("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs23("span", { children: [
1941
+ index > 0 && /* @__PURE__ */ jsx29("span", { "aria-hidden": "true", children: "\u203A " }),
1942
+ /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
1873
1943
  ] }, genrePlaylist.uuid)) }) : song.genre.name
1874
1944
  });
1875
1945
  } else {
1876
- fields.push({ label: "Genre", value: /* @__PURE__ */ jsx26("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
1946
+ fields.push({ label: "Genre", value: /* @__PURE__ */ jsx29("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
1877
1947
  }
1878
1948
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
1879
1949
  const viewCount = song.youtubeVideo?.viewCount;
@@ -1883,11 +1953,11 @@ function SongPanelContent({
1883
1953
  const links = [];
1884
1954
  if (musicBrainzUrl)
1885
1955
  links.push(
1886
- /* @__PURE__ */ jsx26("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
1956
+ /* @__PURE__ */ jsx29("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
1887
1957
  );
1888
1958
  if (song.youtubeVideo)
1889
1959
  links.push(
1890
- /* @__PURE__ */ jsx26(
1960
+ /* @__PURE__ */ jsx29(
1891
1961
  "a",
1892
1962
  {
1893
1963
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -1902,33 +1972,33 @@ function SongPanelContent({
1902
1972
  if (links.length > 0)
1903
1973
  fields.push({
1904
1974
  label: "Links",
1905
- value: /* @__PURE__ */ jsx26("span", { className: "flex gap-3", children: links })
1975
+ value: /* @__PURE__ */ jsx29("span", { className: "flex gap-3", children: links })
1906
1976
  });
1907
- return /* @__PURE__ */ jsxs20(Fragment10, { children: [
1908
- /* @__PURE__ */ jsx26("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs20("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1909
- unplayableReason ? /* @__PURE__ */ jsx26("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs20(
1977
+ return /* @__PURE__ */ jsxs23(Fragment12, { children: [
1978
+ /* @__PURE__ */ jsx29("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs23("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1979
+ unplayableReason ? /* @__PURE__ */ jsx29("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs23(
1910
1980
  "button",
1911
1981
  {
1912
1982
  type: "button",
1913
1983
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
1914
1984
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
1915
1985
  children: [
1916
- /* @__PURE__ */ jsx26("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__ */ jsx26(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx26(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1986
+ /* @__PURE__ */ jsx29("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__ */ jsx29(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx29(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1917
1987
  isPlaying ? "Pause" : "Play"
1918
1988
  ]
1919
1989
  }
1920
1990
  ),
1921
- shareUrl && /* @__PURE__ */ jsx26(ShareActionButton, { target: shareTarget, url: shareUrl })
1991
+ shareUrl && /* @__PURE__ */ jsx29(ShareActionButton, { target: shareTarget, url: shareUrl })
1922
1992
  ] }) }),
1923
- song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx26("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx26(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
1924
- /* @__PURE__ */ jsx26(FieldList2, { items: fields }),
1925
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx26(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx26("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx26("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
1926
- /* @__PURE__ */ jsx26(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
1993
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx29("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx29(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
1994
+ /* @__PURE__ */ jsx29(FieldList2, { items: fields }),
1995
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx29(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx29("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx29("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
1996
+ /* @__PURE__ */ jsx29(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
1927
1997
  ] });
1928
1998
  }
1929
1999
 
1930
2000
  // src/genre-tree/song/useSongPanel.tsx
1931
- import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
2001
+ import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
1932
2002
  function useSongPanel({
1933
2003
  uuid,
1934
2004
  scope,
@@ -1950,24 +2020,24 @@ function useSongPanel({
1950
2020
  return {
1951
2021
  ...panel,
1952
2022
  title: "Song",
1953
- content: /* @__PURE__ */ jsxs21("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1954
- /* @__PURE__ */ jsx27(Skeleton6, { className: "h-3 w-16" }),
1955
- /* @__PURE__ */ jsx27(Skeleton6, { className: "h-4 w-32" }),
1956
- /* @__PURE__ */ jsx27(Skeleton6, { className: "h-3 w-16" }),
1957
- /* @__PURE__ */ jsx27(Skeleton6, { className: "h-4 w-24" })
2023
+ content: /* @__PURE__ */ jsxs24("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2024
+ /* @__PURE__ */ jsx30(Skeleton6, { className: "h-3 w-16" }),
2025
+ /* @__PURE__ */ jsx30(Skeleton6, { className: "h-4 w-32" }),
2026
+ /* @__PURE__ */ jsx30(Skeleton6, { className: "h-3 w-16" }),
2027
+ /* @__PURE__ */ jsx30(Skeleton6, { className: "h-4 w-24" })
1958
2028
  ] })
1959
2029
  };
1960
2030
  case "not-found":
1961
2031
  return {
1962
2032
  ...panel,
1963
2033
  title: "Song not found",
1964
- content: /* @__PURE__ */ jsx27("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2034
+ content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
1965
2035
  };
1966
2036
  case "error":
1967
2037
  return {
1968
2038
  ...panel,
1969
2039
  title: "Song",
1970
- content: /* @__PURE__ */ jsx27("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2040
+ content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
1971
2041
  };
1972
2042
  case "success": {
1973
2043
  const { song } = result;
@@ -1975,7 +2045,7 @@ function useSongPanel({
1975
2045
  return {
1976
2046
  ...panel,
1977
2047
  title: song.title,
1978
- content: /* @__PURE__ */ jsx27(
2048
+ content: /* @__PURE__ */ jsx30(
1979
2049
  SongPanelContent,
1980
2050
  {
1981
2051
  song,
@@ -1995,12 +2065,12 @@ function useSongPanel({
1995
2065
  // src/genre-tree/SizeMetricToggle.tsx
1996
2066
  import { MdMusicNote, MdVisibility } from "react-icons/md";
1997
2067
  import { SegmentedControl } from "@behindthemusictree/ui";
1998
- import { jsx as jsx28 } from "react/jsx-runtime";
2068
+ import { jsx as jsx31 } from "react/jsx-runtime";
1999
2069
  function SizeMetricToggle({
2000
2070
  value,
2001
2071
  onChange
2002
2072
  }) {
2003
- return /* @__PURE__ */ jsx28(
2073
+ return /* @__PURE__ */ jsx31(
2004
2074
  SegmentedControl,
2005
2075
  {
2006
2076
  variant: "floating",
@@ -2008,15 +2078,15 @@ function SizeMetricToggle({
2008
2078
  value,
2009
2079
  onChange,
2010
2080
  options: [
2011
- { value: "songs", label: /* @__PURE__ */ jsx28(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2012
- { value: "views", label: /* @__PURE__ */ jsx28(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2081
+ { value: "songs", label: /* @__PURE__ */ jsx31(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2082
+ { value: "views", label: /* @__PURE__ */ jsx31(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2013
2083
  ]
2014
2084
  }
2015
2085
  );
2016
2086
  }
2017
2087
 
2018
2088
  // src/genre-tree/GenreTreeView.tsx
2019
- import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
2089
+ import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
2020
2090
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2021
2091
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2022
2092
  var noop = () => {
@@ -2050,6 +2120,7 @@ function GenreTreeView({
2050
2120
  artistExpanded: controlledArtistExpanded,
2051
2121
  onArtistExpandedChange,
2052
2122
  getShareUrl,
2123
+ extraSearchGroups = [],
2053
2124
  artistSearch = true,
2054
2125
  onArtistSelect,
2055
2126
  artistSearchBaseUrl,
@@ -2159,7 +2230,7 @@ function GenreTreeView({
2159
2230
  const openSongRef = useRef3(openSong);
2160
2231
  openSongRef.current = openSong;
2161
2232
  const resolvedRenderSongLink = useCallback6(
2162
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2233
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2163
2234
  [renderSongLink]
2164
2235
  );
2165
2236
  const [expandedSongUuid, setExpandedSongUuid] = useState8(null);
@@ -2171,7 +2242,7 @@ function GenreTreeView({
2171
2242
  scope,
2172
2243
  getBackendBaseUrl: artistBaseUrl,
2173
2244
  getShareUrl: getShareUrlRef.current,
2174
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2245
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2175
2246
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2176
2247
  onExpandedChange: (on) => {
2177
2248
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2180,7 +2251,7 @@ function GenreTreeView({
2180
2251
  renderSongLink: resolvedRenderSongLink,
2181
2252
  onClose: () => changeArtist(null)
2182
2253
  });
2183
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2254
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2184
2255
  const getGenrePath = (genre) => {
2185
2256
  const path = [];
2186
2257
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2259,52 +2330,27 @@ function GenreTreeView({
2259
2330
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2260
2331
  const { showPopup, hidePopup } = usePopup5();
2261
2332
  const [searchText, setSearchText] = useState8("");
2262
- const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : "");
2263
- const artistGroup = artistSearch ? {
2264
- key: "artists",
2265
- label: "Artists",
2266
- items: artistSearchResult.artists.map((artist) => ({
2267
- key: artist.uuid,
2268
- content: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2269
- /* @__PURE__ */ jsxs22("span", { className: "block truncate font-medium", children: [
2270
- artist.name,
2271
- artist.disambiguation && /* @__PURE__ */ jsxs22("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2272
- " \xB7 ",
2273
- artist.disambiguation
2274
- ] })
2275
- ] }),
2276
- artist.matchedAlias && /* @__PURE__ */ jsxs22("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2277
- "also known as ",
2278
- artist.matchedAlias
2279
- ] })
2280
- ] }),
2281
- onSelect: () => {
2282
- openArtist(artist.uuid);
2283
- onArtistSelect?.(artist);
2284
- }
2285
- })),
2286
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
2287
- loading: artistSearchResult.pending
2288
- } : void 0;
2289
- const songSearchResult = useSongSearch(songBaseUrl, songSearch ? searchText : "");
2290
- const songGroup = songSearch ? {
2291
- key: "songs",
2292
- label: "Songs",
2293
- items: songSearchResult.songs.map((song) => ({
2294
- key: song.uuid,
2295
- content: /* @__PURE__ */ jsxs22("span", { className: isSongSearchResultPlayable(song) ? void 0 : "opacity-60", children: [
2296
- /* @__PURE__ */ jsx29("span", { className: "block truncate font-medium", children: song.title }),
2297
- /* @__PURE__ */ jsx29("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 ") })
2298
- ] }),
2299
- onSelect: () => {
2300
- openSong(song.uuid);
2301
- onSongSelect?.(song);
2302
- }
2303
- })),
2304
- status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
2305
- loading: songSearchResult.pending
2306
- } : void 0;
2307
- const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
2333
+ const artistGroup = useArtistSearchGroup(
2334
+ artistBaseUrl,
2335
+ searchText,
2336
+ (artist) => {
2337
+ openArtist(artist.uuid);
2338
+ onArtistSelect?.(artist);
2339
+ },
2340
+ Boolean(artistSearch)
2341
+ );
2342
+ const songGroup = useSongSearchGroup(
2343
+ songBaseUrl,
2344
+ searchText,
2345
+ (song) => {
2346
+ openSong(song.uuid);
2347
+ onSongSelect?.(song);
2348
+ },
2349
+ Boolean(songSearch)
2350
+ );
2351
+ const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2352
+ (group) => group !== void 0
2353
+ );
2308
2354
  const handleSelectedNodeChange = (node) => {
2309
2355
  if (node !== null && songUuid !== null) changeSong(null);
2310
2356
  selectNode(node);
@@ -2328,7 +2374,7 @@ function GenreTreeView({
2328
2374
  };
2329
2375
  const renderGenreBody = (relation) => {
2330
2376
  const { node } = relation;
2331
- const family = /* @__PURE__ */ jsx29(
2377
+ const family = /* @__PURE__ */ jsx32(
2332
2378
  GenreNodeFamily,
2333
2379
  {
2334
2380
  node,
@@ -2339,10 +2385,10 @@ function GenreTreeView({
2339
2385
  onSelectNode: selectGenreFromPanel
2340
2386
  }
2341
2387
  );
2342
- const ancestors = /* @__PURE__ */ jsx29(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2388
+ const ancestors = /* @__PURE__ */ jsx32(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2343
2389
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2344
2390
  const criteria = genrePlaylist?.criteria;
2345
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2391
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2346
2392
  family,
2347
2393
  ancestors
2348
2394
  ] }) };
@@ -2362,11 +2408,11 @@ function GenreTreeView({
2362
2408
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2363
2409
  ) : void 0;
2364
2410
  return {
2365
- primary: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2366
- /* @__PURE__ */ jsx29(
2411
+ primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2412
+ /* @__PURE__ */ jsx32(
2367
2413
  GenreInfoPanelBody,
2368
2414
  {
2369
- family: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2415
+ family: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2370
2416
  family,
2371
2417
  ancestors
2372
2418
  ] }),
@@ -2384,7 +2430,7 @@ function GenreTreeView({
2384
2430
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2385
2431
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2386
2432
  onChangeParent: readOnly ? void 0 : () => showPopup(
2387
- /* @__PURE__ */ jsx29(
2433
+ /* @__PURE__ */ jsx32(
2388
2434
  ChangeParentPopup,
2389
2435
  {
2390
2436
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2459,19 +2505,19 @@ function GenreTreeView({
2459
2505
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2460
2506
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2461
2507
  } : void 0;
2462
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx29(
2508
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx32(
2463
2509
  IconButton,
2464
2510
  {
2465
2511
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2466
2512
  style: activePanel.headerAccent && { color: "inherit" },
2467
2513
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2468
- children: activePanel.expanded ? /* @__PURE__ */ jsx29(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx29(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2514
+ children: activePanel.expanded ? /* @__PURE__ */ jsx32(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx32(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2469
2515
  }
2470
2516
  ) : null;
2471
2517
  const renderArtistFilter = () => {
2472
2518
  if (isLoading || !filterArtist) return null;
2473
- return /* @__PURE__ */ jsxs22("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2474
- /* @__PURE__ */ jsx29(
2519
+ return /* @__PURE__ */ jsxs25("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2520
+ /* @__PURE__ */ jsx32(
2475
2521
  "button",
2476
2522
  {
2477
2523
  type: "button",
@@ -2483,7 +2529,7 @@ function GenreTreeView({
2483
2529
  children: filterArtist.name
2484
2530
  }
2485
2531
  ),
2486
- /* @__PURE__ */ jsx29(
2532
+ /* @__PURE__ */ jsx32(
2487
2533
  "button",
2488
2534
  {
2489
2535
  type: "button",
@@ -2495,8 +2541,8 @@ function GenreTreeView({
2495
2541
  )
2496
2542
  ] });
2497
2543
  };
2498
- const actions = /* @__PURE__ */ jsxs22(Fragment11, { children: [
2499
- !isLoading && /* @__PURE__ */ jsx29(
2544
+ const actions = /* @__PURE__ */ jsxs25(Fragment13, { children: [
2545
+ !isLoading && /* @__PURE__ */ jsx32(
2500
2546
  GenreSearch,
2501
2547
  {
2502
2548
  genrePlaylists: visibleGenrePlaylists,
@@ -2504,13 +2550,13 @@ function GenreTreeView({
2504
2550
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2505
2551
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2506
2552
  onPrevious: handlePrevious,
2507
- onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
2553
+ onQueryChange: artistSearch || songSearch || extraSearchGroups.length > 0 ? setSearchText : void 0,
2508
2554
  extraGroups
2509
2555
  }
2510
2556
  ),
2511
- !isLoading && filterArtist && /* @__PURE__ */ jsx29("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2512
- /* @__PURE__ */ jsxs22("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: [
2513
- !isLoading && !isControlled && /* @__PURE__ */ jsx29(
2557
+ !isLoading && filterArtist && /* @__PURE__ */ jsx32("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2558
+ /* @__PURE__ */ jsxs25("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: [
2559
+ !isLoading && !isControlled && /* @__PURE__ */ jsx32(
2514
2560
  SegmentedControl2,
2515
2561
  {
2516
2562
  "aria-label": "Tree view mode",
@@ -2524,13 +2570,13 @@ function GenreTreeView({
2524
2570
  ]
2525
2571
  }
2526
2572
  ),
2527
- !isLoading && showDisplayOptions && /* @__PURE__ */ jsx29(
2573
+ !isLoading && showDisplayOptions && /* @__PURE__ */ jsx32(
2528
2574
  "div",
2529
2575
  {
2530
2576
  className: "flex items-center gap-1",
2531
2577
  role: "group",
2532
2578
  "aria-label": "Tree display options",
2533
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx29(
2579
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx32(
2534
2580
  Button,
2535
2581
  {
2536
2582
  variant: allowWheelRotation ? "default" : "outline",
@@ -2541,7 +2587,7 @@ function GenreTreeView({
2541
2587
  )
2542
2588
  }
2543
2589
  ),
2544
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx29(
2590
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx32(
2545
2591
  IconTextButton,
2546
2592
  {
2547
2593
  icon: Plus2,
@@ -2551,34 +2597,34 @@ function GenreTreeView({
2551
2597
  )
2552
2598
  ] })
2553
2599
  ] });
2554
- return /* @__PURE__ */ jsxs22(
2600
+ return /* @__PURE__ */ jsxs25(
2555
2601
  "div",
2556
2602
  {
2557
2603
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2558
2604
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2559
2605
  children: [
2560
- /* @__PURE__ */ jsx29(
2606
+ /* @__PURE__ */ jsx32(
2561
2607
  "div",
2562
2608
  {
2563
2609
  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"}`,
2564
2610
  children: actions
2565
2611
  }
2566
2612
  ),
2567
- !isLoading && filterArtist && /* @__PURE__ */ jsx29(
2613
+ !isLoading && filterArtist && /* @__PURE__ */ jsx32(
2568
2614
  "div",
2569
2615
  {
2570
2616
  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"}`,
2571
2617
  children: renderArtistFilter()
2572
2618
  }
2573
2619
  ),
2574
- /* @__PURE__ */ jsx29("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs22("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2575
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx29("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2576
- isLoading ? /* @__PURE__ */ jsx29(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2620
+ /* @__PURE__ */ jsx32("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs25("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2621
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx32("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2622
+ isLoading ? /* @__PURE__ */ jsx32(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2577
2623
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2578
2624
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2579
2625
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2580
2626
  // The panel's own Zoom in button is hidden: the search bar carries it.
2581
- /* @__PURE__ */ jsx29("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx29(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx29(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx29(
2627
+ /* @__PURE__ */ jsx32("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx32(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx32(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx32(
2582
2628
  GenrePlaylistTreeWheel,
2583
2629
  {
2584
2630
  scope,
@@ -2595,9 +2641,9 @@ function GenreTreeView({
2595
2641
  allowWheelRotation,
2596
2642
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2597
2643
  busy,
2598
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
2644
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2599
2645
  floatingControlsLeading,
2600
- canSizeByViews && /* @__PURE__ */ jsx29(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2646
+ canSizeByViews && /* @__PURE__ */ jsx32(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2601
2647
  ] }) : void 0,
2602
2648
  defaultViewport,
2603
2649
  viewportRestore,
@@ -2606,7 +2652,7 @@ function GenreTreeView({
2606
2652
  ) }) })
2607
2653
  ) : viewMode === "pop-core" ? (
2608
2654
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2609
- /* @__PURE__ */ jsx29("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx29(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx29(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx29(
2655
+ /* @__PURE__ */ jsx32("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx32(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx32(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx32(
2610
2656
  GenrePlaylistTreeWheelRadialPopCore,
2611
2657
  {
2612
2658
  scope,
@@ -2623,9 +2669,9 @@ function GenreTreeView({
2623
2669
  allowWheelRotation,
2624
2670
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2625
2671
  busy,
2626
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
2672
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2627
2673
  floatingControlsLeading,
2628
- canSizeByViews && /* @__PURE__ */ jsx29(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2674
+ canSizeByViews && /* @__PURE__ */ jsx32(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2629
2675
  ] }) : void 0,
2630
2676
  defaultViewport,
2631
2677
  viewportRestore,
@@ -2633,7 +2679,7 @@ function GenreTreeView({
2633
2679
  ...focus
2634
2680
  }
2635
2681
  ) }) })
2636
- ) : viewMode === "outline" ? /* @__PURE__ */ jsx29("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx29(
2682
+ ) : viewMode === "outline" ? /* @__PURE__ */ jsx32("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx32(
2637
2683
  GenrePlaylistTreeWheelRadialPopCore,
2638
2684
  {
2639
2685
  outline: true,
@@ -2649,13 +2695,13 @@ function GenreTreeView({
2649
2695
  selectedNodeId,
2650
2696
  readOnly
2651
2697
  }
2652
- ) }) : /* @__PURE__ */ jsx29("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(
2698
+ ) }) : /* @__PURE__ */ jsx32("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(
2653
2699
  ([uuid, genrePlaylistTreePerRoot]) => {
2654
- return /* @__PURE__ */ jsx29(
2700
+ return /* @__PURE__ */ jsx32(
2655
2701
  "div",
2656
2702
  {
2657
2703
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2658
- children: /* @__PURE__ */ jsx29("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx29(
2704
+ children: /* @__PURE__ */ jsx32("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx32(
2659
2705
  GenrePlaylistTreePerRoot,
2660
2706
  {
2661
2707
  scope,
@@ -2681,7 +2727,7 @@ function GenreTreeView({
2681
2727
  );
2682
2728
  }
2683
2729
  ) }),
2684
- /* @__PURE__ */ jsx29(
2730
+ /* @__PURE__ */ jsx32(
2685
2731
  DetailPanel,
2686
2732
  {
2687
2733
  ref: panelRef,
@@ -2698,7 +2744,7 @@ function GenreTreeView({
2698
2744
  aside: activePanel?.aside,
2699
2745
  edgeTab: detailTab,
2700
2746
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
2701
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs22(Fragment11, { children: [
2747
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs25(Fragment13, { children: [
2702
2748
  activePanel.content,
2703
2749
  activePanel.aside
2704
2750
  ] }) : activePanel?.content
@@ -2725,7 +2771,7 @@ import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
2725
2771
  import {
2726
2772
  useFetchGenreDetail
2727
2773
  } from "@behindthemusictree/app-kit/genre-tree";
2728
- import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
2774
+ import { Fragment as Fragment14, jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2729
2775
  var RELATIVE_ITEM_COUNT = 1;
2730
2776
  function toNode(genre, parentId) {
2731
2777
  return {
@@ -2780,17 +2826,17 @@ function GenreDetailPanel({
2780
2826
  };
2781
2827
  }, [detail]);
2782
2828
  if (isError) {
2783
- return /* @__PURE__ */ jsx30("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2829
+ return /* @__PURE__ */ jsx33("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2784
2830
  }
2785
2831
  if (isPending || !detail || !view) {
2786
- return /* @__PURE__ */ jsxs23("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2787
- /* @__PURE__ */ jsx30(Skeleton7, { className: "h-6 w-2/3" }),
2788
- /* @__PURE__ */ jsx30(Skeleton7, { className: "h-4 w-full" }),
2789
- /* @__PURE__ */ jsx30(Skeleton7, { className: "h-4 w-3/4" })
2832
+ return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2833
+ /* @__PURE__ */ jsx33(Skeleton7, { className: "h-6 w-2/3" }),
2834
+ /* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-full" }),
2835
+ /* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-3/4" })
2790
2836
  ] });
2791
2837
  }
2792
2838
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
2793
- const family = /* @__PURE__ */ jsx30(
2839
+ const family = /* @__PURE__ */ jsx33(
2794
2840
  GenreNodeFamily2,
2795
2841
  {
2796
2842
  node: view.node,
@@ -2801,20 +2847,20 @@ function GenreDetailPanel({
2801
2847
  onSelectNode: selectGenre
2802
2848
  }
2803
2849
  );
2804
- const ancestors = /* @__PURE__ */ jsx30(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2805
- return /* @__PURE__ */ jsxs23(
2850
+ const ancestors = /* @__PURE__ */ jsx33(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2851
+ return /* @__PURE__ */ jsxs26(
2806
2852
  DetailPanel2,
2807
2853
  {
2808
2854
  side: "left",
2809
2855
  title: view.node.name,
2810
2856
  headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
2811
- headerActions: onExpandedChange ? /* @__PURE__ */ jsx30(
2857
+ headerActions: onExpandedChange ? /* @__PURE__ */ jsx33(
2812
2858
  IconButton2,
2813
2859
  {
2814
2860
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
2815
2861
  style: { color: "inherit" },
2816
2862
  onClick: () => onExpandedChange(!expanded),
2817
- children: expanded ? /* @__PURE__ */ jsx30(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx30(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2863
+ children: expanded ? /* @__PURE__ */ jsx33(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx33(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2818
2864
  }
2819
2865
  ) : void 0,
2820
2866
  onClose,
@@ -2823,10 +2869,10 @@ function GenreDetailPanel({
2823
2869
  onExpandedChange,
2824
2870
  edgeTab,
2825
2871
  children: [
2826
- /* @__PURE__ */ jsx30(
2872
+ /* @__PURE__ */ jsx33(
2827
2873
  GenreInfoPanelBody,
2828
2874
  {
2829
- family: /* @__PURE__ */ jsxs23(Fragment12, { children: [
2875
+ family: /* @__PURE__ */ jsxs26(Fragment14, { children: [
2830
2876
  family,
2831
2877
  ancestors
2832
2878
  ] }),
@@ -2860,7 +2906,7 @@ import { ListMusic } from "lucide-react";
2860
2906
  import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
2861
2907
  import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
2862
2908
  import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
2863
- import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
2909
+ import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
2864
2910
  var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
2865
2911
  function TrackListSidebar({
2866
2912
  className: className2 = "",
@@ -2962,25 +3008,25 @@ function TrackListSidebar({
2962
3008
  }
2963
3009
  };
2964
3010
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
2965
- return trackList ? /* @__PURE__ */ jsxs24(
3011
+ return trackList ? /* @__PURE__ */ jsxs27(
2966
3012
  "div",
2967
3013
  {
2968
3014
  "data-theme": "dark",
2969
3015
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
2970
3016
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
2971
3017
  children: [
2972
- /* @__PURE__ */ jsxs24("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
2973
- /* @__PURE__ */ jsxs24("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
2974
- /* @__PURE__ */ jsxs24("div", { className: "origin flex text-xl", children: [
2975
- /* @__PURE__ */ jsx31("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
2976
- /* @__PURE__ */ jsx31("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__ */ jsx31("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3018
+ /* @__PURE__ */ jsxs27("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3019
+ /* @__PURE__ */ jsxs27("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3020
+ /* @__PURE__ */ jsxs27("div", { className: "origin flex text-xl", children: [
3021
+ /* @__PURE__ */ jsx34("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3022
+ /* @__PURE__ */ jsx34("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__ */ jsx34("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
2977
3023
  ] }),
2978
- /* @__PURE__ */ jsxs24("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3024
+ /* @__PURE__ */ jsxs27("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
2979
3025
  trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
2980
3026
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
2981
3027
  ] })
2982
3028
  ] }),
2983
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx31(
3029
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx34(
2984
3030
  "button",
2985
3031
  {
2986
3032
  type: "button",
@@ -2989,11 +3035,11 @@ function TrackListSidebar({
2989
3035
  "aria-expanded": !isCollapsed,
2990
3036
  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"}`,
2991
3037
  onClick: isCollapsed ? onExpand : onMinimize,
2992
- children: /* @__PURE__ */ jsx31(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3038
+ children: /* @__PURE__ */ jsx34(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
2993
3039
  }
2994
3040
  )
2995
3041
  ] }),
2996
- !isCollapsed && /* @__PURE__ */ jsxs24(
3042
+ !isCollapsed && /* @__PURE__ */ jsxs27(
2997
3043
  "ul",
2998
3044
  {
2999
3045
  ref: trackListRef,
@@ -3003,7 +3049,7 @@ function TrackListSidebar({
3003
3049
  onPointerDown: markUserScrollIntent,
3004
3050
  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",
3005
3051
  children: [
3006
- trackList.tracks.map((track, index) => /* @__PURE__ */ jsx31("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx31(
3052
+ trackList.tracks.map((track, index) => /* @__PURE__ */ jsx34("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx34(
3007
3053
  TrackItem,
3008
3054
  {
3009
3055
  track,
@@ -3014,7 +3060,7 @@ function TrackListSidebar({
3014
3060
  renderSongLink
3015
3061
  }
3016
3062
  ) }, track.uuid)),
3017
- hasMore && /* @__PURE__ */ jsx31("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3063
+ hasMore && /* @__PURE__ */ jsx34("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3018
3064
  ]
3019
3065
  }
3020
3066
  )
@@ -3039,7 +3085,7 @@ import { PanelEdgeTab } from "@behindthemusictree/ui";
3039
3085
  import { Pause as Pause3, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
3040
3086
  import { formatTime, useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
3041
3087
  import { PlayStates as PlayStates4, useCurrentTime, usePlayer as usePlayer10 } from "@behindthemusictree/app-kit/player";
3042
- import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
3088
+ import { jsx as jsx35, jsxs as jsxs28 } from "react/jsx-runtime";
3043
3089
  function PlayerControls() {
3044
3090
  const {
3045
3091
  duration,
@@ -3058,15 +3104,15 @@ function PlayerControls() {
3058
3104
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3059
3105
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3060
3106
  const isPlaying = playState === PlayStates4.PLAYING;
3061
- return /* @__PURE__ */ jsxs25(
3107
+ return /* @__PURE__ */ jsxs28(
3062
3108
  "section",
3063
3109
  {
3064
3110
  "aria-label": "Player controls",
3065
3111
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3066
3112
  children: [
3067
- /* @__PURE__ */ jsxs25("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3068
- /* @__PURE__ */ jsx32("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3069
- /* @__PURE__ */ jsx32(
3113
+ /* @__PURE__ */ jsxs28("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3114
+ /* @__PURE__ */ jsx35("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3115
+ /* @__PURE__ */ jsx35(
3070
3116
  "input",
3071
3117
  {
3072
3118
  "aria-label": "Seek",
@@ -3080,11 +3126,11 @@ function PlayerControls() {
3080
3126
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3081
3127
  }
3082
3128
  ),
3083
- /* @__PURE__ */ jsx32("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3129
+ /* @__PURE__ */ jsx35("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3084
3130
  ] }),
3085
- /* @__PURE__ */ jsxs25("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3086
- /* @__PURE__ */ jsxs25("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3087
- /* @__PURE__ */ jsx32(
3131
+ /* @__PURE__ */ jsxs28("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3132
+ /* @__PURE__ */ jsxs28("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3133
+ /* @__PURE__ */ jsx35(
3088
3134
  "button",
3089
3135
  {
3090
3136
  type: "button",
@@ -3093,10 +3139,10 @@ function PlayerControls() {
3093
3139
  disabled: !isReady || !hasPreviousTrack,
3094
3140
  onClick: playPreviousTrack,
3095
3141
  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",
3096
- children: /* @__PURE__ */ jsx32(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3142
+ children: /* @__PURE__ */ jsx35(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3097
3143
  }
3098
3144
  ),
3099
- /* @__PURE__ */ jsx32(
3145
+ /* @__PURE__ */ jsx35(
3100
3146
  "button",
3101
3147
  {
3102
3148
  type: "button",
@@ -3105,10 +3151,10 @@ function PlayerControls() {
3105
3151
  disabled: !isReady,
3106
3152
  onClick: handlePlayPauseAction,
3107
3153
  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",
3108
- children: isPlaying ? /* @__PURE__ */ jsx32(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx32(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3154
+ children: isPlaying ? /* @__PURE__ */ jsx35(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx35(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3109
3155
  }
3110
3156
  ),
3111
- /* @__PURE__ */ jsx32(
3157
+ /* @__PURE__ */ jsx35(
3112
3158
  "button",
3113
3159
  {
3114
3160
  type: "button",
@@ -3117,14 +3163,14 @@ function PlayerControls() {
3117
3163
  disabled: !isReady || !hasNextTrack,
3118
3164
  onClick: playNextTrack,
3119
3165
  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",
3120
- children: /* @__PURE__ */ jsx32(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3166
+ children: /* @__PURE__ */ jsx35(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3121
3167
  }
3122
3168
  )
3123
3169
  ] }),
3124
- /* @__PURE__ */ jsxs25("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: [
3125
- volume === 0 ? /* @__PURE__ */ jsx32(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx32(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3126
- /* @__PURE__ */ jsx32("span", { className: "sr-only", children: "Volume" }),
3127
- /* @__PURE__ */ jsx32(
3170
+ /* @__PURE__ */ jsxs28("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: [
3171
+ volume === 0 ? /* @__PURE__ */ jsx35(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx35(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3172
+ /* @__PURE__ */ jsx35("span", { className: "sr-only", children: "Volume" }),
3173
+ /* @__PURE__ */ jsx35(
3128
3174
  "input",
3129
3175
  {
3130
3176
  "aria-label": "Volume",
@@ -3146,7 +3192,7 @@ function PlayerControls() {
3146
3192
  }
3147
3193
 
3148
3194
  // src/genre-tree/PlayerPanel.tsx
3149
- import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
3195
+ import { jsx as jsx36, jsxs as jsxs29 } from "react/jsx-runtime";
3150
3196
  var PANEL_ID = "player-panel";
3151
3197
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3152
3198
  function PlayerPanel({
@@ -3186,7 +3232,7 @@ function PlayerPanel({
3186
3232
  onClick: () => setIsExtendRequested(true),
3187
3233
  expanded: false
3188
3234
  } : null;
3189
- return /* @__PURE__ */ jsxs26(
3235
+ return /* @__PURE__ */ jsxs29(
3190
3236
  "div",
3191
3237
  {
3192
3238
  id: PANEL_ID,
@@ -3199,7 +3245,7 @@ function PlayerPanel({
3199
3245
  ),
3200
3246
  style: isExtended ? { width: extendedWidth } : void 0,
3201
3247
  children: [
3202
- /* @__PURE__ */ jsx33(
3248
+ /* @__PURE__ */ jsx36(
3203
3249
  PanelEdgeTab,
3204
3250
  {
3205
3251
  side: "right",
@@ -3209,7 +3255,7 @@ function PlayerPanel({
3209
3255
  className: "absolute right-full top-1/2 -translate-y-1/2"
3210
3256
  }
3211
3257
  ),
3212
- /* @__PURE__ */ jsxs26(
3258
+ /* @__PURE__ */ jsxs29(
3213
3259
  "div",
3214
3260
  {
3215
3261
  className: twMerge(
@@ -3217,20 +3263,20 @@ function PlayerPanel({
3217
3263
  isExtended ? "flex-row" : "flex-col"
3218
3264
  ),
3219
3265
  children: [
3220
- /* @__PURE__ */ jsxs26("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3221
- /* @__PURE__ */ jsx33("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx33("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3222
- /* @__PURE__ */ jsxs26("div", { className: "relative w-full shrink-0", children: [
3223
- /* @__PURE__ */ jsx33(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3224
- playerTrackObject?.loadError && /* @__PURE__ */ jsxs26("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3266
+ /* @__PURE__ */ jsxs29("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3267
+ /* @__PURE__ */ jsx36("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx36("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3268
+ /* @__PURE__ */ jsxs29("div", { className: "relative w-full shrink-0", children: [
3269
+ /* @__PURE__ */ jsx36(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3270
+ playerTrackObject?.loadError && /* @__PURE__ */ jsxs29("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3225
3271
  playerTrackObject.loadError,
3226
- /* @__PURE__ */ jsx33("button", { type: "button", className: "ml-2 underline", onClick: () => {
3272
+ /* @__PURE__ */ jsx36("button", { type: "button", className: "ml-2 underline", onClick: () => {
3227
3273
  void retryPlayback();
3228
3274
  }, children: "Retry" })
3229
3275
  ] })
3230
3276
  ] }),
3231
- /* @__PURE__ */ jsx33(PlayerControls, {})
3277
+ /* @__PURE__ */ jsx36(PlayerControls, {})
3232
3278
  ] }),
3233
- /* @__PURE__ */ jsx33(
3279
+ /* @__PURE__ */ jsx36(
3234
3280
  TrackListSidebar,
3235
3281
  {
3236
3282
  layout: "inline",
@@ -3257,11 +3303,11 @@ function PlayerPanel({
3257
3303
  import { v4 as uuidv4 } from "uuid";
3258
3304
  import { AiFillStar, AiOutlineStar } from "react-icons/ai";
3259
3305
  import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
3260
- import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
3306
+ import { jsx as jsx37, jsxs as jsxs30 } from "react/jsx-runtime";
3261
3307
  function Rating({ rating, handleChange }) {
3262
3308
  const instanceId = uuidv4();
3263
- return /* @__PURE__ */ jsx34("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs27("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3264
- /* @__PURE__ */ jsx34(
3309
+ return /* @__PURE__ */ jsx37("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs30("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3310
+ /* @__PURE__ */ jsx37(
3265
3311
  "input",
3266
3312
  {
3267
3313
  id: `star-${instanceId}-${i}`,
@@ -3273,16 +3319,16 @@ function Rating({ rating, handleChange }) {
3273
3319
  onChange: handleChange
3274
3320
  }
3275
3321
  ),
3276
- /* @__PURE__ */ jsx34("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs27("div", { className: "flex", children: [
3277
- /* @__PURE__ */ jsx34(
3322
+ /* @__PURE__ */ jsx37("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs30("div", { className: "flex", children: [
3323
+ /* @__PURE__ */ jsx37(
3278
3324
  AiOutlineStar,
3279
3325
  {
3280
3326
  className: `w-7 h-7 absolute top-0 left-0 ${rating === FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3281
3327
  }
3282
3328
  ),
3283
- /* @__PURE__ */ jsx34("span", { className: "flex-grow w-8" }),
3329
+ /* @__PURE__ */ jsx37("span", { className: "flex-grow w-8" }),
3284
3330
  " "
3285
- ] }) : /* @__PURE__ */ jsx34(
3331
+ ] }) : /* @__PURE__ */ jsx37(
3286
3332
  AiFillStar,
3287
3333
  {
3288
3334
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3294,6 +3340,12 @@ function Rating({ rating, handleChange }) {
3294
3340
  export {
3295
3341
  useGenrePlaylistPlayback,
3296
3342
  GenrePlaylistTreePerRoot,
3343
+ SearchBox,
3344
+ SEARCH_FAILED_STATUS,
3345
+ searchStatus,
3346
+ useArtistSearchGroup,
3347
+ useSongSearchGroup,
3348
+ useGenreRemoteSearchGroup,
3297
3349
  GenreSearch,
3298
3350
  GenreNodeActionBand,
3299
3351
  GenreInfoPanelBody,
@@ -3309,4 +3361,4 @@ export {
3309
3361
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3310
3362
  GenreTreeOutlineSkeleton
3311
3363
  };
3312
- //# sourceMappingURL=chunk-2M4Q2IJU.js.map
3364
+ //# sourceMappingURL=chunk-FHGQGTCN.js.map