@sinequa/atomic-angular 2.4.0 → 2.5.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.
@@ -2,7 +2,7 @@ import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, HostBinding, HostListener, Inject, Injectable, InjectionToken, Injector, LOCALE_ID, Optional, Pipe, Service, TemplateRef, ViewEncapsulation, afterNextRender, afterRenderEffect, assertInInjectionContext, booleanAttribute, computed, contentChild, contentChildren, effect, inject, input, isDevMode, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, provideEnvironmentInitializer, resource, runInInjectionContext, signal, untracked, viewChild, viewChildren } from "@angular/core";
3
3
  import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, scan, shareReplay, switchMap, tap, throwError } from "rxjs";
4
4
  import { TranslocoPipe, TranslocoService, provideTranslocoScope } from "@jsverse/transloco";
5
- import { ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef } from "@sinequa/galactik";
5
+ import { ArrowDownIcon, ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef, isApplePlatform } from "@sinequa/galactik";
6
6
  import { withDevtools } from "@angular-architects/ngrx-toolkit";
7
7
  import { getState, patchState, signalStore, signalStoreFeature, withComputed, withMethods, withState } from "@ngrx/signals";
8
8
  import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
@@ -6619,7 +6619,7 @@ i0.ɵɵngDeclareClassMetadata({
6619
6619
  }] }
6620
6620
  });
6621
6621
  const Export = createCallable(ExportDialog);
6622
- const multiSelectionToolbarVariants = cva("flex divide-x rounded-xl bg-linear-to-b px-3 py-2 shadow [&_ul]:flex [&_ul]:flex-col [&_ul]:p-1 [&>li]:px-2", {
6622
+ const multiSelectionToolbarVariants = cva("flex divide-x rounded-xl bg-linear-to-b px-3 py-2 shadow [&_ul]:flex [&_ul]:flex-col [&_ul]:p-1 [&>div]:px-2", {
6623
6623
  variants: { variant: {
6624
6624
  dark: "divide-gray-800 bg-gray-900 from-gray-500 to-gray-900 text-white",
6625
6625
  light: "divide-gray-400 bg-white from-white to-purple-50 text-gray-800",
@@ -6635,7 +6635,9 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6635
6635
  appFeatures = this.appStore.general();
6636
6636
  class = input("", ...ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []);
6637
6637
  variant = input("dark", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
6638
+ hint = input(...ngDevMode ? [void 0, { debugName: "hint" }] : /* istanbul ignore next */ []);
6638
6639
  updatedCollections = output();
6640
+ cleared = output();
6639
6641
  count = this.selection.multiSelectCount;
6640
6642
  variants = computed(() => cn(multiSelectionToolbarVariants({
6641
6643
  variant: this.variant(),
@@ -6651,7 +6653,27 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6651
6653
  return `search-results-assistant`;
6652
6654
  }, ...ngDevMode ? [{ debugName: "instanceId" }] : /* istanbul ignore next */ []);
6653
6655
  enabledUserInput = computed(() => this.appStore.assistants()[this.instanceId()]?.modeSettings.enabledUserInput === true, ...ngDevMode ? [{ debugName: "enabledUserInput" }] : /* istanbul ignore next */ []);
6654
- clearSelection = () => this.selection.clearMultiSelection();
6656
+ showHint = signal(false, ...ngDevMode ? [{ debugName: "showHint" }] : /* istanbul ignore next */ []);
6657
+ armed = true;
6658
+ constructor() {
6659
+ effect(() => {
6660
+ const count = this.count();
6661
+ untracked(() => {
6662
+ if (count === 0) {
6663
+ this.armed = true;
6664
+ this.showHint.set(false);
6665
+ } else if (this.armed) {
6666
+ this.armed = false;
6667
+ this.showHint.set(true);
6668
+ } else this.showHint.set(false);
6669
+ });
6670
+ });
6671
+ }
6672
+ clearSelection = () => {
6673
+ if (this.count() === 0) return;
6674
+ this.selection.clearMultiSelection();
6675
+ this.cleared.emit();
6676
+ };
6655
6677
  openCollectionDialog = () => AddToCollection.call(getState(this.selection).multiSelection, { injector: this.injector }).then((event) => {
6656
6678
  if (event === "dialog-no" || event === "dialog-yes") {
6657
6679
  this.updatedCollections.emit();
@@ -6688,9 +6710,19 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6688
6710
  isSignal: true,
6689
6711
  isRequired: false,
6690
6712
  transformFunction: null
6713
+ },
6714
+ hint: {
6715
+ classPropertyName: "hint",
6716
+ publicName: "hint",
6717
+ isSignal: true,
6718
+ isRequired: false,
6719
+ transformFunction: null
6691
6720
  }
6692
6721
  },
6693
- outputs: { updatedCollections: "updatedCollections" },
6722
+ outputs: {
6723
+ updatedCollections: "updatedCollections",
6724
+ cleared: "cleared"
6725
+ },
6694
6726
  host: {
6695
6727
  properties: {
6696
6728
  "class.opacity-100": "count() > 0",
@@ -6701,37 +6733,57 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6701
6733
  providers: [provideTranslocoScope("multi-selection-toolbar", "article", "collections", "export")],
6702
6734
  ngImport: i0,
6703
6735
  template: `
6704
- <menu id="multi-select-toolbar" [class]="cn(variants(), class())">
6705
- <li class="ps-0!">
6736
+ <!-- Région de statut hors de la barre : celle-ci est inerte sans sélection, et une région
6737
+ inerte n'est pas annoncée. Le texte n'existe que tant que la sélection est non vide. -->
6738
+ <span role="status" class="sr-only">
6739
+ @if (count() > 0) {
6740
+ {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6741
+ @if (showHint() && hint()) {
6742
+ . {{ hint() }}
6743
+ }
6744
+ }
6745
+ </span>
6746
+
6747
+ <!-- inert sans sélection : la barre n'est que transparente et hors écran, ses boutons resteraient
6748
+ sinon dans l'ordre de tabulation et activables (Entrée ouvrait « Ajouter à une collection » sur
6749
+ un panier vide, ES-31356). -->
6750
+ <div
6751
+ id="multi-select-toolbar"
6752
+ role="toolbar"
6753
+ [attr.aria-label]="'multiSelectionToolbar.label' | transloco"
6754
+ [attr.inert]="count() === 0 ? '' : null"
6755
+ (keydown.escape)="clearSelection()"
6756
+ [class]="cn(variants(), class())">
6757
+ <div class="ps-0!">
6706
6758
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="clearSelection()">
6707
6759
  {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6708
- <x-mark-icon />
6760
+ <x-mark-icon aria-hidden="true" />
6709
6761
  </button>
6710
- </li>
6762
+ </div>
6711
6763
 
6712
- <li>
6713
- <button class="cursor-pointer inline-flex items-center gap-1" title="menu" [attr.aria-label]="'menu'" (click)="openCollectionDialog()">
6714
- <inbox-icon />
6764
+ <div>
6765
+ <button class="cursor-pointer inline-flex items-center gap-1" (click)="openCollectionDialog()">
6766
+ <inbox-icon aria-hidden="true" />
6715
6767
  {{ 'collections.label' | transloco }}
6716
6768
  </button>
6717
- </li>
6769
+ </div>
6718
6770
 
6719
- <li>
6771
+ <div>
6720
6772
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="openExportDialog()">
6721
- <file-output-icon />
6773
+ <file-output-icon aria-hidden="true" />
6722
6774
  {{ 'export.title' | transloco }}
6723
6775
  </button>
6724
- </li>
6776
+ </div>
6725
6777
 
6726
6778
  @if (enabledUserInput()) {
6727
- <li class="pe-0!">
6779
+ <div class="pe-0!">
6728
6780
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="attachToAssistant()">
6729
- <sparkles-icon />
6781
+ <sparkles-icon aria-hidden="true" />
6730
6782
  {{ 'article.addToAIOverview' | transloco }}
6731
6783
  </button>
6732
- </li>
6784
+ </div>
6733
6785
  }
6734
- </menu>
6786
+ </div>
6735
6787
  `,
6736
6788
  isInline: true,
6737
6789
  dependencies: [
@@ -6785,37 +6837,57 @@ i0.ɵɵngDeclareClassMetadata({
6785
6837
  ],
6786
6838
  providers: [provideTranslocoScope("multi-selection-toolbar", "article", "collections", "export")],
6787
6839
  template: `
6788
- <menu id="multi-select-toolbar" [class]="cn(variants(), class())">
6789
- <li class="ps-0!">
6840
+ <!-- Région de statut hors de la barre : celle-ci est inerte sans sélection, et une région
6841
+ inerte n'est pas annoncée. Le texte n'existe que tant que la sélection est non vide. -->
6842
+ <span role="status" class="sr-only">
6843
+ @if (count() > 0) {
6844
+ {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6845
+ @if (showHint() && hint()) {
6846
+ . {{ hint() }}
6847
+ }
6848
+ }
6849
+ </span>
6850
+
6851
+ <!-- inert sans sélection : la barre n'est que transparente et hors écran, ses boutons resteraient
6852
+ sinon dans l'ordre de tabulation et activables (Entrée ouvrait « Ajouter à une collection » sur
6853
+ un panier vide, ES-31356). -->
6854
+ <div
6855
+ id="multi-select-toolbar"
6856
+ role="toolbar"
6857
+ [attr.aria-label]="'multiSelectionToolbar.label' | transloco"
6858
+ [attr.inert]="count() === 0 ? '' : null"
6859
+ (keydown.escape)="clearSelection()"
6860
+ [class]="cn(variants(), class())">
6861
+ <div class="ps-0!">
6790
6862
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="clearSelection()">
6791
6863
  {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6792
- <x-mark-icon />
6864
+ <x-mark-icon aria-hidden="true" />
6793
6865
  </button>
6794
- </li>
6866
+ </div>
6795
6867
 
6796
- <li>
6797
- <button class="cursor-pointer inline-flex items-center gap-1" title="menu" [attr.aria-label]="'menu'" (click)="openCollectionDialog()">
6798
- <inbox-icon />
6868
+ <div>
6869
+ <button class="cursor-pointer inline-flex items-center gap-1" (click)="openCollectionDialog()">
6870
+ <inbox-icon aria-hidden="true" />
6799
6871
  {{ 'collections.label' | transloco }}
6800
6872
  </button>
6801
- </li>
6873
+ </div>
6802
6874
 
6803
- <li>
6875
+ <div>
6804
6876
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="openExportDialog()">
6805
- <file-output-icon />
6877
+ <file-output-icon aria-hidden="true" />
6806
6878
  {{ 'export.title' | transloco }}
6807
6879
  </button>
6808
- </li>
6880
+ </div>
6809
6881
 
6810
6882
  @if (enabledUserInput()) {
6811
- <li class="pe-0!">
6883
+ <div class="pe-0!">
6812
6884
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="attachToAssistant()">
6813
- <sparkles-icon />
6885
+ <sparkles-icon aria-hidden="true" />
6814
6886
  {{ 'article.addToAIOverview' | transloco }}
6815
6887
  </button>
6816
- </li>
6888
+ </div>
6817
6889
  }
6818
- </menu>
6890
+ </div>
6819
6891
  `,
6820
6892
  host: {
6821
6893
  class: "fixed -bottom-20 left-[50%] translate-x-[-50%] z-50 transition-[translate,opacity,discrete] duration-300 opacity-0",
@@ -6824,6 +6896,7 @@ i0.ɵɵngDeclareClassMetadata({
6824
6896
  }
6825
6897
  }]
6826
6898
  }],
6899
+ ctorParameters: () => [],
6827
6900
  propDecorators: {
6828
6901
  class: [{
6829
6902
  type: i0.Input,
@@ -6841,9 +6914,21 @@ i0.ɵɵngDeclareClassMetadata({
6841
6914
  required: false
6842
6915
  }]
6843
6916
  }],
6917
+ hint: [{
6918
+ type: i0.Input,
6919
+ args: [{
6920
+ isSignal: true,
6921
+ alias: "hint",
6922
+ required: false
6923
+ }]
6924
+ }],
6844
6925
  updatedCollections: [{
6845
6926
  type: i0.Output,
6846
6927
  args: ["updatedCollections"]
6928
+ }],
6929
+ cleared: [{
6930
+ type: i0.Output,
6931
+ args: ["cleared"]
6847
6932
  }]
6848
6933
  }
6849
6934
  });
@@ -9985,20 +10070,12 @@ var AdvancedSearchTabs = class AdvancedSearchTabs {
9985
10070
  executeSearch() {
9986
10071
  this.selectionStore.update({ queryText: this.queryText() });
9987
10072
  }
9988
- constructor(destroyRef) {
9989
- const host = inject(ElementRef).nativeElement;
9990
- const onKeydownCapture = (event) => {
9991
- if (event.key === "Enter") this.executeSearch();
9992
- };
9993
- host.addEventListener("keydown", onKeydownCapture, true);
9994
- destroyRef.onDestroy(() => host.removeEventListener("keydown", onKeydownCapture, true));
9995
- }
9996
10073
  static ɵfac = i0.ɵɵngDeclareFactory({
9997
10074
  minVersion: "12.0.0",
9998
10075
  version: "22.2.1",
9999
10076
  ngImport: i0,
10000
10077
  type: AdvancedSearchTabs,
10001
- deps: [{ token: i0.DestroyRef }],
10078
+ deps: [],
10002
10079
  target: i0.ɵɵFactoryTarget.Component
10003
10080
  });
10004
10081
  static ɵcmp = i0.ɵɵngDeclareComponent({
@@ -10034,24 +10111,12 @@ var AdvancedSearchTabs = class AdvancedSearchTabs {
10034
10111
  host: { classAttribute: "flex min-h-0 flex-col overflow-x-hidden text-(length:--size-xs)" },
10035
10112
  providers: [provideTranslocoScope("drawers")],
10036
10113
  ngImport: i0,
10037
- template: "@if (article()) {\n <section class=\"mx-3 mt-3 mb-3 flex shrink-0 flex-col gap-4\">\n <!-- Enter submits via the capture-phase listener in the component (see the constructor) — a\n bubble-phase (keydown.enter) here never sees it, Combobox's own input listener swallows it\n first. (search) still covers the click-the-send-button path. No debounce: same \"submit on\n Enter/send, not on every keystroke\" contract as the previous input-group implementation. -->\n <SearchBar\n size=\"small\"\n [placeholder]=\"'drawers.inputPlaceholder' | transloco\"\n [ariaLabel]=\"'drawers.inputPlaceholder' | transloco\"\n [(value)]=\"queryText\"\n (search)=\"executeSearch()\" />\n </section>\n\n <!-- flex-1/min-h-0: this is what lets the panel content below be the only thing that scrolls —\n the search bar/tab list above are shrink-0, never pushed off or scrolled themselves. -->\n <Tabs class=\"flex min-h-0 flex-1 flex-col\">\n <!-- noTruncate skips the default `truncate` branch (whose `has-[span]:[&>span]:truncate` would\n otherwise force this span back to single-line ellipsis, defeating the wrap below); overriding\n its own `min-w-fit` with `min-w-[auto]` restores the flex item's normal \"automatic minimum\n size\" instead — for content that can wrap, that floor is the longest word (min-content), not\n the full unwrapped label (max-content, what `fit-content` resolves to here with nothing\n constraining the available space). A single-word label has nothing to wrap, so min-content ==\n max-content and it can't be squeezed below its own readable width — the flex-shrink deficit\n falls on the multi-word labels that actually have room to give, which wrap onto the span's\n line-clamp-2 instead of being cut with an ellipsis (appropriate for a single-line pill, not\n for a tab bar where \"Similar documents\"/\"Match locations\" routinely don't fit on one line).\n h-9 (fixed, not h-auto) + items-center on every tab keeps the row visually even whether a\n given label wraps onto 2 lines or stays on 1. No horizontal scroll/truncation fallback here on\n purpose — see preview-actions.ts's [matchTriggerWidth]=\"false\" on the search popover: it's the\n popover itself that grows (shrink-to-fit, up to max-w-xl) to make room for this row instead of\n this row squeezing to fit a fixed popover width. -->\n <TabList\n class=\"mx-3 shrink-0\"\n variant=\"inner\"\n [selectedTab]=\"selectedTab()\"\n (selectedTabChange)=\"activeTab.set($event)\">\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"extracts\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"extracts\" | transloco }}</span\n >\n </Tab>\n @if (entities.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"entities\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.entitiesTab\" | transloco }}</span\n >\n </Tab>\n }\n @if (matchLocations.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"matchlocations\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"column.matchlocations\" | transloco }}</span\n >\n </Tab>\n }\n @if (labels.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"labels\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"labels\" | transloco }}</span\n >\n </Tab>\n }\n @if (similar.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"similar\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.similarDocuments\" | transloco }}</span\n >\n </Tab>\n }\n </TabList>\n\n <!-- The single scroll container: each TabPanel is `absolute inset-0` (only the active one\n visible, per TabPanelComponent's own `[class.hidden]`), so they all share this one\n min-h-0/flex-1/overflow-y-auto region instead of the whole component scrolling. -->\n <div class=\"relative m-3 min-h-0 flex-1 scrollbar-thin overflow-y-auto\">\n <!-- Every TabPanel below cedes its own tab stop to its content (TabPanelComponent's `tabIndex`\n doc, ui/tabs/tabs.ts): the APG makes a tabpanel focusable only when it holds no focusable\n child, otherwise Tab from the tab lands on the empty container instead of the first row.\n `extracts` is always rendered (own empty/loading states, per the class doc above) so its\n tabindex is computed from whether it currently has any focusable row;\n `entities`/`matchlocations`/`labels`/`similar` are only ever mounted via `hasContent()`,\n which — per their `injectXState` — guarantees at least one row, so a static -1 is safe there. -->\n <TabPanel\n value=\"extracts\"\n [tabindex]=\"\n extracts.loading() || extracts.extracts().length === 0 ? 0 : -1\n \"\n class=\"absolute inset-0\">\n <article-extracts-tab-panel [state]=\"extracts\" />\n </TabPanel>\n @if (entities.hasContent()) {\n <TabPanel value=\"entities\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-entities-tab-panel [state]=\"entities\" />\n </TabPanel>\n }\n @if (matchLocations.hasContent()) {\n <TabPanel value=\"matchlocations\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-match-locations-tab-panel [state]=\"matchLocations\" />\n </TabPanel>\n }\n @if (labels.hasContent()) {\n <TabPanel value=\"labels\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-labels-tab-panel [article]=\"article()\" [state]=\"labels\" />\n </TabPanel>\n }\n @if (similar.hasContent()) {\n <TabPanel value=\"similar\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-similar-documents-tab-panel\n [article]=\"article()\"\n [strategy]=\"previewStrategy()\"\n [state]=\"similar\"\n (selected)=\"selected.emit($event)\" />\n </TabPanel>\n }\n </div>\n </Tabs>\n}\n",
10114
+ template: "@if (article()) {\n <section class=\"mx-3 mt-3 mb-3 flex shrink-0 flex-col gap-4\">\n <!-- Enter submits through (keydown.enter), the send button through (search). No debounce: same \"submit on\n Enter/send, not on every keystroke\" contract as the previous input-group implementation. -->\n <SearchBar\n size=\"small\"\n [placeholder]=\"'drawers.inputPlaceholder' | transloco\"\n [aria-label]=\"'drawers.inputPlaceholder' | transloco\"\n [(value)]=\"queryText\"\n (keydown.enter)=\"executeSearch()\"\n (search)=\"executeSearch()\" />\n </section>\n\n <!-- flex-1/min-h-0: this is what lets the panel content below be the only thing that scrolls —\n the search bar/tab list above are shrink-0, never pushed off or scrolled themselves. -->\n <Tabs class=\"flex min-h-0 flex-1 flex-col\">\n <!-- noTruncate skips the default `truncate` branch (whose `has-[span]:[&>span]:truncate` would\n otherwise force this span back to single-line ellipsis, defeating the wrap below); overriding\n its own `min-w-fit` with `min-w-[auto]` restores the flex item's normal \"automatic minimum\n size\" instead — for content that can wrap, that floor is the longest word (min-content), not\n the full unwrapped label (max-content, what `fit-content` resolves to here with nothing\n constraining the available space). A single-word label has nothing to wrap, so min-content ==\n max-content and it can't be squeezed below its own readable width — the flex-shrink deficit\n falls on the multi-word labels that actually have room to give, which wrap onto the span's\n line-clamp-2 instead of being cut with an ellipsis (appropriate for a single-line pill, not\n for a tab bar where \"Similar documents\"/\"Match locations\" routinely don't fit on one line).\n h-9 (fixed, not h-auto) + items-center on every tab keeps the row visually even whether a\n given label wraps onto 2 lines or stays on 1. No horizontal scroll/truncation fallback here on\n purpose — see preview-actions.ts's [matchTriggerWidth]=\"false\" on the search popover: it's the\n popover itself that grows (shrink-to-fit, up to max-w-xl) to make room for this row instead of\n this row squeezing to fit a fixed popover width. -->\n <TabList\n class=\"mx-3 shrink-0\"\n variant=\"inner\"\n [selectedTab]=\"selectedTab()\"\n (selectedTabChange)=\"activeTab.set($event)\">\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"extracts\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"extracts\" | transloco }}</span\n >\n </Tab>\n @if (entities.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"entities\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.entitiesTab\" | transloco }}</span\n >\n </Tab>\n }\n @if (matchLocations.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"matchlocations\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"column.matchlocations\" | transloco }}</span\n >\n </Tab>\n }\n @if (labels.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"labels\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"labels\" | transloco }}</span\n >\n </Tab>\n }\n @if (similar.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"similar\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.similarDocuments\" | transloco }}</span\n >\n </Tab>\n }\n </TabList>\n\n <!-- The single scroll container: each TabPanel is `absolute inset-0` (only the active one\n visible, per TabPanelComponent's own `[class.hidden]`), so they all share this one\n min-h-0/flex-1/overflow-y-auto region instead of the whole component scrolling. -->\n <div class=\"relative m-3 min-h-0 flex-1 scrollbar-thin overflow-y-auto\">\n <!-- Every TabPanel below cedes its own tab stop to its content (TabPanelComponent's `tabIndex`\n doc, ui/tabs/tabs.ts): the APG makes a tabpanel focusable only when it holds no focusable\n child, otherwise Tab from the tab lands on the empty container instead of the first row.\n `extracts` is always rendered (own empty/loading states, per the class doc above) so its\n tabindex is computed from whether it currently has any focusable row;\n `entities`/`matchlocations`/`labels`/`similar` are only ever mounted via `hasContent()`,\n which — per their `injectXState` — guarantees at least one row, so a static -1 is safe there. -->\n <TabPanel\n value=\"extracts\"\n [tabindex]=\"\n extracts.loading() || extracts.extracts().length === 0 ? 0 : -1\n \"\n class=\"absolute inset-0\">\n <article-extracts-tab-panel [state]=\"extracts\" />\n </TabPanel>\n @if (entities.hasContent()) {\n <TabPanel value=\"entities\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-entities-tab-panel [state]=\"entities\" />\n </TabPanel>\n }\n @if (matchLocations.hasContent()) {\n <TabPanel value=\"matchlocations\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-match-locations-tab-panel [state]=\"matchLocations\" />\n </TabPanel>\n }\n @if (labels.hasContent()) {\n <TabPanel value=\"labels\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-labels-tab-panel [article]=\"article()\" [state]=\"labels\" />\n </TabPanel>\n }\n @if (similar.hasContent()) {\n <TabPanel value=\"similar\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-similar-documents-tab-panel\n [article]=\"article()\"\n [strategy]=\"previewStrategy()\"\n [state]=\"similar\"\n (selected)=\"selected.emit($event)\" />\n </TabPanel>\n }\n </div>\n </Tabs>\n}\n",
10038
10115
  dependencies: [
10039
10116
  {
10040
10117
  kind: "component",
10041
10118
  type: SearchBarComponent,
10042
- selector: "SearchBar, search-bar, searchbar",
10043
- inputs: [
10044
- "value",
10045
- "placeholder",
10046
- "ariaLabel",
10047
- "clearLabel",
10048
- "sendLabel",
10049
- "disabled",
10050
- "hotkey",
10051
- "size",
10052
- "class"
10053
- ],
10054
- outputs: ["valueChange", "search"]
10119
+ selector: "SearchBar:not([suggestions]), search-bar:not([suggestions]), searchbar:not([suggestions])"
10055
10120
  },
10056
10121
  {
10057
10122
  kind: "directive",
@@ -10149,10 +10214,9 @@ i0.ɵɵngDeclareClassMetadata({
10149
10214
  ArticleMatchLocationsTabPanel,
10150
10215
  ArticleSimilarDocumentsTabPanel
10151
10216
  ],
10152
- template: "@if (article()) {\n <section class=\"mx-3 mt-3 mb-3 flex shrink-0 flex-col gap-4\">\n <!-- Enter submits via the capture-phase listener in the component (see the constructor) — a\n bubble-phase (keydown.enter) here never sees it, Combobox's own input listener swallows it\n first. (search) still covers the click-the-send-button path. No debounce: same \"submit on\n Enter/send, not on every keystroke\" contract as the previous input-group implementation. -->\n <SearchBar\n size=\"small\"\n [placeholder]=\"'drawers.inputPlaceholder' | transloco\"\n [ariaLabel]=\"'drawers.inputPlaceholder' | transloco\"\n [(value)]=\"queryText\"\n (search)=\"executeSearch()\" />\n </section>\n\n <!-- flex-1/min-h-0: this is what lets the panel content below be the only thing that scrolls —\n the search bar/tab list above are shrink-0, never pushed off or scrolled themselves. -->\n <Tabs class=\"flex min-h-0 flex-1 flex-col\">\n <!-- noTruncate skips the default `truncate` branch (whose `has-[span]:[&>span]:truncate` would\n otherwise force this span back to single-line ellipsis, defeating the wrap below); overriding\n its own `min-w-fit` with `min-w-[auto]` restores the flex item's normal \"automatic minimum\n size\" instead — for content that can wrap, that floor is the longest word (min-content), not\n the full unwrapped label (max-content, what `fit-content` resolves to here with nothing\n constraining the available space). A single-word label has nothing to wrap, so min-content ==\n max-content and it can't be squeezed below its own readable width — the flex-shrink deficit\n falls on the multi-word labels that actually have room to give, which wrap onto the span's\n line-clamp-2 instead of being cut with an ellipsis (appropriate for a single-line pill, not\n for a tab bar where \"Similar documents\"/\"Match locations\" routinely don't fit on one line).\n h-9 (fixed, not h-auto) + items-center on every tab keeps the row visually even whether a\n given label wraps onto 2 lines or stays on 1. No horizontal scroll/truncation fallback here on\n purpose — see preview-actions.ts's [matchTriggerWidth]=\"false\" on the search popover: it's the\n popover itself that grows (shrink-to-fit, up to max-w-xl) to make room for this row instead of\n this row squeezing to fit a fixed popover width. -->\n <TabList\n class=\"mx-3 shrink-0\"\n variant=\"inner\"\n [selectedTab]=\"selectedTab()\"\n (selectedTabChange)=\"activeTab.set($event)\">\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"extracts\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"extracts\" | transloco }}</span\n >\n </Tab>\n @if (entities.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"entities\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.entitiesTab\" | transloco }}</span\n >\n </Tab>\n }\n @if (matchLocations.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"matchlocations\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"column.matchlocations\" | transloco }}</span\n >\n </Tab>\n }\n @if (labels.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"labels\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"labels\" | transloco }}</span\n >\n </Tab>\n }\n @if (similar.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"similar\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.similarDocuments\" | transloco }}</span\n >\n </Tab>\n }\n </TabList>\n\n <!-- The single scroll container: each TabPanel is `absolute inset-0` (only the active one\n visible, per TabPanelComponent's own `[class.hidden]`), so they all share this one\n min-h-0/flex-1/overflow-y-auto region instead of the whole component scrolling. -->\n <div class=\"relative m-3 min-h-0 flex-1 scrollbar-thin overflow-y-auto\">\n <!-- Every TabPanel below cedes its own tab stop to its content (TabPanelComponent's `tabIndex`\n doc, ui/tabs/tabs.ts): the APG makes a tabpanel focusable only when it holds no focusable\n child, otherwise Tab from the tab lands on the empty container instead of the first row.\n `extracts` is always rendered (own empty/loading states, per the class doc above) so its\n tabindex is computed from whether it currently has any focusable row;\n `entities`/`matchlocations`/`labels`/`similar` are only ever mounted via `hasContent()`,\n which — per their `injectXState` — guarantees at least one row, so a static -1 is safe there. -->\n <TabPanel\n value=\"extracts\"\n [tabindex]=\"\n extracts.loading() || extracts.extracts().length === 0 ? 0 : -1\n \"\n class=\"absolute inset-0\">\n <article-extracts-tab-panel [state]=\"extracts\" />\n </TabPanel>\n @if (entities.hasContent()) {\n <TabPanel value=\"entities\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-entities-tab-panel [state]=\"entities\" />\n </TabPanel>\n }\n @if (matchLocations.hasContent()) {\n <TabPanel value=\"matchlocations\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-match-locations-tab-panel [state]=\"matchLocations\" />\n </TabPanel>\n }\n @if (labels.hasContent()) {\n <TabPanel value=\"labels\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-labels-tab-panel [article]=\"article()\" [state]=\"labels\" />\n </TabPanel>\n }\n @if (similar.hasContent()) {\n <TabPanel value=\"similar\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-similar-documents-tab-panel\n [article]=\"article()\"\n [strategy]=\"previewStrategy()\"\n [state]=\"similar\"\n (selected)=\"selected.emit($event)\" />\n </TabPanel>\n }\n </div>\n </Tabs>\n}\n"
10217
+ template: "@if (article()) {\n <section class=\"mx-3 mt-3 mb-3 flex shrink-0 flex-col gap-4\">\n <!-- Enter submits through (keydown.enter), the send button through (search). No debounce: same \"submit on\n Enter/send, not on every keystroke\" contract as the previous input-group implementation. -->\n <SearchBar\n size=\"small\"\n [placeholder]=\"'drawers.inputPlaceholder' | transloco\"\n [aria-label]=\"'drawers.inputPlaceholder' | transloco\"\n [(value)]=\"queryText\"\n (keydown.enter)=\"executeSearch()\"\n (search)=\"executeSearch()\" />\n </section>\n\n <!-- flex-1/min-h-0: this is what lets the panel content below be the only thing that scrolls —\n the search bar/tab list above are shrink-0, never pushed off or scrolled themselves. -->\n <Tabs class=\"flex min-h-0 flex-1 flex-col\">\n <!-- noTruncate skips the default `truncate` branch (whose `has-[span]:[&>span]:truncate` would\n otherwise force this span back to single-line ellipsis, defeating the wrap below); overriding\n its own `min-w-fit` with `min-w-[auto]` restores the flex item's normal \"automatic minimum\n size\" instead — for content that can wrap, that floor is the longest word (min-content), not\n the full unwrapped label (max-content, what `fit-content` resolves to here with nothing\n constraining the available space). A single-word label has nothing to wrap, so min-content ==\n max-content and it can't be squeezed below its own readable width — the flex-shrink deficit\n falls on the multi-word labels that actually have room to give, which wrap onto the span's\n line-clamp-2 instead of being cut with an ellipsis (appropriate for a single-line pill, not\n for a tab bar where \"Similar documents\"/\"Match locations\" routinely don't fit on one line).\n h-9 (fixed, not h-auto) + items-center on every tab keeps the row visually even whether a\n given label wraps onto 2 lines or stays on 1. No horizontal scroll/truncation fallback here on\n purpose — see preview-actions.ts's [matchTriggerWidth]=\"false\" on the search popover: it's the\n popover itself that grows (shrink-to-fit, up to max-w-xl) to make room for this row instead of\n this row squeezing to fit a fixed popover width. -->\n <TabList\n class=\"mx-3 shrink-0\"\n variant=\"inner\"\n [selectedTab]=\"selectedTab()\"\n (selectedTabChange)=\"activeTab.set($event)\">\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"extracts\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"extracts\" | transloco }}</span\n >\n </Tab>\n @if (entities.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"entities\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.entitiesTab\" | transloco }}</span\n >\n </Tab>\n }\n @if (matchLocations.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"matchlocations\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"column.matchlocations\" | transloco }}</span\n >\n </Tab>\n }\n @if (labels.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"labels\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"labels\" | transloco }}</span\n >\n </Tab>\n }\n @if (similar.hasContent()) {\n <Tab\n variant=\"secondary\"\n size=\"sm\"\n value=\"similar\"\n noTruncate\n class=\"h-9 min-w-[auto] items-center\">\n <span\n class=\"line-clamp-2 text-center leading-tight whitespace-normal\"\n >{{ \"drawers.similarDocuments\" | transloco }}</span\n >\n </Tab>\n }\n </TabList>\n\n <!-- The single scroll container: each TabPanel is `absolute inset-0` (only the active one\n visible, per TabPanelComponent's own `[class.hidden]`), so they all share this one\n min-h-0/flex-1/overflow-y-auto region instead of the whole component scrolling. -->\n <div class=\"relative m-3 min-h-0 flex-1 scrollbar-thin overflow-y-auto\">\n <!-- Every TabPanel below cedes its own tab stop to its content (TabPanelComponent's `tabIndex`\n doc, ui/tabs/tabs.ts): the APG makes a tabpanel focusable only when it holds no focusable\n child, otherwise Tab from the tab lands on the empty container instead of the first row.\n `extracts` is always rendered (own empty/loading states, per the class doc above) so its\n tabindex is computed from whether it currently has any focusable row;\n `entities`/`matchlocations`/`labels`/`similar` are only ever mounted via `hasContent()`,\n which — per their `injectXState` — guarantees at least one row, so a static -1 is safe there. -->\n <TabPanel\n value=\"extracts\"\n [tabindex]=\"\n extracts.loading() || extracts.extracts().length === 0 ? 0 : -1\n \"\n class=\"absolute inset-0\">\n <article-extracts-tab-panel [state]=\"extracts\" />\n </TabPanel>\n @if (entities.hasContent()) {\n <TabPanel value=\"entities\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-entities-tab-panel [state]=\"entities\" />\n </TabPanel>\n }\n @if (matchLocations.hasContent()) {\n <TabPanel value=\"matchlocations\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-match-locations-tab-panel [state]=\"matchLocations\" />\n </TabPanel>\n }\n @if (labels.hasContent()) {\n <TabPanel value=\"labels\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-labels-tab-panel [article]=\"article()\" [state]=\"labels\" />\n </TabPanel>\n }\n @if (similar.hasContent()) {\n <TabPanel value=\"similar\" tabindex=\"-1\" class=\"absolute inset-0\">\n <article-similar-documents-tab-panel\n [article]=\"article()\"\n [strategy]=\"previewStrategy()\"\n [state]=\"similar\"\n (selected)=\"selected.emit($event)\" />\n </TabPanel>\n }\n </div>\n </Tabs>\n}\n"
10153
10218
  }]
10154
10219
  }],
10155
- ctorParameters: () => [{ type: i0.DestroyRef }],
10156
10220
  propDecorators: {
10157
10221
  article: [{
10158
10222
  type: i0.Input,
@@ -10823,7 +10887,8 @@ var PreviewActionsComponent = class PreviewActionsComponent {
10823
10887
  "offset",
10824
10888
  "config",
10825
10889
  "lockPosition",
10826
- "matchTriggerWidth"
10890
+ "matchTriggerWidth",
10891
+ "hideWhenTriggerHidden"
10827
10892
  ],
10828
10893
  exportAs: ["popover", "dropdown"]
10829
10894
  },
@@ -18747,11 +18812,18 @@ var ListPanelComponent = class ListPanelComponent {
18747
18812
  getValue = input.required(...ngDevMode ? [{ debugName: "getValue" }] : /* istanbul ignore next */ []);
18748
18813
  getLabel = input(...ngDevMode ? [void 0, { debugName: "getLabel" }] : /* istanbul ignore next */ []);
18749
18814
  deleteButtonTitle = input("remove", ...ngDevMode ? [{ debugName: "deleteButtonTitle" }] : /* istanbul ignore next */ []);
18815
+ deletable = input(true, {
18816
+ ...ngDevMode ? { debugName: "deletable" } : /* istanbul ignore next */ {},
18817
+ transform: booleanAttribute
18818
+ });
18750
18819
  ariaLabel = input(...ngDevMode ? [void 0, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
18751
18820
  itemClass = input("", ...ngDevMode ? [{ debugName: "itemClass" }] : /* istanbul ignore next */ []);
18752
18821
  itemClick = output();
18753
18822
  delete = output();
18754
18823
  itemTemplate = contentChild(ListPanelItemDirective, ...ngDevMode ? [{ debugName: "itemTemplate" }] : /* istanbul ignore next */ []);
18824
+ deleteKeys = isApplePlatform() ? ["Delete", "Backspace"] : ["Delete"];
18825
+ list = viewChild(ListComponent, ...ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []);
18826
+ keyboardFocused = computed(() => this.list()?.keyboardFocused() ?? false, ...ngDevMode ? [{ debugName: "keyboardFocused" }] : /* istanbul ignore next */ []);
18755
18827
  host = inject(ElementRef);
18756
18828
  injector = inject(Injector);
18757
18829
  pendingFocusIndex = signal(null, ...ngDevMode ? [{ debugName: "pendingFocusIndex" }] : /* istanbul ignore next */ []);
@@ -18827,6 +18899,13 @@ var ListPanelComponent = class ListPanelComponent {
18827
18899
  isRequired: false,
18828
18900
  transformFunction: null
18829
18901
  },
18902
+ deletable: {
18903
+ classPropertyName: "deletable",
18904
+ publicName: "deletable",
18905
+ isSignal: true,
18906
+ isRequired: false,
18907
+ transformFunction: null
18908
+ },
18830
18909
  ariaLabel: {
18831
18910
  classPropertyName: "ariaLabel",
18832
18911
  publicName: "ariaLabel",
@@ -18847,6 +18926,7 @@ var ListPanelComponent = class ListPanelComponent {
18847
18926
  delete: "delete"
18848
18927
  },
18849
18928
  host: { classAttribute: "block max-h-115 overflow-auto" },
18929
+ providers: [provideTranslocoScope("list-panel")],
18850
18930
  queries: [{
18851
18931
  propertyName: "itemTemplate",
18852
18932
  first: true,
@@ -18854,6 +18934,13 @@ var ListPanelComponent = class ListPanelComponent {
18854
18934
  descendants: true,
18855
18935
  isSignal: true
18856
18936
  }],
18937
+ viewQueries: [{
18938
+ propertyName: "list",
18939
+ first: true,
18940
+ predicate: ListComponent,
18941
+ descendants: true,
18942
+ isSignal: true
18943
+ }],
18857
18944
  ngImport: i0,
18858
18945
  template: `
18859
18946
  <List variant="list-navigation" size="medium" class="gap-1" selectionMode="explicit" [attr.aria-label]="ariaLabel()">
@@ -18866,9 +18953,13 @@ var ListPanelComponent = class ListPanelComponent {
18866
18953
  (keydown.enter)="activate(item, $event)"
18867
18954
  (keydown.space)="activate(item, $event)">
18868
18955
  <ng-container *ngTemplateOutlet="itemTemplate()?.template; context: { $implicit: item }" />
18869
- <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" (click)="onDelete(item, i, $event)">
18870
- <TrashIcon />
18871
- </ListItemAction>
18956
+ @if (deletable()) {
18957
+ <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" [keys]="deleteKeys" [shortcut]="'listPanel.deleteShortcut' | transloco"
18958
+ [description]="'listPanel.deleteDescription' | transloco: { key: 'listPanel.deleteShortcut' | transloco }"
18959
+ (click)="onDelete(item, i, $event)">
18960
+ <TrashIcon />
18961
+ </ListItemAction>
18962
+ }
18872
18963
  </ListItem>
18873
18964
  } @empty {
18874
18965
  <span class="py-4 text-center text-neutral-500">
@@ -18923,7 +19014,13 @@ var ListPanelComponent = class ListPanelComponent {
18923
19014
  kind: "component",
18924
19015
  type: ListItemActionComponent,
18925
19016
  selector: "ListItemAction,list-item-action,listitemaction",
18926
- inputs: ["ariaLabel", "disabled"],
19017
+ inputs: [
19018
+ "ariaLabel",
19019
+ "disabled",
19020
+ "keys",
19021
+ "shortcut",
19022
+ "description"
19023
+ ],
18927
19024
  exportAs: ["listItemAction"]
18928
19025
  },
18929
19026
  {
@@ -18943,6 +19040,7 @@ i0.ɵɵngDeclareClassMetadata({
18943
19040
  type: Component,
18944
19041
  args: [{
18945
19042
  selector: "list-panel, ListPanel",
19043
+ providers: [provideTranslocoScope("list-panel")],
18946
19044
  imports: [
18947
19045
  NgTemplateOutlet,
18948
19046
  TrashIcon,
@@ -18962,9 +19060,13 @@ i0.ɵɵngDeclareClassMetadata({
18962
19060
  (keydown.enter)="activate(item, $event)"
18963
19061
  (keydown.space)="activate(item, $event)">
18964
19062
  <ng-container *ngTemplateOutlet="itemTemplate()?.template; context: { $implicit: item }" />
18965
- <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" (click)="onDelete(item, i, $event)">
18966
- <TrashIcon />
18967
- </ListItemAction>
19063
+ @if (deletable()) {
19064
+ <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" [keys]="deleteKeys" [shortcut]="'listPanel.deleteShortcut' | transloco"
19065
+ [description]="'listPanel.deleteDescription' | transloco: { key: 'listPanel.deleteShortcut' | transloco }"
19066
+ (click)="onDelete(item, i, $event)">
19067
+ <TrashIcon />
19068
+ </ListItemAction>
19069
+ }
18968
19070
  </ListItem>
18969
19071
  } @empty {
18970
19072
  <span class="py-4 text-center text-neutral-500">
@@ -19011,6 +19113,14 @@ i0.ɵɵngDeclareClassMetadata({
19011
19113
  required: false
19012
19114
  }]
19013
19115
  }],
19116
+ deletable: [{
19117
+ type: i0.Input,
19118
+ args: [{
19119
+ isSignal: true,
19120
+ alias: "deletable",
19121
+ required: false
19122
+ }]
19123
+ }],
19014
19124
  ariaLabel: [{
19015
19125
  type: i0.Input,
19016
19126
  args: [{
@@ -19038,6 +19148,10 @@ i0.ɵɵngDeclareClassMetadata({
19038
19148
  itemTemplate: [{
19039
19149
  type: i0.ContentChild,
19040
19150
  args: [i0.forwardRef(() => ListPanelItemDirective), { isSignal: true }]
19151
+ }],
19152
+ list: [{
19153
+ type: i0.ViewChild,
19154
+ args: [i0.forwardRef(() => ListComponent), { isSignal: true }]
19041
19155
  }]
19042
19156
  }
19043
19157
  });
@@ -19047,6 +19161,10 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19047
19161
  hasMore = input.required(...ngDevMode ? [{ debugName: "hasMore" }] : /* istanbul ignore next */ []);
19048
19162
  config = input.required(...ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []);
19049
19163
  loadMore = output();
19164
+ navigationHint = input(false, {
19165
+ ...ngDevMode ? { debugName: "navigationHint" } : /* istanbul ignore next */ {},
19166
+ transform: booleanAttribute
19167
+ });
19050
19168
  onLoadMore(event) {
19051
19169
  event.stopPropagation();
19052
19170
  this.loadMore.emit();
@@ -19086,10 +19204,17 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19086
19204
  isSignal: true,
19087
19205
  isRequired: true,
19088
19206
  transformFunction: null
19207
+ },
19208
+ navigationHint: {
19209
+ classPropertyName: "navigationHint",
19210
+ publicName: "navigationHint",
19211
+ isSignal: true,
19212
+ isRequired: false,
19213
+ transformFunction: null
19089
19214
  }
19090
19215
  },
19091
19216
  outputs: { loadMore: "loadMore" },
19092
- host: { properties: { "class": "cn('flex flex-col px-2 mt-2', class())" } },
19217
+ host: { properties: { "class": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" } },
19093
19218
  providers: [provideTranslocoScope("list-panel")],
19094
19219
  ngImport: i0,
19095
19220
  template: `
@@ -19100,9 +19225,33 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19100
19225
  </button>
19101
19226
  }
19102
19227
  @let seeMore = 'listPanel.seeMore' | transloco;
19103
- <button variant="secondary" size="sm" class="ml-auto" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19104
- {{ seeMore }}
19105
- </button>
19228
+ <div class="flex items-center gap-(--spacing-xs)">
19229
+ <!-- Aide de navigation de la liste, tant que navigationHint est vrai (focus clavier dans la liste) : le
19230
+ focus est un arrêt unique de tabulation, les flèches font le reste, et rien ne le dit.
19231
+ aria-hidden : le listbox annonce déjà sa navigation aux lecteurs d'écran, et l'ouverture
19232
+ d'une ligne est ce que tout le monde attend d'Entrée. La touche Suppr est, elle, écrite dans la
19233
+ ligne. -->
19234
+ @if (navigationHint()) {
19235
+ <p aria-hidden="true" class="flex min-w-0 flex-wrap items-center gap-x-(--spacing-xs) gap-y-(--spacing-4xs) text-(length:--size-sm) leading-(--line-height-2) text-(--font-neutral-base)">
19236
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19237
+ <!-- Des icônes et non les caractères ↑ ↓ : le centre visuel d'une flèche typographique ne tombe pas au
19238
+ milieu de la ligne de texte, et elle sortait décentrée de sa touche. -->
19239
+ <span class="inline-flex gap-(--spacing-4xs)">
19240
+ <kbd><arrow-up-icon /></kbd>
19241
+ <kbd><arrow-down-icon /></kbd>
19242
+ </span>
19243
+ {{ 'listPanel.hintNavigate' | transloco }}
19244
+ </span>
19245
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19246
+ <kbd>{{ 'listPanel.keyEnter' | transloco }}</kbd>
19247
+ {{ 'listPanel.hintOpen' | transloco }}
19248
+ </span>
19249
+ </p>
19250
+ }
19251
+ <button variant="secondary" size="sm" class="ml-auto shrink-0" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19252
+ {{ seeMore }}
19253
+ </button>
19254
+ </div>
19106
19255
  `,
19107
19256
  isInline: true,
19108
19257
  dependencies: [
@@ -19137,6 +19286,24 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19137
19286
  "size"
19138
19287
  ]
19139
19288
  },
19289
+ {
19290
+ kind: "directive",
19291
+ type: KbdComponent,
19292
+ selector: "kbd, Kbd",
19293
+ inputs: ["class", "size"]
19294
+ },
19295
+ {
19296
+ kind: "component",
19297
+ type: ArrowUpIcon,
19298
+ selector: "arrow-up, arrow-up-icon, ArrowUp, ArrowUpIcon",
19299
+ inputs: ["class"]
19300
+ },
19301
+ {
19302
+ kind: "component",
19303
+ type: ArrowDownIcon,
19304
+ selector: "arrow-down, arrow-down-icon, ArrowDown, ArrowDownIcon",
19305
+ inputs: ["class"]
19306
+ },
19140
19307
  {
19141
19308
  kind: "pipe",
19142
19309
  type: TranslocoPipe,
@@ -19157,7 +19324,10 @@ i0.ɵɵngDeclareClassMetadata({
19157
19324
  imports: [
19158
19325
  TranslocoPipe,
19159
19326
  RouterLink,
19160
- ButtonComponent
19327
+ ButtonComponent,
19328
+ KbdComponent,
19329
+ ArrowUpIcon,
19330
+ ArrowDownIcon
19161
19331
  ],
19162
19332
  providers: [provideTranslocoScope("list-panel")],
19163
19333
  template: `
@@ -19168,11 +19338,35 @@ i0.ɵɵngDeclareClassMetadata({
19168
19338
  </button>
19169
19339
  }
19170
19340
  @let seeMore = 'listPanel.seeMore' | transloco;
19171
- <button variant="secondary" size="sm" class="ml-auto" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19172
- {{ seeMore }}
19173
- </button>
19341
+ <div class="flex items-center gap-(--spacing-xs)">
19342
+ <!-- Aide de navigation de la liste, tant que navigationHint est vrai (focus clavier dans la liste) : le
19343
+ focus est un arrêt unique de tabulation, les flèches font le reste, et rien ne le dit.
19344
+ aria-hidden : le listbox annonce déjà sa navigation aux lecteurs d'écran, et l'ouverture
19345
+ d'une ligne est ce que tout le monde attend d'Entrée. La touche Suppr est, elle, écrite dans la
19346
+ ligne. -->
19347
+ @if (navigationHint()) {
19348
+ <p aria-hidden="true" class="flex min-w-0 flex-wrap items-center gap-x-(--spacing-xs) gap-y-(--spacing-4xs) text-(length:--size-sm) leading-(--line-height-2) text-(--font-neutral-base)">
19349
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19350
+ <!-- Des icônes et non les caractères ↑ ↓ : le centre visuel d'une flèche typographique ne tombe pas au
19351
+ milieu de la ligne de texte, et elle sortait décentrée de sa touche. -->
19352
+ <span class="inline-flex gap-(--spacing-4xs)">
19353
+ <kbd><arrow-up-icon /></kbd>
19354
+ <kbd><arrow-down-icon /></kbd>
19355
+ </span>
19356
+ {{ 'listPanel.hintNavigate' | transloco }}
19357
+ </span>
19358
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19359
+ <kbd>{{ 'listPanel.keyEnter' | transloco }}</kbd>
19360
+ {{ 'listPanel.hintOpen' | transloco }}
19361
+ </span>
19362
+ </p>
19363
+ }
19364
+ <button variant="secondary" size="sm" class="ml-auto shrink-0" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19365
+ {{ seeMore }}
19366
+ </button>
19367
+ </div>
19174
19368
  `,
19175
- host: { "[class]": "cn('flex flex-col px-2 mt-2', class())" }
19369
+ host: { "[class]": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" }
19176
19370
  }]
19177
19371
  }],
19178
19372
  propDecorators: {
@@ -19203,6 +19397,14 @@ i0.ɵɵngDeclareClassMetadata({
19203
19397
  loadMore: [{
19204
19398
  type: i0.Output,
19205
19399
  args: ["loadMore"]
19400
+ }],
19401
+ navigationHint: [{
19402
+ type: i0.Input,
19403
+ args: [{
19404
+ isSignal: true,
19405
+ alias: "navigationHint",
19406
+ required: false
19407
+ }]
19206
19408
  }]
19207
19409
  }
19208
19410
  });
@@ -19388,7 +19590,7 @@ var BookmarksComponent = class BookmarksComponent {
19388
19590
  } },
19389
19591
  providers: [provideTranslocoScope("bookmarks")],
19390
19592
  ngImport: i0,
19391
- template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19593
+ template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19392
19594
  dependencies: [
19393
19595
  {
19394
19596
  kind: "component",
@@ -19405,6 +19607,7 @@ var BookmarksComponent = class BookmarksComponent {
19405
19607
  "getValue",
19406
19608
  "getLabel",
19407
19609
  "deleteButtonTitle",
19610
+ "deletable",
19408
19611
  "ariaLabel",
19409
19612
  "itemClass"
19410
19613
  ],
@@ -19417,7 +19620,8 @@ var BookmarksComponent = class BookmarksComponent {
19417
19620
  inputs: [
19418
19621
  "class",
19419
19622
  "hasMore",
19420
- "config"
19623
+ "config",
19624
+ "navigationHint"
19421
19625
  ],
19422
19626
  outputs: ["loadMore"]
19423
19627
  },
@@ -19499,7 +19703,7 @@ i0.ɵɵngDeclareClassMetadata({
19499
19703
  TagComponent
19500
19704
  ],
19501
19705
  providers: [provideTranslocoScope("bookmarks")],
19502
- template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19706
+ template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19503
19707
  }]
19504
19708
  }],
19505
19709
  ctorParameters: () => [],
@@ -19755,7 +19959,7 @@ var CollectionsComponent = class CollectionsComponent {
19755
19959
  } },
19756
19960
  providers: [provideTranslocoScope("collections")],
19757
19961
  ngImport: i0,
19758
- template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19962
+ template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19759
19963
  dependencies: [
19760
19964
  {
19761
19965
  kind: "component",
@@ -19772,6 +19976,7 @@ var CollectionsComponent = class CollectionsComponent {
19772
19976
  "getValue",
19773
19977
  "getLabel",
19774
19978
  "deleteButtonTitle",
19979
+ "deletable",
19775
19980
  "ariaLabel",
19776
19981
  "itemClass"
19777
19982
  ],
@@ -19784,7 +19989,8 @@ var CollectionsComponent = class CollectionsComponent {
19784
19989
  inputs: [
19785
19990
  "class",
19786
19991
  "hasMore",
19787
- "config"
19992
+ "config",
19993
+ "navigationHint"
19788
19994
  ],
19789
19995
  outputs: ["loadMore"]
19790
19996
  },
@@ -19840,7 +20046,7 @@ i0.ɵɵngDeclareClassMetadata({
19840
20046
  ListItemTextComponent
19841
20047
  ],
19842
20048
  providers: [provideTranslocoScope("collections")],
19843
- template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
20049
+ template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19844
20050
  }]
19845
20051
  }],
19846
20052
  ctorParameters: () => [],
@@ -20470,6 +20676,7 @@ var FeatureFlagsDialogComponent = class FeatureFlagsDialogComponent {
20470
20676
  "aria-label",
20471
20677
  "aria-labelledby",
20472
20678
  "presentational",
20679
+ "compact",
20473
20680
  "tabIndex"
20474
20681
  ],
20475
20682
  outputs: ["checkedChange"]
@@ -23939,19 +24146,7 @@ var AggregationPanelComponent = class AggregationPanelComponent {
23939
24146
  {
23940
24147
  kind: "component",
23941
24148
  type: SearchInputComponent,
23942
- selector: "SearchInput, search-input, searchinput",
23943
- inputs: [
23944
- "value",
23945
- "placeholder",
23946
- "disabled",
23947
- "aria-label",
23948
- "hotkey",
23949
- "clearLabel",
23950
- "class",
23951
- "size",
23952
- "textSize"
23953
- ],
23954
- outputs: ["valueChange"]
24149
+ selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
23955
24150
  },
23956
24151
  {
23957
24152
  kind: "component",
@@ -27765,7 +27960,8 @@ var FilterButtonComponent = class FilterButtonComponent {
27765
27960
  "offset",
27766
27961
  "config",
27767
27962
  "lockPosition",
27768
- "matchTriggerWidth"
27963
+ "matchTriggerWidth",
27964
+ "hideWhenTriggerHidden"
27769
27965
  ],
27770
27966
  exportAs: ["popover", "dropdown"]
27771
27967
  },
@@ -28186,19 +28382,7 @@ var MoreComponent = class MoreComponent {
28186
28382
  {
28187
28383
  kind: "component",
28188
28384
  type: SearchInputComponent,
28189
- selector: "SearchInput, search-input, searchinput",
28190
- inputs: [
28191
- "value",
28192
- "placeholder",
28193
- "disabled",
28194
- "aria-label",
28195
- "hotkey",
28196
- "clearLabel",
28197
- "class",
28198
- "size",
28199
- "textSize"
28200
- ],
28201
- outputs: ["valueChange"]
28385
+ selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
28202
28386
  },
28203
28387
  {
28204
28388
  kind: "pipe",
@@ -28612,7 +28796,8 @@ var MoreButtonComponent = class MoreButtonComponent {
28612
28796
  "offset",
28613
28797
  "config",
28614
28798
  "lockPosition",
28615
- "matchTriggerWidth"
28799
+ "matchTriggerWidth",
28800
+ "hideWhenTriggerHidden"
28616
28801
  ],
28617
28802
  exportAs: ["popover", "dropdown"]
28618
28803
  },
@@ -30252,7 +30437,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30252
30437
  template: `
30253
30438
  <ListPanelHeader [label]="'searches.recent.label' | transloco" [floating]="!!floating" />
30254
30439
 
30255
- <ListPanel
30440
+ <ListPanel #panel
30256
30441
  [items]="paginatedRecentSearches()"
30257
30442
  [getValue]="urlOf"
30258
30443
  [getLabel]="labelOf"
@@ -30284,7 +30469,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30284
30469
  </ListPanel>
30285
30470
 
30286
30471
  @if (paginatedRecentSearches().length > 0) {
30287
- <ListPanelFooter
30472
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()"
30288
30473
  [hasMore]="hasMore()"
30289
30474
  [config]="config"
30290
30475
  (loadMore)="loadMore()" />
@@ -30361,6 +30546,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30361
30546
  "getValue",
30362
30547
  "getLabel",
30363
30548
  "deleteButtonTitle",
30549
+ "deletable",
30364
30550
  "ariaLabel",
30365
30551
  "itemClass"
30366
30552
  ],
@@ -30373,7 +30559,8 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30373
30559
  inputs: [
30374
30560
  "class",
30375
30561
  "hasMore",
30376
- "config"
30562
+ "config",
30563
+ "navigationHint"
30377
30564
  ],
30378
30565
  outputs: ["loadMore"]
30379
30566
  },
@@ -30411,7 +30598,7 @@ i0.ɵɵngDeclareClassMetadata({
30411
30598
  template: `
30412
30599
  <ListPanelHeader [label]="'searches.recent.label' | transloco" [floating]="!!floating" />
30413
30600
 
30414
- <ListPanel
30601
+ <ListPanel #panel
30415
30602
  [items]="paginatedRecentSearches()"
30416
30603
  [getValue]="urlOf"
30417
30604
  [getLabel]="labelOf"
@@ -30443,7 +30630,7 @@ i0.ɵɵngDeclareClassMetadata({
30443
30630
  </ListPanel>
30444
30631
 
30445
30632
  @if (paginatedRecentSearches().length > 0) {
30446
- <ListPanelFooter
30633
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()"
30447
30634
  [hasMore]="hasMore()"
30448
30635
  [config]="config"
30449
30636
  (loadMore)="loadMore()" />
@@ -30528,7 +30715,7 @@ var SavedSearchDialog = class SavedSearchDialog {
30528
30715
  [formField]="nameField"
30529
30716
  [attr.aria-label]="'searches.saved.saveName' | transloco"
30530
30717
  [attr.placeholder]="'searches.saved.saveName' | transloco"
30531
- (keydown.enter)="saveName().trim() && confirm()" />
30718
+ (keydown.enter)="$event.preventDefault(); saveName().trim() && confirm()" />
30532
30719
  </input-group>
30533
30720
  </DialogBody>
30534
30721
 
@@ -30675,7 +30862,7 @@ i0.ɵɵngDeclareClassMetadata({
30675
30862
  [formField]="nameField"
30676
30863
  [attr.aria-label]="'searches.saved.saveName' | transloco"
30677
30864
  [attr.placeholder]="'searches.saved.saveName' | transloco"
30678
- (keydown.enter)="saveName().trim() && confirm()" />
30865
+ (keydown.enter)="$event.preventDefault(); saveName().trim() && confirm()" />
30679
30866
  </input-group>
30680
30867
  </DialogBody>
30681
30868
 
@@ -30973,7 +31160,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
30973
31160
  template: `
30974
31161
  <ListPanelHeader [label]="'searches.saved.label' | transloco" [floating]="!!floating" />
30975
31162
 
30976
- <ListPanel
31163
+ <ListPanel #panel
30977
31164
  [items]="paginatedSearches()"
30978
31165
  [getValue]="urlOf"
30979
31166
  [getLabel]="labelOf"
@@ -31005,7 +31192,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31005
31192
  </ListPanel>
31006
31193
 
31007
31194
  @if(paginatedSearches().length > 0) {
31008
- <ListPanelFooter [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31195
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()" [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31009
31196
  }
31010
31197
  `,
31011
31198
  isInline: true,
@@ -31079,6 +31266,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31079
31266
  "getValue",
31080
31267
  "getLabel",
31081
31268
  "deleteButtonTitle",
31269
+ "deletable",
31082
31270
  "ariaLabel",
31083
31271
  "itemClass"
31084
31272
  ],
@@ -31091,7 +31279,8 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31091
31279
  inputs: [
31092
31280
  "class",
31093
31281
  "hasMore",
31094
- "config"
31282
+ "config",
31283
+ "navigationHint"
31095
31284
  ],
31096
31285
  outputs: ["loadMore"]
31097
31286
  },
@@ -31129,7 +31318,7 @@ i0.ɵɵngDeclareClassMetadata({
31129
31318
  template: `
31130
31319
  <ListPanelHeader [label]="'searches.saved.label' | transloco" [floating]="!!floating" />
31131
31320
 
31132
- <ListPanel
31321
+ <ListPanel #panel
31133
31322
  [items]="paginatedSearches()"
31134
31323
  [getValue]="urlOf"
31135
31324
  [getLabel]="labelOf"
@@ -31161,7 +31350,7 @@ i0.ɵɵngDeclareClassMetadata({
31161
31350
  </ListPanel>
31162
31351
 
31163
31352
  @if(paginatedSearches().length > 0) {
31164
- <ListPanelFooter [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31353
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()" [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31165
31354
  }
31166
31355
  `
31167
31356
  }]
@@ -31596,7 +31785,11 @@ function splitPropertyId(id) {
31596
31785
  key: id.slice(separator + 1)
31597
31786
  };
31598
31787
  }
31788
+ const FIRST_CONTROL = "button:not(:disabled), textarea, input:not([type='file']), a[href]";
31599
31789
  var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
31790
+ focusOnOpen = input(false, ...ngDevMode ? [{ debugName: "focusOnOpen" }] : /* istanbul ignore next */ []);
31791
+ host = inject(ElementRef);
31792
+ destroyRef = inject(DestroyRef);
31600
31793
  principalStore = inject(PrincipalStore);
31601
31794
  appStore = inject(AppStore);
31602
31795
  client = inject(ATOMIC_CLIENT);
@@ -31609,6 +31802,13 @@ var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
31609
31802
  const { allowChangePassword = false } = this.appStore.general()?.features || {};
31610
31803
  return allowChangePassword && useCredentials && (this.principalStore.editablePartition?.() || false);
31611
31804
  }, ...ngDevMode ? [{ debugName: "allowChangePassword" }] : /* istanbul ignore next */ []);
31805
+ constructor() {
31806
+ afterNextRender(() => {
31807
+ if (!this.focusOnOpen()) return;
31808
+ const frame = requestAnimationFrame(() => focusWhenReady(() => this.host.nativeElement.querySelector(FIRST_CONTROL), "settings-account-panel", { destroyRef: this.destroyRef }));
31809
+ this.destroyRef.onDestroy(() => cancelAnimationFrame(frame));
31810
+ });
31811
+ }
31612
31812
  resetSettings() {
31613
31813
  ResetUserSettings.call(void 0, { injector: this.injector });
31614
31814
  }
@@ -31632,6 +31832,13 @@ var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
31632
31832
  type: SettingsAccountPanelComponent,
31633
31833
  isStandalone: true,
31634
31834
  selector: "settings-account-panel",
31835
+ inputs: { focusOnOpen: {
31836
+ classPropertyName: "focusOnOpen",
31837
+ publicName: "focusOnOpen",
31838
+ isSignal: true,
31839
+ isRequired: false,
31840
+ transformFunction: null
31841
+ } },
31635
31842
  providers: [provideTranslocoScope("settings")],
31636
31843
  ngImport: i0,
31637
31844
  template: `
@@ -31814,7 +32021,16 @@ i0.ɵɵngDeclareClassMetadata({
31814
32021
  </div>
31815
32022
  `
31816
32023
  }]
31817
- }]
32024
+ }],
32025
+ ctorParameters: () => [],
32026
+ propDecorators: { focusOnOpen: [{
32027
+ type: i0.Input,
32028
+ args: [{
32029
+ isSignal: true,
32030
+ alias: "focusOnOpen",
32031
+ required: false
32032
+ }]
32033
+ }] }
31818
32034
  });
31819
32035
  const THEMES$1 = [
31820
32036
  "light",
@@ -32158,6 +32374,11 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32158
32374
  }
32159
32375
  ];
32160
32376
  panel = signal(this.call.props()?.panel ?? "account", ...ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []);
32377
+ navigated = signal(false, ...ngDevMode ? [{ debugName: "navigated" }] : /* istanbul ignore next */ []);
32378
+ navigate(id) {
32379
+ this.navigated.set(true);
32380
+ this.panel.set(id);
32381
+ }
32161
32382
  duplicate = active !== void 0;
32162
32383
  constructor() {
32163
32384
  if (active) {
@@ -32217,7 +32438,7 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32217
32438
  variant="none"
32218
32439
  class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
32219
32440
  [attr.aria-current]="panel() === item.id ? 'page' : null"
32220
- (click)="panel.set(item.id)">
32441
+ (click)="navigate(item.id)">
32221
32442
  @switch (item.id) {
32222
32443
  @case ("account") {
32223
32444
  <user-icon aria-hidden="true" />
@@ -32238,7 +32459,7 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32238
32459
  <div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
32239
32460
  @switch (panel()) {
32240
32461
  @case ("account") {
32241
- <settings-account-panel class="flex min-h-0 flex-1 flex-col" />
32462
+ <settings-account-panel class="flex min-h-0 flex-1 flex-col" [focusOnOpen]="!navigated()" />
32242
32463
  }
32243
32464
  @case ("appearance") {
32244
32465
  <settings-appearance-panel />
@@ -32311,7 +32532,8 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32311
32532
  {
32312
32533
  kind: "component",
32313
32534
  type: SettingsAccountPanelComponent,
32314
- selector: "settings-account-panel"
32535
+ selector: "settings-account-panel",
32536
+ inputs: ["focusOnOpen"]
32315
32537
  },
32316
32538
  {
32317
32539
  kind: "component",
@@ -32377,7 +32599,7 @@ i0.ɵɵngDeclareClassMetadata({
32377
32599
  variant="none"
32378
32600
  class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
32379
32601
  [attr.aria-current]="panel() === item.id ? 'page' : null"
32380
- (click)="panel.set(item.id)">
32602
+ (click)="navigate(item.id)">
32381
32603
  @switch (item.id) {
32382
32604
  @case ("account") {
32383
32605
  <user-icon aria-hidden="true" />
@@ -32398,7 +32620,7 @@ i0.ɵɵngDeclareClassMetadata({
32398
32620
  <div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
32399
32621
  @switch (panel()) {
32400
32622
  @case ("account") {
32401
- <settings-account-panel class="flex min-h-0 flex-1 flex-col" />
32623
+ <settings-account-panel class="flex min-h-0 flex-1 flex-col" [focusOnOpen]="!navigated()" />
32402
32624
  }
32403
32625
  @case ("appearance") {
32404
32626
  <settings-appearance-panel />