@behindthemusictree/app-kit-ui 33.1.0 → 33.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,9 @@ function GenreTreeView({
2050
2120
  artistExpanded: controlledArtistExpanded,
2051
2121
  onArtistExpandedChange,
2052
2122
  getShareUrl,
2123
+ extraSearchGroups = [],
2124
+ onSearchQueryChange,
2125
+ onSearchFocus,
2053
2126
  artistSearch = true,
2054
2127
  onArtistSelect,
2055
2128
  artistSearchBaseUrl,
@@ -2159,7 +2232,7 @@ function GenreTreeView({
2159
2232
  const openSongRef = useRef3(openSong);
2160
2233
  openSongRef.current = openSong;
2161
2234
  const resolvedRenderSongLink = useCallback6(
2162
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2235
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2163
2236
  [renderSongLink]
2164
2237
  );
2165
2238
  const [expandedSongUuid, setExpandedSongUuid] = useState8(null);
@@ -2171,7 +2244,7 @@ function GenreTreeView({
2171
2244
  scope,
2172
2245
  getBackendBaseUrl: artistBaseUrl,
2173
2246
  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 })),
2247
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2175
2248
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2176
2249
  onExpandedChange: (on) => {
2177
2250
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2180,7 +2253,7 @@ function GenreTreeView({
2180
2253
  renderSongLink: resolvedRenderSongLink,
2181
2254
  onClose: () => changeArtist(null)
2182
2255
  });
2183
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2256
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2184
2257
  const getGenrePath = (genre) => {
2185
2258
  const path = [];
2186
2259
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2259,52 +2332,27 @@ function GenreTreeView({
2259
2332
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2260
2333
  const { showPopup, hidePopup } = usePopup5();
2261
2334
  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);
2335
+ const artistGroup = useArtistSearchGroup(
2336
+ artistBaseUrl,
2337
+ searchText,
2338
+ (artist) => {
2339
+ openArtist(artist.uuid);
2340
+ onArtistSelect?.(artist);
2341
+ },
2342
+ Boolean(artistSearch)
2343
+ );
2344
+ const songGroup = useSongSearchGroup(
2345
+ songBaseUrl,
2346
+ searchText,
2347
+ (song) => {
2348
+ openSong(song.uuid);
2349
+ onSongSelect?.(song);
2350
+ },
2351
+ Boolean(songSearch)
2352
+ );
2353
+ const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2354
+ (group) => group !== void 0
2355
+ );
2308
2356
  const handleSelectedNodeChange = (node) => {
2309
2357
  if (node !== null && songUuid !== null) changeSong(null);
2310
2358
  selectNode(node);
@@ -2328,7 +2376,7 @@ function GenreTreeView({
2328
2376
  };
2329
2377
  const renderGenreBody = (relation) => {
2330
2378
  const { node } = relation;
2331
- const family = /* @__PURE__ */ jsx29(
2379
+ const family = /* @__PURE__ */ jsx32(
2332
2380
  GenreNodeFamily,
2333
2381
  {
2334
2382
  node,
@@ -2339,10 +2387,10 @@ function GenreTreeView({
2339
2387
  onSelectNode: selectGenreFromPanel
2340
2388
  }
2341
2389
  );
2342
- const ancestors = /* @__PURE__ */ jsx29(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2390
+ const ancestors = /* @__PURE__ */ jsx32(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2343
2391
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2344
2392
  const criteria = genrePlaylist?.criteria;
2345
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2393
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2346
2394
  family,
2347
2395
  ancestors
2348
2396
  ] }) };
@@ -2362,11 +2410,11 @@ function GenreTreeView({
2362
2410
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2363
2411
  ) : void 0;
2364
2412
  return {
2365
- primary: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2366
- /* @__PURE__ */ jsx29(
2413
+ primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2414
+ /* @__PURE__ */ jsx32(
2367
2415
  GenreInfoPanelBody,
2368
2416
  {
2369
- family: /* @__PURE__ */ jsxs22(Fragment11, { children: [
2417
+ family: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2370
2418
  family,
2371
2419
  ancestors
2372
2420
  ] }),
@@ -2384,7 +2432,7 @@ function GenreTreeView({
2384
2432
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2385
2433
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2386
2434
  onChangeParent: readOnly ? void 0 : () => showPopup(
2387
- /* @__PURE__ */ jsx29(
2435
+ /* @__PURE__ */ jsx32(
2388
2436
  ChangeParentPopup,
2389
2437
  {
2390
2438
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2459,19 +2507,19 @@ function GenreTreeView({
2459
2507
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2460
2508
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2461
2509
  } : void 0;
2462
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx29(
2510
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx32(
2463
2511
  IconButton,
2464
2512
  {
2465
2513
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2466
2514
  style: activePanel.headerAccent && { color: "inherit" },
2467
2515
  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" })
2516
+ 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
2517
  }
2470
2518
  ) : null;
2471
2519
  const renderArtistFilter = () => {
2472
2520
  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(
2521
+ return /* @__PURE__ */ jsxs25("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2522
+ /* @__PURE__ */ jsx32(
2475
2523
  "button",
2476
2524
  {
2477
2525
  type: "button",
@@ -2483,7 +2531,7 @@ function GenreTreeView({
2483
2531
  children: filterArtist.name
2484
2532
  }
2485
2533
  ),
2486
- /* @__PURE__ */ jsx29(
2534
+ /* @__PURE__ */ jsx32(
2487
2535
  "button",
2488
2536
  {
2489
2537
  type: "button",
@@ -2495,8 +2543,8 @@ function GenreTreeView({
2495
2543
  )
2496
2544
  ] });
2497
2545
  };
2498
- const actions = /* @__PURE__ */ jsxs22(Fragment11, { children: [
2499
- !isLoading && /* @__PURE__ */ jsx29(
2546
+ const actions = /* @__PURE__ */ jsxs25(Fragment13, { children: [
2547
+ !isLoading && /* @__PURE__ */ jsx32(
2500
2548
  GenreSearch,
2501
2549
  {
2502
2550
  genrePlaylists: visibleGenrePlaylists,
@@ -2504,13 +2552,17 @@ function GenreTreeView({
2504
2552
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2505
2553
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2506
2554
  onPrevious: handlePrevious,
2507
- onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
2555
+ onQueryChange: (text) => {
2556
+ setSearchText(text);
2557
+ onSearchQueryChange?.(text);
2558
+ },
2559
+ onFocus: onSearchFocus,
2508
2560
  extraGroups
2509
2561
  }
2510
2562
  ),
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(
2563
+ !isLoading && filterArtist && /* @__PURE__ */ jsx32("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2564
+ /* @__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: [
2565
+ !isLoading && !isControlled && /* @__PURE__ */ jsx32(
2514
2566
  SegmentedControl2,
2515
2567
  {
2516
2568
  "aria-label": "Tree view mode",
@@ -2524,13 +2576,13 @@ function GenreTreeView({
2524
2576
  ]
2525
2577
  }
2526
2578
  ),
2527
- !isLoading && showDisplayOptions && /* @__PURE__ */ jsx29(
2579
+ !isLoading && showDisplayOptions && /* @__PURE__ */ jsx32(
2528
2580
  "div",
2529
2581
  {
2530
2582
  className: "flex items-center gap-1",
2531
2583
  role: "group",
2532
2584
  "aria-label": "Tree display options",
2533
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx29(
2585
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx32(
2534
2586
  Button,
2535
2587
  {
2536
2588
  variant: allowWheelRotation ? "default" : "outline",
@@ -2541,7 +2593,7 @@ function GenreTreeView({
2541
2593
  )
2542
2594
  }
2543
2595
  ),
2544
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx29(
2596
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx32(
2545
2597
  IconTextButton,
2546
2598
  {
2547
2599
  icon: Plus2,
@@ -2551,34 +2603,34 @@ function GenreTreeView({
2551
2603
  )
2552
2604
  ] })
2553
2605
  ] });
2554
- return /* @__PURE__ */ jsxs22(
2606
+ return /* @__PURE__ */ jsxs25(
2555
2607
  "div",
2556
2608
  {
2557
2609
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2558
2610
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2559
2611
  children: [
2560
- /* @__PURE__ */ jsx29(
2612
+ /* @__PURE__ */ jsx32(
2561
2613
  "div",
2562
2614
  {
2563
2615
  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
2616
  children: actions
2565
2617
  }
2566
2618
  ),
2567
- !isLoading && filterArtist && /* @__PURE__ */ jsx29(
2619
+ !isLoading && filterArtist && /* @__PURE__ */ jsx32(
2568
2620
  "div",
2569
2621
  {
2570
2622
  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
2623
  children: renderArtistFilter()
2572
2624
  }
2573
2625
  ),
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" ? (
2626
+ /* @__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: [
2627
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx32("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2628
+ isLoading ? /* @__PURE__ */ jsx32(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2577
2629
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2578
2630
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2579
2631
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2580
2632
  // 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(
2633
+ /* @__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
2634
  GenrePlaylistTreeWheel,
2583
2635
  {
2584
2636
  scope,
@@ -2595,9 +2647,9 @@ function GenreTreeView({
2595
2647
  allowWheelRotation,
2596
2648
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2597
2649
  busy,
2598
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
2650
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2599
2651
  floatingControlsLeading,
2600
- canSizeByViews && /* @__PURE__ */ jsx29(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2652
+ canSizeByViews && /* @__PURE__ */ jsx32(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2601
2653
  ] }) : void 0,
2602
2654
  defaultViewport,
2603
2655
  viewportRestore,
@@ -2606,7 +2658,7 @@ function GenreTreeView({
2606
2658
  ) }) })
2607
2659
  ) : viewMode === "pop-core" ? (
2608
2660
  // 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(
2661
+ /* @__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
2662
  GenrePlaylistTreeWheelRadialPopCore,
2611
2663
  {
2612
2664
  scope,
@@ -2623,9 +2675,9 @@ function GenreTreeView({
2623
2675
  allowWheelRotation,
2624
2676
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2625
2677
  busy,
2626
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
2678
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2627
2679
  floatingControlsLeading,
2628
- canSizeByViews && /* @__PURE__ */ jsx29(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2680
+ canSizeByViews && /* @__PURE__ */ jsx32(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2629
2681
  ] }) : void 0,
2630
2682
  defaultViewport,
2631
2683
  viewportRestore,
@@ -2633,7 +2685,7 @@ function GenreTreeView({
2633
2685
  ...focus
2634
2686
  }
2635
2687
  ) }) })
2636
- ) : viewMode === "outline" ? /* @__PURE__ */ jsx29("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx29(
2688
+ ) : viewMode === "outline" ? /* @__PURE__ */ jsx32("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx32(
2637
2689
  GenrePlaylistTreeWheelRadialPopCore,
2638
2690
  {
2639
2691
  outline: true,
@@ -2649,13 +2701,13 @@ function GenreTreeView({
2649
2701
  selectedNodeId,
2650
2702
  readOnly
2651
2703
  }
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(
2704
+ ) }) : /* @__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
2705
  ([uuid, genrePlaylistTreePerRoot]) => {
2654
- return /* @__PURE__ */ jsx29(
2706
+ return /* @__PURE__ */ jsx32(
2655
2707
  "div",
2656
2708
  {
2657
2709
  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(
2710
+ children: /* @__PURE__ */ jsx32("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx32(
2659
2711
  GenrePlaylistTreePerRoot,
2660
2712
  {
2661
2713
  scope,
@@ -2681,7 +2733,7 @@ function GenreTreeView({
2681
2733
  );
2682
2734
  }
2683
2735
  ) }),
2684
- /* @__PURE__ */ jsx29(
2736
+ /* @__PURE__ */ jsx32(
2685
2737
  DetailPanel,
2686
2738
  {
2687
2739
  ref: panelRef,
@@ -2698,7 +2750,7 @@ function GenreTreeView({
2698
2750
  aside: activePanel?.aside,
2699
2751
  edgeTab: detailTab,
2700
2752
  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: [
2753
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs25(Fragment13, { children: [
2702
2754
  activePanel.content,
2703
2755
  activePanel.aside
2704
2756
  ] }) : activePanel?.content
@@ -2725,7 +2777,7 @@ import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
2725
2777
  import {
2726
2778
  useFetchGenreDetail
2727
2779
  } from "@behindthemusictree/app-kit/genre-tree";
2728
- import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
2780
+ import { Fragment as Fragment14, jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2729
2781
  var RELATIVE_ITEM_COUNT = 1;
2730
2782
  function toNode(genre, parentId) {
2731
2783
  return {
@@ -2780,17 +2832,17 @@ function GenreDetailPanel({
2780
2832
  };
2781
2833
  }, [detail]);
2782
2834
  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." });
2835
+ return /* @__PURE__ */ jsx33("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2784
2836
  }
2785
2837
  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" })
2838
+ return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2839
+ /* @__PURE__ */ jsx33(Skeleton7, { className: "h-6 w-2/3" }),
2840
+ /* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-full" }),
2841
+ /* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-3/4" })
2790
2842
  ] });
2791
2843
  }
2792
2844
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
2793
- const family = /* @__PURE__ */ jsx30(
2845
+ const family = /* @__PURE__ */ jsx33(
2794
2846
  GenreNodeFamily2,
2795
2847
  {
2796
2848
  node: view.node,
@@ -2801,20 +2853,20 @@ function GenreDetailPanel({
2801
2853
  onSelectNode: selectGenre
2802
2854
  }
2803
2855
  );
2804
- const ancestors = /* @__PURE__ */ jsx30(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2805
- return /* @__PURE__ */ jsxs23(
2856
+ const ancestors = /* @__PURE__ */ jsx33(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2857
+ return /* @__PURE__ */ jsxs26(
2806
2858
  DetailPanel2,
2807
2859
  {
2808
2860
  side: "left",
2809
2861
  title: view.node.name,
2810
2862
  headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
2811
- headerActions: onExpandedChange ? /* @__PURE__ */ jsx30(
2863
+ headerActions: onExpandedChange ? /* @__PURE__ */ jsx33(
2812
2864
  IconButton2,
2813
2865
  {
2814
2866
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
2815
2867
  style: { color: "inherit" },
2816
2868
  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" })
2869
+ 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
2870
  }
2819
2871
  ) : void 0,
2820
2872
  onClose,
@@ -2823,10 +2875,10 @@ function GenreDetailPanel({
2823
2875
  onExpandedChange,
2824
2876
  edgeTab,
2825
2877
  children: [
2826
- /* @__PURE__ */ jsx30(
2878
+ /* @__PURE__ */ jsx33(
2827
2879
  GenreInfoPanelBody,
2828
2880
  {
2829
- family: /* @__PURE__ */ jsxs23(Fragment12, { children: [
2881
+ family: /* @__PURE__ */ jsxs26(Fragment14, { children: [
2830
2882
  family,
2831
2883
  ancestors
2832
2884
  ] }),
@@ -2860,7 +2912,7 @@ import { ListMusic } from "lucide-react";
2860
2912
  import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
2861
2913
  import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
2862
2914
  import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
2863
- import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
2915
+ import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
2864
2916
  var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
2865
2917
  function TrackListSidebar({
2866
2918
  className: className2 = "",
@@ -2962,25 +3014,25 @@ function TrackListSidebar({
2962
3014
  }
2963
3015
  };
2964
3016
  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(
3017
+ return trackList ? /* @__PURE__ */ jsxs27(
2966
3018
  "div",
2967
3019
  {
2968
3020
  "data-theme": "dark",
2969
3021
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
2970
3022
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
2971
3023
  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 }) })
3024
+ /* @__PURE__ */ jsxs27("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3025
+ /* @__PURE__ */ jsxs27("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3026
+ /* @__PURE__ */ jsxs27("div", { className: "origin flex text-xl", children: [
3027
+ /* @__PURE__ */ jsx34("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3028
+ /* @__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
3029
  ] }),
2978
- /* @__PURE__ */ jsxs24("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3030
+ /* @__PURE__ */ jsxs27("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
2979
3031
  trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
2980
3032
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
2981
3033
  ] })
2982
3034
  ] }),
2983
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx31(
3035
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx34(
2984
3036
  "button",
2985
3037
  {
2986
3038
  type: "button",
@@ -2989,11 +3041,11 @@ function TrackListSidebar({
2989
3041
  "aria-expanded": !isCollapsed,
2990
3042
  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
3043
  onClick: isCollapsed ? onExpand : onMinimize,
2992
- children: /* @__PURE__ */ jsx31(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3044
+ children: /* @__PURE__ */ jsx34(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
2993
3045
  }
2994
3046
  )
2995
3047
  ] }),
2996
- !isCollapsed && /* @__PURE__ */ jsxs24(
3048
+ !isCollapsed && /* @__PURE__ */ jsxs27(
2997
3049
  "ul",
2998
3050
  {
2999
3051
  ref: trackListRef,
@@ -3003,7 +3055,7 @@ function TrackListSidebar({
3003
3055
  onPointerDown: markUserScrollIntent,
3004
3056
  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
3057
  children: [
3006
- trackList.tracks.map((track, index) => /* @__PURE__ */ jsx31("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx31(
3058
+ trackList.tracks.map((track, index) => /* @__PURE__ */ jsx34("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx34(
3007
3059
  TrackItem,
3008
3060
  {
3009
3061
  track,
@@ -3014,7 +3066,7 @@ function TrackListSidebar({
3014
3066
  renderSongLink
3015
3067
  }
3016
3068
  ) }, track.uuid)),
3017
- hasMore && /* @__PURE__ */ jsx31("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3069
+ hasMore && /* @__PURE__ */ jsx34("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3018
3070
  ]
3019
3071
  }
3020
3072
  )
@@ -3039,7 +3091,7 @@ import { PanelEdgeTab } from "@behindthemusictree/ui";
3039
3091
  import { Pause as Pause3, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
3040
3092
  import { formatTime, useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
3041
3093
  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";
3094
+ import { jsx as jsx35, jsxs as jsxs28 } from "react/jsx-runtime";
3043
3095
  function PlayerControls() {
3044
3096
  const {
3045
3097
  duration,
@@ -3058,15 +3110,15 @@ function PlayerControls() {
3058
3110
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3059
3111
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3060
3112
  const isPlaying = playState === PlayStates4.PLAYING;
3061
- return /* @__PURE__ */ jsxs25(
3113
+ return /* @__PURE__ */ jsxs28(
3062
3114
  "section",
3063
3115
  {
3064
3116
  "aria-label": "Player controls",
3065
3117
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3066
3118
  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(
3119
+ /* @__PURE__ */ jsxs28("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3120
+ /* @__PURE__ */ jsx35("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3121
+ /* @__PURE__ */ jsx35(
3070
3122
  "input",
3071
3123
  {
3072
3124
  "aria-label": "Seek",
@@ -3080,11 +3132,11 @@ function PlayerControls() {
3080
3132
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3081
3133
  }
3082
3134
  ),
3083
- /* @__PURE__ */ jsx32("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3135
+ /* @__PURE__ */ jsx35("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3084
3136
  ] }),
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(
3137
+ /* @__PURE__ */ jsxs28("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3138
+ /* @__PURE__ */ jsxs28("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3139
+ /* @__PURE__ */ jsx35(
3088
3140
  "button",
3089
3141
  {
3090
3142
  type: "button",
@@ -3093,10 +3145,10 @@ function PlayerControls() {
3093
3145
  disabled: !isReady || !hasPreviousTrack,
3094
3146
  onClick: playPreviousTrack,
3095
3147
  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" })
3148
+ children: /* @__PURE__ */ jsx35(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3097
3149
  }
3098
3150
  ),
3099
- /* @__PURE__ */ jsx32(
3151
+ /* @__PURE__ */ jsx35(
3100
3152
  "button",
3101
3153
  {
3102
3154
  type: "button",
@@ -3105,10 +3157,10 @@ function PlayerControls() {
3105
3157
  disabled: !isReady,
3106
3158
  onClick: handlePlayPauseAction,
3107
3159
  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" })
3160
+ 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
3161
  }
3110
3162
  ),
3111
- /* @__PURE__ */ jsx32(
3163
+ /* @__PURE__ */ jsx35(
3112
3164
  "button",
3113
3165
  {
3114
3166
  type: "button",
@@ -3117,14 +3169,14 @@ function PlayerControls() {
3117
3169
  disabled: !isReady || !hasNextTrack,
3118
3170
  onClick: playNextTrack,
3119
3171
  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" })
3172
+ children: /* @__PURE__ */ jsx35(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3121
3173
  }
3122
3174
  )
3123
3175
  ] }),
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(
3176
+ /* @__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: [
3177
+ 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" }),
3178
+ /* @__PURE__ */ jsx35("span", { className: "sr-only", children: "Volume" }),
3179
+ /* @__PURE__ */ jsx35(
3128
3180
  "input",
3129
3181
  {
3130
3182
  "aria-label": "Volume",
@@ -3146,7 +3198,7 @@ function PlayerControls() {
3146
3198
  }
3147
3199
 
3148
3200
  // src/genre-tree/PlayerPanel.tsx
3149
- import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
3201
+ import { jsx as jsx36, jsxs as jsxs29 } from "react/jsx-runtime";
3150
3202
  var PANEL_ID = "player-panel";
3151
3203
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3152
3204
  function PlayerPanel({
@@ -3186,7 +3238,7 @@ function PlayerPanel({
3186
3238
  onClick: () => setIsExtendRequested(true),
3187
3239
  expanded: false
3188
3240
  } : null;
3189
- return /* @__PURE__ */ jsxs26(
3241
+ return /* @__PURE__ */ jsxs29(
3190
3242
  "div",
3191
3243
  {
3192
3244
  id: PANEL_ID,
@@ -3199,7 +3251,7 @@ function PlayerPanel({
3199
3251
  ),
3200
3252
  style: isExtended ? { width: extendedWidth } : void 0,
3201
3253
  children: [
3202
- /* @__PURE__ */ jsx33(
3254
+ /* @__PURE__ */ jsx36(
3203
3255
  PanelEdgeTab,
3204
3256
  {
3205
3257
  side: "right",
@@ -3209,7 +3261,7 @@ function PlayerPanel({
3209
3261
  className: "absolute right-full top-1/2 -translate-y-1/2"
3210
3262
  }
3211
3263
  ),
3212
- /* @__PURE__ */ jsxs26(
3264
+ /* @__PURE__ */ jsxs29(
3213
3265
  "div",
3214
3266
  {
3215
3267
  className: twMerge(
@@ -3217,20 +3269,20 @@ function PlayerPanel({
3217
3269
  isExtended ? "flex-row" : "flex-col"
3218
3270
  ),
3219
3271
  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: [
3272
+ /* @__PURE__ */ jsxs29("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3273
+ /* @__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 }) }),
3274
+ /* @__PURE__ */ jsxs29("div", { className: "relative w-full shrink-0", children: [
3275
+ /* @__PURE__ */ jsx36(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3276
+ playerTrackObject?.loadError && /* @__PURE__ */ jsxs29("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3225
3277
  playerTrackObject.loadError,
3226
- /* @__PURE__ */ jsx33("button", { type: "button", className: "ml-2 underline", onClick: () => {
3278
+ /* @__PURE__ */ jsx36("button", { type: "button", className: "ml-2 underline", onClick: () => {
3227
3279
  void retryPlayback();
3228
3280
  }, children: "Retry" })
3229
3281
  ] })
3230
3282
  ] }),
3231
- /* @__PURE__ */ jsx33(PlayerControls, {})
3283
+ /* @__PURE__ */ jsx36(PlayerControls, {})
3232
3284
  ] }),
3233
- /* @__PURE__ */ jsx33(
3285
+ /* @__PURE__ */ jsx36(
3234
3286
  TrackListSidebar,
3235
3287
  {
3236
3288
  layout: "inline",
@@ -3257,11 +3309,11 @@ function PlayerPanel({
3257
3309
  import { v4 as uuidv4 } from "uuid";
3258
3310
  import { AiFillStar, AiOutlineStar } from "react-icons/ai";
3259
3311
  import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
3260
- import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
3312
+ import { jsx as jsx37, jsxs as jsxs30 } from "react/jsx-runtime";
3261
3313
  function Rating({ rating, handleChange }) {
3262
3314
  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(
3315
+ 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: [
3316
+ /* @__PURE__ */ jsx37(
3265
3317
  "input",
3266
3318
  {
3267
3319
  id: `star-${instanceId}-${i}`,
@@ -3273,16 +3325,16 @@ function Rating({ rating, handleChange }) {
3273
3325
  onChange: handleChange
3274
3326
  }
3275
3327
  ),
3276
- /* @__PURE__ */ jsx34("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs27("div", { className: "flex", children: [
3277
- /* @__PURE__ */ jsx34(
3328
+ /* @__PURE__ */ jsx37("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs30("div", { className: "flex", children: [
3329
+ /* @__PURE__ */ jsx37(
3278
3330
  AiOutlineStar,
3279
3331
  {
3280
3332
  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
3333
  }
3282
3334
  ),
3283
- /* @__PURE__ */ jsx34("span", { className: "flex-grow w-8" }),
3335
+ /* @__PURE__ */ jsx37("span", { className: "flex-grow w-8" }),
3284
3336
  " "
3285
- ] }) : /* @__PURE__ */ jsx34(
3337
+ ] }) : /* @__PURE__ */ jsx37(
3286
3338
  AiFillStar,
3287
3339
  {
3288
3340
  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 +3346,12 @@ function Rating({ rating, handleChange }) {
3294
3346
  export {
3295
3347
  useGenrePlaylistPlayback,
3296
3348
  GenrePlaylistTreePerRoot,
3349
+ SearchBox,
3350
+ SEARCH_FAILED_STATUS,
3351
+ searchStatus,
3352
+ useArtistSearchGroup,
3353
+ useSongSearchGroup,
3354
+ useGenreRemoteSearchGroup,
3297
3355
  GenreSearch,
3298
3356
  GenreNodeActionBand,
3299
3357
  GenreInfoPanelBody,
@@ -3309,4 +3367,4 @@ export {
3309
3367
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3310
3368
  GenreTreeOutlineSkeleton
3311
3369
  };
3312
- //# sourceMappingURL=chunk-2M4Q2IJU.js.map
3370
+ //# sourceMappingURL=chunk-QMELESFB.js.map