@sinequa/atomic-angular 2.10.0 → 2.12.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.
@@ -1,8 +1,8 @@
1
1
  import * as i0 from "@angular/core";
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";
2
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, HostBinding, HostListener, Inject, Injectable, InjectionToken, Injector, LOCALE_ID, Optional, Pipe, Service, TemplateRef, ViewContainerRef, 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 { ArrowDownIcon, ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BellSlashIcon, BookmarkIcon, BreakpointObserverService, BuildingIcon, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, ClockIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, GripVerticalIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemIconDirective, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, LockIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MapPinIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PopoverResizeHandleComponent, PresentationIcon, QuestionCircleIcon, QuickFilterIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchBarSuggestionsComponent, SearchIcon, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SidebarInsetDirective, SlashIcon, SlidersIcon, SortableDirective, SortableItemDirective, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagInputComponent, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, TruncatedDirective, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef, injectFloatingPanel, isApplePlatform } from "@sinequa/galactik";
5
+ import { ArrowDownIcon, ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BellSlashIcon, BookmarkIcon, BreakpointObserverService, BuildingIcon, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, ClockIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, GripVerticalIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemIconDirective, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, LockIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MapPinIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PopoverResizeHandleComponent, PresentationIcon, QuestionCircleIcon, QuickFilterIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchBarSuggestionsComponent, SearchIcon, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SidebarInsetDirective, SlashIcon, SlidersIcon, SortableDirective, SortableItemDirective, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagInputComponent, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, TruncatedDirective, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef, injectFloatingPanel, isApplePlatform } from "@sinequa/galactik";
6
6
  import { withDevtools } from "@ngrx-toolkit/core";
7
7
  import { getState, patchState, signalStore, signalStoreFeature, type, withComputed, withMethods, withState } from "@ngrx/signals";
8
8
  import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
@@ -5521,6 +5521,39 @@ i0.ɵɵngDeclareClassMetadata({
5521
5521
  args: [{ name: "fileSize" }]
5522
5522
  }]
5523
5523
  });
5524
+ var LocalizedNumberPipe = class LocalizedNumberPipe {
5525
+ transloco = inject(TranslocoService);
5526
+ lang = toSignal(this.transloco.langChanges$, { initialValue: this.transloco.getActiveLang() });
5527
+ transform(value) {
5528
+ return new Intl.NumberFormat(this.lang()).format(value ?? 0);
5529
+ }
5530
+ static ɵfac = i0.ɵɵngDeclareFactory({
5531
+ minVersion: "12.0.0",
5532
+ version: "22.2.1",
5533
+ ngImport: i0,
5534
+ type: LocalizedNumberPipe,
5535
+ deps: [],
5536
+ target: i0.ɵɵFactoryTarget.Pipe
5537
+ });
5538
+ static ɵpipe = i0.ɵɵngDeclarePipe({
5539
+ minVersion: "14.0.0",
5540
+ version: "22.2.1",
5541
+ ngImport: i0,
5542
+ type: LocalizedNumberPipe,
5543
+ isStandalone: true,
5544
+ name: "localizedNumber"
5545
+ });
5546
+ };
5547
+ i0.ɵɵngDeclareClassMetadata({
5548
+ minVersion: "12.0.0",
5549
+ version: "22.2.1",
5550
+ ngImport: i0,
5551
+ type: LocalizedNumberPipe,
5552
+ decorators: [{
5553
+ type: Pipe,
5554
+ args: [{ name: "localizedNumber" }]
5555
+ }]
5556
+ });
5524
5557
  var MetadataComponent = class MetadataComponent {
5525
5558
  click = output();
5526
5559
  class = input(...ngDevMode ? [void 0, { debugName: "class" }] : /* istanbul ignore next */ []);
@@ -8218,6 +8251,8 @@ var NavbarTabsComponent = class NavbarTabsComponent {
8218
8251
  appStore = inject(AppStore);
8219
8252
  class = input(...ngDevMode ? [void 0, { debugName: "class" }] : /* istanbul ignore next */ []);
8220
8253
  ariaLabel = input(...ngDevMode ? [void 0, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
8254
+ variant = input("primary", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
8255
+ isPill = computed(() => this.variant() === "primary", ...ngDevMode ? [{ debugName: "isPill" }] : /* istanbul ignore next */ []);
8221
8256
  showCount = input(true, {
8222
8257
  ...ngDevMode ? { debugName: "showCount" } : /* istanbul ignore next */ {},
8223
8258
  transform: booleanAttribute
@@ -8325,6 +8360,13 @@ var NavbarTabsComponent = class NavbarTabsComponent {
8325
8360
  isRequired: false,
8326
8361
  transformFunction: null
8327
8362
  },
8363
+ variant: {
8364
+ classPropertyName: "variant",
8365
+ publicName: "variant",
8366
+ isSignal: true,
8367
+ isRequired: false,
8368
+ transformFunction: null
8369
+ },
8328
8370
  showCount: {
8329
8371
  classPropertyName: "showCount",
8330
8372
  publicName: "showCount",
@@ -8382,10 +8424,11 @@ var NavbarTabsComponent = class NavbarTabsComponent {
8382
8424
  WHICH list it landed in — and a page may hold more than one. The default is translated
8383
8425
  so every consumer gets a named tab list without passing anything.
8384
8426
  (No backticks in this comment: the template is a template literal.) -->
8385
- <TabList class="w-full" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
8427
+ <TabList class="w-full" [variant]="variant()" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
8386
8428
  @for (tab of nav.tabs(); track $index) {
8387
8429
  <Tab
8388
- [class]="cn('group', !noTruncate() && 'basis-50')"
8430
+ [variant]="variant()"
8431
+ [class]="cn('group', !noTruncate() && 'basis-50', !isPill() && 'h-11 px-(--spacing-xs) text-(length:--size-base) font-semibold')"
8389
8432
  [value]="tab.path"
8390
8433
  overflowItem
8391
8434
  [noTruncate]="noTruncate()"
@@ -8416,315 +8459,11 @@ var NavbarTabsComponent = class NavbarTabsComponent {
8416
8459
  </div>
8417
8460
  </Tab>
8418
8461
  }
8419
- <!-- The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
8420
- contain tab elements, so a plain button here would break the announced tab count and
8421
- the arrow-key navigation. ngTab gives it the role and enrols it in the roving tabindex
8422
- — so the list keeps ONE tab stop and the arrows reach it like any other tab.
8423
-
8424
- It stays a real <button> because that is what menuTrigger needs: it wires the menu
8425
- through the native popovertarget attribute, which only works on a button.
8426
-
8427
- ms-auto pins it to the right end of the flex tab list. overflowStop is found by content
8428
- query, so the measurement is unaffected by where it sits. Always mounted so the stop
8429
- position is stable; hidden when every tab fits.
8430
- (No backticks in this comment: the template is a template literal.) -->
8431
- <button
8432
- ngTab
8433
- [value]="MORE_TABS_VALUE"
8434
- [menuTrigger]="moreTabsMenu"
8435
- variant="tertiary" [iconOnly]="true"
8436
- [attr.aria-label]="'navbarTabs.moreTabs' | transloco" [tooltip]="'navbarTabs.moreTabs' | transloco" overflowStop
8437
- [disabled]="moreTabs().length === 0"
8438
- class="ms-auto shrink-0"
8439
- [class.invisible]="moreTabs().length === 0">
8440
- <EllipsisIcon orientation="vertical"></EllipsisIcon>
8441
- <span class="sr-only">{{ "navbarTabs.moreTabs" | transloco }}</span>
8442
- </button>
8443
- </TabList>
8444
- </Tabs>
8462
+ <!-- Pill strip: ms-auto pins the trigger to the right end of the strip. Underline variants: no pin, the trigger
8463
+ follows the last visible tab, which is where the eye expects the rest of the list to be. The overflow
8464
+ manager reads only its size, never its position, so both placements measure the same.
8445
8465
 
8446
- <Menu #moreTabsMenu="menu" placement="bottom-end">
8447
- <MenuContent class="z-500 w-fit">
8448
- @for (tab of moreTabs(); track $index) {
8449
- <MenuItem [value]="tab" [disabled]="isTabDisabled(tab)">
8450
- <a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
8451
- [routerLink]="isTabDisabled(tab) ? null : [tab.routerLink]"
8452
- [queryParams]="getQueryParams(tab)"
8453
- [queryParamsHandling]="getQueryParamsHandling()"
8454
- [attr.aria-selected]="nav.currentPath() === tab.path"
8455
- [attr.aria-label]="tab.display | syslang | transloco"
8456
- [attr.aria-disabled]="isTabDisabled(tab) ? true : null"
8457
- [attr.tabindex]="isTabDisabled(tab) ? -1 : null"
8458
- (click)="onOverflowItemClick($event, tab)"
8459
- >
8460
- @if (tab.icon) {
8461
- <FaIcon [faClass]="tab.icon"/>
8462
- }
8463
- {{ tab.display | syslang | transloco }}
8464
- </a>
8465
- </MenuItem>
8466
- }
8467
- </MenuContent>
8468
- </Menu>
8469
- </div>
8470
- }
8471
- `,
8472
- isInline: true,
8473
- dependencies: [
8474
- {
8475
- kind: "directive",
8476
- type: RouterLink,
8477
- selector: "[routerLink]",
8478
- inputs: [
8479
- "target",
8480
- "queryParams",
8481
- "fragment",
8482
- "queryParamsHandling",
8483
- "state",
8484
- "info",
8485
- "relativeTo",
8486
- "preserveFragment",
8487
- "skipLocationChange",
8488
- "replaceUrl",
8489
- "browserUrl",
8490
- "routerLink"
8491
- ]
8492
- },
8493
- {
8494
- kind: "directive",
8495
- type: ButtonComponent,
8496
- selector: "button",
8497
- inputs: [
8498
- "class",
8499
- "variant",
8500
- "scheme",
8501
- "iconOnly",
8502
- "size"
8503
- ]
8504
- },
8505
- {
8506
- kind: "directive",
8507
- type: TabsComponent,
8508
- selector: "Tabs",
8509
- inputs: ["class"]
8510
- },
8511
- {
8512
- kind: "directive",
8513
- type: TabListComponent,
8514
- selector: "TabList",
8515
- inputs: ["class", "variant"]
8516
- },
8517
- {
8518
- kind: "directive",
8519
- type: TabComponent,
8520
- selector: "Tab",
8521
- inputs: [
8522
- "class",
8523
- "variant",
8524
- "size",
8525
- "noTruncate"
8526
- ]
8527
- },
8528
- {
8529
- kind: "directive",
8530
- type: Tab,
8531
- selector: "[ngTab]",
8532
- inputs: [
8533
- "id",
8534
- "disabled",
8535
- "value"
8536
- ],
8537
- exportAs: ["ngTab"]
8538
- },
8539
- {
8540
- kind: "directive",
8541
- type: OverflowManagerDirective,
8542
- selector: "[overflowManager]",
8543
- inputs: [
8544
- "target",
8545
- "margin",
8546
- "direction",
8547
- "reserveStop"
8548
- ],
8549
- outputs: ["count"]
8550
- },
8551
- {
8552
- kind: "directive",
8553
- type: OverflowItemDirective,
8554
- selector: "[overflowItem]"
8555
- },
8556
- {
8557
- kind: "directive",
8558
- type: OverflowStopDirective,
8559
- selector: "[overflowStop]"
8560
- },
8561
- {
8562
- kind: "directive",
8563
- type: BadgeComponent,
8564
- selector: "badge, Badge",
8565
- inputs: [
8566
- "class",
8567
- "variant",
8568
- "fill",
8569
- "scheme",
8570
- "size",
8571
- "aria-label"
8572
- ]
8573
- },
8574
- {
8575
- kind: "component",
8576
- type: EllipsisIcon,
8577
- selector: "ellipsis-icon, EllipsisIcon, ellipsisicon",
8578
- inputs: ["class", "orientation"]
8579
- },
8580
- {
8581
- kind: "directive",
8582
- type: TooltipDirective,
8583
- selector: "[tooltip]",
8584
- inputs: [
8585
- "tooltip",
8586
- "tooltipPosition",
8587
- "tooltipEvent",
8588
- "showDelay",
8589
- "hideDelay",
8590
- "life",
8591
- "tooltipOffset",
8592
- "autoHide",
8593
- "hideOnEscape",
8594
- "escape",
8595
- "tooltipDisabled",
8596
- "tooltipArrow",
8597
- "tooltipStyleClass",
8598
- "tooltipOptions",
8599
- "tooltip-position",
8600
- "tooltip-delay",
8601
- "tooltip-duration",
8602
- "tooltip-offset"
8603
- ],
8604
- exportAs: ["tooltip"]
8605
- },
8606
- {
8607
- kind: "component",
8608
- type: FaIconComponent,
8609
- selector: "fa-icon, FaIcon",
8610
- inputs: ["faClass", "class"]
8611
- },
8612
- {
8613
- kind: "directive",
8614
- type: MenuTriggerDirective,
8615
- selector: "[menuTrigger]",
8616
- inputs: ["menuTrigger"],
8617
- exportAs: ["menuTrigger"]
8618
- },
8619
- {
8620
- kind: "directive",
8621
- type: MenuComponent,
8622
- selector: "Menu,menu",
8623
- inputs: ["class"],
8624
- exportAs: ["menu"]
8625
- },
8626
- {
8627
- kind: "component",
8628
- type: MenuContentComponent,
8629
- selector: "menu-content,MenuContent,menucontent",
8630
- inputs: ["class"]
8631
- },
8632
- {
8633
- kind: "directive",
8634
- type: MenuItemComponent,
8635
- selector: "menu-item,MenuItem,menuitem",
8636
- inputs: ["class", "checked"],
8637
- exportAs: ["menuitem"]
8638
- },
8639
- {
8640
- kind: "pipe",
8641
- type: TranslocoPipe,
8642
- name: "transloco"
8643
- },
8644
- {
8645
- kind: "pipe",
8646
- type: SyslangPipe,
8647
- name: "syslang"
8648
- }
8649
- ]
8650
- });
8651
- };
8652
- i0.ɵɵngDeclareClassMetadata({
8653
- minVersion: "12.0.0",
8654
- version: "22.2.1",
8655
- ngImport: i0,
8656
- type: NavbarTabsComponent,
8657
- decorators: [{
8658
- type: Component,
8659
- args: [{
8660
- selector: "navbar-tabs",
8661
- imports: [
8662
- RouterLink,
8663
- TranslocoPipe,
8664
- SyslangPipe,
8665
- ButtonComponent,
8666
- TabsComponent,
8667
- TabListComponent,
8668
- TabComponent,
8669
- Tab,
8670
- OverflowManagerDirective,
8671
- OverflowItemDirective,
8672
- OverflowStopDirective,
8673
- BadgeComponent,
8674
- EllipsisIcon,
8675
- TooltipDirective,
8676
- FaIconComponent,
8677
- MenuTriggerDirective,
8678
- MenuComponent,
8679
- MenuContentComponent,
8680
- MenuItemComponent
8681
- ],
8682
- providers: [provideTranslocoScope("navbar-tabs")],
8683
- template: `
8684
- <!-- do not display the tabs if there are no tabs -->
8685
- @if (nav.tabs().length > 0) {
8686
- <div overflowManager (count)="visibleTabCount.set($event)" class="flex items-end rounded-[inherit] bg-inherit">
8687
- <Tabs class="flex-1 min-w-0 overflow-hidden">
8688
- <!-- The tab list needs a name of its own: the tablist role announces itself and its item
8689
- count, but without an accessible name a screen reader has nothing to tell the user
8690
- WHICH list it landed in — and a page may hold more than one. The default is translated
8691
- so every consumer gets a named tab list without passing anything.
8692
- (No backticks in this comment: the template is a template literal.) -->
8693
- <TabList class="w-full" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
8694
- @for (tab of nav.tabs(); track $index) {
8695
- <Tab
8696
- [class]="cn('group', !noTruncate() && 'basis-50')"
8697
- [value]="tab.path"
8698
- overflowItem
8699
- [noTruncate]="noTruncate()"
8700
- [style.--tab-min-width]="tabMinWidth(!!tab.icon)"
8701
- [disabled]="isTabDisabled(tab) || !isTabVisible($index)"
8702
- >
8703
- <div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
8704
- @if (tab.icon) {
8705
- <!-- the icon never shrinks: the label truncates first -->
8706
- <FaIcon [faClass]="tab.icon" class="shrink-0" aria-hidden="true" />
8707
- }
8708
- <!-- when the tab gets too narrow to show anything meaningful next to
8709
- the icon, hide the label entirely instead of a clipped sliver.
8710
- w-0 + invisible (not display: none) keeps the label's line box,
8711
- so the tab height doesn't collapse to the icon's height -->
8712
- <span [class]="cn(!noTruncate() && 'truncate', !noTruncate() && tab.icon && '@max-[3rem]:w-0 @max-[3rem]:invisible')">{{ tab.display | syslang | transloco }}</span>
8713
- <!-- Show count badge only if count is > 0 -->
8714
- @if((tab.count ?? 0) > 0) {
8715
- <Badge
8716
- variant="number"
8717
- fill="secondary"
8718
- scheme="sage"
8719
- size="xs"
8720
- [aria-label]="tab.count + ' ' + (tab.count === 1 ? ('navbarTabs.result' | transloco) : ('navbarTabs.results' | transloco))">
8721
- {{ tab.count }}
8722
- </Badge>
8723
- }
8724
- </div>
8725
- </Tab>
8726
- }
8727
- <!-- The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
8466
+ The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
8728
8467
  contain tab elements, so a plain button here would break the announced tab count and
8729
8468
  the arrow-key navigation. ngTab gives it the role and enrols it in the roving tabindex
8730
8469
  — so the list keeps ONE tab stop and the arrows reach it like any other tab.
@@ -8743,9 +8482,322 @@ i0.ɵɵngDeclareClassMetadata({
8743
8482
  variant="tertiary" [iconOnly]="true"
8744
8483
  [attr.aria-label]="'navbarTabs.moreTabs' | transloco" [tooltip]="'navbarTabs.moreTabs' | transloco" overflowStop
8745
8484
  [disabled]="moreTabs().length === 0"
8746
- class="ms-auto shrink-0"
8485
+ [class]="cn('shrink-0', isPill() ? 'ms-auto' : 'self-center')"
8747
8486
  [class.invisible]="moreTabs().length === 0">
8748
- <EllipsisIcon orientation="vertical"></EllipsisIcon>
8487
+ <EllipsisIcon [orientation]="isPill() ? 'vertical' : 'horizontal'"></EllipsisIcon>
8488
+ <span class="sr-only">{{ "navbarTabs.moreTabs" | transloco }}</span>
8489
+ </button>
8490
+ </TabList>
8491
+ </Tabs>
8492
+
8493
+ <Menu #moreTabsMenu="menu" placement="bottom-end">
8494
+ <MenuContent class="z-500 w-fit">
8495
+ @for (tab of moreTabs(); track $index) {
8496
+ <MenuItem [value]="tab" [disabled]="isTabDisabled(tab)">
8497
+ <a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
8498
+ [routerLink]="isTabDisabled(tab) ? null : [tab.routerLink]"
8499
+ [queryParams]="getQueryParams(tab)"
8500
+ [queryParamsHandling]="getQueryParamsHandling()"
8501
+ [attr.aria-selected]="nav.currentPath() === tab.path"
8502
+ [attr.aria-label]="tab.display | syslang | transloco"
8503
+ [attr.aria-disabled]="isTabDisabled(tab) ? true : null"
8504
+ [attr.tabindex]="isTabDisabled(tab) ? -1 : null"
8505
+ (click)="onOverflowItemClick($event, tab)"
8506
+ >
8507
+ @if (tab.icon) {
8508
+ <FaIcon [faClass]="tab.icon"/>
8509
+ }
8510
+ {{ tab.display | syslang | transloco }}
8511
+ </a>
8512
+ </MenuItem>
8513
+ }
8514
+ </MenuContent>
8515
+ </Menu>
8516
+ </div>
8517
+ }
8518
+ `,
8519
+ isInline: true,
8520
+ dependencies: [
8521
+ {
8522
+ kind: "directive",
8523
+ type: RouterLink,
8524
+ selector: "[routerLink]",
8525
+ inputs: [
8526
+ "target",
8527
+ "queryParams",
8528
+ "fragment",
8529
+ "queryParamsHandling",
8530
+ "state",
8531
+ "info",
8532
+ "relativeTo",
8533
+ "preserveFragment",
8534
+ "skipLocationChange",
8535
+ "replaceUrl",
8536
+ "browserUrl",
8537
+ "routerLink"
8538
+ ]
8539
+ },
8540
+ {
8541
+ kind: "directive",
8542
+ type: ButtonComponent,
8543
+ selector: "button",
8544
+ inputs: [
8545
+ "class",
8546
+ "variant",
8547
+ "scheme",
8548
+ "iconOnly",
8549
+ "size"
8550
+ ]
8551
+ },
8552
+ {
8553
+ kind: "directive",
8554
+ type: TabsComponent,
8555
+ selector: "Tabs",
8556
+ inputs: ["class"]
8557
+ },
8558
+ {
8559
+ kind: "directive",
8560
+ type: TabListComponent,
8561
+ selector: "TabList",
8562
+ inputs: ["class", "variant"]
8563
+ },
8564
+ {
8565
+ kind: "directive",
8566
+ type: TabComponent,
8567
+ selector: "Tab",
8568
+ inputs: [
8569
+ "class",
8570
+ "variant",
8571
+ "size",
8572
+ "noTruncate"
8573
+ ]
8574
+ },
8575
+ {
8576
+ kind: "directive",
8577
+ type: Tab,
8578
+ selector: "[ngTab]",
8579
+ inputs: [
8580
+ "id",
8581
+ "disabled",
8582
+ "value"
8583
+ ],
8584
+ exportAs: ["ngTab"]
8585
+ },
8586
+ {
8587
+ kind: "directive",
8588
+ type: OverflowManagerDirective,
8589
+ selector: "[overflowManager]",
8590
+ inputs: [
8591
+ "target",
8592
+ "margin",
8593
+ "direction",
8594
+ "reserveStop"
8595
+ ],
8596
+ outputs: ["count"]
8597
+ },
8598
+ {
8599
+ kind: "directive",
8600
+ type: OverflowItemDirective,
8601
+ selector: "[overflowItem]"
8602
+ },
8603
+ {
8604
+ kind: "directive",
8605
+ type: OverflowStopDirective,
8606
+ selector: "[overflowStop]"
8607
+ },
8608
+ {
8609
+ kind: "directive",
8610
+ type: BadgeComponent,
8611
+ selector: "badge, Badge",
8612
+ inputs: [
8613
+ "class",
8614
+ "variant",
8615
+ "fill",
8616
+ "scheme",
8617
+ "size",
8618
+ "aria-label"
8619
+ ]
8620
+ },
8621
+ {
8622
+ kind: "component",
8623
+ type: EllipsisIcon,
8624
+ selector: "ellipsis-icon, EllipsisIcon, ellipsisicon",
8625
+ inputs: ["class", "orientation"]
8626
+ },
8627
+ {
8628
+ kind: "directive",
8629
+ type: TooltipDirective,
8630
+ selector: "[tooltip]",
8631
+ inputs: [
8632
+ "tooltip",
8633
+ "tooltipPosition",
8634
+ "tooltipEvent",
8635
+ "showDelay",
8636
+ "hideDelay",
8637
+ "life",
8638
+ "tooltipOffset",
8639
+ "autoHide",
8640
+ "hideOnEscape",
8641
+ "escape",
8642
+ "tooltipDisabled",
8643
+ "tooltipArrow",
8644
+ "tooltipStyleClass",
8645
+ "tooltipOptions",
8646
+ "tooltip-position",
8647
+ "tooltip-delay",
8648
+ "tooltip-duration",
8649
+ "tooltip-offset"
8650
+ ],
8651
+ exportAs: ["tooltip"]
8652
+ },
8653
+ {
8654
+ kind: "component",
8655
+ type: FaIconComponent,
8656
+ selector: "fa-icon, FaIcon",
8657
+ inputs: ["faClass", "class"]
8658
+ },
8659
+ {
8660
+ kind: "directive",
8661
+ type: MenuTriggerDirective,
8662
+ selector: "[menuTrigger]",
8663
+ inputs: ["menuTrigger"],
8664
+ exportAs: ["menuTrigger"]
8665
+ },
8666
+ {
8667
+ kind: "directive",
8668
+ type: MenuComponent,
8669
+ selector: "Menu,menu",
8670
+ inputs: ["class"],
8671
+ exportAs: ["menu"]
8672
+ },
8673
+ {
8674
+ kind: "component",
8675
+ type: MenuContentComponent,
8676
+ selector: "menu-content,MenuContent,menucontent",
8677
+ inputs: ["class"]
8678
+ },
8679
+ {
8680
+ kind: "directive",
8681
+ type: MenuItemComponent,
8682
+ selector: "menu-item,MenuItem,menuitem",
8683
+ inputs: ["class", "checked"],
8684
+ exportAs: ["menuitem"]
8685
+ },
8686
+ {
8687
+ kind: "pipe",
8688
+ type: TranslocoPipe,
8689
+ name: "transloco"
8690
+ },
8691
+ {
8692
+ kind: "pipe",
8693
+ type: SyslangPipe,
8694
+ name: "syslang"
8695
+ }
8696
+ ]
8697
+ });
8698
+ };
8699
+ i0.ɵɵngDeclareClassMetadata({
8700
+ minVersion: "12.0.0",
8701
+ version: "22.2.1",
8702
+ ngImport: i0,
8703
+ type: NavbarTabsComponent,
8704
+ decorators: [{
8705
+ type: Component,
8706
+ args: [{
8707
+ selector: "navbar-tabs",
8708
+ imports: [
8709
+ RouterLink,
8710
+ TranslocoPipe,
8711
+ SyslangPipe,
8712
+ ButtonComponent,
8713
+ TabsComponent,
8714
+ TabListComponent,
8715
+ TabComponent,
8716
+ Tab,
8717
+ OverflowManagerDirective,
8718
+ OverflowItemDirective,
8719
+ OverflowStopDirective,
8720
+ BadgeComponent,
8721
+ EllipsisIcon,
8722
+ TooltipDirective,
8723
+ FaIconComponent,
8724
+ MenuTriggerDirective,
8725
+ MenuComponent,
8726
+ MenuContentComponent,
8727
+ MenuItemComponent
8728
+ ],
8729
+ providers: [provideTranslocoScope("navbar-tabs")],
8730
+ template: `
8731
+ <!-- do not display the tabs if there are no tabs -->
8732
+ @if (nav.tabs().length > 0) {
8733
+ <div overflowManager (count)="visibleTabCount.set($event)" class="flex items-end rounded-[inherit] bg-inherit">
8734
+ <Tabs class="flex-1 min-w-0 overflow-hidden">
8735
+ <!-- The tab list needs a name of its own: the tablist role announces itself and its item
8736
+ count, but without an accessible name a screen reader has nothing to tell the user
8737
+ WHICH list it landed in — and a page may hold more than one. The default is translated
8738
+ so every consumer gets a named tab list without passing anything.
8739
+ (No backticks in this comment: the template is a template literal.) -->
8740
+ <TabList class="w-full" [variant]="variant()" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
8741
+ @for (tab of nav.tabs(); track $index) {
8742
+ <Tab
8743
+ [variant]="variant()"
8744
+ [class]="cn('group', !noTruncate() && 'basis-50', !isPill() && 'h-11 px-(--spacing-xs) text-(length:--size-base) font-semibold')"
8745
+ [value]="tab.path"
8746
+ overflowItem
8747
+ [noTruncate]="noTruncate()"
8748
+ [style.--tab-min-width]="tabMinWidth(!!tab.icon)"
8749
+ [disabled]="isTabDisabled(tab) || !isTabVisible($index)"
8750
+ >
8751
+ <div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
8752
+ @if (tab.icon) {
8753
+ <!-- the icon never shrinks: the label truncates first -->
8754
+ <FaIcon [faClass]="tab.icon" class="shrink-0" aria-hidden="true" />
8755
+ }
8756
+ <!-- when the tab gets too narrow to show anything meaningful next to
8757
+ the icon, hide the label entirely instead of a clipped sliver.
8758
+ w-0 + invisible (not display: none) keeps the label's line box,
8759
+ so the tab height doesn't collapse to the icon's height -->
8760
+ <span [class]="cn(!noTruncate() && 'truncate', !noTruncate() && tab.icon && '@max-[3rem]:w-0 @max-[3rem]:invisible')">{{ tab.display | syslang | transloco }}</span>
8761
+ <!-- Show count badge only if count is > 0 -->
8762
+ @if((tab.count ?? 0) > 0) {
8763
+ <Badge
8764
+ variant="number"
8765
+ fill="secondary"
8766
+ scheme="sage"
8767
+ size="xs"
8768
+ [aria-label]="tab.count + ' ' + (tab.count === 1 ? ('navbarTabs.result' | transloco) : ('navbarTabs.results' | transloco))">
8769
+ {{ tab.count }}
8770
+ </Badge>
8771
+ }
8772
+ </div>
8773
+ </Tab>
8774
+ }
8775
+ <!-- Pill strip: ms-auto pins the trigger to the right end of the strip. Underline variants: no pin, the trigger
8776
+ follows the last visible tab, which is where the eye expects the rest of the list to be. The overflow
8777
+ manager reads only its size, never its position, so both placements measure the same.
8778
+
8779
+ The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
8780
+ contain tab elements, so a plain button here would break the announced tab count and
8781
+ the arrow-key navigation. ngTab gives it the role and enrols it in the roving tabindex
8782
+ — so the list keeps ONE tab stop and the arrows reach it like any other tab.
8783
+
8784
+ It stays a real <button> because that is what menuTrigger needs: it wires the menu
8785
+ through the native popovertarget attribute, which only works on a button.
8786
+
8787
+ ms-auto pins it to the right end of the flex tab list. overflowStop is found by content
8788
+ query, so the measurement is unaffected by where it sits. Always mounted so the stop
8789
+ position is stable; hidden when every tab fits.
8790
+ (No backticks in this comment: the template is a template literal.) -->
8791
+ <button
8792
+ ngTab
8793
+ [value]="MORE_TABS_VALUE"
8794
+ [menuTrigger]="moreTabsMenu"
8795
+ variant="tertiary" [iconOnly]="true"
8796
+ [attr.aria-label]="'navbarTabs.moreTabs' | transloco" [tooltip]="'navbarTabs.moreTabs' | transloco" overflowStop
8797
+ [disabled]="moreTabs().length === 0"
8798
+ [class]="cn('shrink-0', isPill() ? 'ms-auto' : 'self-center')"
8799
+ [class.invisible]="moreTabs().length === 0">
8800
+ <EllipsisIcon [orientation]="isPill() ? 'vertical' : 'horizontal'"></EllipsisIcon>
8749
8801
  <span class="sr-only">{{ "navbarTabs.moreTabs" | transloco }}</span>
8750
8802
  </button>
8751
8803
  </TabList>
@@ -8802,6 +8854,14 @@ i0.ɵɵngDeclareClassMetadata({
8802
8854
  required: false
8803
8855
  }]
8804
8856
  }],
8857
+ variant: [{
8858
+ type: i0.Input,
8859
+ args: [{
8860
+ isSignal: true,
8861
+ alias: "variant",
8862
+ required: false
8863
+ }]
8864
+ }],
8805
8865
  showCount: [{
8806
8866
  type: i0.Input,
8807
8867
  args: [{
@@ -23041,6 +23101,8 @@ var SearchFeedbackComponent = class SearchFeedbackComponent {
23041
23101
  injector = inject(Injector);
23042
23102
  pages = input.required(...ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []);
23043
23103
  variant = input("secondary", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
23104
+ size = input("md", ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
23105
+ dismissible = input(true, ...ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []);
23044
23106
  auditService = inject(AuditService);
23045
23107
  queryParamsStore = inject(QueryParamsStore);
23046
23108
  transloco = inject(TranslocoService);
@@ -23160,12 +23222,26 @@ var SearchFeedbackComponent = class SearchFeedbackComponent {
23160
23222
  isSignal: true,
23161
23223
  isRequired: false,
23162
23224
  transformFunction: null
23225
+ },
23226
+ size: {
23227
+ classPropertyName: "size",
23228
+ publicName: "size",
23229
+ isSignal: true,
23230
+ isRequired: false,
23231
+ transformFunction: null
23232
+ },
23233
+ dismissible: {
23234
+ classPropertyName: "dismissible",
23235
+ publicName: "dismissible",
23236
+ isSignal: true,
23237
+ isRequired: false,
23238
+ transformFunction: null
23163
23239
  }
23164
23240
  },
23165
23241
  outputs: { onClose: "onClose" },
23166
23242
  providers: [provideTranslocoScope("feedback")],
23167
23243
  ngImport: i0,
23168
- template: "@if (showButton()) {\n @let feedback = \"feedback.label\" | transloco;\n <button\n [menuTrigger]=\"feedbackMenu\"\n [variant]=\"variant()\"\n [aria-label]=\"feedback\">\n <comment-icon />\n <span>{{ feedback }}</span>\n <x-mark-icon class=\"ms-2\" (click)=\"close($event)\" />\n </button>\n\n <menu #feedbackMenu=\"menu\" (itemSelected)=\"onMenuItemSelected($event)\">\n <MenuContent>\n @if (showLike() && !disliked()) {\n @let feedbackLike = \"feedback.like\" | transloco;\n @let feedbackLiked = \"feedback.liked\" | transloco;\n <MenuItem value=\"like\" aria-label=\"feedback\">\n @if (liked()) {\n <thumbs-up-icon [solid]=\"true\" />\n {{ feedbackLiked }}\n } @else {\n <thumbs-up-icon />\n {{ feedbackLike }}\n }\n </MenuItem>\n }\n @if (showDislike() && !liked()) {\n <MenuItem value=\"dislike\" [aria-label]=\"'feedback.dislike' | transloco\">\n @if (disliked()) {\n <thumbs-down-icon [solid]=\"true\" />\n {{ \"feedback.disliked\" | transloco }}\n } @else {\n <thumbs-down-icon />\n {{ \"feedback.dislike\" | transloco }}\n }\n </MenuItem>\n }\n @for (menu of visibleMenus(); track menu.type) {\n @let feedbackTitle = \"feedback.\" + menu.type + \".title\" | transloco;\n <MenuItem [value]=\"menu.type\" [aria-label]=\"feedbackTitle\">\n <fa-icon [faClass]=\"menu.icon\" />\n {{ feedbackTitle }}\n </MenuItem>\n }\n </MenuContent>\n </menu>\n}\n",
23244
+ template: "@if (showButton()) {\n @let feedback = \"feedback.label\" | transloco;\n <button\n [menuTrigger]=\"feedbackMenu\"\n [variant]=\"variant()\"\n [size]=\"size()\"\n [aria-label]=\"feedback\">\n <comment-icon />\n <span>{{ feedback }}</span>\n @if (dismissible()) {\n <x-mark-icon class=\"ms-2\" (click)=\"close($event)\" />\n }\n </button>\n\n <menu #feedbackMenu=\"menu\" (itemSelected)=\"onMenuItemSelected($event)\">\n <MenuContent>\n @if (showLike() && !disliked()) {\n @let feedbackLike = \"feedback.like\" | transloco;\n @let feedbackLiked = \"feedback.liked\" | transloco;\n <MenuItem value=\"like\" aria-label=\"feedback\">\n @if (liked()) {\n <thumbs-up-icon [solid]=\"true\" />\n {{ feedbackLiked }}\n } @else {\n <thumbs-up-icon />\n {{ feedbackLike }}\n }\n </MenuItem>\n }\n @if (showDislike() && !liked()) {\n <MenuItem value=\"dislike\" [aria-label]=\"'feedback.dislike' | transloco\">\n @if (disliked()) {\n <thumbs-down-icon [solid]=\"true\" />\n {{ \"feedback.disliked\" | transloco }}\n } @else {\n <thumbs-down-icon />\n {{ \"feedback.dislike\" | transloco }}\n }\n </MenuItem>\n }\n @for (menu of visibleMenus(); track menu.type) {\n @let feedbackTitle = \"feedback.\" + menu.type + \".title\" | transloco;\n <MenuItem [value]=\"menu.type\" [aria-label]=\"feedbackTitle\">\n <fa-icon [faClass]=\"menu.icon\" />\n {{ feedbackTitle }}\n </MenuItem>\n }\n </MenuContent>\n </menu>\n}\n",
23169
23245
  dependencies: [
23170
23246
  {
23171
23247
  kind: "directive",
@@ -23272,7 +23348,7 @@ i0.ɵɵngDeclareClassMetadata({
23272
23348
  A11yModule
23273
23349
  ],
23274
23350
  providers: [provideTranslocoScope("feedback")],
23275
- template: "@if (showButton()) {\n @let feedback = \"feedback.label\" | transloco;\n <button\n [menuTrigger]=\"feedbackMenu\"\n [variant]=\"variant()\"\n [aria-label]=\"feedback\">\n <comment-icon />\n <span>{{ feedback }}</span>\n <x-mark-icon class=\"ms-2\" (click)=\"close($event)\" />\n </button>\n\n <menu #feedbackMenu=\"menu\" (itemSelected)=\"onMenuItemSelected($event)\">\n <MenuContent>\n @if (showLike() && !disliked()) {\n @let feedbackLike = \"feedback.like\" | transloco;\n @let feedbackLiked = \"feedback.liked\" | transloco;\n <MenuItem value=\"like\" aria-label=\"feedback\">\n @if (liked()) {\n <thumbs-up-icon [solid]=\"true\" />\n {{ feedbackLiked }}\n } @else {\n <thumbs-up-icon />\n {{ feedbackLike }}\n }\n </MenuItem>\n }\n @if (showDislike() && !liked()) {\n <MenuItem value=\"dislike\" [aria-label]=\"'feedback.dislike' | transloco\">\n @if (disliked()) {\n <thumbs-down-icon [solid]=\"true\" />\n {{ \"feedback.disliked\" | transloco }}\n } @else {\n <thumbs-down-icon />\n {{ \"feedback.dislike\" | transloco }}\n }\n </MenuItem>\n }\n @for (menu of visibleMenus(); track menu.type) {\n @let feedbackTitle = \"feedback.\" + menu.type + \".title\" | transloco;\n <MenuItem [value]=\"menu.type\" [aria-label]=\"feedbackTitle\">\n <fa-icon [faClass]=\"menu.icon\" />\n {{ feedbackTitle }}\n </MenuItem>\n }\n </MenuContent>\n </menu>\n}\n"
23351
+ template: "@if (showButton()) {\n @let feedback = \"feedback.label\" | transloco;\n <button\n [menuTrigger]=\"feedbackMenu\"\n [variant]=\"variant()\"\n [size]=\"size()\"\n [aria-label]=\"feedback\">\n <comment-icon />\n <span>{{ feedback }}</span>\n @if (dismissible()) {\n <x-mark-icon class=\"ms-2\" (click)=\"close($event)\" />\n }\n </button>\n\n <menu #feedbackMenu=\"menu\" (itemSelected)=\"onMenuItemSelected($event)\">\n <MenuContent>\n @if (showLike() && !disliked()) {\n @let feedbackLike = \"feedback.like\" | transloco;\n @let feedbackLiked = \"feedback.liked\" | transloco;\n <MenuItem value=\"like\" aria-label=\"feedback\">\n @if (liked()) {\n <thumbs-up-icon [solid]=\"true\" />\n {{ feedbackLiked }}\n } @else {\n <thumbs-up-icon />\n {{ feedbackLike }}\n }\n </MenuItem>\n }\n @if (showDislike() && !liked()) {\n <MenuItem value=\"dislike\" [aria-label]=\"'feedback.dislike' | transloco\">\n @if (disliked()) {\n <thumbs-down-icon [solid]=\"true\" />\n {{ \"feedback.disliked\" | transloco }}\n } @else {\n <thumbs-down-icon />\n {{ \"feedback.dislike\" | transloco }}\n }\n </MenuItem>\n }\n @for (menu of visibleMenus(); track menu.type) {\n @let feedbackTitle = \"feedback.\" + menu.type + \".title\" | transloco;\n <MenuItem [value]=\"menu.type\" [aria-label]=\"feedbackTitle\">\n <fa-icon [faClass]=\"menu.icon\" />\n {{ feedbackTitle }}\n </MenuItem>\n }\n </MenuContent>\n </menu>\n}\n"
23276
23352
  }]
23277
23353
  }],
23278
23354
  propDecorators: {
@@ -23295,6 +23371,22 @@ i0.ɵɵngDeclareClassMetadata({
23295
23371
  alias: "variant",
23296
23372
  required: false
23297
23373
  }]
23374
+ }],
23375
+ size: [{
23376
+ type: i0.Input,
23377
+ args: [{
23378
+ isSignal: true,
23379
+ alias: "size",
23380
+ required: false
23381
+ }]
23382
+ }],
23383
+ dismissible: [{
23384
+ type: i0.Input,
23385
+ args: [{
23386
+ isSignal: true,
23387
+ alias: "dismissible",
23388
+ required: false
23389
+ }]
23298
23390
  }]
23299
23391
  }
23300
23392
  });
@@ -24533,8 +24625,8 @@ var AggregationDateComponent = class AggregationDateComponent {
24533
24625
  host: { properties: { "class": "cn(\"@container flex h-[inherit] max-h-[inherit] flex-col\", class(), isEmpty() && \"hidden\")" } },
24534
24626
  providers: [provideTranslocoScope("filters")],
24535
24627
  ngImport: i0,
24536
- template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-1 details-content:flex-col\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n <ng-container [ngTemplateOutlet]=\"content\" />\n </details>\n}\n",
24537
- styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"],
24628
+ template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-col\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n <ng-container [ngTemplateOutlet]=\"content\" />\n </details>\n}\n",
24629
+ styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n", ":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"],
24538
24630
  dependencies: [
24539
24631
  {
24540
24632
  kind: "directive",
@@ -24665,8 +24757,8 @@ i0.ɵɵngDeclareClassMetadata({
24665
24757
  TooltipDirective
24666
24758
  ],
24667
24759
  host: { "[class]": "cn(\"@container flex h-[inherit] max-h-[inherit] flex-col\", class(), isEmpty() && \"hidden\")" },
24668
- template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-1 details-content:flex-col\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n <ng-container [ngTemplateOutlet]=\"content\" />\n </details>\n}\n",
24669
- styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"]
24760
+ template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-col\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n <ng-container [ngTemplateOutlet]=\"content\" />\n </details>\n}\n",
24761
+ styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n", ":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"]
24670
24762
  }]
24671
24763
  }],
24672
24764
  ctorParameters: () => [],
@@ -24869,12 +24961,30 @@ var AggregationPanelComponent = class AggregationPanelComponent {
24869
24961
  isCollapsed = linkedSignal(() => this.collapsed(), ...ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []);
24870
24962
  expanded = computed(() => this.isCollapsed() ? null : "", ...ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []);
24871
24963
  searchBox = viewChild(SearchInputComponent, ...ngDevMode ? [{ debugName: "searchBox" }] : /* istanbul ignore next */ []);
24964
+ searchToggle = viewChild("searchToggle", ...ngDevMode ? [{ debugName: "searchToggle" }] : /* istanbul ignore next */ []);
24965
+ injector = inject(Injector);
24872
24966
  isInPopover = !!inject(PopoverDirective, { optional: true });
24873
- constructor() {
24874
- effect(() => {
24875
- const searchBox = this.searchBox();
24876
- if (this.isInPopover && searchBox && this.expanded() !== null) setTimeout(() => searchBox.searchInput().nativeElement.focus(), 0);
24877
- });
24967
+ static nextId = 0;
24968
+ searchId = `aggregation-search-${AggregationPanelComponent.nextId++}`;
24969
+ searchRequested = signal(false, ...ngDevMode ? [{ debugName: "searchRequested" }] : /* istanbul ignore next */ []);
24970
+ canSearch = computed(() => !!this.aggregation()?.searchable && this.itemsLength() > 0, ...ngDevMode ? [{ debugName: "canSearch" }] : /* istanbul ignore next */ []);
24971
+ showSearch = computed(() => this.canSearch() && (this.searchRequested() || this.searchText() !== ""), ...ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []);
24972
+ toggleSearch(event) {
24973
+ event?.stopPropagation();
24974
+ event?.preventDefault();
24975
+ if (this.showSearch()) {
24976
+ this.searchText.set("");
24977
+ this.searchRequested.set(false);
24978
+ afterNextRender(() => this.searchToggle()?.nativeElement.focus(), { injector: this.injector });
24979
+ return;
24980
+ }
24981
+ this.searchRequested.set(true);
24982
+ afterNextRender(() => this.searchBox()?.searchInput().nativeElement.focus(), { injector: this.injector });
24983
+ }
24984
+ onSearchEscape(event) {
24985
+ if (!this.showSearch()) return;
24986
+ event.stopPropagation();
24987
+ this.toggleSearch();
24878
24988
  }
24879
24989
  onHeaderClick(event) {
24880
24990
  if (!this.isDate() && this.isEmpty()) {
@@ -25039,9 +25149,16 @@ var AggregationPanelComponent = class AggregationPanelComponent {
25039
25149
  predicate: SearchInputComponent,
25040
25150
  descendants: true,
25041
25151
  isSignal: true
25152
+ }, {
25153
+ propertyName: "searchToggle",
25154
+ first: true,
25155
+ predicate: ["searchToggle"],
25156
+ descendants: true,
25157
+ isSignal: true
25042
25158
  }],
25043
25159
  ngImport: i0,
25044
- template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (aggregation()?.searchable && itemsLength() > 0) {\n <!-- No `hotkey`: this field is one of many facet search boxes on the page, so it must not\n advertise a global shortcut. It used to pass `''` to neutralize SearchInput's old\n `\"shift+/\"` default, which left an empty `aria-keyshortcuts` attribute behind; the default\n is gone, so omitting the input is enough. -->\n <!-- A placeholder is not a label (RGAA 11.1): it goes away on the first keystroke, and it is\n the same word in every facet panel of the page. The name says WHICH facet this box\n filters — the one thing that tells a dozen identical fields apart. -->\n <!-- mt-2: without it, the search box sits flush under <summary>, and the header's focus ring reads as touching it.\n Headless has no <summary>, so nothing to keep apart from: the popover's own space (its resize handle, or the header of\n the \"more filters\" facet) is enough. SearchInput also hands its class to its inner field, so the margin counts twice. -->\n <SearchInput\n [class]=\"headless() ? 'mb-1 shrink-0' : 'mt-2 mb-1 shrink-0'\"\n [placeholder]=\"'search' | transloco\"\n [clearLabel]=\"'filters.clearSearch' | transloco\"\n [aria-label]=\"\n 'filters.searchInFacet'\n | transloco\n : { facet: (aggregation()?.display | syslang | transloco) }\n \"\n [(value)]=\"searchText\">\n <ng-content select=\"[search-addon]\" />\n </SearchInput>\n }\n\n @if (itemsLength() > 0 || hasFilters() || selection()) {\n <!-- The bar under the search field, in a popover and in the filters drawer alike: select all (the count of selected items in its tooltip), then clear and apply. Clear is a button\n with a text, not an icon, and stays in place (disabled) when there is nothing to clear. Apply only shows once\n something is pending, as it always did, and sits at the same level. The checkbox keeps its own name: the text\n beside it is a count, not the label of what it does. -->\n <div class=\"mb-1 flex shrink-0 items-center gap-1 pl-1.5\">\n <!-- How many are selected is in the tooltip of the checkbox, not written beside it: that room is what clear and apply need.\n Screen readers keep it as text (the tooltip only shows on hover and focus, and describes). -->\n <span\n class=\"inline-flex shrink-0\"\n [tooltip]=\"\n 'filters.selectedCount' | transloco: { count: selectedCount() }\n \"\n tooltipPosition=\"bottom\">\n <checkbox\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selectedCount() > 0 && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"\n $event ? allSelected.emit() : allUnselected.emit()\n \" />\n </span>\n <span class=\"sr-only\" aria-live=\"polite\">{{\n \"filters.selectedCount\" | transloco: { count: selectedCount() }\n }}</span>\n <span class=\"flex-1\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && selectedCount() === 0\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"cleared.emit()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (selection()) {\n <!-- No icon: the room is short, and the word says it. -->\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"applied.emit()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n }\n\n <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full shrink-0 justify-center\"\n [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadedMore.emit()\">\n {{ \"loadMore\" | transloco }}\n </button>\n }\n </div>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col p-0.5\">\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col p-0.5 details-content:flex details-content:min-h-0 details-content:flex-1 details-content:flex-col\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"\n (isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\n \"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </details>\n}\n",
25160
+ template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #searchToggleSlot>\n <button\n #searchToggle\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0 cursor-pointer\"\n [attr.aria-expanded]=\"showSearch()\"\n [attr.aria-controls]=\"searchId\"\n [tooltip]=\"\n 'filters.searchInFacet'\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n \"\n (click)=\"toggleSearch($event)\">\n <search-icon class=\"shrink-0\" />\n <span class=\"sr-only\">{{\n \"filters.searchInFacet\"\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n }}</span>\n </button>\n</ng-template>\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (canSearch() && !isCollapsed()) {\n <!-- The magnifier opens the search field of the facet (and closes it): the field is not on screen until then. It is\n in the header, next to the chevron, and only while the facet is open — there is nothing to search in a closed\n one. Interactive content in a summary does not toggle the details by itself, and the click is stopped anyway. -->\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (canSearch()) {\n <!-- The field is always in the DOM and folded to nothing until it is asked for: a grid row that goes from 0fr to 1fr is\n the one height animation that needs no measuring. `inert` takes the folded field out of the tab order and of the\n accessibility tree. The margins live in the inner box: a padding on the folded element itself would keep it\n from reaching zero.\n\n No `hotkey`: this field is one of many facet search boxes on the page, so it must not advertise a global shortcut.\n A placeholder is not a label (RGAA 11.1): the name says WHICH facet this box filters — the one thing that tells a\n dozen identical fields apart. SearchInput hands its class to its inner field, so a margin on it would count twice,\n which is one more reason for the padding on the box around it. -->\n <div\n [id]=\"searchId\"\n class=\"grid shrink-0 transition-[grid-template-rows,opacity] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\"\n [class]=\"\n showSearch()\n ? 'grid-rows-[1fr] opacity-100'\n : 'grid-rows-[0fr] opacity-0'\n \"\n [inert]=\"!showSearch()\">\n <!-- `-mx-1 px-1`: the box clips what is outside it, and the focus ring of the field is drawn outside the field,\n so it needs a few pixels of room on each side — taken back by the negative margin so nothing moves. -->\n <div class=\"-mx-1 min-h-0 overflow-hidden px-1\">\n <div [class]=\"headless() ? 'pt-0.5 pb-1' : 'pt-2 pb-1'\">\n <SearchInput\n [placeholder]=\"'search' | transloco\"\n [clearLabel]=\"'filters.clearSearch' | transloco\"\n [aria-label]=\"\n 'filters.searchInFacet'\n | transloco\n : { facet: (aggregation()?.display | syslang | transloco) }\n \"\n [(value)]=\"searchText\"\n (keydown.escape)=\"onSearchEscape($event)\">\n <ng-content select=\"[search-addon]\" />\n </SearchInput>\n </div>\n </div>\n </div>\n }\n\n @if (itemsLength() > 0 || hasFilters() || selection()) {\n <!-- The bar under the search field, in a popover and in the filters drawer alike: clear and apply, and nothing else, so\n both keep their full text in the little room there is. Clear is a button with a text, not an icon, and stays in\n place (disabled) when there is nothing to clear. Apply only shows once something is pending, as it always did, and\n sits at the same level. -->\n <div\n data-bar=\"actions\"\n class=\"mb-1 flex shrink-0 items-center justify-end gap-1\">\n @if (headless() && canSearch()) {\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && selectedCount() === 0\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"cleared.emit()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (selection()) {\n <!-- No icon: the room is short, and the word says it. -->\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"applied.emit()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n }\n\n @if (itemsLength() > 0) {\n <!-- Select all is the first row of the list, written like one: the box lines up with the boxes of the items it drives and\n carries its own text, which a bare box next to two buttons never did. The whole row is the label, so it is as easy\n to hit as an item. The count of checked items is part of that same label (\"Select all (3)\"), so the two can never\n overlap: when the room is short the label is cut with an ellipsis as a whole. It stays out of the scroller of the list, so it does not scroll away with the items. -->\n <div\n data-bar=\"select-all\"\n class=\"mb-1 flex shrink-0 items-center pr-3 pl-1.5 text-sm\">\n <checkbox\n class=\"min-w-0 flex-1 items-center py-1 [&_.checkbox-label]:truncate [&_.checkbox-text-block]:min-w-0\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selectedCount() > 0 && !isAllSelected()\"\n (checkedChange)=\"$event ? allSelected.emit() : allUnselected.emit()\">\n {{\n (isAllSelected() ? \"filters.unselectAll\" : \"filters.selectAll\")\n | transloco\n }}\n @if (selectedCount() > 0) {\n <span class=\"text-(--font-primary-muted)\"\n >({{ selectedCount() }})</span\n >\n }\n </checkbox>\n <span class=\"sr-only\" aria-live=\"polite\">{{\n \"filters.selectedCount\" | transloco: { count: selectedCount() }\n }}</span>\n </div>\n }\n\n <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full shrink-0 justify-center\"\n [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadedMore.emit()\">\n {{ \"loadMore\" | transloco }}\n </button>\n }\n </div>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col p-0.5\">\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col p-0.5 details-content:flex details-content:min-h-0 details-content:flex-col\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"\n (isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\n \"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </details>\n}\n",
25161
+ styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n"],
25045
25162
  dependencies: [
25046
25163
  {
25047
25164
  kind: "directive",
@@ -25084,6 +25201,12 @@ var AggregationPanelComponent = class AggregationPanelComponent {
25084
25201
  selector: "chevron-right, chevron-right-icon, ChevronRight, ChevronRightIcon",
25085
25202
  inputs: ["class"]
25086
25203
  },
25204
+ {
25205
+ kind: "component",
25206
+ type: SearchIcon,
25207
+ selector: "search, search-icon, Search, SearchIcon",
25208
+ inputs: ["class"]
25209
+ },
25087
25210
  {
25088
25211
  kind: "component",
25089
25212
  type: SearchInputComponent,
@@ -25164,6 +25287,7 @@ i0.ɵɵngDeclareClassMetadata({
25164
25287
  TranslocoPipe,
25165
25288
  BadgeComponent,
25166
25289
  ChevronRightIcon,
25290
+ SearchIcon,
25167
25291
  SearchInputComponent,
25168
25292
  FaIconComponent,
25169
25293
  CheckboxComponent,
@@ -25171,10 +25295,10 @@ i0.ɵɵngDeclareClassMetadata({
25171
25295
  ],
25172
25296
  providers: [provideTranslocoScope("filters")],
25173
25297
  host: { class: "flex min-h-0 flex-1 flex-col" },
25174
- template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (aggregation()?.searchable && itemsLength() > 0) {\n <!-- No `hotkey`: this field is one of many facet search boxes on the page, so it must not\n advertise a global shortcut. It used to pass `''` to neutralize SearchInput's old\n `\"shift+/\"` default, which left an empty `aria-keyshortcuts` attribute behind; the default\n is gone, so omitting the input is enough. -->\n <!-- A placeholder is not a label (RGAA 11.1): it goes away on the first keystroke, and it is\n the same word in every facet panel of the page. The name says WHICH facet this box\n filters — the one thing that tells a dozen identical fields apart. -->\n <!-- mt-2: without it, the search box sits flush under <summary>, and the header's focus ring reads as touching it.\n Headless has no <summary>, so nothing to keep apart from: the popover's own space (its resize handle, or the header of\n the \"more filters\" facet) is enough. SearchInput also hands its class to its inner field, so the margin counts twice. -->\n <SearchInput\n [class]=\"headless() ? 'mb-1 shrink-0' : 'mt-2 mb-1 shrink-0'\"\n [placeholder]=\"'search' | transloco\"\n [clearLabel]=\"'filters.clearSearch' | transloco\"\n [aria-label]=\"\n 'filters.searchInFacet'\n | transloco\n : { facet: (aggregation()?.display | syslang | transloco) }\n \"\n [(value)]=\"searchText\">\n <ng-content select=\"[search-addon]\" />\n </SearchInput>\n }\n\n @if (itemsLength() > 0 || hasFilters() || selection()) {\n <!-- The bar under the search field, in a popover and in the filters drawer alike: select all (the count of selected items in its tooltip), then clear and apply. Clear is a button\n with a text, not an icon, and stays in place (disabled) when there is nothing to clear. Apply only shows once\n something is pending, as it always did, and sits at the same level. The checkbox keeps its own name: the text\n beside it is a count, not the label of what it does. -->\n <div class=\"mb-1 flex shrink-0 items-center gap-1 pl-1.5\">\n <!-- How many are selected is in the tooltip of the checkbox, not written beside it: that room is what clear and apply need.\n Screen readers keep it as text (the tooltip only shows on hover and focus, and describes). -->\n <span\n class=\"inline-flex shrink-0\"\n [tooltip]=\"\n 'filters.selectedCount' | transloco: { count: selectedCount() }\n \"\n tooltipPosition=\"bottom\">\n <checkbox\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selectedCount() > 0 && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"\n $event ? allSelected.emit() : allUnselected.emit()\n \" />\n </span>\n <span class=\"sr-only\" aria-live=\"polite\">{{\n \"filters.selectedCount\" | transloco: { count: selectedCount() }\n }}</span>\n <span class=\"flex-1\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && selectedCount() === 0\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"cleared.emit()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (selection()) {\n <!-- No icon: the room is short, and the word says it. -->\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"applied.emit()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n }\n\n <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full shrink-0 justify-center\"\n [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadedMore.emit()\">\n {{ \"loadMore\" | transloco }}\n </button>\n }\n </div>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col p-0.5\">\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col p-0.5 details-content:flex details-content:min-h-0 details-content:flex-1 details-content:flex-col\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"\n (isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\n \"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </details>\n}\n"
25298
+ template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #searchToggleSlot>\n <button\n #searchToggle\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0 cursor-pointer\"\n [attr.aria-expanded]=\"showSearch()\"\n [attr.aria-controls]=\"searchId\"\n [tooltip]=\"\n 'filters.searchInFacet'\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n \"\n (click)=\"toggleSearch($event)\">\n <search-icon class=\"shrink-0\" />\n <span class=\"sr-only\">{{\n \"filters.searchInFacet\"\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n }}</span>\n </button>\n</ng-template>\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (canSearch() && !isCollapsed()) {\n <!-- The magnifier opens the search field of the facet (and closes it): the field is not on screen until then. It is\n in the header, next to the chevron, and only while the facet is open — there is nothing to search in a closed\n one. Interactive content in a summary does not toggle the details by itself, and the click is stopped anyway. -->\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (canSearch()) {\n <!-- The field is always in the DOM and folded to nothing until it is asked for: a grid row that goes from 0fr to 1fr is\n the one height animation that needs no measuring. `inert` takes the folded field out of the tab order and of the\n accessibility tree. The margins live in the inner box: a padding on the folded element itself would keep it\n from reaching zero.\n\n No `hotkey`: this field is one of many facet search boxes on the page, so it must not advertise a global shortcut.\n A placeholder is not a label (RGAA 11.1): the name says WHICH facet this box filters — the one thing that tells a\n dozen identical fields apart. SearchInput hands its class to its inner field, so a margin on it would count twice,\n which is one more reason for the padding on the box around it. -->\n <div\n [id]=\"searchId\"\n class=\"grid shrink-0 transition-[grid-template-rows,opacity] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\"\n [class]=\"\n showSearch()\n ? 'grid-rows-[1fr] opacity-100'\n : 'grid-rows-[0fr] opacity-0'\n \"\n [inert]=\"!showSearch()\">\n <!-- `-mx-1 px-1`: the box clips what is outside it, and the focus ring of the field is drawn outside the field,\n so it needs a few pixels of room on each side — taken back by the negative margin so nothing moves. -->\n <div class=\"-mx-1 min-h-0 overflow-hidden px-1\">\n <div [class]=\"headless() ? 'pt-0.5 pb-1' : 'pt-2 pb-1'\">\n <SearchInput\n [placeholder]=\"'search' | transloco\"\n [clearLabel]=\"'filters.clearSearch' | transloco\"\n [aria-label]=\"\n 'filters.searchInFacet'\n | transloco\n : { facet: (aggregation()?.display | syslang | transloco) }\n \"\n [(value)]=\"searchText\"\n (keydown.escape)=\"onSearchEscape($event)\">\n <ng-content select=\"[search-addon]\" />\n </SearchInput>\n </div>\n </div>\n </div>\n }\n\n @if (itemsLength() > 0 || hasFilters() || selection()) {\n <!-- The bar under the search field, in a popover and in the filters drawer alike: clear and apply, and nothing else, so\n both keep their full text in the little room there is. Clear is a button with a text, not an icon, and stays in\n place (disabled) when there is nothing to clear. Apply only shows once something is pending, as it always did, and\n sits at the same level. -->\n <div\n data-bar=\"actions\"\n class=\"mb-1 flex shrink-0 items-center justify-end gap-1\">\n @if (headless() && canSearch()) {\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && selectedCount() === 0\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"cleared.emit()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (selection()) {\n <!-- No icon: the room is short, and the word says it. -->\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"applied.emit()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n }\n\n @if (itemsLength() > 0) {\n <!-- Select all is the first row of the list, written like one: the box lines up with the boxes of the items it drives and\n carries its own text, which a bare box next to two buttons never did. The whole row is the label, so it is as easy\n to hit as an item. The count of checked items is part of that same label (\"Select all (3)\"), so the two can never\n overlap: when the room is short the label is cut with an ellipsis as a whole. It stays out of the scroller of the list, so it does not scroll away with the items. -->\n <div\n data-bar=\"select-all\"\n class=\"mb-1 flex shrink-0 items-center pr-3 pl-1.5 text-sm\">\n <checkbox\n class=\"min-w-0 flex-1 items-center py-1 [&_.checkbox-label]:truncate [&_.checkbox-text-block]:min-w-0\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selectedCount() > 0 && !isAllSelected()\"\n (checkedChange)=\"$event ? allSelected.emit() : allUnselected.emit()\">\n {{\n (isAllSelected() ? \"filters.unselectAll\" : \"filters.selectAll\")\n | transloco\n }}\n @if (selectedCount() > 0) {\n <span class=\"text-(--font-primary-muted)\"\n >({{ selectedCount() }})</span\n >\n }\n </checkbox>\n <span class=\"sr-only\" aria-live=\"polite\">{{\n \"filters.selectedCount\" | transloco: { count: selectedCount() }\n }}</span>\n </div>\n }\n\n <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full shrink-0 justify-center\"\n [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadedMore.emit()\">\n {{ \"loadMore\" | transloco }}\n </button>\n }\n </div>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col p-0.5\">\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col p-0.5 details-content:flex details-content:min-h-0 details-content:flex-col\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"\n (isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\n \"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </details>\n}\n",
25299
+ styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n"]
25175
25300
  }]
25176
25301
  }],
25177
- ctorParameters: () => [],
25178
25302
  propDecorators: {
25179
25303
  id: [{
25180
25304
  type: i0.Input,
@@ -25338,6 +25462,10 @@ i0.ɵɵngDeclareClassMetadata({
25338
25462
  searchBox: [{
25339
25463
  type: i0.ViewChild,
25340
25464
  args: [i0.forwardRef(() => SearchInputComponent), { isSignal: true }]
25465
+ }],
25466
+ searchToggle: [{
25467
+ type: i0.ViewChild,
25468
+ args: ["searchToggle", { isSignal: true }]
25341
25469
  }]
25342
25470
  }
25343
25471
  });
@@ -26038,7 +26166,7 @@ var AggregationTreeComponent = class AggregationTreeComponent {
26038
26166
  isSignal: true
26039
26167
  }],
26040
26168
  ngImport: i0,
26041
- template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the &lt;Aggregation /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <span\n class=\"grow truncate select-none\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isTreeItemFiltered(node)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n label\n : label\n \"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n",
26169
+ template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the &lt;Aggregation /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <!-- Cut off, the label shows whole in a tooltip; a label that fits needs none. -->\n <span\n class=\"grow truncate select-none\"\n truncated\n #cutCustom=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cutCustom.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n",
26042
26170
  dependencies: [
26043
26171
  {
26044
26172
  kind: "directive",
@@ -26088,12 +26216,45 @@ var AggregationTreeComponent = class AggregationTreeComponent {
26088
26216
  selector: "folder, folder-icon, Folder, FolderIcon",
26089
26217
  inputs: ["class"]
26090
26218
  },
26219
+ {
26220
+ kind: "directive",
26221
+ type: TooltipDirective,
26222
+ selector: "[tooltip]",
26223
+ inputs: [
26224
+ "tooltip",
26225
+ "tooltipPosition",
26226
+ "tooltipEvent",
26227
+ "showDelay",
26228
+ "hideDelay",
26229
+ "life",
26230
+ "tooltipOffset",
26231
+ "autoHide",
26232
+ "hideOnEscape",
26233
+ "escape",
26234
+ "tooltipDisabled",
26235
+ "tooltipArrow",
26236
+ "tooltipStyleClass",
26237
+ "tooltipOptions",
26238
+ "tooltip-position",
26239
+ "tooltip-delay",
26240
+ "tooltip-duration",
26241
+ "tooltip-offset"
26242
+ ],
26243
+ exportAs: ["tooltip"]
26244
+ },
26091
26245
  {
26092
26246
  kind: "component",
26093
26247
  type: TriangleAlertIcon,
26094
26248
  selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
26095
26249
  inputs: ["class"]
26096
26250
  },
26251
+ {
26252
+ kind: "directive",
26253
+ type: TruncatedDirective,
26254
+ selector: "[truncated]",
26255
+ inputs: ["truncated"],
26256
+ exportAs: ["truncated"]
26257
+ },
26097
26258
  {
26098
26259
  kind: "directive",
26099
26260
  type: Tree,
@@ -26205,7 +26366,9 @@ i0.ɵɵngDeclareClassMetadata({
26205
26366
  FaIconComponent,
26206
26367
  FileIcon,
26207
26368
  FolderIcon,
26369
+ TooltipDirective,
26208
26370
  TriangleAlertIcon,
26371
+ TruncatedDirective,
26209
26372
  HighlightWordPipe,
26210
26373
  SyslangPipe,
26211
26374
  TranslocoPipe,
@@ -26215,7 +26378,7 @@ i0.ɵɵngDeclareClassMetadata({
26215
26378
  AggregationPanelComponent
26216
26379
  ],
26217
26380
  host: { "[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isHidden())) && \"hidden\")" },
26218
- template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the &lt;Aggregation /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <span\n class=\"grow truncate select-none\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isTreeItemFiltered(node)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n label\n : label\n \"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n"
26381
+ template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the &lt;Aggregation /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <!-- Cut off, the label shows whole in a tooltip; a label that fits needs none. -->\n <span\n class=\"grow truncate select-none\"\n truncated\n #cutCustom=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cutCustom.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n"
26219
26382
  }]
26220
26383
  }],
26221
26384
  ctorParameters: () => [],
@@ -26923,7 +27086,7 @@ var AggregationListComponent = class AggregationListComponent {
26923
27086
  }
26924
27087
  ],
26925
27088
  ngImport: i0,
26926
- template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the &lt;AggregationTree /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isListItemFiltered(item, field)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n (listItemName(item) | syslang)\n : (listItemName(item) | syslang)\n \"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n",
27089
+ template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the &lt;AggregationTree /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"listItemName(item) | syslang\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n",
26927
27090
  dependencies: [
26928
27091
  {
26929
27092
  kind: "directive",
@@ -26974,12 +27137,45 @@ var AggregationListComponent = class AggregationListComponent {
26974
27137
  selector: "fa-icon, FaIcon",
26975
27138
  inputs: ["faClass", "class"]
26976
27139
  },
27140
+ {
27141
+ kind: "directive",
27142
+ type: TooltipDirective,
27143
+ selector: "[tooltip]",
27144
+ inputs: [
27145
+ "tooltip",
27146
+ "tooltipPosition",
27147
+ "tooltipEvent",
27148
+ "showDelay",
27149
+ "hideDelay",
27150
+ "life",
27151
+ "tooltipOffset",
27152
+ "autoHide",
27153
+ "hideOnEscape",
27154
+ "escape",
27155
+ "tooltipDisabled",
27156
+ "tooltipArrow",
27157
+ "tooltipStyleClass",
27158
+ "tooltipOptions",
27159
+ "tooltip-position",
27160
+ "tooltip-delay",
27161
+ "tooltip-duration",
27162
+ "tooltip-offset"
27163
+ ],
27164
+ exportAs: ["tooltip"]
27165
+ },
26977
27166
  {
26978
27167
  kind: "component",
26979
27168
  type: TriangleAlertIcon,
26980
27169
  selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
26981
27170
  inputs: ["class"]
26982
27171
  },
27172
+ {
27173
+ kind: "directive",
27174
+ type: TruncatedDirective,
27175
+ selector: "[truncated]",
27176
+ inputs: ["truncated"],
27177
+ exportAs: ["truncated"]
27178
+ },
26983
27179
  {
26984
27180
  kind: "component",
26985
27181
  type: AggregationPanelComponent,
@@ -27060,7 +27256,9 @@ i0.ɵɵngDeclareClassMetadata({
27060
27256
  SyslangPipe,
27061
27257
  TranslocoPipe,
27062
27258
  FaIconComponent,
27259
+ TooltipDirective,
27063
27260
  TriangleAlertIcon,
27261
+ TruncatedDirective,
27064
27262
  HighlightWordPipe,
27065
27263
  AggregationPanelComponent,
27066
27264
  CheckboxComponent
@@ -27069,7 +27267,7 @@ i0.ɵɵngDeclareClassMetadata({
27069
27267
  "[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")",
27070
27268
  "(keydown.enter)": "onHostEnterKey($event)"
27071
27269
  },
27072
- template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the &lt;AggregationTree /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isListItemFiltered(item, field)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n (listItemName(item) | syslang)\n : (listItemName(item) | syslang)\n \"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n"
27270
+ template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the &lt;AggregationTree /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"listItemName(item) | syslang\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n"
27073
27271
  }]
27074
27272
  }],
27075
27273
  ctorParameters: () => [],
@@ -28095,6 +28293,17 @@ function chipsForLeaf(leaf, source) {
28095
28293
  }
28096
28294
  var AppliedFiltersComponent = class AppliedFiltersComponent {
28097
28295
  queryParamsStore = inject(QueryParamsStore);
28296
+ static nextId = 0;
28297
+ listId = `applied-filters-${AppliedFiltersComponent.nextId++}`;
28298
+ list = viewChild("list", ...ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []);
28299
+ foldable = signal(false, ...ngDevMode ? [{ debugName: "foldable" }] : /* istanbul ignore next */ []);
28300
+ expanded = signal(false, ...ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []);
28301
+ foldedHeight = signal(0, ...ngDevMode ? [{ debugName: "foldedHeight" }] : /* istanbul ignore next */ []);
28302
+ fullHeight = signal(0, ...ngDevMode ? [{ debugName: "fullHeight" }] : /* istanbul ignore next */ []);
28303
+ listHeight = computed(() => this.foldable() ? this.expanded() ? this.fullHeight() : this.foldedHeight() : null, ...ngDevMode ? [{ debugName: "listHeight" }] : /* istanbul ignore next */ []);
28304
+ toggle() {
28305
+ this.expanded.update((open) => !open);
28306
+ }
28098
28307
  filters = input(void 0, ...ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []);
28099
28308
  syncUrl = input(true, {
28100
28309
  ...ngDevMode ? { debugName: "syncUrl" } : /* istanbul ignore next */ {},
@@ -28108,10 +28317,62 @@ var AppliedFiltersComponent = class AppliedFiltersComponent {
28108
28317
  scheme = input("grey", ...ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []);
28109
28318
  size = input("sm", ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
28110
28319
  chipClass = input("", ...ngDevMode ? [{ debugName: "chipClass" }] : /* istanbul ignore next */ []);
28320
+ maxLines = input(2, {
28321
+ ...ngDevMode ? { debugName: "maxLines" } : /* istanbul ignore next */ {},
28322
+ transform: numberAttribute
28323
+ });
28324
+ showClearAll = input(true, {
28325
+ ...ngDevMode ? { debugName: "showClearAll" } : /* istanbul ignore next */ {},
28326
+ transform: booleanAttribute
28327
+ });
28111
28328
  removed = output();
28112
28329
  cleared = output();
28113
28330
  filtersChange = output();
28114
28331
  changed = output();
28332
+ constructor() {
28333
+ const destroyRef = inject(DestroyRef);
28334
+ afterRenderEffect(() => {
28335
+ this.chips();
28336
+ this.maxLines();
28337
+ this.expanded();
28338
+ this.measure();
28339
+ });
28340
+ afterRenderEffect((onCleanup) => {
28341
+ const element = this.list()?.nativeElement;
28342
+ if (!element || typeof ResizeObserver === "undefined") return;
28343
+ const observer = new ResizeObserver(() => this.measure());
28344
+ observer.observe(element);
28345
+ onCleanup(() => observer.disconnect());
28346
+ });
28347
+ destroyRef.onDestroy(() => this.foldable.set(false));
28348
+ }
28349
+ measure() {
28350
+ const element = this.list()?.nativeElement;
28351
+ const max = this.maxLines();
28352
+ if (!element || max <= 0) {
28353
+ this.foldable.set(false);
28354
+ return;
28355
+ }
28356
+ const items = Array.from(element.children);
28357
+ const first = items[0];
28358
+ if (!first) {
28359
+ this.foldable.set(false);
28360
+ return;
28361
+ }
28362
+ const origin = first.offsetTop;
28363
+ const tops = Array.from(new Set(items.map((item) => item.offsetTop - origin))).sort((a, b) => a - b);
28364
+ const gap = Number.parseFloat(getComputedStyle(element).rowGap) || 0;
28365
+ if (tops.length <= max) {
28366
+ this.foldable.set(false);
28367
+ items.forEach((item) => item.inert = false);
28368
+ return;
28369
+ }
28370
+ this.foldable.set(true);
28371
+ this.foldedHeight.set(Math.max(0, tops[max] - gap));
28372
+ this.fullHeight.set(element.scrollHeight);
28373
+ const folded = this.foldedHeight();
28374
+ items.forEach((item) => item.inert = !this.expanded() && item.offsetTop - origin >= folded);
28375
+ }
28115
28376
  chips = computed(() => {
28116
28377
  return (this.filters() ?? this.queryParamsStore.filters()).flatMap((source) => filterLeaves(source).flatMap((leaf) => chipsForLeaf(leaf, source)));
28117
28378
  }, ...ngDevMode ? [{ debugName: "chips" }] : /* istanbul ignore next */ []);
@@ -28207,6 +28468,20 @@ var AppliedFiltersComponent = class AppliedFiltersComponent {
28207
28468
  isSignal: true,
28208
28469
  isRequired: false,
28209
28470
  transformFunction: null
28471
+ },
28472
+ maxLines: {
28473
+ classPropertyName: "maxLines",
28474
+ publicName: "maxLines",
28475
+ isSignal: true,
28476
+ isRequired: false,
28477
+ transformFunction: null
28478
+ },
28479
+ showClearAll: {
28480
+ classPropertyName: "showClearAll",
28481
+ publicName: "showClearAll",
28482
+ isSignal: true,
28483
+ isRequired: false,
28484
+ transformFunction: null
28210
28485
  }
28211
28486
  },
28212
28487
  outputs: {
@@ -28217,39 +28492,75 @@ var AppliedFiltersComponent = class AppliedFiltersComponent {
28217
28492
  },
28218
28493
  host: { properties: { "class.hidden": "chips().length === 0" } },
28219
28494
  providers: [provideTranslocoScope("filters")],
28495
+ viewQueries: [{
28496
+ propertyName: "list",
28497
+ first: true,
28498
+ predicate: ["list"],
28499
+ descendants: true,
28500
+ isSignal: true
28501
+ }],
28220
28502
  ngImport: i0,
28221
28503
  template: `
28222
28504
  @if (chips().length > 0) {
28223
- <div class="flex flex-wrap items-center gap-1.5" role="list">
28224
- @for (chip of chips(); track chip.id) {
28225
- <tag
28226
- role="listitem"
28227
- [variant]="variant()"
28228
- [scheme]="scheme()"
28229
- [size]="size()"
28230
- [class]="'max-w-full items-center gap-1 ' + chipClass()"
28231
- >
28232
- @if (showField()) {
28233
- <span>{{ (chip.name ?? chip.field) | syslang }}:</span>
28234
- }
28235
- <span class="truncate">{{ chip.display | syslang }}</span>
28236
- <button
28237
- variant="none"
28238
- size="xs"
28239
- [iconOnly]="true"
28240
- class="shrink-0"
28241
- [attr.aria-label]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28242
- [title]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28243
- (click)="remove(chip)"
28505
+ <!-- The zone the "show more" button belongs to: it appears when the pointer is on it or the focus is in it. -->
28506
+ <div class="group/applied relative" [class.pb-7]="foldable() && expanded()">
28507
+ <!-- Past maxLines lines the list is folded to that height — the rest is behind a fade, and a height that grows and
28508
+ shrinks shows it. The chips that fall in the folded part are inert (see measure): a remove button nobody can see
28509
+ must not stay in the tab order. The clip keeps a margin of a few pixels, so the focus ring of the visible chips is whole. -->
28510
+ <div
28511
+ #list
28512
+ [id]="listId"
28513
+ class="flex flex-wrap content-start items-center gap-1.5 [overflow-clip-margin:0.25rem] overflow-clip transition-[height] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
28514
+ [style.height.px]="listHeight()"
28515
+ [style.mask-image]="foldable() && !expanded() ? 'linear-gradient(to bottom, black calc(100% - 1.5rem), transparent)' : null"
28516
+ role="list"
28517
+ >
28518
+ @for (chip of chips(); track chip.id) {
28519
+ <tag
28520
+ role="listitem"
28521
+ [variant]="variant()"
28522
+ [scheme]="scheme()"
28523
+ [size]="size()"
28524
+ [class]="'max-w-full items-center gap-1 ' + chipClass()"
28244
28525
  >
28245
- <x-mark-icon class="size-3" />
28526
+ @if (showField()) {
28527
+ <span>{{ (chip.name ?? chip.field) | syslang }}:</span>
28528
+ }
28529
+ <span class="truncate">{{ chip.display | syslang }}</span>
28530
+ <button
28531
+ variant="none"
28532
+ size="xs"
28533
+ [iconOnly]="true"
28534
+ class="shrink-0"
28535
+ [attr.aria-label]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28536
+ [title]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28537
+ (click)="remove(chip)"
28538
+ >
28539
+ <x-mark-icon class="size-3" />
28540
+ </button>
28541
+ </tag>
28542
+ }
28543
+
28544
+ @if (showClearAll() && chips().length > 1) {
28545
+ <button variant="tertiary" size="xs" (click)="clear()">
28546
+ {{ "filters.clearAllFilters" | transloco }}
28246
28547
  </button>
28247
- </tag>
28248
- }
28548
+ }
28549
+ </div>
28249
28550
 
28250
- @if (chips().length > 1) {
28251
- <button variant="tertiary" size="xs" (click)="clear()">
28252
- {{ "filters.clearAllFilters" | transloco }}
28551
+ @if (foldable()) {
28552
+ <!-- Out of sight until the zone is hovered or holds the focus — and always there on a screen with no hover. Still in the
28553
+ tab order, and revealed by the focus. Its own surface, since it lies over the chips. -->
28554
+ <button
28555
+ type="button"
28556
+ variant="secondary"
28557
+ size="xs"
28558
+ class="absolute right-0 bottom-0 bg-(--bg-neutral-white) opacity-0 shadow-(--shadow-1) transition-opacity duration-150 group-hover/applied:opacity-100 group-focus-within/applied:opacity-100 focus-visible:opacity-100 motion-reduce:transition-none [@media(hover:none)]:opacity-100"
28559
+ [attr.aria-expanded]="expanded()"
28560
+ [attr.aria-controls]="listId"
28561
+ (click)="toggle()"
28562
+ >
28563
+ {{ (expanded() ? "filters.showLess" : "filters.showMore") | transloco }}
28253
28564
  </button>
28254
28565
  }
28255
28566
  </div>
@@ -28320,36 +28631,65 @@ i0.ɵɵngDeclareClassMetadata({
28320
28631
  providers: [provideTranslocoScope("filters")],
28321
28632
  template: `
28322
28633
  @if (chips().length > 0) {
28323
- <div class="flex flex-wrap items-center gap-1.5" role="list">
28324
- @for (chip of chips(); track chip.id) {
28325
- <tag
28326
- role="listitem"
28327
- [variant]="variant()"
28328
- [scheme]="scheme()"
28329
- [size]="size()"
28330
- [class]="'max-w-full items-center gap-1 ' + chipClass()"
28331
- >
28332
- @if (showField()) {
28333
- <span>{{ (chip.name ?? chip.field) | syslang }}:</span>
28334
- }
28335
- <span class="truncate">{{ chip.display | syslang }}</span>
28336
- <button
28337
- variant="none"
28338
- size="xs"
28339
- [iconOnly]="true"
28340
- class="shrink-0"
28341
- [attr.aria-label]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28342
- [title]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28343
- (click)="remove(chip)"
28634
+ <!-- The zone the "show more" button belongs to: it appears when the pointer is on it or the focus is in it. -->
28635
+ <div class="group/applied relative" [class.pb-7]="foldable() && expanded()">
28636
+ <!-- Past maxLines lines the list is folded to that height — the rest is behind a fade, and a height that grows and
28637
+ shrinks shows it. The chips that fall in the folded part are inert (see measure): a remove button nobody can see
28638
+ must not stay in the tab order. The clip keeps a margin of a few pixels, so the focus ring of the visible chips is whole. -->
28639
+ <div
28640
+ #list
28641
+ [id]="listId"
28642
+ class="flex flex-wrap content-start items-center gap-1.5 [overflow-clip-margin:0.25rem] overflow-clip transition-[height] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
28643
+ [style.height.px]="listHeight()"
28644
+ [style.mask-image]="foldable() && !expanded() ? 'linear-gradient(to bottom, black calc(100% - 1.5rem), transparent)' : null"
28645
+ role="list"
28646
+ >
28647
+ @for (chip of chips(); track chip.id) {
28648
+ <tag
28649
+ role="listitem"
28650
+ [variant]="variant()"
28651
+ [scheme]="scheme()"
28652
+ [size]="size()"
28653
+ [class]="'max-w-full items-center gap-1 ' + chipClass()"
28344
28654
  >
28345
- <x-mark-icon class="size-3" />
28655
+ @if (showField()) {
28656
+ <span>{{ (chip.name ?? chip.field) | syslang }}:</span>
28657
+ }
28658
+ <span class="truncate">{{ chip.display | syslang }}</span>
28659
+ <button
28660
+ variant="none"
28661
+ size="xs"
28662
+ [iconOnly]="true"
28663
+ class="shrink-0"
28664
+ [attr.aria-label]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28665
+ [title]="('filters.removeFilter' | transloco) + ': ' + chip.display"
28666
+ (click)="remove(chip)"
28667
+ >
28668
+ <x-mark-icon class="size-3" />
28669
+ </button>
28670
+ </tag>
28671
+ }
28672
+
28673
+ @if (showClearAll() && chips().length > 1) {
28674
+ <button variant="tertiary" size="xs" (click)="clear()">
28675
+ {{ "filters.clearAllFilters" | transloco }}
28346
28676
  </button>
28347
- </tag>
28348
- }
28677
+ }
28678
+ </div>
28349
28679
 
28350
- @if (chips().length > 1) {
28351
- <button variant="tertiary" size="xs" (click)="clear()">
28352
- {{ "filters.clearAllFilters" | transloco }}
28680
+ @if (foldable()) {
28681
+ <!-- Out of sight until the zone is hovered or holds the focus — and always there on a screen with no hover. Still in the
28682
+ tab order, and revealed by the focus. Its own surface, since it lies over the chips. -->
28683
+ <button
28684
+ type="button"
28685
+ variant="secondary"
28686
+ size="xs"
28687
+ class="absolute right-0 bottom-0 bg-(--bg-neutral-white) opacity-0 shadow-(--shadow-1) transition-opacity duration-150 group-hover/applied:opacity-100 group-focus-within/applied:opacity-100 focus-visible:opacity-100 motion-reduce:transition-none [@media(hover:none)]:opacity-100"
28688
+ [attr.aria-expanded]="expanded()"
28689
+ [attr.aria-controls]="listId"
28690
+ (click)="toggle()"
28691
+ >
28692
+ {{ (expanded() ? "filters.showLess" : "filters.showMore") | transloco }}
28353
28693
  </button>
28354
28694
  }
28355
28695
  </div>
@@ -28358,7 +28698,12 @@ i0.ɵɵngDeclareClassMetadata({
28358
28698
  host: { "[class.hidden]": "chips().length === 0" }
28359
28699
  }]
28360
28700
  }],
28701
+ ctorParameters: () => [],
28361
28702
  propDecorators: {
28703
+ list: [{
28704
+ type: i0.ViewChild,
28705
+ args: ["list", { isSignal: true }]
28706
+ }],
28362
28707
  filters: [{
28363
28708
  type: i0.Input,
28364
28709
  args: [{
@@ -28415,6 +28760,22 @@ i0.ɵɵngDeclareClassMetadata({
28415
28760
  required: false
28416
28761
  }]
28417
28762
  }],
28763
+ maxLines: [{
28764
+ type: i0.Input,
28765
+ args: [{
28766
+ isSignal: true,
28767
+ alias: "maxLines",
28768
+ required: false
28769
+ }]
28770
+ }],
28771
+ showClearAll: [{
28772
+ type: i0.Input,
28773
+ args: [{
28774
+ isSignal: true,
28775
+ alias: "showClearAll",
28776
+ required: false
28777
+ }]
28778
+ }],
28418
28779
  removed: [{
28419
28780
  type: i0.Output,
28420
28781
  args: ["removed"]
@@ -31619,6 +31980,1049 @@ i0.ɵɵngDeclareClassMetadata({
31619
31980
  }]
31620
31981
  }
31621
31982
  });
31983
+ function filterLabel(filter) {
31984
+ return filter.name || filter.field || "";
31985
+ }
31986
+ function filterValue(filter) {
31987
+ if (filter.filters?.length) return filter.filters.map((f) => filterLabel(f) ? `${filterLabel(f)}: ${filterValue(f)}` : filterValue(f)).join(", ");
31988
+ if (filter.values?.length) return filter.values.join(", ");
31989
+ return filter.display ?? filter.value ?? [filter.start, filter.end].filter((v) => v !== void 0 && v !== "").join(" – ");
31990
+ }
31991
+ var QuickFiltersListComponent = class QuickFiltersListComponent {
31992
+ searchable = input(false, ...ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []);
31993
+ deletable = input(true, ...ngDevMode ? [{ debugName: "deletable" }] : /* istanbul ignore next */ []);
31994
+ emptyHint = input(...ngDevMode ? [void 0, { debugName: "emptyHint" }] : /* istanbul ignore next */ []);
31995
+ applied = output();
31996
+ deleted = output();
31997
+ userSettingsStore = inject(UserSettingsStore);
31998
+ queryParamsStore = inject(QueryParamsStore);
31999
+ appStore = inject(AppStore);
32000
+ transloco = inject(TranslocoService);
32001
+ host = inject(ElementRef);
32002
+ injector = inject(Injector);
32003
+ deleteKeys = isApplePlatform() ? ["Delete", "Backspace"] : ["Delete"];
32004
+ quickFilters = computed(() => this.userSettingsStore.quickFilters() ?? [], ...ngDevMode ? [{ debugName: "quickFilters" }] : /* istanbul ignore next */ []);
32005
+ query = signal("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
32006
+ filtered = computed(() => {
32007
+ const query = this.query().trim().toLowerCase();
32008
+ return query ? this.quickFilters().filter((quickFilter) => quickFilter.name.toLowerCase().includes(query)) : this.quickFilters();
32009
+ }, ...ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []);
32010
+ announcement = signal("", ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
32011
+ details = signal(null, ...ngDevMode ? [{ debugName: "details" }] : /* istanbul ignore next */ []);
32012
+ detailsCard = viewChild("detailsCard", ...ngDevMode ? [{ debugName: "detailsCard" }] : /* istanbul ignore next */ []);
32013
+ hideTimer;
32014
+ deleting = signal(false, ...ngDevMode ? [{ debugName: "deleting" }] : /* istanbul ignore next */ []);
32015
+ constructor() {
32016
+ injectFloatingPanel({
32017
+ panel: () => this.detailsCard()?.nativeElement,
32018
+ anchor: () => this.details()?.anchor,
32019
+ placement: "bottom-start",
32020
+ offset: 4,
32021
+ topLayer: true
32022
+ });
32023
+ inject(DestroyRef).onDestroy(() => this.cancelHideDetails());
32024
+ }
32025
+ chips(quickFilter) {
32026
+ return quickFilter.filters.map((filter) => {
32027
+ const icon = (filter.field ? this.appStore.getAggregationCustomization(filter.field, filter.name) : void 0)?.icon;
32028
+ const value = filterValue(filter);
32029
+ const label = filterLabel(filter);
32030
+ return {
32031
+ text: icon || !label || !value ? value || label : `${label}: ${value}`,
32032
+ icon
32033
+ };
32034
+ }).filter((chip) => chip.text);
32035
+ }
32036
+ showDetails(quickFilter, anchor) {
32037
+ this.cancelHideDetails();
32038
+ this.details.set({
32039
+ quickFilter,
32040
+ anchor
32041
+ });
32042
+ }
32043
+ scheduleHideDetails() {
32044
+ this.cancelHideDetails();
32045
+ this.hideTimer = setTimeout(() => this.details.set(null), 100);
32046
+ }
32047
+ cancelHideDetails() {
32048
+ clearTimeout(this.hideTimer);
32049
+ }
32050
+ onEscape(event) {
32051
+ if (!this.details()) return;
32052
+ event.stopPropagation();
32053
+ this.cancelHideDetails();
32054
+ this.details.set(null);
32055
+ }
32056
+ picked = signal([], ...ngDevMode ? [{ debugName: "picked" }] : /* istanbul ignore next */ []);
32057
+ pick(values) {
32058
+ const quickFilter = values.at(-1);
32059
+ this.picked.set([]);
32060
+ if (quickFilter) this.apply(quickFilter);
32061
+ }
32062
+ apply(quickFilter) {
32063
+ this.cancelHideDetails();
32064
+ this.details.set(null);
32065
+ this.queryParamsStore.patch({ filters: structuredClone(quickFilter.filters) });
32066
+ this.applied.emit(quickFilter);
32067
+ }
32068
+ async remove(quickFilter, index, event) {
32069
+ event.stopPropagation();
32070
+ if (this.deleting()) return;
32071
+ this.deleting.set(true);
32072
+ this.cancelHideDetails();
32073
+ this.details.set(null);
32074
+ const hadFocus = this.host.nativeElement.contains(document.activeElement);
32075
+ try {
32076
+ await this.userSettingsStore.removeQuickFilter(quickFilter.id);
32077
+ const message = this.transloco.translate("quickFilters.deleted", { name: quickFilter.name });
32078
+ notify.success(message, { duration: 2e3 });
32079
+ this.announcement.set(message);
32080
+ this.deleted.emit(quickFilter);
32081
+ if (hadFocus) afterNextRender(() => {
32082
+ const rows = this.host.nativeElement.querySelectorAll("[role='option']");
32083
+ (rows.length ? rows[Math.min(index, rows.length - 1)] : this.host.nativeElement).focus();
32084
+ }, { injector: this.injector });
32085
+ } finally {
32086
+ this.deleting.set(false);
32087
+ }
32088
+ }
32089
+ static ɵfac = i0.ɵɵngDeclareFactory({
32090
+ minVersion: "12.0.0",
32091
+ version: "22.2.1",
32092
+ ngImport: i0,
32093
+ type: QuickFiltersListComponent,
32094
+ deps: [],
32095
+ target: i0.ɵɵFactoryTarget.Component
32096
+ });
32097
+ static ɵcmp = i0.ɵɵngDeclareComponent({
32098
+ minVersion: "17.0.0",
32099
+ version: "22.2.1",
32100
+ type: QuickFiltersListComponent,
32101
+ isStandalone: true,
32102
+ selector: "QuickFiltersList, quick-filters-list",
32103
+ inputs: {
32104
+ searchable: {
32105
+ classPropertyName: "searchable",
32106
+ publicName: "searchable",
32107
+ isSignal: true,
32108
+ isRequired: false,
32109
+ transformFunction: null
32110
+ },
32111
+ deletable: {
32112
+ classPropertyName: "deletable",
32113
+ publicName: "deletable",
32114
+ isSignal: true,
32115
+ isRequired: false,
32116
+ transformFunction: null
32117
+ },
32118
+ emptyHint: {
32119
+ classPropertyName: "emptyHint",
32120
+ publicName: "emptyHint",
32121
+ isSignal: true,
32122
+ isRequired: false,
32123
+ transformFunction: null
32124
+ }
32125
+ },
32126
+ outputs: {
32127
+ applied: "applied",
32128
+ deleted: "deleted"
32129
+ },
32130
+ host: {
32131
+ attributes: { "tabindex": "-1" },
32132
+ listeners: { "keydown.escape": "onEscape($event)" },
32133
+ classAttribute: "flex min-h-0 flex-col gap-(--spacing-xs) outline-none"
32134
+ },
32135
+ providers: [provideTranslocoScope("quick-filters")],
32136
+ viewQueries: [{
32137
+ propertyName: "detailsCard",
32138
+ first: true,
32139
+ predicate: ["detailsCard"],
32140
+ descendants: true,
32141
+ isSignal: true
32142
+ }],
32143
+ ngImport: i0,
32144
+ template: `
32145
+ @if (searchable() && quickFilters().length > 0) {
32146
+ <SearchInput
32147
+ size="md"
32148
+ class="border-(--stroke-primary-light)"
32149
+ [(value)]="query"
32150
+ [placeholder]="'quickFilters.search' | transloco"
32151
+ [aria-label]="'quickFilters.search' | transloco"
32152
+ [clearLabel]="'quickFilters.clearSearch' | transloco" />
32153
+ }
32154
+
32155
+ @if (filtered().length) {
32156
+ <!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of filters.
32157
+ Choosing a row (click, Enter) applies it; the selection is not kept. The cross at the end of the row deletes it. -->
32158
+ <List
32159
+ variant="list-menu"
32160
+ size="medium"
32161
+ selectionMode="explicit"
32162
+ class="-mx-1 min-h-0 flex-1 overflow-auto px-1 [scrollbar-color:color-mix(in_srgb,currentColor_40%,transparent)_transparent] [scrollbar-width:thin]"
32163
+ [attr.aria-label]="'quickFilters.list' | transloco"
32164
+ [value]="picked()"
32165
+ (valueChange)="pick($event)">
32166
+ @for (quickFilter of filtered(); track quickFilter.id; let index = $index) {
32167
+ <ListItem
32168
+ [value]="quickFilter"
32169
+ [label]="quickFilter.name"
32170
+ (focusin)="showDetails(quickFilter, count)"
32171
+ (focusout)="scheduleHideDetails()">
32172
+ <ListItemLeft>
32173
+ <ListItemIcon><quick-filter-icon /></ListItemIcon>
32174
+ <ListItemText>
32175
+ <!-- The tooltip shows the whole name, and only when it is cut off: a full name needs none. -->
32176
+ <span truncated #cut="truncated" [tooltip]="quickFilter.name" [tooltipDisabled]="!cut.isTruncated()" class="block truncate">{{ quickFilter.name }}</span>
32177
+ <!-- The line of the count of filters is the one that shows what the quick filter holds, on hover (the row itself only applies
32178
+ it); on the keyboard, the row having the focus shows it. It takes the look of the subtext of a row. -->
32179
+ <span
32180
+ #count
32181
+ data-count
32182
+ class="list-item-subtext block w-fit! decoration-dotted underline-offset-2 hover:underline"
32183
+ (mouseenter)="showDetails(quickFilter, count)"
32184
+ (mouseleave)="scheduleHideDetails()">
32185
+ {{ (quickFilter.filters.length === 1 ? "quickFilters.countOne" : "quickFilters.countOther") | transloco: { count: quickFilter.filters.length } }}
32186
+ </span>
32187
+ </ListItemText>
32188
+ </ListItemLeft>
32189
+ @if (deletable()) {
32190
+ <ListItemAction
32191
+ [ariaLabel]="'quickFilters.delete' | transloco: { name: quickFilter.name }"
32192
+ [keys]="deleteKeys"
32193
+ [shortcut]="'quickFilters.deleteShortcut' | transloco"
32194
+ [description]="'quickFilters.deleteDescription' | transloco: { key: 'quickFilters.deleteShortcut' | transloco }"
32195
+ (click)="remove(quickFilter, index, $event)">
32196
+ <x-mark-icon />
32197
+ </ListItemAction>
32198
+ }
32199
+ </ListItem>
32200
+ }
32201
+ </List>
32202
+
32203
+ <!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with Floating UI in
32204
+ the top layer, so it is above whatever clips the list. It stays while the pointer is on it, and while the focus is on the row. -->
32205
+ @if (details(); as shown) {
32206
+ <div
32207
+ #detailsCard
32208
+ role="tooltip"
32209
+ class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
32210
+ (mouseenter)="cancelHideDetails()"
32211
+ (mouseleave)="scheduleHideDetails()">
32212
+ @for (chip of chips(shown.quickFilter); track $index) {
32213
+ <span
32214
+ data-chip
32215
+ class="inline-flex max-w-full min-w-0 items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
32216
+ <!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
32217
+ @if (chip.icon) {
32218
+ <fa-icon [faClass]="chip.icon" class="shrink-0" />
32219
+ }
32220
+ <!-- A chip that is cut off shows its whole text in a tooltip; one that fits has none. -->
32221
+ <span class="truncate" truncated #cutChip="truncated" [tooltip]="chip.text" [tooltipDisabled]="!cutChip.isTruncated()">{{ chip.text }}</span>
32222
+ </span>
32223
+ }
32224
+ </div>
32225
+ }
32226
+ } @else {
32227
+ <!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
32228
+ <div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
32229
+ <span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
32230
+ <quick-filter-icon class="size-6 opacity-80" />
32231
+ </span>
32232
+ <span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{ (query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco }}</span>
32233
+ @if (!query()) {
32234
+ <span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ emptyHint() ?? ("quickFilters.emptyHint" | transloco) }}</span>
32235
+ }
32236
+ </div>
32237
+ }
32238
+
32239
+ <span role="status" class="sr-only">{{ announcement() }}</span>
32240
+ `,
32241
+ isInline: true,
32242
+ dependencies: [
32243
+ {
32244
+ kind: "component",
32245
+ type: FaIconComponent,
32246
+ selector: "fa-icon, FaIcon",
32247
+ inputs: ["faClass", "class"]
32248
+ },
32249
+ {
32250
+ kind: "directive",
32251
+ type: ListComponent,
32252
+ selector: "List,list",
32253
+ inputs: [
32254
+ "class",
32255
+ "variant",
32256
+ "size"
32257
+ ],
32258
+ exportAs: ["list"]
32259
+ },
32260
+ {
32261
+ kind: "directive",
32262
+ type: ListItemComponent,
32263
+ selector: "ListItem,list-item,listitem",
32264
+ inputs: [
32265
+ "class",
32266
+ "variant",
32267
+ "size",
32268
+ "depth"
32269
+ ],
32270
+ exportAs: ["listItem"]
32271
+ },
32272
+ {
32273
+ kind: "component",
32274
+ type: ListItemActionComponent,
32275
+ selector: "ListItemAction,list-item-action,listitemaction",
32276
+ inputs: [
32277
+ "ariaLabel",
32278
+ "disabled",
32279
+ "keys",
32280
+ "shortcut",
32281
+ "description"
32282
+ ],
32283
+ exportAs: ["listItemAction"]
32284
+ },
32285
+ {
32286
+ kind: "directive",
32287
+ type: ListItemIconDirective,
32288
+ selector: "ListItemIcon,list-item-icon,listitemicon",
32289
+ inputs: ["class"]
32290
+ },
32291
+ {
32292
+ kind: "directive",
32293
+ type: ListItemLeftDirective,
32294
+ selector: "ListItemLeft,list-item-left,listitemleft",
32295
+ inputs: ["class"]
32296
+ },
32297
+ {
32298
+ kind: "component",
32299
+ type: ListItemTextComponent,
32300
+ selector: "ListItemText,list-item-text,listitemtext",
32301
+ inputs: ["class", "subtext"],
32302
+ exportAs: ["listItemText"]
32303
+ },
32304
+ {
32305
+ kind: "component",
32306
+ type: QuickFilterIcon,
32307
+ selector: "quick-filter, quick-filter-icon, QuickFilter, QuickFilterIcon",
32308
+ inputs: ["class"]
32309
+ },
32310
+ {
32311
+ kind: "component",
32312
+ type: SearchInputComponent,
32313
+ selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
32314
+ },
32315
+ {
32316
+ kind: "directive",
32317
+ type: TooltipDirective,
32318
+ selector: "[tooltip]",
32319
+ inputs: [
32320
+ "tooltip",
32321
+ "tooltipPosition",
32322
+ "tooltipEvent",
32323
+ "showDelay",
32324
+ "hideDelay",
32325
+ "life",
32326
+ "tooltipOffset",
32327
+ "autoHide",
32328
+ "hideOnEscape",
32329
+ "escape",
32330
+ "tooltipDisabled",
32331
+ "tooltipArrow",
32332
+ "tooltipStyleClass",
32333
+ "tooltipOptions",
32334
+ "tooltip-position",
32335
+ "tooltip-delay",
32336
+ "tooltip-duration",
32337
+ "tooltip-offset"
32338
+ ],
32339
+ exportAs: ["tooltip"]
32340
+ },
32341
+ {
32342
+ kind: "directive",
32343
+ type: TruncatedDirective,
32344
+ selector: "[truncated]",
32345
+ inputs: ["truncated"],
32346
+ exportAs: ["truncated"]
32347
+ },
32348
+ {
32349
+ kind: "component",
32350
+ type: XMarkIcon,
32351
+ selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
32352
+ inputs: ["class"]
32353
+ },
32354
+ {
32355
+ kind: "pipe",
32356
+ type: TranslocoPipe,
32357
+ name: "transloco"
32358
+ }
32359
+ ]
32360
+ });
32361
+ };
32362
+ i0.ɵɵngDeclareClassMetadata({
32363
+ minVersion: "12.0.0",
32364
+ version: "22.2.1",
32365
+ ngImport: i0,
32366
+ type: QuickFiltersListComponent,
32367
+ decorators: [{
32368
+ type: Component,
32369
+ args: [{
32370
+ selector: "QuickFiltersList, quick-filters-list",
32371
+ imports: [
32372
+ TranslocoPipe,
32373
+ FaIconComponent,
32374
+ ListComponent,
32375
+ ListItemComponent,
32376
+ ListItemActionComponent,
32377
+ ListItemIconDirective,
32378
+ ListItemLeftDirective,
32379
+ ListItemTextComponent,
32380
+ QuickFilterIcon,
32381
+ SearchInputComponent,
32382
+ TooltipDirective,
32383
+ TruncatedDirective,
32384
+ XMarkIcon
32385
+ ],
32386
+ providers: [provideTranslocoScope("quick-filters")],
32387
+ host: {
32388
+ class: "flex min-h-0 flex-col gap-(--spacing-xs) outline-none",
32389
+ tabindex: "-1",
32390
+ "(keydown.escape)": "onEscape($event)"
32391
+ },
32392
+ template: `
32393
+ @if (searchable() && quickFilters().length > 0) {
32394
+ <SearchInput
32395
+ size="md"
32396
+ class="border-(--stroke-primary-light)"
32397
+ [(value)]="query"
32398
+ [placeholder]="'quickFilters.search' | transloco"
32399
+ [aria-label]="'quickFilters.search' | transloco"
32400
+ [clearLabel]="'quickFilters.clearSearch' | transloco" />
32401
+ }
32402
+
32403
+ @if (filtered().length) {
32404
+ <!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of filters.
32405
+ Choosing a row (click, Enter) applies it; the selection is not kept. The cross at the end of the row deletes it. -->
32406
+ <List
32407
+ variant="list-menu"
32408
+ size="medium"
32409
+ selectionMode="explicit"
32410
+ class="-mx-1 min-h-0 flex-1 overflow-auto px-1 [scrollbar-color:color-mix(in_srgb,currentColor_40%,transparent)_transparent] [scrollbar-width:thin]"
32411
+ [attr.aria-label]="'quickFilters.list' | transloco"
32412
+ [value]="picked()"
32413
+ (valueChange)="pick($event)">
32414
+ @for (quickFilter of filtered(); track quickFilter.id; let index = $index) {
32415
+ <ListItem
32416
+ [value]="quickFilter"
32417
+ [label]="quickFilter.name"
32418
+ (focusin)="showDetails(quickFilter, count)"
32419
+ (focusout)="scheduleHideDetails()">
32420
+ <ListItemLeft>
32421
+ <ListItemIcon><quick-filter-icon /></ListItemIcon>
32422
+ <ListItemText>
32423
+ <!-- The tooltip shows the whole name, and only when it is cut off: a full name needs none. -->
32424
+ <span truncated #cut="truncated" [tooltip]="quickFilter.name" [tooltipDisabled]="!cut.isTruncated()" class="block truncate">{{ quickFilter.name }}</span>
32425
+ <!-- The line of the count of filters is the one that shows what the quick filter holds, on hover (the row itself only applies
32426
+ it); on the keyboard, the row having the focus shows it. It takes the look of the subtext of a row. -->
32427
+ <span
32428
+ #count
32429
+ data-count
32430
+ class="list-item-subtext block w-fit! decoration-dotted underline-offset-2 hover:underline"
32431
+ (mouseenter)="showDetails(quickFilter, count)"
32432
+ (mouseleave)="scheduleHideDetails()">
32433
+ {{ (quickFilter.filters.length === 1 ? "quickFilters.countOne" : "quickFilters.countOther") | transloco: { count: quickFilter.filters.length } }}
32434
+ </span>
32435
+ </ListItemText>
32436
+ </ListItemLeft>
32437
+ @if (deletable()) {
32438
+ <ListItemAction
32439
+ [ariaLabel]="'quickFilters.delete' | transloco: { name: quickFilter.name }"
32440
+ [keys]="deleteKeys"
32441
+ [shortcut]="'quickFilters.deleteShortcut' | transloco"
32442
+ [description]="'quickFilters.deleteDescription' | transloco: { key: 'quickFilters.deleteShortcut' | transloco }"
32443
+ (click)="remove(quickFilter, index, $event)">
32444
+ <x-mark-icon />
32445
+ </ListItemAction>
32446
+ }
32447
+ </ListItem>
32448
+ }
32449
+ </List>
32450
+
32451
+ <!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with Floating UI in
32452
+ the top layer, so it is above whatever clips the list. It stays while the pointer is on it, and while the focus is on the row. -->
32453
+ @if (details(); as shown) {
32454
+ <div
32455
+ #detailsCard
32456
+ role="tooltip"
32457
+ class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
32458
+ (mouseenter)="cancelHideDetails()"
32459
+ (mouseleave)="scheduleHideDetails()">
32460
+ @for (chip of chips(shown.quickFilter); track $index) {
32461
+ <span
32462
+ data-chip
32463
+ class="inline-flex max-w-full min-w-0 items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
32464
+ <!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
32465
+ @if (chip.icon) {
32466
+ <fa-icon [faClass]="chip.icon" class="shrink-0" />
32467
+ }
32468
+ <!-- A chip that is cut off shows its whole text in a tooltip; one that fits has none. -->
32469
+ <span class="truncate" truncated #cutChip="truncated" [tooltip]="chip.text" [tooltipDisabled]="!cutChip.isTruncated()">{{ chip.text }}</span>
32470
+ </span>
32471
+ }
32472
+ </div>
32473
+ }
32474
+ } @else {
32475
+ <!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
32476
+ <div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
32477
+ <span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
32478
+ <quick-filter-icon class="size-6 opacity-80" />
32479
+ </span>
32480
+ <span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{ (query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco }}</span>
32481
+ @if (!query()) {
32482
+ <span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ emptyHint() ?? ("quickFilters.emptyHint" | transloco) }}</span>
32483
+ }
32484
+ </div>
32485
+ }
32486
+
32487
+ <span role="status" class="sr-only">{{ announcement() }}</span>
32488
+ `
32489
+ }]
32490
+ }],
32491
+ ctorParameters: () => [],
32492
+ propDecorators: {
32493
+ searchable: [{
32494
+ type: i0.Input,
32495
+ args: [{
32496
+ isSignal: true,
32497
+ alias: "searchable",
32498
+ required: false
32499
+ }]
32500
+ }],
32501
+ deletable: [{
32502
+ type: i0.Input,
32503
+ args: [{
32504
+ isSignal: true,
32505
+ alias: "deletable",
32506
+ required: false
32507
+ }]
32508
+ }],
32509
+ emptyHint: [{
32510
+ type: i0.Input,
32511
+ args: [{
32512
+ isSignal: true,
32513
+ alias: "emptyHint",
32514
+ required: false
32515
+ }]
32516
+ }],
32517
+ applied: [{
32518
+ type: i0.Output,
32519
+ args: ["applied"]
32520
+ }],
32521
+ deleted: [{
32522
+ type: i0.Output,
32523
+ args: ["deleted"]
32524
+ }],
32525
+ detailsCard: [{
32526
+ type: i0.ViewChild,
32527
+ args: ["detailsCard", { isSignal: true }]
32528
+ }]
32529
+ }
32530
+ });
32531
+ let nextPanelId = 0;
32532
+ var FiltersPanelComponent = class FiltersPanelComponent {
32533
+ documentCount = input(0, ...ngDevMode ? [{ debugName: "documentCount" }] : /* istanbul ignore next */ []);
32534
+ closed = output();
32535
+ appStore = inject(AppStore);
32536
+ aggregationsStore = inject(AggregationsStore);
32537
+ queryParamsStore = inject(QueryParamsStore);
32538
+ userSettingsStore = inject(UserSettingsStore);
32539
+ transloco = inject(TranslocoService);
32540
+ injector = inject(Injector);
32541
+ nameId = `filters-panel-quick-filter-name-${nextPanelId++}`;
32542
+ tab = signal("all", ...ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []);
32543
+ query = signal("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
32544
+ saving = signal(false, ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
32545
+ announcement = signal("", ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
32546
+ name = signal("", ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
32547
+ appliedCount = computed(() => this.queryParamsStore.filters().flatMap((filter) => this.leaves(filter)).reduce((count, leaf) => count + this.valuesOf(leaf), 0), ...ngDevMode ? [{ debugName: "appliedCount" }] : /* istanbul ignore next */ []);
32548
+ nameInput = viewChild("nameInput", ...ngDevMode ? [{ debugName: "nameInput" }] : /* istanbul ignore next */ []);
32549
+ saveButton = viewChild("saveButton", ...ngDevMode ? [{ debugName: "saveButton" }] : /* istanbul ignore next */ []);
32550
+ matching = computed(() => {
32551
+ const needle = this.query().trim().toLocaleLowerCase();
32552
+ if (!needle) return [];
32553
+ const aside = this.appStore.filters().filter((f) => f.position === "left" || f.position === "both");
32554
+ return this.appStore.getAuthorized(aside).map((f) => this.aggregationsStore.getAggregation(f.name, "name") ?? this.aggregationsStore.getAggregation(f.column, "column")).filter((agg) => !!agg && `${agg.name} ${agg.column}`.toLocaleLowerCase().includes(needle)).map((agg) => agg.name);
32555
+ }, ...ngDevMode ? [{ debugName: "matching" }] : /* istanbul ignore next */ []);
32556
+ selectTab(values) {
32557
+ const next = values.at(-1);
32558
+ if (next) this.tab.set(next);
32559
+ }
32560
+ leaves(filter) {
32561
+ return filter.filters?.length ? filter.filters.flatMap((f) => this.leaves(f)) : [filter];
32562
+ }
32563
+ valuesOf(leaf) {
32564
+ if (leaf.values?.length) return leaf.values.length;
32565
+ return leaf.value !== void 0 && leaf.value !== null ? 1 : 0;
32566
+ }
32567
+ startSaving() {
32568
+ this.name.set("");
32569
+ this.saving.set(true);
32570
+ afterNextRender(() => this.nameInput()?.nativeElement.focus(), { injector: this.injector });
32571
+ }
32572
+ closeSaving() {
32573
+ this.saving.set(false);
32574
+ afterNextRender(() => this.saveButton()?.nativeElement.focus(), { injector: this.injector });
32575
+ }
32576
+ onSaveEscape(event) {
32577
+ event.stopPropagation();
32578
+ this.closeSaving();
32579
+ }
32580
+ async saveQuickFilter(event) {
32581
+ event.preventDefault();
32582
+ const saved = await this.userSettingsStore.addQuickFilter(this.name(), structuredClone(this.queryParamsStore.filters()));
32583
+ if (!saved) return;
32584
+ this.closeSaving();
32585
+ this.announcement.set(this.transloco.translate("filters.panelQuickFilterSaved", { name: saved.name }));
32586
+ }
32587
+ onQuickFilterApplied(quickFilter) {
32588
+ this.announcement.set(this.transloco.translate("filters.panelQuickFilterApplied", { name: quickFilter.name }));
32589
+ }
32590
+ static ɵfac = i0.ɵɵngDeclareFactory({
32591
+ minVersion: "12.0.0",
32592
+ version: "22.2.1",
32593
+ ngImport: i0,
32594
+ type: FiltersPanelComponent,
32595
+ deps: [],
32596
+ target: i0.ɵɵFactoryTarget.Component
32597
+ });
32598
+ static ɵcmp = i0.ɵɵngDeclareComponent({
32599
+ minVersion: "17.0.0",
32600
+ version: "22.2.1",
32601
+ type: FiltersPanelComponent,
32602
+ isStandalone: true,
32603
+ selector: "filters-panel, FiltersPanel, filterspanel",
32604
+ inputs: { documentCount: {
32605
+ classPropertyName: "documentCount",
32606
+ publicName: "documentCount",
32607
+ isSignal: true,
32608
+ isRequired: false,
32609
+ transformFunction: null
32610
+ } },
32611
+ outputs: { closed: "closed" },
32612
+ host: { classAttribute: "block h-full min-h-0" },
32613
+ providers: [provideTranslocoScope("filters")],
32614
+ viewQueries: [{
32615
+ propertyName: "nameInput",
32616
+ first: true,
32617
+ predicate: ["nameInput"],
32618
+ descendants: true,
32619
+ isSignal: true
32620
+ }, {
32621
+ propertyName: "saveButton",
32622
+ first: true,
32623
+ predicate: ["saveButton"],
32624
+ descendants: true,
32625
+ isSignal: true
32626
+ }],
32627
+ ngImport: i0,
32628
+ template: `
32629
+ <div class="flex h-full min-h-0 flex-col gap-(--spacing-xs)">
32630
+ <header class="flex items-center justify-between gap-(--spacing-xs)">
32631
+ <h2 class="flex items-center gap-(--spacing-2xs) text-(length:--size-lg) font-semibold">
32632
+ <filter-icon class="text-(length:--icon-sm) [&_svg]:fill-current" aria-hidden="true" />
32633
+ {{ "filters.panelTitle" | transloco }}
32634
+ </h2>
32635
+ <button variant="tertiary" size="sm" [iconOnly]="true" (click)="closed.emit()" [tooltip]="'filters.panelClose' | transloco" tooltipPosition="bottom">
32636
+ <x-mark-icon />
32637
+ <span class="sr-only">{{ "filters.panelClose" | transloco }}</span>
32638
+ </button>
32639
+ </header>
32640
+
32641
+ <!-- A two-way switch that must always have one side on: the group lets a click on the active item
32642
+ clear the selection, which would leave the panel with no content, so that case is ignored. -->
32643
+ <ToggleGroup class="w-full" [attr.aria-label]="'filters.panelViews' | transloco" [value]="[tab()]" (valueChange)="selectTab($event)">
32644
+ <Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="all">{{ "filters.panelTabAll" | transloco }}</Toggle>
32645
+ <Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="quick">{{ "filters.panelTabQuick" | transloco }}</Toggle>
32646
+ </ToggleGroup>
32647
+
32648
+ @if (tab() === "all") {
32649
+ <SearchInput
32650
+ size="md"
32651
+ class="[&>*]:h-8 [&>*]:rounded-full"
32652
+ [(value)]="query"
32653
+ [placeholder]="'filters.panelSearchPlaceholder' | transloco"
32654
+ [aria-label]="'filters.searchFilters' | transloco"
32655
+ [clearLabel]="'filters.clearSearch' | transloco" />
32656
+
32657
+ <!-- The bar is the anchor of the card: the card hangs under the button, as wide as the panel's content, and covers the facets
32658
+ below it until it is closed. -->
32659
+ <div class="relative flex h-8 items-center justify-between gap-(--spacing-xs) text-(length:--size-sm) text-(--font-primary-muted)">
32660
+ <span>{{ (documentCount() === 1 ? "filters.panelDocumentsOne" : "filters.panelDocumentsOther") | transloco: { count: documentCount() | localizedNumber } }}</span>
32661
+ <button
32662
+ #saveButton
32663
+ variant="secondary"
32664
+ size="sm"
32665
+ aria-haspopup="dialog"
32666
+ [attr.aria-expanded]="saving()"
32667
+ [disabled]="appliedCount() === 0"
32668
+ (click)="saving() ? closeSaving() : startSaving()">
32669
+ <quick-filter-icon />
32670
+ {{ "filters.panelSaveQuickFilter" | transloco }}
32671
+ </button>
32672
+
32673
+ @if (saving()) {
32674
+ <form
32675
+ role="dialog"
32676
+ [attr.aria-label]="'filters.panelSaveQuickFilter' | transloco"
32677
+ class="absolute inset-x-0 top-full z-10 mt-(--spacing-2xs) flex flex-col gap-(--spacing-sm) rounded-(--radius-lg) border border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) text-(--font-neutral-black) shadow-(--shadow-2)"
32678
+ (submit)="saveQuickFilter($event)"
32679
+ (keydown.escape)="onSaveEscape($event)">
32680
+ <div class="flex flex-col gap-(--spacing-3xs)">
32681
+ <div class="flex items-center justify-between">
32682
+ <label [for]="nameId" class="text-(length:--size-sm) font-medium">{{ "filters.panelNameLabel" | transloco }}</label>
32683
+ <span class="rounded-(--radius-xs) bg-(--bg-neutral-base) px-1.5 py-px text-(length:--size-xs) text-(--font-neutral-muted)">{{ "filters.panelRequired" | transloco }}</span>
32684
+ </div>
32685
+ <input-group size="sm" class="w-full">
32686
+ <input
32687
+ #nameInput
32688
+ input-control
32689
+ [id]="nameId"
32690
+ type="text"
32691
+ autocomplete="off"
32692
+ required
32693
+ [placeholder]="'filters.panelQuickFilterName' | transloco"
32694
+ [value]="name()"
32695
+ (input)="name.set(nameInput.value)" />
32696
+ </input-group>
32697
+ </div>
32698
+ <div class="flex justify-end gap-(--spacing-2xs)">
32699
+ <button type="button" variant="secondary" size="sm" (click)="closeSaving()">{{ "filters.panelCancel" | transloco }}</button>
32700
+ <button type="submit" variant="primary" size="sm" [disabled]="!name().trim()">{{ "filters.panelSave" | transloco }}</button>
32701
+ </div>
32702
+ </form>
32703
+ }
32704
+ </div>
32705
+
32706
+ <!-- single outer scroll lives here; aside-filters' own overflow is neutralized so it is not a second, default-styled scroll container.
32707
+ relative, and it is load-bearing: a scroll container only clips the descendants it is a containing block for. The facets hold sr-only
32708
+ elements, which are position: absolute; with this div left static they escaped the clip, sat at their flow position far below the
32709
+ visible part of a long facet, and made the whole page scroll — a second scrollbar next to the facet's own. -->
32710
+ <div class="relative -mx-1 flex min-h-0 flex-1 scrollbar-thin flex-col overflow-y-auto px-1">
32711
+ @if (query() && matching().length === 0) {
32712
+ <p class="py-(--spacing-md) text-center text-(length:--size-sm) text-(--font-neutral-muted)">{{ "filters.noFilterMatch" | transloco }}</p>
32713
+ } @else {
32714
+ <aside-filters class="overflow-visible" [aggregations]="query() ? matching() : []" />
32715
+ }
32716
+ </div>
32717
+ } @else {
32718
+ <!-- The list of the quick filters pill, inline: it owns the rows, the hover card, the cross and the empty state. -->
32719
+ <quick-filters-list
32720
+ class="flex-1"
32721
+ [emptyHint]="'filters.panelNoQuickFiltersHint' | transloco"
32722
+ (applied)="onQuickFilterApplied($event)" />
32723
+ }
32724
+
32725
+ <span role="status" class="sr-only">{{ announcement() }}</span>
32726
+ </div>
32727
+ `,
32728
+ isInline: true,
32729
+ dependencies: [
32730
+ {
32731
+ kind: "component",
32732
+ type: AsideFiltersComponent,
32733
+ selector: "aside-filters, AsideFilters, asidefilters",
32734
+ inputs: [
32735
+ "class",
32736
+ "aggregations",
32737
+ "exclusive",
32738
+ "collapsible",
32739
+ "collapsed",
32740
+ "hideWhenEmpty"
32741
+ ]
32742
+ },
32743
+ {
32744
+ kind: "directive",
32745
+ type: ButtonComponent,
32746
+ selector: "button",
32747
+ inputs: [
32748
+ "class",
32749
+ "variant",
32750
+ "scheme",
32751
+ "iconOnly",
32752
+ "size"
32753
+ ]
32754
+ },
32755
+ {
32756
+ kind: "component",
32757
+ type: FilterIcon,
32758
+ selector: "filter, filter-icon, Filter, FilterIcon",
32759
+ inputs: ["class"]
32760
+ },
32761
+ {
32762
+ kind: "directive",
32763
+ type: InputComponent,
32764
+ selector: "input-group, inputgroup, InputGroup",
32765
+ inputs: [
32766
+ "class",
32767
+ "size",
32768
+ "scheme",
32769
+ "filled",
32770
+ "numbers",
32771
+ "autoHeight",
32772
+ "textSize"
32773
+ ]
32774
+ },
32775
+ {
32776
+ kind: "directive",
32777
+ type: InputControlDirective,
32778
+ selector: "input[input-control], input[inputControl]",
32779
+ inputs: ["class"]
32780
+ },
32781
+ {
32782
+ kind: "component",
32783
+ type: QuickFilterIcon,
32784
+ selector: "quick-filter, quick-filter-icon, QuickFilter, QuickFilterIcon",
32785
+ inputs: ["class"]
32786
+ },
32787
+ {
32788
+ kind: "component",
32789
+ type: QuickFiltersListComponent,
32790
+ selector: "QuickFiltersList, quick-filters-list",
32791
+ inputs: [
32792
+ "searchable",
32793
+ "deletable",
32794
+ "emptyHint"
32795
+ ],
32796
+ outputs: ["applied", "deleted"]
32797
+ },
32798
+ {
32799
+ kind: "component",
32800
+ type: SearchInputComponent,
32801
+ selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
32802
+ },
32803
+ {
32804
+ kind: "directive",
32805
+ type: ToggleGroupComponent,
32806
+ selector: "ToggleGroup, toggle-group, togglegroup",
32807
+ inputs: [
32808
+ "class",
32809
+ "value",
32810
+ "multi"
32811
+ ],
32812
+ outputs: ["valueChange"],
32813
+ exportAs: ["toggleGroup"]
32814
+ },
32815
+ {
32816
+ kind: "directive",
32817
+ type: ToggleItemComponent,
32818
+ selector: "Toggle[value]",
32819
+ inputs: [
32820
+ "value",
32821
+ "class",
32822
+ "variant",
32823
+ "size",
32824
+ "iconOnly",
32825
+ "showIcon"
32826
+ ],
32827
+ exportAs: ["toggle"]
32828
+ },
32829
+ {
32830
+ kind: "directive",
32831
+ type: TooltipDirective,
32832
+ selector: "[tooltip]",
32833
+ inputs: [
32834
+ "tooltip",
32835
+ "tooltipPosition",
32836
+ "tooltipEvent",
32837
+ "showDelay",
32838
+ "hideDelay",
32839
+ "life",
32840
+ "tooltipOffset",
32841
+ "autoHide",
32842
+ "hideOnEscape",
32843
+ "escape",
32844
+ "tooltipDisabled",
32845
+ "tooltipArrow",
32846
+ "tooltipStyleClass",
32847
+ "tooltipOptions",
32848
+ "tooltip-position",
32849
+ "tooltip-delay",
32850
+ "tooltip-duration",
32851
+ "tooltip-offset"
32852
+ ],
32853
+ exportAs: ["tooltip"]
32854
+ },
32855
+ {
32856
+ kind: "component",
32857
+ type: XMarkIcon,
32858
+ selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
32859
+ inputs: ["class"]
32860
+ },
32861
+ {
32862
+ kind: "pipe",
32863
+ type: TranslocoPipe,
32864
+ name: "transloco"
32865
+ },
32866
+ {
32867
+ kind: "pipe",
32868
+ type: LocalizedNumberPipe,
32869
+ name: "localizedNumber"
32870
+ }
32871
+ ]
32872
+ });
32873
+ };
32874
+ i0.ɵɵngDeclareClassMetadata({
32875
+ minVersion: "12.0.0",
32876
+ version: "22.2.1",
32877
+ ngImport: i0,
32878
+ type: FiltersPanelComponent,
32879
+ decorators: [{
32880
+ type: Component,
32881
+ args: [{
32882
+ selector: "filters-panel, FiltersPanel, filterspanel",
32883
+ imports: [
32884
+ TranslocoPipe,
32885
+ LocalizedNumberPipe,
32886
+ AsideFiltersComponent,
32887
+ ButtonComponent,
32888
+ FilterIcon,
32889
+ InputComponent,
32890
+ InputControlDirective,
32891
+ QuickFilterIcon,
32892
+ QuickFiltersListComponent,
32893
+ SearchInputComponent,
32894
+ ToggleGroupComponent,
32895
+ ToggleItemComponent,
32896
+ TooltipDirective,
32897
+ XMarkIcon
32898
+ ],
32899
+ providers: [provideTranslocoScope("filters")],
32900
+ host: { class: "block h-full min-h-0" },
32901
+ template: `
32902
+ <div class="flex h-full min-h-0 flex-col gap-(--spacing-xs)">
32903
+ <header class="flex items-center justify-between gap-(--spacing-xs)">
32904
+ <h2 class="flex items-center gap-(--spacing-2xs) text-(length:--size-lg) font-semibold">
32905
+ <filter-icon class="text-(length:--icon-sm) [&_svg]:fill-current" aria-hidden="true" />
32906
+ {{ "filters.panelTitle" | transloco }}
32907
+ </h2>
32908
+ <button variant="tertiary" size="sm" [iconOnly]="true" (click)="closed.emit()" [tooltip]="'filters.panelClose' | transloco" tooltipPosition="bottom">
32909
+ <x-mark-icon />
32910
+ <span class="sr-only">{{ "filters.panelClose" | transloco }}</span>
32911
+ </button>
32912
+ </header>
32913
+
32914
+ <!-- A two-way switch that must always have one side on: the group lets a click on the active item
32915
+ clear the selection, which would leave the panel with no content, so that case is ignored. -->
32916
+ <ToggleGroup class="w-full" [attr.aria-label]="'filters.panelViews' | transloco" [value]="[tab()]" (valueChange)="selectTab($event)">
32917
+ <Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="all">{{ "filters.panelTabAll" | transloco }}</Toggle>
32918
+ <Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="quick">{{ "filters.panelTabQuick" | transloco }}</Toggle>
32919
+ </ToggleGroup>
32920
+
32921
+ @if (tab() === "all") {
32922
+ <SearchInput
32923
+ size="md"
32924
+ class="[&>*]:h-8 [&>*]:rounded-full"
32925
+ [(value)]="query"
32926
+ [placeholder]="'filters.panelSearchPlaceholder' | transloco"
32927
+ [aria-label]="'filters.searchFilters' | transloco"
32928
+ [clearLabel]="'filters.clearSearch' | transloco" />
32929
+
32930
+ <!-- The bar is the anchor of the card: the card hangs under the button, as wide as the panel's content, and covers the facets
32931
+ below it until it is closed. -->
32932
+ <div class="relative flex h-8 items-center justify-between gap-(--spacing-xs) text-(length:--size-sm) text-(--font-primary-muted)">
32933
+ <span>{{ (documentCount() === 1 ? "filters.panelDocumentsOne" : "filters.panelDocumentsOther") | transloco: { count: documentCount() | localizedNumber } }}</span>
32934
+ <button
32935
+ #saveButton
32936
+ variant="secondary"
32937
+ size="sm"
32938
+ aria-haspopup="dialog"
32939
+ [attr.aria-expanded]="saving()"
32940
+ [disabled]="appliedCount() === 0"
32941
+ (click)="saving() ? closeSaving() : startSaving()">
32942
+ <quick-filter-icon />
32943
+ {{ "filters.panelSaveQuickFilter" | transloco }}
32944
+ </button>
32945
+
32946
+ @if (saving()) {
32947
+ <form
32948
+ role="dialog"
32949
+ [attr.aria-label]="'filters.panelSaveQuickFilter' | transloco"
32950
+ class="absolute inset-x-0 top-full z-10 mt-(--spacing-2xs) flex flex-col gap-(--spacing-sm) rounded-(--radius-lg) border border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) text-(--font-neutral-black) shadow-(--shadow-2)"
32951
+ (submit)="saveQuickFilter($event)"
32952
+ (keydown.escape)="onSaveEscape($event)">
32953
+ <div class="flex flex-col gap-(--spacing-3xs)">
32954
+ <div class="flex items-center justify-between">
32955
+ <label [for]="nameId" class="text-(length:--size-sm) font-medium">{{ "filters.panelNameLabel" | transloco }}</label>
32956
+ <span class="rounded-(--radius-xs) bg-(--bg-neutral-base) px-1.5 py-px text-(length:--size-xs) text-(--font-neutral-muted)">{{ "filters.panelRequired" | transloco }}</span>
32957
+ </div>
32958
+ <input-group size="sm" class="w-full">
32959
+ <input
32960
+ #nameInput
32961
+ input-control
32962
+ [id]="nameId"
32963
+ type="text"
32964
+ autocomplete="off"
32965
+ required
32966
+ [placeholder]="'filters.panelQuickFilterName' | transloco"
32967
+ [value]="name()"
32968
+ (input)="name.set(nameInput.value)" />
32969
+ </input-group>
32970
+ </div>
32971
+ <div class="flex justify-end gap-(--spacing-2xs)">
32972
+ <button type="button" variant="secondary" size="sm" (click)="closeSaving()">{{ "filters.panelCancel" | transloco }}</button>
32973
+ <button type="submit" variant="primary" size="sm" [disabled]="!name().trim()">{{ "filters.panelSave" | transloco }}</button>
32974
+ </div>
32975
+ </form>
32976
+ }
32977
+ </div>
32978
+
32979
+ <!-- single outer scroll lives here; aside-filters' own overflow is neutralized so it is not a second, default-styled scroll container.
32980
+ relative, and it is load-bearing: a scroll container only clips the descendants it is a containing block for. The facets hold sr-only
32981
+ elements, which are position: absolute; with this div left static they escaped the clip, sat at their flow position far below the
32982
+ visible part of a long facet, and made the whole page scroll — a second scrollbar next to the facet's own. -->
32983
+ <div class="relative -mx-1 flex min-h-0 flex-1 scrollbar-thin flex-col overflow-y-auto px-1">
32984
+ @if (query() && matching().length === 0) {
32985
+ <p class="py-(--spacing-md) text-center text-(length:--size-sm) text-(--font-neutral-muted)">{{ "filters.noFilterMatch" | transloco }}</p>
32986
+ } @else {
32987
+ <aside-filters class="overflow-visible" [aggregations]="query() ? matching() : []" />
32988
+ }
32989
+ </div>
32990
+ } @else {
32991
+ <!-- The list of the quick filters pill, inline: it owns the rows, the hover card, the cross and the empty state. -->
32992
+ <quick-filters-list
32993
+ class="flex-1"
32994
+ [emptyHint]="'filters.panelNoQuickFiltersHint' | transloco"
32995
+ (applied)="onQuickFilterApplied($event)" />
32996
+ }
32997
+
32998
+ <span role="status" class="sr-only">{{ announcement() }}</span>
32999
+ </div>
33000
+ `
33001
+ }]
33002
+ }],
33003
+ propDecorators: {
33004
+ documentCount: [{
33005
+ type: i0.Input,
33006
+ args: [{
33007
+ isSignal: true,
33008
+ alias: "documentCount",
33009
+ required: false
33010
+ }]
33011
+ }],
33012
+ closed: [{
33013
+ type: i0.Output,
33014
+ args: ["closed"]
33015
+ }],
33016
+ nameInput: [{
33017
+ type: i0.ViewChild,
33018
+ args: ["nameInput", { isSignal: true }]
33019
+ }],
33020
+ saveButton: [{
33021
+ type: i0.ViewChild,
33022
+ args: ["saveButton", { isSignal: true }]
33023
+ }]
33024
+ }
33025
+ });
31622
33026
  var LabelService = class LabelService {
31623
33027
  appStore = inject(AppStore);
31624
33028
  client = inject(ATOMIC_CLIENT);
@@ -32901,43 +34305,21 @@ i0.ɵɵngDeclareClassMetadata({
32901
34305
  }
32902
34306
  });
32903
34307
  let popoverUid = 0;
32904
- function filterLabel(filter) {
32905
- return filter.name || filter.field || "";
32906
- }
32907
- function filterValue(filter) {
32908
- if (filter.filters?.length) return filter.filters.map((f) => filterLabel(f) ? `${filterLabel(f)}: ${filterValue(f)}` : filterValue(f)).join(", ");
32909
- if (filter.values?.length) return filter.values.join(", ");
32910
- return filter.display ?? filter.value ?? [filter.start, filter.end].filter((v) => v !== void 0 && v !== "").join(" – ");
32911
- }
32912
34308
  var QuickFiltersComponent = class QuickFiltersComponent {
32913
34309
  scheme = input("sage", ...ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []);
32914
34310
  appearance = input("pill", ...ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []);
32915
34311
  position = input("bottom-start", ...ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []);
32916
34312
  offset = input(8, ...ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []);
32917
34313
  applied = output();
32918
- userSettingsStore = inject(UserSettingsStore);
32919
- queryParamsStore = inject(QueryParamsStore);
32920
- appStore = inject(AppStore);
32921
34314
  transloco = inject(TranslocoService);
32922
34315
  host = inject(ElementRef);
32923
34316
  popoverId = `quick-filters-popover-${popoverUid++}`;
32924
34317
  resized = signal(false, ...ngDevMode ? [{ debugName: "resized" }] : /* istanbul ignore next */ []);
32925
34318
  flippedAbove = signal(false, ...ngDevMode ? [{ debugName: "flippedAbove" }] : /* istanbul ignore next */ []);
32926
34319
  placement = computed(() => this.resized() && this.flippedAbove() ? this.position().replace("bottom", "top") : this.position(), ...ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []);
32927
- details = signal(null, ...ngDevMode ? [{ debugName: "details" }] : /* istanbul ignore next */ []);
32928
- detailsCard = viewChild("detailsCard", ...ngDevMode ? [{ debugName: "detailsCard" }] : /* istanbul ignore next */ []);
32929
- hideTimer;
32930
34320
  popover = viewChild(PopoverDirective, ...ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []);
32931
34321
  isOpen = computed(() => this.popover()?.state() === "open", ...ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []);
32932
34322
  constructor() {
32933
- injectFloatingPanel({
32934
- panel: () => this.detailsCard()?.nativeElement,
32935
- anchor: () => this.details()?.anchor,
32936
- placement: "bottom-start",
32937
- offset: 4,
32938
- topLayer: true
32939
- });
32940
- inject(DestroyRef).onDestroy(() => this.cancelHideDetails());
32941
34323
  effect((onCleanup) => {
32942
34324
  if (!this.resized()) return;
32943
34325
  const onScroll = (event) => {
@@ -32957,27 +34339,9 @@ var QuickFiltersComponent = class QuickFiltersComponent {
32957
34339
  selected: false,
32958
34340
  open: this.isOpen()
32959
34341
  }) : this.isOpen() ? "border border-(--stroke-neutral-dark)/50" : "", ...ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []);
32960
- quickFilters = computed(() => this.userSettingsStore.quickFilters() ?? [], ...ngDevMode ? [{ debugName: "quickFilters" }] : /* istanbul ignore next */ []);
32961
- query = signal("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
32962
- filtered = computed(() => {
32963
- const query = this.query().trim().toLowerCase();
32964
- return query ? this.quickFilters().filter((quickFilter) => quickFilter.name.toLowerCase().includes(query)) : this.quickFilters();
32965
- }, ...ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []);
32966
34342
  announcement = signal("", ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
32967
- chips(quickFilter) {
32968
- return quickFilter.filters.map((filter) => {
32969
- const icon = (filter.field ? this.appStore.getAggregationCustomization(filter.field, filter.name) : void 0)?.icon;
32970
- const value = filterValue(filter);
32971
- const label = filterLabel(filter);
32972
- return {
32973
- text: icon || !label || !value ? value || label : `${label}: ${value}`,
32974
- icon
32975
- };
32976
- }).filter((chip) => chip.text);
32977
- }
32978
34343
  onToggle() {
32979
34344
  if (this.popover()?.state() === "open") return;
32980
- this.reset();
32981
34345
  this.unpin();
32982
34346
  }
32983
34347
  unpin() {
@@ -32996,44 +34360,12 @@ var QuickFiltersComponent = class QuickFiltersComponent {
32996
34360
  if (popover && trigger && element) this.flippedAbove.set(element.getBoundingClientRect().bottom <= trigger.getBoundingClientRect().top + 1);
32997
34361
  this.resized.set(true);
32998
34362
  }
32999
- showDetails(quickFilter, event) {
33000
- this.cancelHideDetails();
33001
- this.details.set({
33002
- quickFilter,
33003
- anchor: event.currentTarget
33004
- });
33005
- }
33006
- scheduleHideDetails() {
33007
- this.cancelHideDetails();
33008
- this.hideTimer = setTimeout(() => this.details.set(null), 100);
33009
- }
33010
- cancelHideDetails() {
33011
- clearTimeout(this.hideTimer);
33012
- }
33013
- hideDetails() {
33014
- this.cancelHideDetails();
33015
- this.details.set(null);
33016
- }
33017
- reset() {
33018
- this.cancelHideDetails();
33019
- this.details.set(null);
33020
- this.query.set("");
33021
- }
33022
34363
  closeOnFocusLeave(event) {
33023
34364
  const next = event.relatedTarget;
33024
34365
  if (!next || !(next instanceof Node) || this.host.nativeElement.contains(next)) return;
33025
34366
  this.popover()?.close();
33026
34367
  }
33027
- picked = signal([], ...ngDevMode ? [{ debugName: "picked" }] : /* istanbul ignore next */ []);
33028
- pick(values) {
33029
- const quickFilter = values.at(-1);
33030
- this.picked.set([]);
33031
- if (quickFilter) this.apply(quickFilter);
33032
- }
33033
34368
  apply(quickFilter) {
33034
- this.cancelHideDetails();
33035
- this.details.set(null);
33036
- this.queryParamsStore.patch({ filters: structuredClone(quickFilter.filters) });
33037
34369
  this.popover()?.close();
33038
34370
  this.applied.emit(quickFilter);
33039
34371
  this.announcement.set(this.transloco.translate("quickFilters.applied", { name: quickFilter.name }));
@@ -33084,20 +34416,11 @@ var QuickFiltersComponent = class QuickFiltersComponent {
33084
34416
  },
33085
34417
  outputs: { applied: "applied" },
33086
34418
  host: {
33087
- listeners: {
33088
- "focusout": "closeOnFocusLeave($event)",
33089
- "keydown.escape": "hideDetails()"
33090
- },
34419
+ listeners: { "focusout": "closeOnFocusLeave($event)" },
33091
34420
  classAttribute: "flex rounded-(--radius-2xl)"
33092
34421
  },
33093
34422
  providers: [provideTranslocoScope("quick-filters")],
33094
34423
  viewQueries: [{
33095
- propertyName: "detailsCard",
33096
- first: true,
33097
- predicate: ["detailsCard"],
33098
- descendants: true,
33099
- isSignal: true
33100
- }, {
33101
34424
  propertyName: "popover",
33102
34425
  first: true,
33103
34426
  predicate: PopoverDirective,
@@ -33137,87 +34460,7 @@ var QuickFiltersComponent = class QuickFiltersComponent {
33137
34460
  there is room under it. The list takes the height the popover is given. The popover has no padding above and under:
33138
34461
  the handles (12px each) are that space, and hug the edges. -->
33139
34462
  <PopoverResizeHandle edge="top" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
33140
- <div class="flex min-h-0 flex-1 flex-col gap-(--spacing-xs)">
33141
- @if (quickFilters().length > 0) {
33142
- <SearchInput
33143
- size="md"
33144
- class="border-(--stroke-primary-light)"
33145
- [(value)]="query"
33146
- [placeholder]="'quickFilters.search' | transloco"
33147
- [aria-label]="'quickFilters.search' | transloco"
33148
- [clearLabel]="'quickFilters.clearSearch' | transloco" />
33149
- }
33150
-
33151
- @if (filtered().length) {
33152
- <!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of
33153
- filters. Choosing a row (click, Enter) applies it; the selection is not kept, the popover closes. Hovering a
33154
- row lists the filters it holds in a card. -->
33155
- <List
33156
- variant="list-menu"
33157
- size="medium"
33158
- selectionMode="explicit"
33159
- class="-mx-1 min-h-0 flex-1 overflow-auto px-1"
33160
- [attr.aria-label]="'quickFilters.list' | transloco"
33161
- [value]="picked()"
33162
- (valueChange)="pick($event)">
33163
- @for (quickFilter of filtered(); track quickFilter.id) {
33164
- <ListItem
33165
- [value]="quickFilter"
33166
- [label]="quickFilter.name"
33167
- (mouseenter)="showDetails(quickFilter, $event)"
33168
- (mouseleave)="scheduleHideDetails()"
33169
- (focusin)="showDetails(quickFilter, $event)"
33170
- (focusout)="scheduleHideDetails()">
33171
- <ListItemLeft>
33172
- <ListItemIcon><quick-filter-icon /></ListItemIcon>
33173
- <ListItemText
33174
- [subtext]="(quickFilter.filters.length === 1 ? 'quickFilters.countOne' : 'quickFilters.countOther') | transloco: { count: quickFilter.filters.length }">
33175
- {{ quickFilter.name }}
33176
- </ListItemText>
33177
- </ListItemLeft>
33178
- </ListItem>
33179
- }
33180
- </List>
33181
-
33182
- <!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with
33183
- Floating UI like the popovers, in the top layer so it is above the popover it comes from. It stays while the
33184
- pointer is on it, and while the focus is on the row. -->
33185
- @if (details(); as shown) {
33186
- <div
33187
- #detailsCard
33188
- role="tooltip"
33189
- class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
33190
- (mouseenter)="cancelHideDetails()"
33191
- (mouseleave)="scheduleHideDetails()">
33192
- @for (chip of chips(shown.quickFilter); track $index) {
33193
- <span
33194
- data-chip
33195
- class="inline-flex items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
33196
- <!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
33197
- @if (chip.icon) {
33198
- <fa-icon [faClass]="chip.icon" class="shrink-0" />
33199
- }
33200
- {{ chip.text }}
33201
- </span>
33202
- }
33203
- </div>
33204
- }
33205
- } @else {
33206
- <!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
33207
- <div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
33208
- <span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
33209
- <quick-filter-icon class="size-6 opacity-80" />
33210
- </span>
33211
- <span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{
33212
- (query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco
33213
- }}</span>
33214
- @if (!query()) {
33215
- <span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ "quickFilters.emptyHint" | transloco }}</span>
33216
- }
33217
- </div>
33218
- }
33219
-
33220
- </div>
34463
+ <quick-filters-list class="flex-1" [searchable]="true" (applied)="apply($event)" />
33221
34464
  <PopoverResizeHandle edge="bottom" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
33222
34465
  }
33223
34466
  </div>
@@ -33226,12 +34469,6 @@ var QuickFiltersComponent = class QuickFiltersComponent {
33226
34469
  `,
33227
34470
  isInline: true,
33228
34471
  dependencies: [
33229
- {
33230
- kind: "component",
33231
- type: FaIconComponent,
33232
- selector: "fa-icon, FaIcon",
33233
- inputs: ["faClass", "class"]
33234
- },
33235
34472
  {
33236
34473
  kind: "directive",
33237
34474
  type: ButtonComponent,
@@ -33244,48 +34481,6 @@ var QuickFiltersComponent = class QuickFiltersComponent {
33244
34481
  "size"
33245
34482
  ]
33246
34483
  },
33247
- {
33248
- kind: "directive",
33249
- type: ListComponent,
33250
- selector: "List,list",
33251
- inputs: [
33252
- "class",
33253
- "variant",
33254
- "size"
33255
- ],
33256
- exportAs: ["list"]
33257
- },
33258
- {
33259
- kind: "directive",
33260
- type: ListItemComponent,
33261
- selector: "ListItem,list-item,listitem",
33262
- inputs: [
33263
- "class",
33264
- "variant",
33265
- "size",
33266
- "depth"
33267
- ],
33268
- exportAs: ["listItem"]
33269
- },
33270
- {
33271
- kind: "directive",
33272
- type: ListItemIconDirective,
33273
- selector: "ListItemIcon,list-item-icon,listitemicon",
33274
- inputs: ["class"]
33275
- },
33276
- {
33277
- kind: "directive",
33278
- type: ListItemLeftDirective,
33279
- selector: "ListItemLeft,list-item-left,listitemleft",
33280
- inputs: ["class"]
33281
- },
33282
- {
33283
- kind: "component",
33284
- type: ListItemTextComponent,
33285
- selector: "ListItemText,list-item-text,listitemtext",
33286
- inputs: ["class", "subtext"],
33287
- exportAs: ["listItemText"]
33288
- },
33289
34484
  {
33290
34485
  kind: "directive",
33291
34486
  type: PopoverDirective,
@@ -33314,17 +34509,23 @@ var QuickFiltersComponent = class QuickFiltersComponent {
33314
34509
  ],
33315
34510
  outputs: ["resizeStart"]
33316
34511
  },
33317
- {
33318
- kind: "component",
33319
- type: SearchInputComponent,
33320
- selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
33321
- },
33322
34512
  {
33323
34513
  kind: "component",
33324
34514
  type: QuickFilterIcon,
33325
34515
  selector: "quick-filter, quick-filter-icon, QuickFilter, QuickFilterIcon",
33326
34516
  inputs: ["class"]
33327
34517
  },
34518
+ {
34519
+ kind: "component",
34520
+ type: QuickFiltersListComponent,
34521
+ selector: "QuickFiltersList, quick-filters-list",
34522
+ inputs: [
34523
+ "searchable",
34524
+ "deletable",
34525
+ "emptyHint"
34526
+ ],
34527
+ outputs: ["applied", "deleted"]
34528
+ },
33328
34529
  {
33329
34530
  kind: "pipe",
33330
34531
  type: TranslocoPipe,
@@ -33344,23 +34545,16 @@ i0.ɵɵngDeclareClassMetadata({
33344
34545
  selector: "QuickFilters, quick-filters",
33345
34546
  imports: [
33346
34547
  TranslocoPipe,
33347
- FaIconComponent,
33348
34548
  ButtonComponent,
33349
- ListComponent,
33350
- ListItemComponent,
33351
- ListItemIconDirective,
33352
- ListItemLeftDirective,
33353
- ListItemTextComponent,
33354
34549
  PopoverDirective,
33355
34550
  PopoverResizeHandleComponent,
33356
- SearchInputComponent,
33357
- QuickFilterIcon
34551
+ QuickFilterIcon,
34552
+ QuickFiltersListComponent
33358
34553
  ],
33359
34554
  providers: [provideTranslocoScope("quick-filters")],
33360
34555
  host: {
33361
34556
  class: "flex rounded-(--radius-2xl)",
33362
- "(focusout)": "closeOnFocusLeave($event)",
33363
- "(keydown.escape)": "hideDetails()"
34557
+ "(focusout)": "closeOnFocusLeave($event)"
33364
34558
  },
33365
34559
  template: `
33366
34560
  <button
@@ -33394,87 +34588,7 @@ i0.ɵɵngDeclareClassMetadata({
33394
34588
  there is room under it. The list takes the height the popover is given. The popover has no padding above and under:
33395
34589
  the handles (12px each) are that space, and hug the edges. -->
33396
34590
  <PopoverResizeHandle edge="top" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
33397
- <div class="flex min-h-0 flex-1 flex-col gap-(--spacing-xs)">
33398
- @if (quickFilters().length > 0) {
33399
- <SearchInput
33400
- size="md"
33401
- class="border-(--stroke-primary-light)"
33402
- [(value)]="query"
33403
- [placeholder]="'quickFilters.search' | transloco"
33404
- [aria-label]="'quickFilters.search' | transloco"
33405
- [clearLabel]="'quickFilters.clearSearch' | transloco" />
33406
- }
33407
-
33408
- @if (filtered().length) {
33409
- <!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of
33410
- filters. Choosing a row (click, Enter) applies it; the selection is not kept, the popover closes. Hovering a
33411
- row lists the filters it holds in a card. -->
33412
- <List
33413
- variant="list-menu"
33414
- size="medium"
33415
- selectionMode="explicit"
33416
- class="-mx-1 min-h-0 flex-1 overflow-auto px-1"
33417
- [attr.aria-label]="'quickFilters.list' | transloco"
33418
- [value]="picked()"
33419
- (valueChange)="pick($event)">
33420
- @for (quickFilter of filtered(); track quickFilter.id) {
33421
- <ListItem
33422
- [value]="quickFilter"
33423
- [label]="quickFilter.name"
33424
- (mouseenter)="showDetails(quickFilter, $event)"
33425
- (mouseleave)="scheduleHideDetails()"
33426
- (focusin)="showDetails(quickFilter, $event)"
33427
- (focusout)="scheduleHideDetails()">
33428
- <ListItemLeft>
33429
- <ListItemIcon><quick-filter-icon /></ListItemIcon>
33430
- <ListItemText
33431
- [subtext]="(quickFilter.filters.length === 1 ? 'quickFilters.countOne' : 'quickFilters.countOther') | transloco: { count: quickFilter.filters.length }">
33432
- {{ quickFilter.name }}
33433
- </ListItemText>
33434
- </ListItemLeft>
33435
- </ListItem>
33436
- }
33437
- </List>
33438
-
33439
- <!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with
33440
- Floating UI like the popovers, in the top layer so it is above the popover it comes from. It stays while the
33441
- pointer is on it, and while the focus is on the row. -->
33442
- @if (details(); as shown) {
33443
- <div
33444
- #detailsCard
33445
- role="tooltip"
33446
- class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
33447
- (mouseenter)="cancelHideDetails()"
33448
- (mouseleave)="scheduleHideDetails()">
33449
- @for (chip of chips(shown.quickFilter); track $index) {
33450
- <span
33451
- data-chip
33452
- class="inline-flex items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
33453
- <!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
33454
- @if (chip.icon) {
33455
- <fa-icon [faClass]="chip.icon" class="shrink-0" />
33456
- }
33457
- {{ chip.text }}
33458
- </span>
33459
- }
33460
- </div>
33461
- }
33462
- } @else {
33463
- <!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
33464
- <div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
33465
- <span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
33466
- <quick-filter-icon class="size-6 opacity-80" />
33467
- </span>
33468
- <span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{
33469
- (query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco
33470
- }}</span>
33471
- @if (!query()) {
33472
- <span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ "quickFilters.emptyHint" | transloco }}</span>
33473
- }
33474
- </div>
33475
- }
33476
-
33477
- </div>
34591
+ <quick-filters-list class="flex-1" [searchable]="true" (applied)="apply($event)" />
33478
34592
  <PopoverResizeHandle edge="bottom" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
33479
34593
  }
33480
34594
  </div>
@@ -33521,10 +34635,6 @@ i0.ɵɵngDeclareClassMetadata({
33521
34635
  type: i0.Output,
33522
34636
  args: ["applied"]
33523
34637
  }],
33524
- detailsCard: [{
33525
- type: i0.ViewChild,
33526
- args: ["detailsCard", { isSignal: true }]
33527
- }],
33528
34638
  popover: [{
33529
34639
  type: i0.ViewChild,
33530
34640
  args: [i0.forwardRef(() => PopoverDirective), { isSignal: true }]
@@ -33965,6 +35075,115 @@ i0.ɵɵngDeclareClassMetadata({
33965
35075
  }]
33966
35076
  }
33967
35077
  });
35078
+ var PopupContentOutlet = class PopupContentOutlet {
35079
+ container = inject(ViewContainerRef);
35080
+ injector = inject(Injector);
35081
+ popupContentOutlet = input.required(...ngDevMode ? [{ debugName: "popupContentOutlet" }] : /* istanbul ignore next */ []);
35082
+ popupContentContext = input.required(...ngDevMode ? [{ debugName: "popupContentContext" }] : /* istanbul ignore next */ []);
35083
+ constructor() {
35084
+ effect(() => {
35085
+ const template = this.popupContentOutlet();
35086
+ const context = this.popupContentContext();
35087
+ untracked(() => {
35088
+ this.container.clear();
35089
+ this.container.createEmbeddedView(template, context, { injector: this.injector });
35090
+ });
35091
+ });
35092
+ }
35093
+ static ɵfac = i0.ɵɵngDeclareFactory({
35094
+ minVersion: "12.0.0",
35095
+ version: "22.2.1",
35096
+ ngImport: i0,
35097
+ type: PopupContentOutlet,
35098
+ deps: [],
35099
+ target: i0.ɵɵFactoryTarget.Directive
35100
+ });
35101
+ static ɵdir = i0.ɵɵngDeclareDirective({
35102
+ minVersion: "17.1.0",
35103
+ version: "22.2.1",
35104
+ type: PopupContentOutlet,
35105
+ isStandalone: true,
35106
+ selector: "[popupContentOutlet]",
35107
+ inputs: {
35108
+ popupContentOutlet: {
35109
+ classPropertyName: "popupContentOutlet",
35110
+ publicName: "popupContentOutlet",
35111
+ isSignal: true,
35112
+ isRequired: true,
35113
+ transformFunction: null
35114
+ },
35115
+ popupContentContext: {
35116
+ classPropertyName: "popupContentContext",
35117
+ publicName: "popupContentContext",
35118
+ isSignal: true,
35119
+ isRequired: true,
35120
+ transformFunction: null
35121
+ }
35122
+ },
35123
+ ngImport: i0
35124
+ });
35125
+ };
35126
+ i0.ɵɵngDeclareClassMetadata({
35127
+ minVersion: "12.0.0",
35128
+ version: "22.2.1",
35129
+ ngImport: i0,
35130
+ type: PopupContentOutlet,
35131
+ decorators: [{
35132
+ type: Directive,
35133
+ args: [{ selector: "[popupContentOutlet]" }]
35134
+ }],
35135
+ ctorParameters: () => [],
35136
+ propDecorators: {
35137
+ popupContentOutlet: [{
35138
+ type: i0.Input,
35139
+ args: [{
35140
+ isSignal: true,
35141
+ alias: "popupContentOutlet",
35142
+ required: true
35143
+ }]
35144
+ }],
35145
+ popupContentContext: [{
35146
+ type: i0.Input,
35147
+ args: [{
35148
+ isSignal: true,
35149
+ alias: "popupContentContext",
35150
+ required: true
35151
+ }]
35152
+ }]
35153
+ }
35154
+ });
35155
+ var SearchBoxPopupDirective = class SearchBoxPopupDirective {
35156
+ template = inject(TemplateRef);
35157
+ static ngTemplateContextGuard(_dir, _ctx) {
35158
+ return true;
35159
+ }
35160
+ static ɵfac = i0.ɵɵngDeclareFactory({
35161
+ minVersion: "12.0.0",
35162
+ version: "22.2.1",
35163
+ ngImport: i0,
35164
+ type: SearchBoxPopupDirective,
35165
+ deps: [],
35166
+ target: i0.ɵɵFactoryTarget.Directive
35167
+ });
35168
+ static ɵdir = i0.ɵɵngDeclareDirective({
35169
+ minVersion: "14.0.0",
35170
+ version: "22.2.1",
35171
+ type: SearchBoxPopupDirective,
35172
+ isStandalone: true,
35173
+ selector: "ng-template[searchBoxPopup]",
35174
+ ngImport: i0
35175
+ });
35176
+ };
35177
+ i0.ɵɵngDeclareClassMetadata({
35178
+ minVersion: "12.0.0",
35179
+ version: "22.2.1",
35180
+ ngImport: i0,
35181
+ type: SearchBoxPopupDirective,
35182
+ decorators: [{
35183
+ type: Directive,
35184
+ args: [{ selector: "ng-template[searchBoxPopup]" }]
35185
+ }]
35186
+ });
33968
35187
  let nextHintId = 0;
33969
35188
  const SEARCH_BOX_CATEGORIES_SORT = new InjectionToken("Order by preference for the categories of the suggestions", { factory: () => [
33970
35189
  "full-text",
@@ -33998,10 +35217,20 @@ var SearchBoxComponent = class SearchBoxComponent {
33998
35217
  ...ngDevMode ? { debugName: "popupOffset" } : /* istanbul ignore next */ {},
33999
35218
  transform: numberAttribute
34000
35219
  });
35220
+ popupFitScreen = input(void 0, {
35221
+ ...ngDevMode ? { debugName: "popupFitScreen" } : /* istanbul ignore next */ {},
35222
+ transform: (value) => value === void 0 ? void 0 : booleanAttribute(value)
35223
+ });
35224
+ popupFlip = input(void 0, {
35225
+ ...ngDevMode ? { debugName: "popupFlip" } : /* istanbul ignore next */ {},
35226
+ transform: (value) => value === void 0 ? void 0 : booleanAttribute(value)
35227
+ });
34001
35228
  placeholderOverride = input(void 0, {
34002
35229
  ...ngDevMode ? { debugName: "placeholderOverride" } : /* istanbul ignore next */ {},
34003
35230
  alias: "placeholder"
34004
35231
  });
35232
+ suggestionsProvider = input(void 0, ...ngDevMode ? [{ debugName: "suggestionsProvider" }] : /* istanbul ignore next */ []);
35233
+ customPopup = contentChild(SearchBoxPopupDirective, ...ngDevMode ? [{ debugName: "customPopup" }] : /* istanbul ignore next */ []);
34005
35234
  searched = output();
34006
35235
  debounced = output();
34007
35236
  saved = output();
@@ -34043,9 +35272,12 @@ var SearchBoxComponent = class SearchBoxComponent {
34043
35272
  savedSearches = computed(() => this.userSettingsStore.savedSearches() ?? [], ...ngDevMode ? [{ debugName: "savedSearches" }] : /* istanbul ignore next */ []);
34044
35273
  savedTexts = computed(() => new Set(this.savedSearches().map((s) => s.display || s.label)), ...ngDevMode ? [{ debugName: "savedTexts" }] : /* istanbul ignore next */ []);
34045
35274
  inline = computed(() => this.popup() === "inline", ...ngDevMode ? [{ debugName: "inline" }] : /* istanbul ignore next */ []);
35275
+ fitScreen = computed(() => this.popupFitScreen() ?? (this.inline() && this.size() === "hero"), ...ngDevMode ? [{ debugName: "fitScreen" }] : /* istanbul ignore next */ []);
35276
+ flip = computed(() => this.popupFlip() ?? this.size() !== "hero", ...ngDevMode ? [{ debugName: "flip" }] : /* istanbul ignore next */ []);
35277
+ floating = computed(() => !this.inline() || this.fitScreen(), ...ngDevMode ? [{ debugName: "floating" }] : /* istanbul ignore next */ []);
34046
35278
  barClass = computed(() => this.size() === "hero" ? "w-full" : "w-full gap-(--spacing-3xs)! pe-(--spacing-3xs)", ...ngDevMode ? [{ debugName: "barClass" }] : /* istanbul ignore next */ []);
34047
- popupClass = computed(() => cn("flex flex-col transition-[opacity,translate] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] starting:-translate-y-1.5 starting:opacity-0 motion-reduce:transition-none", this.inline() ? "absolute inset-x-0 top-full z-20 mx-auto mt-(--spacing-2xs) w-[calc(100%-74px)] gap-(--spacing-3xs)" : "fixed z-20 gap-(--spacing-2xs) overflow-hidden rounded-(--radius-xl) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-xs) shadow-(--shadow-2)"), ...ngDevMode ? [{ debugName: "popupClass" }] : /* istanbul ignore next */ []);
34048
- listClass = computed(() => cn("h-68 scroll-pt-8 overflow-auto overscroll-contain text-(--font-primary-base) focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus)", this.inline() ? "bg-transparent" : "bg-(--bg-neutral-white)"), ...ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []);
35279
+ popupClass = computed(() => cn("flex flex-col transition-[opacity,translate] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] starting:-translate-y-1.5 starting:opacity-0 motion-reduce:transition-none", this.inline() ? this.fitScreen() ? "fixed z-20 max-h-(--search-box-max-h) w-[calc(var(--search-box-w,100vw)-74px)] gap-(--spacing-3xs)" : "absolute inset-x-0 top-full z-20 mx-auto mt-(--spacing-2xs) w-[calc(100%-74px)] gap-(--spacing-3xs)" : "fixed z-20 gap-(--spacing-2xs) overflow-hidden rounded-(--radius-xl) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-xs) shadow-(--shadow-2)", !this.inline() && this.fitScreen() && "max-h-(--search-box-max-h)"), ...ngDevMode ? [{ debugName: "popupClass" }] : /* istanbul ignore next */ []);
35280
+ listClass = computed(() => cn("h-68 scroll-pt-8 overflow-auto overscroll-contain text-(--font-primary-base) focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus)", this.fitScreen() && "min-h-0 shrink", this.inline() ? "bg-transparent" : "bg-(--bg-neutral-white)"), ...ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []);
34049
35281
  rowClass = computed(() => cn("group/row data-list-item flex min-h-9 w-full cursor-pointer items-center gap-(--spacing-2xs) rounded-(--radius-sm) px-(--spacing-2xs) py-(--spacing-3xs) text-left text-(length:--size-base) text-(--font-primary-muted) transition-[background-color] duration-100 ease-[ease] hover:bg-(--bg-primary-lighter) hover:outline-none focus-visible:bg-(--bg-primary-lighter) aria-selected:text-(--font-primary-dark) data-[active=true]:bg-(--bg-primary-lighter) motion-reduce:transition-none"), ...ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []);
34050
35282
  titleClass = computed(() => cn("sticky top-0 px-(--spacing-2xs) pt-(--spacing-xs) pb-(--spacing-3xs) text-(length:--size-xs) font-semibold tracking-wider text-(--font-primary-muted) uppercase", this.inline() ? "bg-(--layer-page-layer,var(--bg-neutral-base))" : "bg-(--bg-neutral-white)"), ...ngDevMode ? [{ debugName: "titleClass" }] : /* istanbul ignore next */ []);
34051
35283
  hasFilters = computed(() => {
@@ -34064,9 +35296,10 @@ var SearchBoxComponent = class SearchBoxComponent {
34064
35296
  params: () => ({
34065
35297
  text: this.text(),
34066
35298
  wasSearchClicked: this.wasSearchClicked(),
34067
- autocomplete: this.autocomplete()
35299
+ autocomplete: this.autocomplete(),
35300
+ provider: this.suggestionsProvider()
34068
35301
  }),
34069
- loader: async ({ params: { text, autocomplete } }) => this.fetchSuggestions({
35302
+ loader: async ({ params: { text, autocomplete, provider } }) => provider ? this.fetchFromProvider(provider, text) : this.fetchSuggestions({
34070
35303
  text,
34071
35304
  autocomplete
34072
35305
  })
@@ -34081,14 +35314,33 @@ var SearchBoxComponent = class SearchBoxComponent {
34081
35314
  }
34082
35315
  return isLoading ? this.previousSuggestions : [];
34083
35316
  }, ...ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []);
35317
+ popupTemplateContext = { $implicit: {
35318
+ text: this.text,
35319
+ typed: this.typed,
35320
+ suggestions: this.suggestions,
35321
+ loading: this.suggestionsResource.isLoading,
35322
+ savedSearches: this.savedSearches,
35323
+ navigatedSinceTyping: this.navigatedSinceTyping,
35324
+ commit: (text) => this.commitText(text),
35325
+ preview: (text) => this.previewSuggestion(text),
35326
+ openSaved: (saved) => this.openSaved(saved),
35327
+ isSaved: (text) => this.savedTexts().has(text),
35328
+ toggleSaved: (text) => this.toggleSavedText(text),
35329
+ collapse: () => this.collapse(),
35330
+ focusField: () => this.focusField()
35331
+ } };
34084
35332
  inputListenersAttached = false;
34085
35333
  constructor() {
34086
35334
  this.destroyRef.onDestroy(() => this.field().reset());
34087
35335
  injectFloatingPanel({
34088
- panel: () => this.inline() ? null : this.popupCard()?.nativeElement,
35336
+ panel: () => this.floating() ? this.popupCard()?.nativeElement : null,
34089
35337
  anchor: () => this.barElement()?.nativeElement,
34090
- offset: () => this.popupOffset(),
34091
- matchAnchorWidth: true
35338
+ placement: () => this.inline() ? "bottom" : "bottom-start",
35339
+ offset: () => this.inline() ? 8 : this.popupOffset(),
35340
+ flip: () => this.flip(),
35341
+ matchAnchorWidth: () => !this.inline(),
35342
+ anchorWidthVariable: "--search-box-w",
35343
+ maxHeightVariable: "--search-box-max-h"
34092
35344
  });
34093
35345
  this.eventManager.addEventListener(this.host.nativeElement, "click", () => this.wasSearchClicked.set(true));
34094
35346
  let firstText = true;
@@ -34135,7 +35387,15 @@ var SearchBoxComponent = class SearchBoxComponent {
34135
35387
  const onInput = () => this.navigatedSinceTyping.set(false);
34136
35388
  element.addEventListener("keydown", onKeydown);
34137
35389
  element.addEventListener("input", onInput);
35390
+ const activeObserver = new MutationObserver(() => {
35391
+ if (this.customPopup()) this.revealActiveOption();
35392
+ });
35393
+ activeObserver.observe(element, {
35394
+ attributes: true,
35395
+ attributeFilter: ["aria-activedescendant"]
35396
+ });
34138
35397
  this.destroyRef.onDestroy(() => {
35398
+ activeObserver.disconnect();
34139
35399
  element.removeEventListener("keydown", onKeydown);
34140
35400
  element.removeEventListener("input", onInput);
34141
35401
  });
@@ -34166,6 +35426,10 @@ var SearchBoxComponent = class SearchBoxComponent {
34166
35426
  if (this.host.nativeElement.contains(next)) return;
34167
35427
  this.collapse();
34168
35428
  }
35429
+ revealActiveOption() {
35430
+ const id = this.inputComponent()?.searchInput().nativeElement.getAttribute("aria-activedescendant");
35431
+ if (id) document.getElementById(id)?.scrollIntoView({ block: "nearest" });
35432
+ }
34169
35433
  collapse() {
34170
35434
  this.combobox()?.expanded.set(false);
34171
35435
  }
@@ -34194,9 +35458,12 @@ var SearchBoxComponent = class SearchBoxComponent {
34194
35458
  commitSuggestion(suggestion, event) {
34195
35459
  if (!suggestion.display) return;
34196
35460
  const text = suggestion.display;
35461
+ this.commitText(text, event.currentTarget?.getAttribute("data-text") || text);
35462
+ }
35463
+ commitText(text, searched = text) {
34197
35464
  this.searchInputText.set(text);
34198
35465
  this.collapse();
34199
- this.runSearch(event.currentTarget?.getAttribute("data-text") || text);
35466
+ this.runSearch(searched);
34200
35467
  }
34201
35468
  openSaved(saved) {
34202
35469
  const queryParams = savedSearchQueryParams(saved);
@@ -34328,6 +35595,14 @@ var SearchBoxComponent = class SearchBoxComponent {
34328
35595
  }
34329
35596
  return category;
34330
35597
  }
35598
+ async fetchFromProvider(provider, text) {
35599
+ try {
35600
+ return await provider(text);
35601
+ } catch (err) {
35602
+ error("Error getting suggestions from the suggestions provider", err);
35603
+ return [];
35604
+ }
35605
+ }
34331
35606
  fetchSuggestions = async ({ text, autocomplete = 3 }) => {
34332
35607
  const fromUserSettings = this.autocompleteService.getFromUserSettingsForText(text, autocomplete);
34333
35608
  if (!text) return fromUserSettings;
@@ -34405,6 +35680,20 @@ var SearchBoxComponent = class SearchBoxComponent {
34405
35680
  isRequired: false,
34406
35681
  transformFunction: null
34407
35682
  },
35683
+ popupFitScreen: {
35684
+ classPropertyName: "popupFitScreen",
35685
+ publicName: "popupFitScreen",
35686
+ isSignal: true,
35687
+ isRequired: false,
35688
+ transformFunction: null
35689
+ },
35690
+ popupFlip: {
35691
+ classPropertyName: "popupFlip",
35692
+ publicName: "popupFlip",
35693
+ isSignal: true,
35694
+ isRequired: false,
35695
+ transformFunction: null
35696
+ },
34408
35697
  placeholderOverride: {
34409
35698
  classPropertyName: "placeholderOverride",
34410
35699
  publicName: "placeholder",
@@ -34412,6 +35701,13 @@ var SearchBoxComponent = class SearchBoxComponent {
34412
35701
  isRequired: false,
34413
35702
  transformFunction: null
34414
35703
  },
35704
+ suggestionsProvider: {
35705
+ classPropertyName: "suggestionsProvider",
35706
+ publicName: "suggestionsProvider",
35707
+ isSignal: true,
35708
+ isRequired: false,
35709
+ transformFunction: null
35710
+ },
34415
35711
  searchInputText: {
34416
35712
  classPropertyName: "searchInputText",
34417
35713
  publicName: "searchInputText",
@@ -34436,6 +35732,13 @@ var SearchBoxComponent = class SearchBoxComponent {
34436
35732
  classAttribute: "relative block"
34437
35733
  },
34438
35734
  providers: [provideTranslocoScope("search-box"), provideTranslocoScope("searches")],
35735
+ queries: [{
35736
+ propertyName: "customPopup",
35737
+ first: true,
35738
+ predicate: SearchBoxPopupDirective,
35739
+ descendants: true,
35740
+ isSignal: true
35741
+ }],
34439
35742
  viewQueries: [
34440
35743
  {
34441
35744
  propertyName: "inputComponent",
@@ -34517,6 +35820,10 @@ var SearchBoxComponent = class SearchBoxComponent {
34517
35820
  page, where it takes the place of the filters) or a floating card (the results header). It fades and slides in
34518
35821
  once, when the popup opens (starting: gives the transition its first frame). -->
34519
35822
  <div #popupCard [class]="popupClass()" (mousedown)="$event.preventDefault()">
35823
+ @if (customPopup(); as custom) {
35824
+ <!-- The application's own content (searchBoxPopup): the card, its placement and its opening stay the search box's. -->
35825
+ <ng-container [popupContentOutlet]="custom.template" [popupContentContext]="popupTemplateContext" />
35826
+ } @else {
34520
35827
  <!-- The tabs hold no panel of their own: only one list exists at a time, since two listboxes would both
34521
35828
  register with the combobox. The div below is the one tabpanel, named by the selected tab. -->
34522
35829
  <Tabs
@@ -34540,7 +35847,9 @@ var SearchBoxComponent = class SearchBoxComponent {
34540
35847
  </TabList>
34541
35848
  </Tabs>
34542
35849
 
34543
- <div role="tabpanel" [attr.aria-label]="(tab() === 'saved' ? 'searchBox.tabs.saved' : 'searchBox.tabs.suggestions') | transloco">
35850
+ <!-- A flex column with min-h-0, so that the list inside can shrink and scroll when the popup is capped (popupFitScreen):
35851
+ a plain block would keep the height of its list as its minimum, and the card would only cut the bottom off. -->
35852
+ <div role="tabpanel" class="flex min-h-0 flex-col" [attr.aria-label]="(tab() === 'saved' ? 'searchBox.tabs.saved' : 'searchBox.tabs.suggestions') | transloco">
34544
35853
  @if (tab() === "saved") {
34545
35854
  <ul
34546
35855
  #savedList
@@ -34571,7 +35880,14 @@ var SearchBoxComponent = class SearchBoxComponent {
34571
35880
  (click)="removeSaved(saved, $event)">
34572
35881
  <star-icon solid class="size-(--icon-sm)" />
34573
35882
  </span>
34574
- <p class="min-w-0 flex-1 truncate text-(--font-primary-base)">{{ saved.display || saved.label }}</p>
35883
+ <p
35884
+ class="min-w-0 flex-1 truncate text-(--font-primary-base)"
35885
+ truncated
35886
+ #cutSaved="truncated"
35887
+ [tooltip]="saved.display || saved.label || ''"
35888
+ [tooltipDisabled]="!cutSaved.isTruncated()">
35889
+ {{ saved.display || saved.label }}
35890
+ </p>
34575
35891
  </li>
34576
35892
  } @empty {
34577
35893
  <!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
@@ -34642,7 +35958,12 @@ var SearchBoxComponent = class SearchBoxComponent {
34642
35958
  </span>
34643
35959
  </span>
34644
35960
 
34645
- <p class="min-w-0 flex-1 truncate text-(--font-primary-base)">
35961
+ <p
35962
+ class="min-w-0 flex-1 truncate text-(--font-primary-base)"
35963
+ truncated
35964
+ #cutSuggest="truncated"
35965
+ [tooltip]="suggest.display ?? ''"
35966
+ [tooltipDisabled]="!cutSuggest.isTruncated()">
34646
35967
  @for (chunk of suggest.display! | highlightWord: text() : 10; track $index) {
34647
35968
  <span [class]="{ 'font-semibold': chunk.match }">{{ chunk.text }}</span>
34648
35969
  }
@@ -34663,6 +35984,7 @@ var SearchBoxComponent = class SearchBoxComponent {
34663
35984
  </ul>
34664
35985
  }
34665
35986
  </div>
35987
+ }
34666
35988
  </div>
34667
35989
  </ng-template>
34668
35990
  }
@@ -34685,6 +36007,12 @@ var SearchBoxComponent = class SearchBoxComponent {
34685
36007
  ],
34686
36008
  exportAs: ["ngComponentOutlet"]
34687
36009
  },
36010
+ {
36011
+ kind: "directive",
36012
+ type: PopupContentOutlet,
36013
+ selector: "[popupContentOutlet]",
36014
+ inputs: ["popupContentOutlet", "popupContentContext"]
36015
+ },
34688
36016
  {
34689
36017
  kind: "directive",
34690
36018
  type: FormField,
@@ -34805,6 +36133,13 @@ var SearchBoxComponent = class SearchBoxComponent {
34805
36133
  ],
34806
36134
  exportAs: ["tooltip"]
34807
36135
  },
36136
+ {
36137
+ kind: "directive",
36138
+ type: TruncatedDirective,
36139
+ selector: "[truncated]",
36140
+ inputs: ["truncated"],
36141
+ exportAs: ["truncated"]
36142
+ },
34808
36143
  {
34809
36144
  kind: "component",
34810
36145
  type: SavedSearchPopover,
@@ -34836,6 +36171,7 @@ i0.ɵɵngDeclareClassMetadata({
34836
36171
  selector: "SearchBox, search-box",
34837
36172
  imports: [
34838
36173
  NgComponentOutlet,
36174
+ PopupContentOutlet,
34839
36175
  FormField,
34840
36176
  FormRoot,
34841
36177
  ComboboxPopup,
@@ -34850,6 +36186,7 @@ i0.ɵɵngDeclareClassMetadata({
34850
36186
  TabListComponent,
34851
36187
  TabComponent,
34852
36188
  TooltipDirective,
36189
+ TruncatedDirective,
34853
36190
  SavedSearchPopover
34854
36191
  ],
34855
36192
  providers: [provideTranslocoScope("search-box"), provideTranslocoScope("searches")],
@@ -34901,6 +36238,10 @@ i0.ɵɵngDeclareClassMetadata({
34901
36238
  page, where it takes the place of the filters) or a floating card (the results header). It fades and slides in
34902
36239
  once, when the popup opens (starting: gives the transition its first frame). -->
34903
36240
  <div #popupCard [class]="popupClass()" (mousedown)="$event.preventDefault()">
36241
+ @if (customPopup(); as custom) {
36242
+ <!-- The application's own content (searchBoxPopup): the card, its placement and its opening stay the search box's. -->
36243
+ <ng-container [popupContentOutlet]="custom.template" [popupContentContext]="popupTemplateContext" />
36244
+ } @else {
34904
36245
  <!-- The tabs hold no panel of their own: only one list exists at a time, since two listboxes would both
34905
36246
  register with the combobox. The div below is the one tabpanel, named by the selected tab. -->
34906
36247
  <Tabs
@@ -34924,7 +36265,9 @@ i0.ɵɵngDeclareClassMetadata({
34924
36265
  </TabList>
34925
36266
  </Tabs>
34926
36267
 
34927
- <div role="tabpanel" [attr.aria-label]="(tab() === 'saved' ? 'searchBox.tabs.saved' : 'searchBox.tabs.suggestions') | transloco">
36268
+ <!-- A flex column with min-h-0, so that the list inside can shrink and scroll when the popup is capped (popupFitScreen):
36269
+ a plain block would keep the height of its list as its minimum, and the card would only cut the bottom off. -->
36270
+ <div role="tabpanel" class="flex min-h-0 flex-col" [attr.aria-label]="(tab() === 'saved' ? 'searchBox.tabs.saved' : 'searchBox.tabs.suggestions') | transloco">
34928
36271
  @if (tab() === "saved") {
34929
36272
  <ul
34930
36273
  #savedList
@@ -34955,7 +36298,14 @@ i0.ɵɵngDeclareClassMetadata({
34955
36298
  (click)="removeSaved(saved, $event)">
34956
36299
  <star-icon solid class="size-(--icon-sm)" />
34957
36300
  </span>
34958
- <p class="min-w-0 flex-1 truncate text-(--font-primary-base)">{{ saved.display || saved.label }}</p>
36301
+ <p
36302
+ class="min-w-0 flex-1 truncate text-(--font-primary-base)"
36303
+ truncated
36304
+ #cutSaved="truncated"
36305
+ [tooltip]="saved.display || saved.label || ''"
36306
+ [tooltipDisabled]="!cutSaved.isTruncated()">
36307
+ {{ saved.display || saved.label }}
36308
+ </p>
34959
36309
  </li>
34960
36310
  } @empty {
34961
36311
  <!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
@@ -35026,7 +36376,12 @@ i0.ɵɵngDeclareClassMetadata({
35026
36376
  </span>
35027
36377
  </span>
35028
36378
 
35029
- <p class="min-w-0 flex-1 truncate text-(--font-primary-base)">
36379
+ <p
36380
+ class="min-w-0 flex-1 truncate text-(--font-primary-base)"
36381
+ truncated
36382
+ #cutSuggest="truncated"
36383
+ [tooltip]="suggest.display ?? ''"
36384
+ [tooltipDisabled]="!cutSuggest.isTruncated()">
35030
36385
  @for (chunk of suggest.display! | highlightWord: text() : 10; track $index) {
35031
36386
  <span [class]="{ 'font-semibold': chunk.match }">{{ chunk.text }}</span>
35032
36387
  }
@@ -35047,6 +36402,7 @@ i0.ɵɵngDeclareClassMetadata({
35047
36402
  </ul>
35048
36403
  }
35049
36404
  </div>
36405
+ }
35050
36406
  </div>
35051
36407
  </ng-template>
35052
36408
  }
@@ -35097,6 +36453,22 @@ i0.ɵɵngDeclareClassMetadata({
35097
36453
  required: false
35098
36454
  }]
35099
36455
  }],
36456
+ popupFitScreen: [{
36457
+ type: i0.Input,
36458
+ args: [{
36459
+ isSignal: true,
36460
+ alias: "popupFitScreen",
36461
+ required: false
36462
+ }]
36463
+ }],
36464
+ popupFlip: [{
36465
+ type: i0.Input,
36466
+ args: [{
36467
+ isSignal: true,
36468
+ alias: "popupFlip",
36469
+ required: false
36470
+ }]
36471
+ }],
35100
36472
  placeholderOverride: [{
35101
36473
  type: i0.Input,
35102
36474
  args: [{
@@ -35105,6 +36477,18 @@ i0.ɵɵngDeclareClassMetadata({
35105
36477
  required: false
35106
36478
  }]
35107
36479
  }],
36480
+ suggestionsProvider: [{
36481
+ type: i0.Input,
36482
+ args: [{
36483
+ isSignal: true,
36484
+ alias: "suggestionsProvider",
36485
+ required: false
36486
+ }]
36487
+ }],
36488
+ customPopup: [{
36489
+ type: i0.ContentChild,
36490
+ args: [i0.forwardRef(() => SearchBoxPopupDirective), { isSignal: true }]
36491
+ }],
35108
36492
  searched: [{
35109
36493
  type: i0.Output,
35110
36494
  args: ["searched"]
@@ -37783,6 +39167,6 @@ function provideSessionExpiry(options = {}) {
37783
39167
  const queryNameResolver = () => {
37784
39168
  return inject(AppStore).getDefaultQuery()?.name || "";
37785
39169
  };
37786
- export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertFormComponent, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, HistoryPanelComponent, HistoryTitleMarqueeDirective, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, QuickFiltersComponent, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SEARCH_BOX_CATEGORIES_SORT, SEARCH_BOX_CATEGORY_ICONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchBoxComponent, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, SettingsAlertsPanelComponent, SettingsAppearancePanelComponent, SettingsDialogComponent, SettingsShortcutsPanelComponent, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, groupSearchesByDate, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, searchItemKey, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFavoriteSearchesFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withQuickFiltersFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
39170
+ export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertFormComponent, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FiltersPanelComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, HistoryPanelComponent, HistoryTitleMarqueeDirective, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, LocalizedNumberPipe, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, QuickFiltersComponent, QuickFiltersListComponent, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SEARCH_BOX_CATEGORIES_SORT, SEARCH_BOX_CATEGORY_ICONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchBoxComponent, SearchBoxPopupDirective, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, SettingsAlertsPanelComponent, SettingsAppearancePanelComponent, SettingsDialogComponent, SettingsShortcutsPanelComponent, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, groupSearchesByDate, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, searchItemKey, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFavoriteSearchesFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withQuickFiltersFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
37787
39171
 
37788
39172
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map