@sinequa/atomic-angular 2.4.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, HostBinding, HostListener, Inject, Injectable, InjectionToken, Injector, LOCALE_ID, Optional, Pipe, Service, TemplateRef, ViewEncapsulation, afterNextRender, afterRenderEffect, assertInInjectionContext, booleanAttribute, computed, contentChild, contentChildren, effect, inject, input, isDevMode, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, provideEnvironmentInitializer, resource, runInInjectionContext, signal, untracked, viewChild, viewChildren } from "@angular/core";
3
3
  import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, scan, shareReplay, switchMap, tap, throwError } from "rxjs";
4
4
  import { TranslocoPipe, TranslocoService, provideTranslocoScope } from "@jsverse/transloco";
5
- import { ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef } from "@sinequa/galactik";
5
+ import { ArrowDownIcon, ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef, isApplePlatform } from "@sinequa/galactik";
6
6
  import { withDevtools } from "@angular-architects/ngrx-toolkit";
7
7
  import { getState, patchState, signalStore, signalStoreFeature, withComputed, withMethods, withState } from "@ngrx/signals";
8
8
  import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
@@ -214,47 +214,55 @@ function withAppFeatures() {
214
214
  data: {}
215
215
  }), withMethods((store) => {
216
216
  const client = inject(ATOMIC_CLIENT);
217
- return {
218
- async initialize() {
219
- let app;
220
- try {
221
- app = await client.api.app.get();
222
- } catch (error) {
223
- console.error("Error fetching app data:", error);
224
- throw error;
225
- }
226
- patchState(store, app);
227
- const columnMap = {};
228
- Object.values(extraColumns).forEach((col) => {
229
- columnMap[col.name.toLocaleLowerCase()] = col;
217
+ const hydrate = (app) => {
218
+ patchState(store, app);
219
+ const columnMap = {};
220
+ Object.values(extraColumns).forEach((col) => {
221
+ columnMap[col.name.toLocaleLowerCase()] = col;
222
+ if (col.aliases) col.aliases.forEach((alias) => {
223
+ columnMap[alias.toLocaleLowerCase()] = col;
224
+ });
225
+ });
226
+ const schema = app.indexes._?.columns;
227
+ if (schema) {
228
+ Object.keys(schema).forEach((schemaKey) => {
229
+ const col = schema[schemaKey.toLocaleLowerCase()];
230
+ if (!col) return;
231
+ columnMap[schemaKey.toLocaleLowerCase()] = col;
230
232
  if (col.aliases) col.aliases.forEach((alias) => {
231
233
  columnMap[alias.toLocaleLowerCase()] = col;
232
234
  });
233
235
  });
234
- const schema = app.indexes._?.columns;
235
- if (schema) {
236
- Object.keys(schema).forEach((schemaKey) => {
237
- const col = schema[schemaKey.toLocaleLowerCase()];
236
+ Object.keys(app.queries).forEach((queryKey) => {
237
+ const query = app.queries[queryKey];
238
+ if (query.columnsInfo?.columns) Object.values(query.columnsInfo.columns).forEach((c) => {
239
+ const col = schema[c.name.toLocaleLowerCase()];
238
240
  if (!col) return;
239
- columnMap[schemaKey.toLocaleLowerCase()] = col;
240
- if (col.aliases) col.aliases.forEach((alias) => {
241
+ columnMap[c.name.toLocaleLowerCase()] = col;
242
+ if (c.aliases) c.aliases.split(/[,;|]/).map((a) => a.trim()).forEach((alias) => {
241
243
  columnMap[alias.toLocaleLowerCase()] = col;
242
244
  });
243
245
  });
244
- Object.keys(app.queries).forEach((queryKey) => {
245
- const query = app.queries[queryKey];
246
- if (query.columnsInfo?.columns) Object.values(query.columnsInfo.columns).forEach((c) => {
247
- const col = schema[c.name.toLocaleLowerCase()];
248
- if (!col) return;
249
- columnMap[c.name.toLocaleLowerCase()] = col;
250
- if (c.aliases) c.aliases.split(/[,;|]/).map((a) => a.trim()).forEach((alias) => {
251
- columnMap[alias.toLocaleLowerCase()] = col;
252
- });
253
- });
254
- });
255
- patchState(store, { columnMap });
246
+ });
247
+ patchState(store, { columnMap });
248
+ }
249
+ };
250
+ const fail = (error) => {
251
+ console.error("Error fetching app data:", error);
252
+ throw error;
253
+ };
254
+ return {
255
+ async initialize() {
256
+ let app;
257
+ try {
258
+ app = await client.api.app.get();
259
+ } catch (error) {
260
+ return fail(error);
256
261
  }
262
+ hydrate(app);
257
263
  },
264
+ hydrate,
265
+ fail,
258
266
  async initializeWithAppName(appName) {
259
267
  const app = await client.api.app.get(appName);
260
268
  patchState(store, app);
@@ -500,7 +508,23 @@ const PrincipalStore = signalStore({ providedIn: "root" }, withDevtools("Princip
500
508
  })), withMethods((store) => {
501
509
  const client = inject(ATOMIC_CLIENT);
502
510
  let pendingInitialization = null;
511
+ const hydrate = (principal) => {
512
+ const { userOverrideActive = false } = client.config;
513
+ patchState(store, {
514
+ ...initialPrincipal,
515
+ ...principal,
516
+ userOverrideActive,
517
+ state: "loaded"
518
+ });
519
+ };
520
+ const fail = (error) => {
521
+ console.error("Principal fetch failed", error);
522
+ patchState(store, { state: "error" });
523
+ throw error;
524
+ };
503
525
  return {
526
+ hydrate,
527
+ fail,
504
528
  principal() {
505
529
  const { userOverrideActive: _, ...rest } = getState(store);
506
530
  return Object.entries(rest).reduce((acc, [k, v]) => {
@@ -513,18 +537,9 @@ const PrincipalStore = signalStore({ providedIn: "root" }, withDevtools("Princip
513
537
  pendingInitialization = (async () => {
514
538
  patchState(store, { state: "loading" });
515
539
  try {
516
- const principal = await client.api.principal.get();
517
- const { userOverrideActive = false } = client.config;
518
- patchState(store, {
519
- ...initialPrincipal,
520
- ...principal,
521
- userOverrideActive,
522
- state: "loaded"
523
- });
540
+ hydrate(await client.api.principal.get());
524
541
  } catch (error) {
525
- console.error("Principal fetch failed", error);
526
- patchState(store, { state: "error" });
527
- throw error;
542
+ fail(error);
528
543
  } finally {
529
544
  pendingInitialization = null;
530
545
  }
@@ -1256,6 +1271,16 @@ function AuthGuard() {
1256
1271
  return true;
1257
1272
  };
1258
1273
  }
1274
+ const STARTUP_DATA_SOURCE = new InjectionToken("STARTUP_DATA_SOURCE", { factory: () => null });
1275
+ function provideStartupAggregation(endpoint = "bff/bootstrap") {
1276
+ return makeEnvironmentProviders([{
1277
+ provide: STARTUP_DATA_SOURCE,
1278
+ useFactory: () => {
1279
+ const client = inject(ATOMIC_CLIENT);
1280
+ return () => client.http.get(endpoint);
1281
+ }
1282
+ }]);
1283
+ }
1259
1284
  var UserSettingsPersistence = class UserSettingsPersistence {
1260
1285
  loaded = signal(false, ...ngDevMode ? [{ debugName: "loaded" }] : /* istanbul ignore next */ []);
1261
1286
  isLoaded = this.loaded.asReadonly();
@@ -1735,18 +1760,25 @@ function withUserSettingsFeatures() {
1735
1760
  }), withMethods((store) => {
1736
1761
  const client = inject(ATOMIC_CLIENT);
1737
1762
  const persistence = inject(UserSettingsPersistence);
1763
+ const hydrate = (settings) => {
1764
+ patchState(store, {
1765
+ ...USER_SETTINGS_DEFAULTS,
1766
+ ...settings
1767
+ });
1768
+ persistence.markLoaded();
1769
+ };
1770
+ const fail = (err) => {
1771
+ error("Error fetching user settings:", err);
1772
+ persistence.markUnloaded();
1773
+ };
1738
1774
  return {
1775
+ hydrate,
1776
+ fail,
1739
1777
  async initialize() {
1740
1778
  try {
1741
- const settings = await client.api.userSettings.get();
1742
- patchState(store, {
1743
- ...USER_SETTINGS_DEFAULTS,
1744
- ...settings
1745
- });
1746
- persistence.markLoaded();
1779
+ hydrate(await client.api.userSettings.get());
1747
1780
  } catch (err) {
1748
- error("Error fetching user settings:", err);
1749
- persistence.markUnloaded();
1781
+ fail(err);
1750
1782
  }
1751
1783
  },
1752
1784
  async reset() {
@@ -1776,6 +1808,14 @@ function withAgentsFeatures() {
1776
1808
  }));
1777
1809
  }
1778
1810
  const UserSettingsStore = signalStore({ providedIn: "root" }, withDevtools("UserSettings"), withBookmarkFeatures(), withRecentSearchesFeatures(), withSavedSearchesFeatures(), withBasketsFeatures(), withAssistantFeatures(), withUserSettingsFeatures(), withAlertsFeatures(), withAgentsFeatures(), withDarkModeFeatures(), withHighlightCategoriesFeatures());
1811
+ const toPart = (result) => result.status === "fulfilled" ? {
1812
+ ok: true,
1813
+ value: result.value
1814
+ } : {
1815
+ ok: false,
1816
+ error: result.reason
1817
+ };
1818
+ const provided = (part) => part && typeof part === "object" && part.ok === true ? Promise.resolve(part.value) : void 0;
1779
1819
  const ROUTE_COMPONENTS = new InjectionToken("ROUTE_COMPONENTS", { factory: () => [] });
1780
1820
  var ApplicationService = class ApplicationService {
1781
1821
  userSettingsStore = inject(UserSettingsStore);
@@ -1785,6 +1825,7 @@ var ApplicationService = class ApplicationService {
1785
1825
  router = inject(Router);
1786
1826
  titleService = inject(Title);
1787
1827
  client = inject(ATOMIC_CLIENT);
1828
+ startupDataSource = inject(STARTUP_DATA_SOURCE);
1788
1829
  components = inject(ROUTE_COMPONENTS);
1789
1830
  defaultComponent = computed(() => this.components.find((c) => c.path === "all")?.component, ...ngDevMode ? [{ debugName: "defaultComponent" }] : /* istanbul ignore next */ []);
1790
1831
  defaultLayoutComponent = computed(() => this.components.find((c) => c.isRoot)?.component, ...ngDevMode ? [{ debugName: "defaultLayoutComponent" }] : /* istanbul ignore next */ []);
@@ -1807,18 +1848,46 @@ var ApplicationService = class ApplicationService {
1807
1848
  }
1808
1849
  async initializeStores(options) {
1809
1850
  try {
1810
- await this.appStore.initialize();
1851
+ const data = await this.loadStartupData();
1852
+ this.apply(data.app, this.appStore);
1811
1853
  info("appStore", getState(this.appStore));
1812
- await this.principalStore.initialize();
1854
+ this.apply(data.principal, this.principalStore);
1813
1855
  info("principalStore", getState(this.principalStore));
1814
1856
  this.queryParamsStore.reset({ syncUrl: options?.syncUrl ?? !options?.isBootstrap });
1815
- await this.userSettingsStore.initialize();
1857
+ this.apply(data.userSettings, this.userSettingsStore);
1816
1858
  info("userSettingsStore", getState(this.userSettingsStore));
1817
1859
  } catch (err) {
1818
1860
  error("Error during application initialization", err);
1819
1861
  throw err;
1820
1862
  }
1821
1863
  }
1864
+ async loadStartupData() {
1865
+ const source = await this.readStartupDataSource();
1866
+ const [app, principal, userSettings] = await Promise.allSettled([
1867
+ provided(source?.app) ?? this.client.api.app.get(),
1868
+ provided(source?.principal) ?? this.client.api.principal.get(),
1869
+ provided(source?.userSettings) ?? this.client.api.userSettings.get()
1870
+ ]);
1871
+ return {
1872
+ app: toPart(app),
1873
+ principal: toPart(principal),
1874
+ userSettings: toPart(userSettings)
1875
+ };
1876
+ }
1877
+ async readStartupDataSource() {
1878
+ if (!this.startupDataSource) return void 0;
1879
+ try {
1880
+ const data = await this.startupDataSource();
1881
+ if (data && typeof data === "object") return data;
1882
+ warn("Start-up aggregation answered no data, reading each part instead", data);
1883
+ } catch (err) {
1884
+ warn("Start-up aggregation failed, reading each part instead", err);
1885
+ }
1886
+ }
1887
+ apply(part, store) {
1888
+ if (part.ok) store.hydrate(part.value);
1889
+ else store.fail(part.error);
1890
+ }
1822
1891
  createRoutes() {
1823
1892
  const { queries, data: custom, customJSONs = [] } = getState(this.appStore);
1824
1893
  const queriesMap = Object.entries(queries).map(([key, value]) => ({
@@ -6619,7 +6688,7 @@ i0.ɵɵngDeclareClassMetadata({
6619
6688
  }] }
6620
6689
  });
6621
6690
  const Export = createCallable(ExportDialog);
6622
- const multiSelectionToolbarVariants = cva("flex divide-x rounded-xl bg-linear-to-b px-3 py-2 shadow [&_ul]:flex [&_ul]:flex-col [&_ul]:p-1 [&>li]:px-2", {
6691
+ const multiSelectionToolbarVariants = cva("flex divide-x rounded-xl bg-linear-to-b px-3 py-2 shadow [&_ul]:flex [&_ul]:flex-col [&_ul]:p-1 [&>div]:px-2", {
6623
6692
  variants: { variant: {
6624
6693
  dark: "divide-gray-800 bg-gray-900 from-gray-500 to-gray-900 text-white",
6625
6694
  light: "divide-gray-400 bg-white from-white to-purple-50 text-gray-800",
@@ -6635,7 +6704,9 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6635
6704
  appFeatures = this.appStore.general();
6636
6705
  class = input("", ...ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []);
6637
6706
  variant = input("dark", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
6707
+ hint = input(...ngDevMode ? [void 0, { debugName: "hint" }] : /* istanbul ignore next */ []);
6638
6708
  updatedCollections = output();
6709
+ cleared = output();
6639
6710
  count = this.selection.multiSelectCount;
6640
6711
  variants = computed(() => cn(multiSelectionToolbarVariants({
6641
6712
  variant: this.variant(),
@@ -6650,8 +6721,30 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6650
6721
  }
6651
6722
  return `search-results-assistant`;
6652
6723
  }, ...ngDevMode ? [{ debugName: "instanceId" }] : /* istanbul ignore next */ []);
6653
- enabledUserInput = computed(() => this.appStore.assistants()[this.instanceId()]?.modeSettings.enabledUserInput === true, ...ngDevMode ? [{ debugName: "enabledUserInput" }] : /* istanbul ignore next */ []);
6654
- clearSelection = () => this.selection.clearMultiSelection();
6724
+ enabledUserInput = computed(() => this.appStore.assistants()[this.instanceId()]?.modeSettings?.enabledUserInput === true, ...ngDevMode ? [{ debugName: "enabledUserInput" }] : /* istanbul ignore next */ []);
6725
+ allowAI = input(false, ...ngDevMode ? [{ debugName: "allowAI" }] : /* istanbul ignore next */ []);
6726
+ showAssistantAction = computed(() => this.allowAI() && this.appStore.isAssistantAllowed(this.instanceId()) && this.enabledUserInput(), ...ngDevMode ? [{ debugName: "showAssistantAction" }] : /* istanbul ignore next */ []);
6727
+ showHint = signal(false, ...ngDevMode ? [{ debugName: "showHint" }] : /* istanbul ignore next */ []);
6728
+ armed = true;
6729
+ constructor() {
6730
+ effect(() => {
6731
+ const count = this.count();
6732
+ untracked(() => {
6733
+ if (count === 0) {
6734
+ this.armed = true;
6735
+ this.showHint.set(false);
6736
+ } else if (this.armed) {
6737
+ this.armed = false;
6738
+ this.showHint.set(true);
6739
+ } else this.showHint.set(false);
6740
+ });
6741
+ });
6742
+ }
6743
+ clearSelection = () => {
6744
+ if (this.count() === 0) return;
6745
+ this.selection.clearMultiSelection();
6746
+ this.cleared.emit();
6747
+ };
6655
6748
  openCollectionDialog = () => AddToCollection.call(getState(this.selection).multiSelection, { injector: this.injector }).then((event) => {
6656
6749
  if (event === "dialog-no" || event === "dialog-yes") {
6657
6750
  this.updatedCollections.emit();
@@ -6688,9 +6781,26 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6688
6781
  isSignal: true,
6689
6782
  isRequired: false,
6690
6783
  transformFunction: null
6784
+ },
6785
+ hint: {
6786
+ classPropertyName: "hint",
6787
+ publicName: "hint",
6788
+ isSignal: true,
6789
+ isRequired: false,
6790
+ transformFunction: null
6791
+ },
6792
+ allowAI: {
6793
+ classPropertyName: "allowAI",
6794
+ publicName: "allowAI",
6795
+ isSignal: true,
6796
+ isRequired: false,
6797
+ transformFunction: null
6691
6798
  }
6692
6799
  },
6693
- outputs: { updatedCollections: "updatedCollections" },
6800
+ outputs: {
6801
+ updatedCollections: "updatedCollections",
6802
+ cleared: "cleared"
6803
+ },
6694
6804
  host: {
6695
6805
  properties: {
6696
6806
  "class.opacity-100": "count() > 0",
@@ -6701,37 +6811,57 @@ var MultiSelectionToolbarComponent = class MultiSelectionToolbarComponent {
6701
6811
  providers: [provideTranslocoScope("multi-selection-toolbar", "article", "collections", "export")],
6702
6812
  ngImport: i0,
6703
6813
  template: `
6704
- <menu id="multi-select-toolbar" [class]="cn(variants(), class())">
6705
- <li class="ps-0!">
6814
+ <!-- Région de statut hors de la barre : celle-ci est inerte sans sélection, et une région
6815
+ inerte n'est pas annoncée. Le texte n'existe que tant que la sélection est non vide. -->
6816
+ <span role="status" class="sr-only">
6817
+ @if (count() > 0) {
6818
+ {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6819
+ @if (showHint() && hint()) {
6820
+ . {{ hint() }}
6821
+ }
6822
+ }
6823
+ </span>
6824
+
6825
+ <!-- inert sans sélection : la barre n'est que transparente et hors écran, ses boutons resteraient
6826
+ sinon dans l'ordre de tabulation et activables (Entrée ouvrait « Ajouter à une collection » sur
6827
+ un panier vide, ES-31356). -->
6828
+ <div
6829
+ id="multi-select-toolbar"
6830
+ role="toolbar"
6831
+ [attr.aria-label]="'multiSelectionToolbar.label' | transloco"
6832
+ [attr.inert]="count() === 0 ? '' : null"
6833
+ (keydown.escape)="clearSelection()"
6834
+ [class]="cn(variants(), class())">
6835
+ <div class="ps-0!">
6706
6836
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="clearSelection()">
6707
6837
  {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6708
- <x-mark-icon />
6838
+ <x-mark-icon aria-hidden="true" />
6709
6839
  </button>
6710
- </li>
6840
+ </div>
6711
6841
 
6712
- <li>
6713
- <button class="cursor-pointer inline-flex items-center gap-1" title="menu" [attr.aria-label]="'menu'" (click)="openCollectionDialog()">
6714
- <inbox-icon />
6842
+ <div>
6843
+ <button class="cursor-pointer inline-flex items-center gap-1" (click)="openCollectionDialog()">
6844
+ <inbox-icon aria-hidden="true" />
6715
6845
  {{ 'collections.label' | transloco }}
6716
6846
  </button>
6717
- </li>
6847
+ </div>
6718
6848
 
6719
- <li>
6849
+ <div>
6720
6850
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="openExportDialog()">
6721
- <file-output-icon />
6851
+ <file-output-icon aria-hidden="true" />
6722
6852
  {{ 'export.title' | transloco }}
6723
6853
  </button>
6724
- </li>
6854
+ </div>
6725
6855
 
6726
- @if (enabledUserInput()) {
6727
- <li class="pe-0!">
6856
+ @if (showAssistantAction()) {
6857
+ <div class="pe-0!">
6728
6858
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="attachToAssistant()">
6729
- <sparkles-icon />
6859
+ <sparkles-icon aria-hidden="true" />
6730
6860
  {{ 'article.addToAIOverview' | transloco }}
6731
6861
  </button>
6732
- </li>
6862
+ </div>
6733
6863
  }
6734
- </menu>
6864
+ </div>
6735
6865
  `,
6736
6866
  isInline: true,
6737
6867
  dependencies: [
@@ -6785,37 +6915,57 @@ i0.ɵɵngDeclareClassMetadata({
6785
6915
  ],
6786
6916
  providers: [provideTranslocoScope("multi-selection-toolbar", "article", "collections", "export")],
6787
6917
  template: `
6788
- <menu id="multi-select-toolbar" [class]="cn(variants(), class())">
6789
- <li class="ps-0!">
6918
+ <!-- Région de statut hors de la barre : celle-ci est inerte sans sélection, et une région
6919
+ inerte n'est pas annoncée. Le texte n'existe que tant que la sélection est non vide. -->
6920
+ <span role="status" class="sr-only">
6921
+ @if (count() > 0) {
6922
+ {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6923
+ @if (showHint() && hint()) {
6924
+ . {{ hint() }}
6925
+ }
6926
+ }
6927
+ </span>
6928
+
6929
+ <!-- inert sans sélection : la barre n'est que transparente et hors écran, ses boutons resteraient
6930
+ sinon dans l'ordre de tabulation et activables (Entrée ouvrait « Ajouter à une collection » sur
6931
+ un panier vide, ES-31356). -->
6932
+ <div
6933
+ id="multi-select-toolbar"
6934
+ role="toolbar"
6935
+ [attr.aria-label]="'multiSelectionToolbar.label' | transloco"
6936
+ [attr.inert]="count() === 0 ? '' : null"
6937
+ (keydown.escape)="clearSelection()"
6938
+ [class]="cn(variants(), class())">
6939
+ <div class="ps-0!">
6790
6940
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="clearSelection()">
6791
6941
  {{ 'multiSelectionToolbar.itemsSelected' | transloco: { count: count() } }}
6792
- <x-mark-icon />
6942
+ <x-mark-icon aria-hidden="true" />
6793
6943
  </button>
6794
- </li>
6944
+ </div>
6795
6945
 
6796
- <li>
6797
- <button class="cursor-pointer inline-flex items-center gap-1" title="menu" [attr.aria-label]="'menu'" (click)="openCollectionDialog()">
6798
- <inbox-icon />
6946
+ <div>
6947
+ <button class="cursor-pointer inline-flex items-center gap-1" (click)="openCollectionDialog()">
6948
+ <inbox-icon aria-hidden="true" />
6799
6949
  {{ 'collections.label' | transloco }}
6800
6950
  </button>
6801
- </li>
6951
+ </div>
6802
6952
 
6803
- <li>
6953
+ <div>
6804
6954
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="openExportDialog()">
6805
- <file-output-icon />
6955
+ <file-output-icon aria-hidden="true" />
6806
6956
  {{ 'export.title' | transloco }}
6807
6957
  </button>
6808
- </li>
6958
+ </div>
6809
6959
 
6810
- @if (enabledUserInput()) {
6811
- <li class="pe-0!">
6960
+ @if (showAssistantAction()) {
6961
+ <div class="pe-0!">
6812
6962
  <button class="cursor-pointer inline-flex items-center gap-1" (click)="attachToAssistant()">
6813
- <sparkles-icon />
6963
+ <sparkles-icon aria-hidden="true" />
6814
6964
  {{ 'article.addToAIOverview' | transloco }}
6815
6965
  </button>
6816
- </li>
6966
+ </div>
6817
6967
  }
6818
- </menu>
6968
+ </div>
6819
6969
  `,
6820
6970
  host: {
6821
6971
  class: "fixed -bottom-20 left-[50%] translate-x-[-50%] z-50 transition-[translate,opacity,discrete] duration-300 opacity-0",
@@ -6824,6 +6974,7 @@ i0.ɵɵngDeclareClassMetadata({
6824
6974
  }
6825
6975
  }]
6826
6976
  }],
6977
+ ctorParameters: () => [],
6827
6978
  propDecorators: {
6828
6979
  class: [{
6829
6980
  type: i0.Input,
@@ -6841,9 +6992,29 @@ i0.ɵɵngDeclareClassMetadata({
6841
6992
  required: false
6842
6993
  }]
6843
6994
  }],
6995
+ hint: [{
6996
+ type: i0.Input,
6997
+ args: [{
6998
+ isSignal: true,
6999
+ alias: "hint",
7000
+ required: false
7001
+ }]
7002
+ }],
6844
7003
  updatedCollections: [{
6845
7004
  type: i0.Output,
6846
7005
  args: ["updatedCollections"]
7006
+ }],
7007
+ cleared: [{
7008
+ type: i0.Output,
7009
+ args: ["cleared"]
7010
+ }],
7011
+ allowAI: [{
7012
+ type: i0.Input,
7013
+ args: [{
7014
+ isSignal: true,
7015
+ alias: "allowAI",
7016
+ required: false
7017
+ }]
6847
7018
  }]
6848
7019
  }
6849
7020
  });
@@ -18727,11 +18898,18 @@ var ListPanelComponent = class ListPanelComponent {
18727
18898
  getValue = input.required(...ngDevMode ? [{ debugName: "getValue" }] : /* istanbul ignore next */ []);
18728
18899
  getLabel = input(...ngDevMode ? [void 0, { debugName: "getLabel" }] : /* istanbul ignore next */ []);
18729
18900
  deleteButtonTitle = input("remove", ...ngDevMode ? [{ debugName: "deleteButtonTitle" }] : /* istanbul ignore next */ []);
18901
+ deletable = input(true, {
18902
+ ...ngDevMode ? { debugName: "deletable" } : /* istanbul ignore next */ {},
18903
+ transform: booleanAttribute
18904
+ });
18730
18905
  ariaLabel = input(...ngDevMode ? [void 0, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
18731
18906
  itemClass = input("", ...ngDevMode ? [{ debugName: "itemClass" }] : /* istanbul ignore next */ []);
18732
18907
  itemClick = output();
18733
18908
  delete = output();
18734
18909
  itemTemplate = contentChild(ListPanelItemDirective, ...ngDevMode ? [{ debugName: "itemTemplate" }] : /* istanbul ignore next */ []);
18910
+ deleteKeys = isApplePlatform() ? ["Delete", "Backspace"] : ["Delete"];
18911
+ list = viewChild(ListComponent, ...ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []);
18912
+ keyboardFocused = computed(() => this.list()?.keyboardFocused() ?? false, ...ngDevMode ? [{ debugName: "keyboardFocused" }] : /* istanbul ignore next */ []);
18735
18913
  host = inject(ElementRef);
18736
18914
  injector = inject(Injector);
18737
18915
  pendingFocusIndex = signal(null, ...ngDevMode ? [{ debugName: "pendingFocusIndex" }] : /* istanbul ignore next */ []);
@@ -18807,6 +18985,13 @@ var ListPanelComponent = class ListPanelComponent {
18807
18985
  isRequired: false,
18808
18986
  transformFunction: null
18809
18987
  },
18988
+ deletable: {
18989
+ classPropertyName: "deletable",
18990
+ publicName: "deletable",
18991
+ isSignal: true,
18992
+ isRequired: false,
18993
+ transformFunction: null
18994
+ },
18810
18995
  ariaLabel: {
18811
18996
  classPropertyName: "ariaLabel",
18812
18997
  publicName: "ariaLabel",
@@ -18827,6 +19012,7 @@ var ListPanelComponent = class ListPanelComponent {
18827
19012
  delete: "delete"
18828
19013
  },
18829
19014
  host: { classAttribute: "block max-h-115 overflow-auto" },
19015
+ providers: [provideTranslocoScope("list-panel")],
18830
19016
  queries: [{
18831
19017
  propertyName: "itemTemplate",
18832
19018
  first: true,
@@ -18834,6 +19020,13 @@ var ListPanelComponent = class ListPanelComponent {
18834
19020
  descendants: true,
18835
19021
  isSignal: true
18836
19022
  }],
19023
+ viewQueries: [{
19024
+ propertyName: "list",
19025
+ first: true,
19026
+ predicate: ListComponent,
19027
+ descendants: true,
19028
+ isSignal: true
19029
+ }],
18837
19030
  ngImport: i0,
18838
19031
  template: `
18839
19032
  <List variant="list-navigation" size="medium" class="gap-1" selectionMode="explicit" [attr.aria-label]="ariaLabel()">
@@ -18846,9 +19039,13 @@ var ListPanelComponent = class ListPanelComponent {
18846
19039
  (keydown.enter)="activate(item, $event)"
18847
19040
  (keydown.space)="activate(item, $event)">
18848
19041
  <ng-container *ngTemplateOutlet="itemTemplate()?.template; context: { $implicit: item }" />
18849
- <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" (click)="onDelete(item, i, $event)">
18850
- <TrashIcon />
18851
- </ListItemAction>
19042
+ @if (deletable()) {
19043
+ <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" [keys]="deleteKeys" [shortcut]="'listPanel.deleteShortcut' | transloco"
19044
+ [description]="'listPanel.deleteDescription' | transloco: { key: 'listPanel.deleteShortcut' | transloco }"
19045
+ (click)="onDelete(item, i, $event)">
19046
+ <TrashIcon />
19047
+ </ListItemAction>
19048
+ }
18852
19049
  </ListItem>
18853
19050
  } @empty {
18854
19051
  <span class="py-4 text-center text-neutral-500">
@@ -18903,7 +19100,13 @@ var ListPanelComponent = class ListPanelComponent {
18903
19100
  kind: "component",
18904
19101
  type: ListItemActionComponent,
18905
19102
  selector: "ListItemAction,list-item-action,listitemaction",
18906
- inputs: ["ariaLabel", "disabled"],
19103
+ inputs: [
19104
+ "ariaLabel",
19105
+ "disabled",
19106
+ "keys",
19107
+ "shortcut",
19108
+ "description"
19109
+ ],
18907
19110
  exportAs: ["listItemAction"]
18908
19111
  },
18909
19112
  {
@@ -18923,6 +19126,7 @@ i0.ɵɵngDeclareClassMetadata({
18923
19126
  type: Component,
18924
19127
  args: [{
18925
19128
  selector: "list-panel, ListPanel",
19129
+ providers: [provideTranslocoScope("list-panel")],
18926
19130
  imports: [
18927
19131
  NgTemplateOutlet,
18928
19132
  TrashIcon,
@@ -18942,9 +19146,13 @@ i0.ɵɵngDeclareClassMetadata({
18942
19146
  (keydown.enter)="activate(item, $event)"
18943
19147
  (keydown.space)="activate(item, $event)">
18944
19148
  <ng-container *ngTemplateOutlet="itemTemplate()?.template; context: { $implicit: item }" />
18945
- <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" (click)="onDelete(item, i, $event)">
18946
- <TrashIcon />
18947
- </ListItemAction>
19149
+ @if (deletable()) {
19150
+ <ListItemAction [ariaLabel]="deleteButtonTitle() | transloco" [keys]="deleteKeys" [shortcut]="'listPanel.deleteShortcut' | transloco"
19151
+ [description]="'listPanel.deleteDescription' | transloco: { key: 'listPanel.deleteShortcut' | transloco }"
19152
+ (click)="onDelete(item, i, $event)">
19153
+ <TrashIcon />
19154
+ </ListItemAction>
19155
+ }
18948
19156
  </ListItem>
18949
19157
  } @empty {
18950
19158
  <span class="py-4 text-center text-neutral-500">
@@ -18991,6 +19199,14 @@ i0.ɵɵngDeclareClassMetadata({
18991
19199
  required: false
18992
19200
  }]
18993
19201
  }],
19202
+ deletable: [{
19203
+ type: i0.Input,
19204
+ args: [{
19205
+ isSignal: true,
19206
+ alias: "deletable",
19207
+ required: false
19208
+ }]
19209
+ }],
18994
19210
  ariaLabel: [{
18995
19211
  type: i0.Input,
18996
19212
  args: [{
@@ -19018,6 +19234,10 @@ i0.ɵɵngDeclareClassMetadata({
19018
19234
  itemTemplate: [{
19019
19235
  type: i0.ContentChild,
19020
19236
  args: [i0.forwardRef(() => ListPanelItemDirective), { isSignal: true }]
19237
+ }],
19238
+ list: [{
19239
+ type: i0.ViewChild,
19240
+ args: [i0.forwardRef(() => ListComponent), { isSignal: true }]
19021
19241
  }]
19022
19242
  }
19023
19243
  });
@@ -19027,6 +19247,10 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19027
19247
  hasMore = input.required(...ngDevMode ? [{ debugName: "hasMore" }] : /* istanbul ignore next */ []);
19028
19248
  config = input.required(...ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []);
19029
19249
  loadMore = output();
19250
+ navigationHint = input(false, {
19251
+ ...ngDevMode ? { debugName: "navigationHint" } : /* istanbul ignore next */ {},
19252
+ transform: booleanAttribute
19253
+ });
19030
19254
  onLoadMore(event) {
19031
19255
  event.stopPropagation();
19032
19256
  this.loadMore.emit();
@@ -19066,10 +19290,17 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19066
19290
  isSignal: true,
19067
19291
  isRequired: true,
19068
19292
  transformFunction: null
19293
+ },
19294
+ navigationHint: {
19295
+ classPropertyName: "navigationHint",
19296
+ publicName: "navigationHint",
19297
+ isSignal: true,
19298
+ isRequired: false,
19299
+ transformFunction: null
19069
19300
  }
19070
19301
  },
19071
19302
  outputs: { loadMore: "loadMore" },
19072
- host: { properties: { "class": "cn('flex flex-col px-2 mt-2', class())" } },
19303
+ host: { properties: { "class": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" } },
19073
19304
  providers: [provideTranslocoScope("list-panel")],
19074
19305
  ngImport: i0,
19075
19306
  template: `
@@ -19080,9 +19311,33 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19080
19311
  </button>
19081
19312
  }
19082
19313
  @let seeMore = 'listPanel.seeMore' | transloco;
19083
- <button variant="secondary" size="sm" class="ml-auto" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19084
- {{ seeMore }}
19085
- </button>
19314
+ <div class="flex items-center gap-(--spacing-xs)">
19315
+ <!-- Aide de navigation de la liste, tant que navigationHint est vrai (focus clavier dans la liste) : le
19316
+ focus est un arrêt unique de tabulation, les flèches font le reste, et rien ne le dit.
19317
+ aria-hidden : le listbox annonce déjà sa navigation aux lecteurs d'écran, et l'ouverture
19318
+ d'une ligne est ce que tout le monde attend d'Entrée. La touche Suppr est, elle, écrite dans la
19319
+ ligne. -->
19320
+ @if (navigationHint()) {
19321
+ <p aria-hidden="true" class="flex min-w-0 flex-wrap items-center gap-x-(--spacing-xs) gap-y-(--spacing-4xs) text-(length:--size-sm) leading-(--line-height-2) text-(--font-neutral-base)">
19322
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19323
+ <!-- Des icônes et non les caractères ↑ ↓ : le centre visuel d'une flèche typographique ne tombe pas au
19324
+ milieu de la ligne de texte, et elle sortait décentrée de sa touche. -->
19325
+ <span class="inline-flex gap-(--spacing-4xs)">
19326
+ <kbd><arrow-up-icon /></kbd>
19327
+ <kbd><arrow-down-icon /></kbd>
19328
+ </span>
19329
+ {{ 'listPanel.hintNavigate' | transloco }}
19330
+ </span>
19331
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19332
+ <kbd>{{ 'listPanel.keyEnter' | transloco }}</kbd>
19333
+ {{ 'listPanel.hintOpen' | transloco }}
19334
+ </span>
19335
+ </p>
19336
+ }
19337
+ <button variant="secondary" size="sm" class="ml-auto shrink-0" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19338
+ {{ seeMore }}
19339
+ </button>
19340
+ </div>
19086
19341
  `,
19087
19342
  isInline: true,
19088
19343
  dependencies: [
@@ -19117,6 +19372,24 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19117
19372
  "size"
19118
19373
  ]
19119
19374
  },
19375
+ {
19376
+ kind: "directive",
19377
+ type: KbdComponent,
19378
+ selector: "kbd, Kbd",
19379
+ inputs: ["class", "size"]
19380
+ },
19381
+ {
19382
+ kind: "component",
19383
+ type: ArrowUpIcon,
19384
+ selector: "arrow-up, arrow-up-icon, ArrowUp, ArrowUpIcon",
19385
+ inputs: ["class"]
19386
+ },
19387
+ {
19388
+ kind: "component",
19389
+ type: ArrowDownIcon,
19390
+ selector: "arrow-down, arrow-down-icon, ArrowDown, ArrowDownIcon",
19391
+ inputs: ["class"]
19392
+ },
19120
19393
  {
19121
19394
  kind: "pipe",
19122
19395
  type: TranslocoPipe,
@@ -19137,7 +19410,10 @@ i0.ɵɵngDeclareClassMetadata({
19137
19410
  imports: [
19138
19411
  TranslocoPipe,
19139
19412
  RouterLink,
19140
- ButtonComponent
19413
+ ButtonComponent,
19414
+ KbdComponent,
19415
+ ArrowUpIcon,
19416
+ ArrowDownIcon
19141
19417
  ],
19142
19418
  providers: [provideTranslocoScope("list-panel")],
19143
19419
  template: `
@@ -19148,11 +19424,35 @@ i0.ɵɵngDeclareClassMetadata({
19148
19424
  </button>
19149
19425
  }
19150
19426
  @let seeMore = 'listPanel.seeMore' | transloco;
19151
- <button variant="secondary" size="sm" class="ml-auto" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19152
- {{ seeMore }}
19153
- </button>
19427
+ <div class="flex items-center gap-(--spacing-xs)">
19428
+ <!-- Aide de navigation de la liste, tant que navigationHint est vrai (focus clavier dans la liste) : le
19429
+ focus est un arrêt unique de tabulation, les flèches font le reste, et rien ne le dit.
19430
+ aria-hidden : le listbox annonce déjà sa navigation aux lecteurs d'écran, et l'ouverture
19431
+ d'une ligne est ce que tout le monde attend d'Entrée. La touche Suppr est, elle, écrite dans la
19432
+ ligne. -->
19433
+ @if (navigationHint()) {
19434
+ <p aria-hidden="true" class="flex min-w-0 flex-wrap items-center gap-x-(--spacing-xs) gap-y-(--spacing-4xs) text-(length:--size-sm) leading-(--line-height-2) text-(--font-neutral-base)">
19435
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19436
+ <!-- Des icônes et non les caractères ↑ ↓ : le centre visuel d'une flèche typographique ne tombe pas au
19437
+ milieu de la ligne de texte, et elle sortait décentrée de sa touche. -->
19438
+ <span class="inline-flex gap-(--spacing-4xs)">
19439
+ <kbd><arrow-up-icon /></kbd>
19440
+ <kbd><arrow-down-icon /></kbd>
19441
+ </span>
19442
+ {{ 'listPanel.hintNavigate' | transloco }}
19443
+ </span>
19444
+ <span class="inline-flex items-center gap-(--spacing-2xs)">
19445
+ <kbd>{{ 'listPanel.keyEnter' | transloco }}</kbd>
19446
+ {{ 'listPanel.hintOpen' | transloco }}
19447
+ </span>
19448
+ </p>
19449
+ }
19450
+ <button variant="secondary" size="sm" class="ml-auto shrink-0" [attr.title]="seeMore" [routerLink]="[config().routerLink]">
19451
+ {{ seeMore }}
19452
+ </button>
19453
+ </div>
19154
19454
  `,
19155
- host: { "[class]": "cn('flex flex-col px-2 mt-2', class())" }
19455
+ host: { "[class]": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" }
19156
19456
  }]
19157
19457
  }],
19158
19458
  propDecorators: {
@@ -19183,6 +19483,14 @@ i0.ɵɵngDeclareClassMetadata({
19183
19483
  loadMore: [{
19184
19484
  type: i0.Output,
19185
19485
  args: ["loadMore"]
19486
+ }],
19487
+ navigationHint: [{
19488
+ type: i0.Input,
19489
+ args: [{
19490
+ isSignal: true,
19491
+ alias: "navigationHint",
19492
+ required: false
19493
+ }]
19186
19494
  }]
19187
19495
  }
19188
19496
  });
@@ -19368,7 +19676,7 @@ var BookmarksComponent = class BookmarksComponent {
19368
19676
  } },
19369
19677
  providers: [provideTranslocoScope("bookmarks")],
19370
19678
  ngImport: i0,
19371
- template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19679
+ template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19372
19680
  dependencies: [
19373
19681
  {
19374
19682
  kind: "component",
@@ -19385,6 +19693,7 @@ var BookmarksComponent = class BookmarksComponent {
19385
19693
  "getValue",
19386
19694
  "getLabel",
19387
19695
  "deleteButtonTitle",
19696
+ "deletable",
19388
19697
  "ariaLabel",
19389
19698
  "itemClass"
19390
19699
  ],
@@ -19397,7 +19706,8 @@ var BookmarksComponent = class BookmarksComponent {
19397
19706
  inputs: [
19398
19707
  "class",
19399
19708
  "hasMore",
19400
- "config"
19709
+ "config",
19710
+ "navigationHint"
19401
19711
  ],
19402
19712
  outputs: ["loadMore"]
19403
19713
  },
@@ -19479,7 +19789,7 @@ i0.ɵɵngDeclareClassMetadata({
19479
19789
  TagComponent
19480
19790
  ],
19481
19791
  providers: [provideTranslocoScope("bookmarks")],
19482
- template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19792
+ template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19483
19793
  }]
19484
19794
  }],
19485
19795
  ctorParameters: () => [],
@@ -19735,7 +20045,7 @@ var CollectionsComponent = class CollectionsComponent {
19735
20045
  } },
19736
20046
  providers: [provideTranslocoScope("collections")],
19737
20047
  ngImport: i0,
19738
- template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
20048
+ template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19739
20049
  dependencies: [
19740
20050
  {
19741
20051
  kind: "component",
@@ -19752,6 +20062,7 @@ var CollectionsComponent = class CollectionsComponent {
19752
20062
  "getValue",
19753
20063
  "getLabel",
19754
20064
  "deleteButtonTitle",
20065
+ "deletable",
19755
20066
  "ariaLabel",
19756
20067
  "itemClass"
19757
20068
  ],
@@ -19764,7 +20075,8 @@ var CollectionsComponent = class CollectionsComponent {
19764
20075
  inputs: [
19765
20076
  "class",
19766
20077
  "hasMore",
19767
- "config"
20078
+ "config",
20079
+ "navigationHint"
19768
20080
  ],
19769
20081
  outputs: ["loadMore"]
19770
20082
  },
@@ -19820,7 +20132,7 @@ i0.ɵɵngDeclareClassMetadata({
19820
20132
  ListItemTextComponent
19821
20133
  ],
19822
20134
  providers: [provideTranslocoScope("collections")],
19823
- template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
20135
+ template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19824
20136
  }]
19825
20137
  }],
19826
20138
  ctorParameters: () => [],
@@ -22795,6 +23107,7 @@ var AggregationDateComponent = class AggregationDateComponent {
22795
23107
  uid = nextAggregationDateId++;
22796
23108
  startId = `datepicker-range-start-${this.uid}`;
22797
23109
  endId = `datepicker-range-end-${this.uid}`;
23110
+ class = input("", ...ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []);
22798
23111
  name = input(null, ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
22799
23112
  column = input.required(...ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []);
22800
23113
  id = input(null, ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
@@ -23281,6 +23594,13 @@ var AggregationDateComponent = class AggregationDateComponent {
23281
23594
  isStandalone: true,
23282
23595
  selector: "aggregation-date, AggregationDate, aggregationdate",
23283
23596
  inputs: {
23597
+ class: {
23598
+ classPropertyName: "class",
23599
+ publicName: "class",
23600
+ isSignal: true,
23601
+ isRequired: false,
23602
+ transformFunction: null
23603
+ },
23284
23604
  name: {
23285
23605
  classPropertyName: "name",
23286
23606
  publicName: "name",
@@ -23396,10 +23716,10 @@ var AggregationDateComponent = class AggregationDateComponent {
23396
23716
  aggregationOverridesChange: "aggregationOverridesChange",
23397
23717
  searchText: "searchTextChange"
23398
23718
  },
23399
- host: { properties: { "class": "cn(\"@container block\", isEmpty() && \"hidden\")" } },
23719
+ host: { properties: { "class": "cn(\"@container flex h-[inherit] max-h-[inherit] flex-col\", class(), isEmpty() && \"hidden\")" } },
23400
23720
  providers: [provideTranslocoScope("filters")],
23401
23721
  ngImport: i0,
23402
- 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 buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\n<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\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 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 (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\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\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 (keydown.enter)=\"onOptionEnter($event)\">\n <div\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex max-h-(--scroll-height,20rem) 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</details>\n",
23722
+ 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 buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\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 (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\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\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 <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</details>\n",
23403
23723
  styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"],
23404
23724
  dependencies: [
23405
23725
  {
@@ -23533,13 +23853,21 @@ i0.ɵɵngDeclareClassMetadata({
23533
23853
  RadioComponent,
23534
23854
  TooltipDirective
23535
23855
  ],
23536
- host: { "[class]": "cn(\"@container block\", isEmpty() && \"hidden\")" },
23537
- 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 buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\n<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\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 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 (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\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\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 (keydown.enter)=\"onOptionEnter($event)\">\n <div\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex max-h-(--scroll-height,20rem) 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</details>\n",
23856
+ host: { "[class]": "cn(\"@container flex h-[inherit] max-h-[inherit] flex-col\", class(), isEmpty() && \"hidden\")" },
23857
+ 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 buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\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 (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\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\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 <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</details>\n",
23538
23858
  styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"]
23539
23859
  }]
23540
23860
  }],
23541
23861
  ctorParameters: () => [],
23542
23862
  propDecorators: {
23863
+ class: [{
23864
+ type: i0.Input,
23865
+ args: [{
23866
+ isSignal: true,
23867
+ alias: "class",
23868
+ required: false
23869
+ }]
23870
+ }],
23543
23871
  name: [{
23544
23872
  type: i0.Input,
23545
23873
  args: [{
@@ -23865,6 +24193,7 @@ var AggregationPanelComponent = class AggregationPanelComponent {
23865
24193
  allUnselected: "allUnselected",
23866
24194
  loadedMore: "loadedMore"
23867
24195
  },
24196
+ host: { classAttribute: "flex min-h-0 flex-1 flex-col" },
23868
24197
  providers: [provideTranslocoScope("filters")],
23869
24198
  viewQueries: [{
23870
24199
  propertyName: "searchBox",
@@ -23874,7 +24203,7 @@ var AggregationPanelComponent = class AggregationPanelComponent {
23874
24203
  isSignal: true
23875
24204
  }],
23876
24205
  ngImport: i0,
23877
- 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 buttons (apply, clear,\n select-all, chevron), never the header row, and the chevron is the keyboard way to collapse.\n Label, header actions 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 (!isCollapsed()) {\n @if (hasFilters()) {\n @let label = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); cleared.emit()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n @if (selection()) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); applied.emit()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n\n <!-- Nothing is projected into this box, so it had no accessible name at all (RGAA 11.1) —\n only its position next to the facet's title said what it selects. The name is stable\n whichever way it is about to act: `aria-checked` (true / mixed / false) is what tells the\n user where the selection stands. -->\n <checkbox\n class=\"shrink-0\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selection() && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"$event ? allSelected.emit() : allUnselected.emit()\" />\n }\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>\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 <SearchInput\n class=\"mt-2 mb-3\"\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 <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full 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<details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group p-0.5\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"(isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\"\n class=\"m-0 flex h-8 w-full 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",
24206
+ 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 buttons (apply, clear,\n select-all, chevron), never the header row, and the chevron is the keyboard way to collapse.\n Label, header actions 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 (!isCollapsed()) {\n @if (hasFilters()) {\n @let label = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); cleared.emit()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n @if (selection()) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); applied.emit()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n\n <!-- Nothing is projected into this box, so it had no accessible name at all (RGAA 11.1) —\n only its position next to the facet's title said what it selects. The name is stable\n whichever way it is about to act: `aria-checked` (true / mixed / false) is what tells the\n user where the selection stands. -->\n <checkbox\n class=\"shrink-0\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selection() && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"$event ? allSelected.emit() : allUnselected.emit()\" />\n }\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 <SearchInput\n class=\"mt-2 mb-3 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 <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<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]=\"(isEmpty() ? 'text-(--font-neutral-muted) ' : '') + 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-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n</details>\n",
23878
24207
  dependencies: [
23879
24208
  {
23880
24209
  kind: "directive",
@@ -24017,7 +24346,8 @@ i0.ɵɵngDeclareClassMetadata({
24017
24346
  TooltipDirective
24018
24347
  ],
24019
24348
  providers: [provideTranslocoScope("filters")],
24020
- 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 buttons (apply, clear,\n select-all, chevron), never the header row, and the chevron is the keyboard way to collapse.\n Label, header actions 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 (!isCollapsed()) {\n @if (hasFilters()) {\n @let label = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); cleared.emit()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n @if (selection()) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); applied.emit()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n\n <!-- Nothing is projected into this box, so it had no accessible name at all (RGAA 11.1) —\n only its position next to the facet's title said what it selects. The name is stable\n whichever way it is about to act: `aria-checked` (true / mixed / false) is what tells the\n user where the selection stands. -->\n <checkbox\n class=\"shrink-0\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selection() && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"$event ? allSelected.emit() : allUnselected.emit()\" />\n }\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>\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 <SearchInput\n class=\"mt-2 mb-3\"\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 <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full 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<details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group p-0.5\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"(isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\"\n class=\"m-0 flex h-8 w-full 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"
24349
+ host: { class: "flex min-h-0 flex-1 flex-col" },
24350
+ 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 buttons (apply, clear,\n select-all, chevron), never the header row, and the chevron is the keyboard way to collapse.\n Label, header actions 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 (!isCollapsed()) {\n @if (hasFilters()) {\n @let label = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); cleared.emit()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n @if (selection()) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); applied.emit()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n\n <!-- Nothing is projected into this box, so it had no accessible name at all (RGAA 11.1) —\n only its position next to the facet's title said what it selects. The name is stable\n whichever way it is about to act: `aria-checked` (true / mixed / false) is what tells the\n user where the selection stands. -->\n <checkbox\n class=\"shrink-0\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selection() && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"$event ? allSelected.emit() : allUnselected.emit()\" />\n }\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 <SearchInput\n class=\"mt-2 mb-3 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 <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<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]=\"(isEmpty() ? 'text-(--font-neutral-muted) ' : '') + 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-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n</details>\n"
24021
24351
  }]
24022
24352
  }],
24023
24353
  ctorParameters: () => [],
@@ -24848,7 +25178,7 @@ var AggregationTreeComponent = class AggregationTreeComponent {
24848
25178
  filtersChange: "filtersChange",
24849
25179
  searchText: "searchTextChange"
24850
25180
  },
24851
- host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class(), (isMissing() || (hideWhenEmpty() && isHidden())) && \"hidden\")" } },
25181
+ host: { properties: { "class": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isHidden())) && \"hidden\")" } },
24852
25182
  queries: [{
24853
25183
  propertyName: "itemDirective",
24854
25184
  first: true,
@@ -24857,7 +25187,7 @@ var AggregationTreeComponent = class AggregationTreeComponent {
24857
25187
  isSignal: true
24858
25188
  }],
24859
25189
  ngImport: i0,
24860
- 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 [(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 class=\"max-h-(--scroll-height,20rem) w-full 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 -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-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",
25190
+ 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 [(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 -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-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",
24861
25191
  dependencies: [
24862
25192
  {
24863
25193
  kind: "directive",
@@ -25031,8 +25361,8 @@ i0.ɵɵngDeclareClassMetadata({
25031
25361
  TreeItemGroup,
25032
25362
  AggregationPanelComponent
25033
25363
  ],
25034
- host: { "[class]": "cn(\"block h-[inherit] max-h-[inherit]\", class(), (isMissing() || (hideWhenEmpty() && isHidden())) && \"hidden\")" },
25035
- 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 [(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 class=\"max-h-(--scroll-height,20rem) w-full 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 -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-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"
25364
+ host: { "[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isHidden())) && \"hidden\")" },
25365
+ 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 [(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 -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-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"
25036
25366
  }]
25037
25367
  }],
25038
25368
  ctorParameters: () => [],
@@ -25689,7 +26019,7 @@ var AggregationListComponent = class AggregationListComponent {
25689
26019
  },
25690
26020
  host: {
25691
26021
  listeners: { "keydown.enter": "onHostEnterKey($event)" },
25692
- properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")" }
26022
+ properties: { "class": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")" }
25693
26023
  },
25694
26024
  queries: [{
25695
26025
  propertyName: "itemDirective",
@@ -25721,7 +26051,7 @@ var AggregationListComponent = class AggregationListComponent {
25721
26051
  }
25722
26052
  ],
25723
26053
  ngImport: i0,
25724
- 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 [(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=\"max-h-(--scroll-height,20rem) w-full 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",
26054
+ 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 [(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",
25725
26055
  dependencies: [
25726
26056
  {
25727
26057
  kind: "directive",
@@ -25862,10 +26192,10 @@ i0.ɵɵngDeclareClassMetadata({
25862
26192
  CheckboxComponent
25863
26193
  ],
25864
26194
  host: {
25865
- "[class]": "cn(\"block h-[inherit] max-h-[inherit]\", class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")",
26195
+ "[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")",
25866
26196
  "(keydown.enter)": "onHostEnterKey($event)"
25867
26197
  },
25868
- 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 [(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=\"max-h-(--scroll-height,20rem) w-full 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"
26198
+ 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 [(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"
25869
26199
  }]
25870
26200
  }],
25871
26201
  ctorParameters: () => [],
@@ -26312,7 +26642,7 @@ var AggregationComponent = class AggregationComponent {
26312
26642
  selectionChange: "selectionChange",
26313
26643
  filtersChange: "filtersChange"
26314
26644
  },
26315
- host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")" } },
26645
+ host: { properties: { "class": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\",class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")" } },
26316
26646
  queries: [{
26317
26647
  propertyName: "itemDirective",
26318
26648
  first: true,
@@ -26348,6 +26678,7 @@ var AggregationComponent = class AggregationComponent {
26348
26678
  @switch (resolvedType()) {
26349
26679
  @case ("date") {
26350
26680
  <AggregationDate
26681
+ class="min-h-0 flex-1"
26351
26682
  [id]="id()"
26352
26683
  [name]="name()"
26353
26684
  [column]="column()"
@@ -26369,6 +26700,7 @@ var AggregationComponent = class AggregationComponent {
26369
26700
  }
26370
26701
  @case ("tree") {
26371
26702
  <AggregationTree
26703
+ class="min-h-0 w-auto flex-1"
26372
26704
  [id]="id()"
26373
26705
  [name]="name()"
26374
26706
  [column]="column()"
@@ -26395,6 +26727,7 @@ var AggregationComponent = class AggregationComponent {
26395
26727
  }
26396
26728
  @default {
26397
26729
  <AggregationList
26730
+ class="min-h-0 flex-1"
26398
26731
  [id]="id()"
26399
26732
  [name]="name()"
26400
26733
  [column]="column()"
@@ -26494,6 +26827,7 @@ var AggregationComponent = class AggregationComponent {
26494
26827
  type: AggregationDateComponent,
26495
26828
  selector: "aggregation-date, AggregationDate, aggregationdate",
26496
26829
  inputs: [
26830
+ "class",
26497
26831
  "name",
26498
26832
  "column",
26499
26833
  "id",
@@ -26541,6 +26875,7 @@ i0.ɵɵngDeclareClassMetadata({
26541
26875
  @switch (resolvedType()) {
26542
26876
  @case ("date") {
26543
26877
  <AggregationDate
26878
+ class="min-h-0 flex-1"
26544
26879
  [id]="id()"
26545
26880
  [name]="name()"
26546
26881
  [column]="column()"
@@ -26562,6 +26897,7 @@ i0.ɵɵngDeclareClassMetadata({
26562
26897
  }
26563
26898
  @case ("tree") {
26564
26899
  <AggregationTree
26900
+ class="min-h-0 w-auto flex-1"
26565
26901
  [id]="id()"
26566
26902
  [name]="name()"
26567
26903
  [column]="column()"
@@ -26588,6 +26924,7 @@ i0.ɵɵngDeclareClassMetadata({
26588
26924
  }
26589
26925
  @default {
26590
26926
  <AggregationList
26927
+ class="min-h-0 flex-1"
26591
26928
  [id]="id()"
26592
26929
  [name]="name()"
26593
26930
  [column]="column()"
@@ -26613,7 +26950,7 @@ i0.ɵɵngDeclareClassMetadata({
26613
26950
  }
26614
26951
  }
26615
26952
  `,
26616
- host: { "[class]": "cn(\"block h-[inherit] max-h-[inherit]\",class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")" }
26953
+ host: { "[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\",class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")" }
26617
26954
  }]
26618
26955
  }],
26619
26956
  propDecorators: {
@@ -27473,6 +27810,10 @@ var FilterButtonComponent = class FilterButtonComponent {
27473
27810
  ...ngDevMode ? { debugName: "expandedLevel" } : /* istanbul ignore next */ {},
27474
27811
  transform: numberAttribute
27475
27812
  });
27813
+ overflowed = input(false, {
27814
+ ...ngDevMode ? { debugName: "overflowed" } : /* istanbul ignore next */ {},
27815
+ transform: booleanAttribute
27816
+ });
27476
27817
  variant = signal("tertiary", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
27477
27818
  filter = signal({
27478
27819
  name: "",
@@ -27487,6 +27828,7 @@ var FilterButtonComponent = class FilterButtonComponent {
27487
27828
  popoverId = `filter-popover-${popoverUid++}`;
27488
27829
  popoverRef = viewChild(PopoverDirective, ...ngDevMode ? [{ debugName: "popoverRef" }] : /* istanbul ignore next */ []);
27489
27830
  isOpen = computed(() => this.popoverRef()?.state() === "open", ...ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []);
27831
+ toolbar = inject(Toolbar, { optional: true });
27490
27832
  nativeElement = inject(ElementRef).nativeElement;
27491
27833
  aggregationsStore = inject(AggregationsStore);
27492
27834
  queryParamsStore = inject(QueryParamsStore);
@@ -27599,6 +27941,13 @@ var FilterButtonComponent = class FilterButtonComponent {
27599
27941
  isSignal: true,
27600
27942
  isRequired: false,
27601
27943
  transformFunction: null
27944
+ },
27945
+ overflowed: {
27946
+ classPropertyName: "overflowed",
27947
+ publicName: "overflowed",
27948
+ isSignal: true,
27949
+ isRequired: false,
27950
+ transformFunction: null
27602
27951
  }
27603
27952
  },
27604
27953
  host: {
@@ -27620,13 +27969,7 @@ var FilterButtonComponent = class FilterButtonComponent {
27620
27969
  }],
27621
27970
  ngImport: i0,
27622
27971
  template: `
27623
- <button
27624
- [variant]="variant()"
27625
- [attr.popovertarget]="popoverId"
27626
- [class]="isOpen() ? 'border border-(--stroke-neutral-dark)/50' : ''"
27627
- [attr.data-disabled]="filter().disabled"
27628
- [disabled]="filter().disabled || null"
27629
- >
27972
+ <ng-template #label>
27630
27973
  <!-- display the filter icon if set -->
27631
27974
  @let icon = filter().icon;
27632
27975
  @if (icon) {
@@ -27646,7 +27989,35 @@ var FilterButtonComponent = class FilterButtonComponent {
27646
27989
  @if (filter().count > 1) {
27647
27990
  <Badge variant="number" fill="secondary" scheme="sage" size="xs">+{{ filter().count - 1 }}</Badge>
27648
27991
  }
27649
- </button>
27992
+ </ng-template>
27993
+
27994
+ @if (toolbar) {
27995
+ <!-- Inside a toolbar the BUTTON is the roving widget, not the host: the host is an element with
27996
+ no role of its own, so making it the widget gave two tab stops — an inert host that the
27997
+ roving focused, then this button with its own stop. A native disabled attribute would also make a
27998
+ widget unfocusable, so the toolbar's own disabled input (soft: still focusable, aria-disabled)
27999
+ takes over and the popover trigger is simply not wired. The same disabled state removes a filter
28000
+ hidden by the overflow manager (display: none) from the roving: arrow keys must not spend
28001
+ presses on filters nobody can see. -->
28002
+ <button
28003
+ ngToolbarWidget
28004
+ [variant]="variant()"
28005
+ [attr.popovertarget]="filter().disabled ? null : popoverId"
28006
+ [class]="isOpen() ? 'border border-(--stroke-neutral-dark)/50' : ''"
28007
+ [attr.data-disabled]="filter().disabled"
28008
+ [disabled]="filter().disabled || overflowed()">
28009
+ <ng-container *ngTemplateOutlet="label" />
28010
+ </button>
28011
+ } @else {
28012
+ <button
28013
+ [variant]="variant()"
28014
+ [attr.popovertarget]="popoverId"
28015
+ [class]="isOpen() ? 'border border-(--stroke-neutral-dark)/50' : ''"
28016
+ [attr.data-disabled]="filter().disabled"
28017
+ [disabled]="filter().disabled || null">
28018
+ <ng-container *ngTemplateOutlet="label" />
28019
+ </button>
28020
+ }
27650
28021
 
27651
28022
  <!-- Native HTML Popover (top-layer). The popover content stays a DOM
27652
28023
  descendant of the surrounding @angular/aria toolbar, so pointer/click
@@ -27660,8 +28031,10 @@ var FilterButtonComponent = class FilterButtonComponent {
27660
28031
  trigger-width matching so the panel sizes to its content. overflow-hidden
27661
28032
  resets the browser's default overflow:auto on [popover] elements, which
27662
28033
  would otherwise scroll on its own in addition to Aggregation's own
27663
- scroll-height-capped list (a double scrollbar) once its fit-content
27664
- height would otherwise exceed the viewport. -->
28034
+ facet list (a double scrollbar) once its fit-content height would
28035
+ otherwise exceed the viewport. The facet list is the only thing that
28036
+ scrolls: the facet itself is capped by the room the popover has (see
28037
+ below). -->
27665
28038
  <div
27666
28039
  popover
27667
28040
  [id]="popoverId"
@@ -27679,21 +28052,23 @@ var FilterButtonComponent = class FilterButtonComponent {
27679
28052
  rings (2px outlines) of the facet header and its buttons sit flush against the wrapper's edges —
27680
28053
  they were cut off on three sides. The 4px of padding gives them room, the matching negative
27681
28054
  margin leaves the layout unchanged. Width and max-height grow by the 0.5rem of padding
27682
- (15.5rem = the former w-60 + 0.5rem, 1rem = the former 1.5rem - 0.5rem) because box-sizing is
27683
- border-box here: the facet keeps its 15rem and the popover its size. -->
27684
- <!-- Real max-height + overflow-y-auto on a plain wrapper, kept off <Aggregation> itself:
27685
- size() (added to injectPopover) exposes the actual room available in the chosen
27686
- direction as the --popover-available-height CSS var. flip()/shift() alone only
27687
- reposition the popover, they never shrink it, so a facet with many values could
27688
- otherwise overflow past the viewport with nothing to scroll it back into view. The
27689
- 1.5rem subtracted is this popover's own vertical padding (p-3), which eats into that
27690
- same available space. Same pattern as more-button.component.ts's own wrapper. -->
27691
- <div class="-m-1 max-h-[calc(var(--popover-available-height,55vh)_-_1rem)] w-[15.5rem] overflow-y-auto overscroll-contain p-1">
28055
+ (15.5rem = the former w-60 + 0.5rem) because box-sizing is border-box here: the facet keeps
28056
+ its 15rem and the popover its size. The wrapper no longer scrolls (and no longer clips the
28057
+ rings): the facet's own list is the one scrollbar. -->
28058
+ <!-- The facet is capped by the smaller of two heights: its comfortable size (19 lines of list +
28059
+ 7rem of header, search box and paddings), and the room the popover really has.
28060
+ size() (added to injectPopover) exposes that room, in the chosen direction, as the
28061
+ --popover-available-height CSS var — flip()/shift() alone only reposition the popover, they
28062
+ never shrink it. The 1.5rem subtracted is this popover's own vertical padding (p-3), which
28063
+ eats into that same available space (this wrapper's p-1 / -m-1 cancel out). The facet is a
28064
+ flex column whose list is min-h-0 flex-1, so it is the list that shrinks and scrolls — a
28065
+ capped wrapper with overflow-y-auto around it would add a second, outer scrollbar. -->
28066
+ <div class="-m-1 w-[15.5rem] p-1">
27692
28067
  @let name = filter().name;
27693
28068
  @let column = filter().column;
27694
28069
  @if (name) {
27695
28070
  <Aggregation
27696
- class="block w-full [--scroll-height:19lh]"
28071
+ class="w-full max-h-[min(calc(19lh+7rem),calc(var(--popover-available-height,55vh)_-_1.5rem))]"
27697
28072
  id="filter-{{ filter().column }}"
27698
28073
  [name]="name"
27699
28074
  [column]="column"
@@ -27712,6 +28087,23 @@ var FilterButtonComponent = class FilterButtonComponent {
27712
28087
  `,
27713
28088
  isInline: true,
27714
28089
  dependencies: [
28090
+ {
28091
+ kind: "directive",
28092
+ type: NgTemplateOutlet,
28093
+ selector: "[ngTemplateOutlet]",
28094
+ inputs: [
28095
+ "ngTemplateOutletContext",
28096
+ "ngTemplateOutlet",
28097
+ "ngTemplateOutletInjector"
28098
+ ]
28099
+ },
28100
+ {
28101
+ kind: "directive",
28102
+ type: ToolbarWidget,
28103
+ selector: "[ngToolbarWidget]",
28104
+ inputs: ["id", "disabled"],
28105
+ exportAs: ["ngToolbarWidget"]
28106
+ },
27715
28107
  {
27716
28108
  kind: "directive",
27717
28109
  type: ButtonComponent,
@@ -27819,6 +28211,8 @@ i0.ɵɵngDeclareClassMetadata({
27819
28211
  args: [{
27820
28212
  selector: "filter-button, FilterButton",
27821
28213
  imports: [
28214
+ NgTemplateOutlet,
28215
+ ToolbarWidget,
27822
28216
  ButtonComponent,
27823
28217
  PopoverDirective,
27824
28218
  OperatorPipe,
@@ -27829,13 +28223,7 @@ i0.ɵɵngDeclareClassMetadata({
27829
28223
  FaIconComponent
27830
28224
  ],
27831
28225
  template: `
27832
- <button
27833
- [variant]="variant()"
27834
- [attr.popovertarget]="popoverId"
27835
- [class]="isOpen() ? 'border border-(--stroke-neutral-dark)/50' : ''"
27836
- [attr.data-disabled]="filter().disabled"
27837
- [disabled]="filter().disabled || null"
27838
- >
28226
+ <ng-template #label>
27839
28227
  <!-- display the filter icon if set -->
27840
28228
  @let icon = filter().icon;
27841
28229
  @if (icon) {
@@ -27855,7 +28243,35 @@ i0.ɵɵngDeclareClassMetadata({
27855
28243
  @if (filter().count > 1) {
27856
28244
  <Badge variant="number" fill="secondary" scheme="sage" size="xs">+{{ filter().count - 1 }}</Badge>
27857
28245
  }
27858
- </button>
28246
+ </ng-template>
28247
+
28248
+ @if (toolbar) {
28249
+ <!-- Inside a toolbar the BUTTON is the roving widget, not the host: the host is an element with
28250
+ no role of its own, so making it the widget gave two tab stops — an inert host that the
28251
+ roving focused, then this button with its own stop. A native disabled attribute would also make a
28252
+ widget unfocusable, so the toolbar's own disabled input (soft: still focusable, aria-disabled)
28253
+ takes over and the popover trigger is simply not wired. The same disabled state removes a filter
28254
+ hidden by the overflow manager (display: none) from the roving: arrow keys must not spend
28255
+ presses on filters nobody can see. -->
28256
+ <button
28257
+ ngToolbarWidget
28258
+ [variant]="variant()"
28259
+ [attr.popovertarget]="filter().disabled ? null : popoverId"
28260
+ [class]="isOpen() ? 'border border-(--stroke-neutral-dark)/50' : ''"
28261
+ [attr.data-disabled]="filter().disabled"
28262
+ [disabled]="filter().disabled || overflowed()">
28263
+ <ng-container *ngTemplateOutlet="label" />
28264
+ </button>
28265
+ } @else {
28266
+ <button
28267
+ [variant]="variant()"
28268
+ [attr.popovertarget]="popoverId"
28269
+ [class]="isOpen() ? 'border border-(--stroke-neutral-dark)/50' : ''"
28270
+ [attr.data-disabled]="filter().disabled"
28271
+ [disabled]="filter().disabled || null">
28272
+ <ng-container *ngTemplateOutlet="label" />
28273
+ </button>
28274
+ }
27859
28275
 
27860
28276
  <!-- Native HTML Popover (top-layer). The popover content stays a DOM
27861
28277
  descendant of the surrounding @angular/aria toolbar, so pointer/click
@@ -27869,8 +28285,10 @@ i0.ɵɵngDeclareClassMetadata({
27869
28285
  trigger-width matching so the panel sizes to its content. overflow-hidden
27870
28286
  resets the browser's default overflow:auto on [popover] elements, which
27871
28287
  would otherwise scroll on its own in addition to Aggregation's own
27872
- scroll-height-capped list (a double scrollbar) once its fit-content
27873
- height would otherwise exceed the viewport. -->
28288
+ facet list (a double scrollbar) once its fit-content height would
28289
+ otherwise exceed the viewport. The facet list is the only thing that
28290
+ scrolls: the facet itself is capped by the room the popover has (see
28291
+ below). -->
27874
28292
  <div
27875
28293
  popover
27876
28294
  [id]="popoverId"
@@ -27888,21 +28306,23 @@ i0.ɵɵngDeclareClassMetadata({
27888
28306
  rings (2px outlines) of the facet header and its buttons sit flush against the wrapper's edges —
27889
28307
  they were cut off on three sides. The 4px of padding gives them room, the matching negative
27890
28308
  margin leaves the layout unchanged. Width and max-height grow by the 0.5rem of padding
27891
- (15.5rem = the former w-60 + 0.5rem, 1rem = the former 1.5rem - 0.5rem) because box-sizing is
27892
- border-box here: the facet keeps its 15rem and the popover its size. -->
27893
- <!-- Real max-height + overflow-y-auto on a plain wrapper, kept off <Aggregation> itself:
27894
- size() (added to injectPopover) exposes the actual room available in the chosen
27895
- direction as the --popover-available-height CSS var. flip()/shift() alone only
27896
- reposition the popover, they never shrink it, so a facet with many values could
27897
- otherwise overflow past the viewport with nothing to scroll it back into view. The
27898
- 1.5rem subtracted is this popover's own vertical padding (p-3), which eats into that
27899
- same available space. Same pattern as more-button.component.ts's own wrapper. -->
27900
- <div class="-m-1 max-h-[calc(var(--popover-available-height,55vh)_-_1rem)] w-[15.5rem] overflow-y-auto overscroll-contain p-1">
28309
+ (15.5rem = the former w-60 + 0.5rem) because box-sizing is border-box here: the facet keeps
28310
+ its 15rem and the popover its size. The wrapper no longer scrolls (and no longer clips the
28311
+ rings): the facet's own list is the one scrollbar. -->
28312
+ <!-- The facet is capped by the smaller of two heights: its comfortable size (19 lines of list +
28313
+ 7rem of header, search box and paddings), and the room the popover really has.
28314
+ size() (added to injectPopover) exposes that room, in the chosen direction, as the
28315
+ --popover-available-height CSS var — flip()/shift() alone only reposition the popover, they
28316
+ never shrink it. The 1.5rem subtracted is this popover's own vertical padding (p-3), which
28317
+ eats into that same available space (this wrapper's p-1 / -m-1 cancel out). The facet is a
28318
+ flex column whose list is min-h-0 flex-1, so it is the list that shrinks and scrolls — a
28319
+ capped wrapper with overflow-y-auto around it would add a second, outer scrollbar. -->
28320
+ <div class="-m-1 w-[15.5rem] p-1">
27901
28321
  @let name = filter().name;
27902
28322
  @let column = filter().column;
27903
28323
  @if (name) {
27904
28324
  <Aggregation
27905
- class="block w-full [--scroll-height:19lh]"
28325
+ class="w-full max-h-[min(calc(19lh+7rem),calc(var(--popover-available-height,55vh)_-_1.5rem))]"
27906
28326
  id="filter-{{ filter().column }}"
27907
28327
  [name]="name"
27908
28328
  [column]="column"
@@ -27979,6 +28399,14 @@ i0.ɵɵngDeclareClassMetadata({
27979
28399
  required: false
27980
28400
  }]
27981
28401
  }],
28402
+ overflowed: [{
28403
+ type: i0.Input,
28404
+ args: [{
28405
+ isSignal: true,
28406
+ alias: "overflowed",
28407
+ required: false
28408
+ }]
28409
+ }],
27982
28410
  popoverRef: [{
27983
28411
  type: i0.ViewChild,
27984
28412
  args: [i0.forwardRef(() => PopoverDirective), { isSignal: true }]
@@ -28102,7 +28530,7 @@ var MoreComponent = class MoreComponent {
28102
28530
  <div role="group" [attr.aria-label]="'filters.moreFilters' | transloco" class="divide-y divide-(--font-neutral-muted)/18 pt-1 pb-1">
28103
28531
  @for (filter of filteredFilters(); track filter.name) {
28104
28532
  <Aggregation
28105
- class="w-full px-1 [--scroll-height:15lh]"
28533
+ class="w-full px-1 max-h-[calc(15lh+7rem)]"
28106
28534
  id="more-filters"
28107
28535
  [attr.title]="'filters.openFilter' | transloco: { filter: filter.name }"
28108
28536
  showFiltersCount
@@ -28205,7 +28633,7 @@ i0.ɵɵngDeclareClassMetadata({
28205
28633
  <div role="group" [attr.aria-label]="'filters.moreFilters' | transloco" class="divide-y divide-(--font-neutral-muted)/18 pt-1 pb-1">
28206
28634
  @for (filter of filteredFilters(); track filter.name) {
28207
28635
  <Aggregation
28208
- class="w-full px-1 [--scroll-height:15lh]"
28636
+ class="w-full px-1 max-h-[calc(15lh+7rem)]"
28209
28637
  id="more-filters"
28210
28638
  [attr.title]="'filters.openFilter' | transloco: { filter: filter.name }"
28211
28639
  showFiltersCount
@@ -28293,6 +28721,15 @@ var MoreButtonComponent = class MoreButtonComponent {
28293
28721
  nativeElement = inject(ElementRef).nativeElement;
28294
28722
  breakpointObserver = inject(BreakpointObserverService);
28295
28723
  aggregationsService = inject(AggregationsService);
28724
+ toolbar = inject(Toolbar, { optional: true });
28725
+ disabled = input(false, {
28726
+ ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
28727
+ transform: booleanAttribute
28728
+ });
28729
+ triggerClass = "gap-1 truncate font-semibold";
28730
+ openTriggerClass = "gap-1 truncate font-semibold border border-(--stroke-neutral-dark)/18";
28731
+ openSheet = () => this.sheetRef()?.open();
28732
+ openDialog = () => this.dialogRef()?.showModal();
28296
28733
  count = input(2, {
28297
28734
  ...ngDevMode ? { debugName: "count" } : /* istanbul ignore next */ {},
28298
28735
  transform: numberAttribute
@@ -28346,6 +28783,13 @@ var MoreButtonComponent = class MoreButtonComponent {
28346
28783
  isStandalone: true,
28347
28784
  selector: "more-button, MoreButton",
28348
28785
  inputs: {
28786
+ disabled: {
28787
+ classPropertyName: "disabled",
28788
+ publicName: "disabled",
28789
+ isSignal: true,
28790
+ isRequired: false,
28791
+ transformFunction: null
28792
+ },
28349
28793
  count: {
28350
28794
  classPropertyName: "count",
28351
28795
  publicName: "count",
@@ -28447,10 +28891,24 @@ var MoreButtonComponent = class MoreButtonComponent {
28447
28891
  }
28448
28892
  </ng-template>
28449
28893
 
28894
+ <!-- The trigger button, shared by the three variants below. Inside a toolbar the BUTTON is the
28895
+ roving widget and not the host: the host is an element with no role of its own and no key
28896
+ handler, so an arrow key landed on a dead element, then the button was a second tab stop.
28897
+ Outside a toolbar it is a plain button. -->
28898
+ <ng-template #button let-onClick="onClick" let-popoverTarget="popoverTarget" let-cls="cls">
28899
+ @if (toolbar) {
28900
+ <button ngToolbarWidget variant="tertiary" [attr.popovertarget]="popoverTarget" [class]="cls" aria-label="more filters" [disabled]="disabled()" (click)="onClick?.()">
28901
+ <ng-container *ngTemplateOutlet="trigger" />
28902
+ </button>
28903
+ } @else {
28904
+ <button variant="tertiary" [attr.popovertarget]="popoverTarget" [class]="cls" aria-label="more filters" [disabled]="disabled() || null" (click)="onClick?.()">
28905
+ <ng-container *ngTemplateOutlet="trigger" />
28906
+ </button>
28907
+ }
28908
+ </ng-template>
28909
+
28450
28910
  @if (breakpointObserver.isMobile()) {
28451
- <button variant="tertiary" class="gap-1 truncate font-semibold" aria-label="more filters" (click)="sheetRef()?.open()">
28452
- <ng-container *ngTemplateOutlet="trigger" />
28453
- </button>
28911
+ <ng-container *ngTemplateOutlet="button; context: { onClick: openSheet, cls: triggerClass }" />
28454
28912
 
28455
28913
  <Sheet side="bottom" size="lg">
28456
28914
  <SheetHeader [closeLabel]="'filters.close' | transloco">
@@ -28470,9 +28928,7 @@ var MoreButtonComponent = class MoreButtonComponent {
28470
28928
  </SheetBody>
28471
28929
  </Sheet>
28472
28930
  } @else if (isEnriched()) {
28473
- <button variant="tertiary" class="gap-1 truncate font-semibold" aria-label="more filters" (click)="dialogRef()?.showModal()">
28474
- <ng-container *ngTemplateOutlet="trigger" />
28475
- </button>
28931
+ <ng-container *ngTemplateOutlet="button; context: { onClick: openDialog, cls: triggerClass }" />
28476
28932
 
28477
28933
  <dialog>
28478
28934
  <DialogContent size="md">
@@ -28492,13 +28948,7 @@ var MoreButtonComponent = class MoreButtonComponent {
28492
28948
  </DialogContent>
28493
28949
  </dialog>
28494
28950
  } @else {
28495
- <button
28496
- variant="tertiary"
28497
- [attr.popovertarget]="popoverId"
28498
- [class]="isOpen() ? 'gap-1 truncate font-semibold border border-(--stroke-neutral-dark)/18' : 'gap-1 truncate font-semibold'"
28499
- aria-label="more filters">
28500
- <ng-container *ngTemplateOutlet="trigger" />
28501
- </button>
28951
+ <ng-container *ngTemplateOutlet="button; context: { popoverTarget: popoverId, cls: isOpen() ? openTriggerClass : triggerClass }" />
28502
28952
 
28503
28953
  <!-- Native HTML Popover (top-layer). Fully isolate pointer/click events from
28504
28954
  the surrounding @angular/aria toolbar (see FilterButton for the rationale:
@@ -28548,6 +28998,13 @@ var MoreButtonComponent = class MoreButtonComponent {
28548
28998
  "ngTemplateOutletInjector"
28549
28999
  ]
28550
29000
  },
29001
+ {
29002
+ kind: "directive",
29003
+ type: ToolbarWidget,
29004
+ selector: "[ngToolbarWidget]",
29005
+ inputs: ["id", "disabled"],
29006
+ exportAs: ["ngToolbarWidget"]
29007
+ },
28551
29008
  {
28552
29009
  kind: "directive",
28553
29010
  type: ButtonComponent,
@@ -28693,6 +29150,7 @@ i0.ɵɵngDeclareClassMetadata({
28693
29150
  selector: "more-button, MoreButton",
28694
29151
  imports: [
28695
29152
  NgTemplateOutlet,
29153
+ ToolbarWidget,
28696
29154
  ButtonComponent,
28697
29155
  PopoverDirective,
28698
29156
  TranslocoPipe,
@@ -28720,10 +29178,24 @@ i0.ɵɵngDeclareClassMetadata({
28720
29178
  }
28721
29179
  </ng-template>
28722
29180
 
29181
+ <!-- The trigger button, shared by the three variants below. Inside a toolbar the BUTTON is the
29182
+ roving widget and not the host: the host is an element with no role of its own and no key
29183
+ handler, so an arrow key landed on a dead element, then the button was a second tab stop.
29184
+ Outside a toolbar it is a plain button. -->
29185
+ <ng-template #button let-onClick="onClick" let-popoverTarget="popoverTarget" let-cls="cls">
29186
+ @if (toolbar) {
29187
+ <button ngToolbarWidget variant="tertiary" [attr.popovertarget]="popoverTarget" [class]="cls" aria-label="more filters" [disabled]="disabled()" (click)="onClick?.()">
29188
+ <ng-container *ngTemplateOutlet="trigger" />
29189
+ </button>
29190
+ } @else {
29191
+ <button variant="tertiary" [attr.popovertarget]="popoverTarget" [class]="cls" aria-label="more filters" [disabled]="disabled() || null" (click)="onClick?.()">
29192
+ <ng-container *ngTemplateOutlet="trigger" />
29193
+ </button>
29194
+ }
29195
+ </ng-template>
29196
+
28723
29197
  @if (breakpointObserver.isMobile()) {
28724
- <button variant="tertiary" class="gap-1 truncate font-semibold" aria-label="more filters" (click)="sheetRef()?.open()">
28725
- <ng-container *ngTemplateOutlet="trigger" />
28726
- </button>
29198
+ <ng-container *ngTemplateOutlet="button; context: { onClick: openSheet, cls: triggerClass }" />
28727
29199
 
28728
29200
  <Sheet side="bottom" size="lg">
28729
29201
  <SheetHeader [closeLabel]="'filters.close' | transloco">
@@ -28743,9 +29215,7 @@ i0.ɵɵngDeclareClassMetadata({
28743
29215
  </SheetBody>
28744
29216
  </Sheet>
28745
29217
  } @else if (isEnriched()) {
28746
- <button variant="tertiary" class="gap-1 truncate font-semibold" aria-label="more filters" (click)="dialogRef()?.showModal()">
28747
- <ng-container *ngTemplateOutlet="trigger" />
28748
- </button>
29218
+ <ng-container *ngTemplateOutlet="button; context: { onClick: openDialog, cls: triggerClass }" />
28749
29219
 
28750
29220
  <dialog>
28751
29221
  <DialogContent size="md">
@@ -28765,13 +29235,7 @@ i0.ɵɵngDeclareClassMetadata({
28765
29235
  </DialogContent>
28766
29236
  </dialog>
28767
29237
  } @else {
28768
- <button
28769
- variant="tertiary"
28770
- [attr.popovertarget]="popoverId"
28771
- [class]="isOpen() ? 'gap-1 truncate font-semibold border border-(--stroke-neutral-dark)/18' : 'gap-1 truncate font-semibold'"
28772
- aria-label="more filters">
28773
- <ng-container *ngTemplateOutlet="trigger" />
28774
- </button>
29238
+ <ng-container *ngTemplateOutlet="button; context: { popoverTarget: popoverId, cls: isOpen() ? openTriggerClass : triggerClass }" />
28775
29239
 
28776
29240
  <!-- Native HTML Popover (top-layer). Fully isolate pointer/click events from
28777
29241
  the surrounding @angular/aria toolbar (see FilterButton for the rationale:
@@ -28813,6 +29277,14 @@ i0.ɵɵngDeclareClassMetadata({
28813
29277
  }]
28814
29278
  }],
28815
29279
  propDecorators: {
29280
+ disabled: [{
29281
+ type: i0.Input,
29282
+ args: [{
29283
+ isSignal: true,
29284
+ alias: "disabled",
29285
+ required: false
29286
+ }]
29287
+ }],
28816
29288
  count: [{
28817
29289
  type: i0.Input,
28818
29290
  args: [{
@@ -29127,7 +29599,7 @@ var FiltersBarComponent = class FiltersBarComponent {
29127
29599
  tabindex with it: the keyboard stopped on an invisible strip of zero height, announced
29128
29600
  "Filter controls" and disabled at once. -->
29129
29601
  @if (hasFilters() || currentBasket() || hasAggregations()) {
29130
- <div ngToolbar [attr.aria-label]="ariaLabel() ?? ('filters.filterControls' | transloco)" overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
29602
+ <div ngToolbar [softDisabled]="false" [attr.aria-label]="ariaLabel() ?? ('filters.filterControls' | transloco)" overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
29131
29603
  @if (hasFilters()) {
29132
29604
  <button
29133
29605
  ngToolbarWidget
@@ -29165,10 +29637,13 @@ var FiltersBarComponent = class FiltersBarComponent {
29165
29637
  : 'flex-row'
29166
29638
  )">
29167
29639
 
29168
- @for (filter of authorizedFilters(); track filter.name) {
29640
+ @for (filter of authorizedFilters(); track filter.name; let i = $index) {
29641
+ <!-- No ngToolbarWidget here: FilterButton makes its own inner button the roving widget, so the
29642
+ bar has one tab stop per filter and not two (host, then button). A filter hidden by the
29643
+ overflow manager is disabled, hence skipped by the roving (the toolbar is not soft-disabled). -->
29169
29644
  <FilterButton
29170
- ngToolbarWidget
29171
29645
  overflowItem
29646
+ [overflowed]="i >= visibleFiltersCount()"
29172
29647
  [name]="filter.name"
29173
29648
  [column]="filter.column"
29174
29649
  [position]="position()"
@@ -29184,8 +29659,8 @@ var FiltersBarComponent = class FiltersBarComponent {
29184
29659
  forbids the attribute. The name belongs to the <button> inside MoreButton, which
29185
29660
  already carries it. -->
29186
29661
  <MoreButton
29187
- ngToolbarWidget
29188
29662
  overflowStop
29663
+ [disabled]="hasMoreFilters().length === 0"
29189
29664
  [class]="cn('shrink-0', hasMoreFilters().length === 0 && 'invisible pointer-events-none')"
29190
29665
  [count]="visibleFiltersCount()"
29191
29666
  [position]="morePosition()"
@@ -29218,6 +29693,7 @@ var FiltersBarComponent = class FiltersBarComponent {
29218
29693
  type: MoreButtonComponent,
29219
29694
  selector: "more-button, MoreButton",
29220
29695
  inputs: [
29696
+ "disabled",
29221
29697
  "count",
29222
29698
  "position",
29223
29699
  "lockPosition",
@@ -29239,7 +29715,8 @@ var FiltersBarComponent = class FiltersBarComponent {
29239
29715
  "position",
29240
29716
  "offset",
29241
29717
  "lockPosition",
29242
- "expandedLevel"
29718
+ "expandedLevel",
29719
+ "overflowed"
29243
29720
  ]
29244
29721
  },
29245
29722
  {
@@ -29331,7 +29808,7 @@ i0.ɵɵngDeclareClassMetadata({
29331
29808
  tabindex with it: the keyboard stopped on an invisible strip of zero height, announced
29332
29809
  "Filter controls" and disabled at once. -->
29333
29810
  @if (hasFilters() || currentBasket() || hasAggregations()) {
29334
- <div ngToolbar [attr.aria-label]="ariaLabel() ?? ('filters.filterControls' | transloco)" overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
29811
+ <div ngToolbar [softDisabled]="false" [attr.aria-label]="ariaLabel() ?? ('filters.filterControls' | transloco)" overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
29335
29812
  @if (hasFilters()) {
29336
29813
  <button
29337
29814
  ngToolbarWidget
@@ -29369,10 +29846,13 @@ i0.ɵɵngDeclareClassMetadata({
29369
29846
  : 'flex-row'
29370
29847
  )">
29371
29848
 
29372
- @for (filter of authorizedFilters(); track filter.name) {
29849
+ @for (filter of authorizedFilters(); track filter.name; let i = $index) {
29850
+ <!-- No ngToolbarWidget here: FilterButton makes its own inner button the roving widget, so the
29851
+ bar has one tab stop per filter and not two (host, then button). A filter hidden by the
29852
+ overflow manager is disabled, hence skipped by the roving (the toolbar is not soft-disabled). -->
29373
29853
  <FilterButton
29374
- ngToolbarWidget
29375
29854
  overflowItem
29855
+ [overflowed]="i >= visibleFiltersCount()"
29376
29856
  [name]="filter.name"
29377
29857
  [column]="filter.column"
29378
29858
  [position]="position()"
@@ -29388,8 +29868,8 @@ i0.ɵɵngDeclareClassMetadata({
29388
29868
  forbids the attribute. The name belongs to the <button> inside MoreButton, which
29389
29869
  already carries it. -->
29390
29870
  <MoreButton
29391
- ngToolbarWidget
29392
29871
  overflowStop
29872
+ [disabled]="hasMoreFilters().length === 0"
29393
29873
  [class]="cn('shrink-0', hasMoreFilters().length === 0 && 'invisible pointer-events-none')"
29394
29874
  [count]="visibleFiltersCount()"
29395
29875
  [position]="morePosition()"
@@ -30211,7 +30691,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30211
30691
  template: `
30212
30692
  <ListPanelHeader [label]="'searches.recent.label' | transloco" [floating]="!!floating" />
30213
30693
 
30214
- <ListPanel
30694
+ <ListPanel #panel
30215
30695
  [items]="paginatedRecentSearches()"
30216
30696
  [getValue]="urlOf"
30217
30697
  [getLabel]="labelOf"
@@ -30243,7 +30723,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30243
30723
  </ListPanel>
30244
30724
 
30245
30725
  @if (paginatedRecentSearches().length > 0) {
30246
- <ListPanelFooter
30726
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()"
30247
30727
  [hasMore]="hasMore()"
30248
30728
  [config]="config"
30249
30729
  (loadMore)="loadMore()" />
@@ -30320,6 +30800,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30320
30800
  "getValue",
30321
30801
  "getLabel",
30322
30802
  "deleteButtonTitle",
30803
+ "deletable",
30323
30804
  "ariaLabel",
30324
30805
  "itemClass"
30325
30806
  ],
@@ -30332,7 +30813,8 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30332
30813
  inputs: [
30333
30814
  "class",
30334
30815
  "hasMore",
30335
- "config"
30816
+ "config",
30817
+ "navigationHint"
30336
30818
  ],
30337
30819
  outputs: ["loadMore"]
30338
30820
  },
@@ -30370,7 +30852,7 @@ i0.ɵɵngDeclareClassMetadata({
30370
30852
  template: `
30371
30853
  <ListPanelHeader [label]="'searches.recent.label' | transloco" [floating]="!!floating" />
30372
30854
 
30373
- <ListPanel
30855
+ <ListPanel #panel
30374
30856
  [items]="paginatedRecentSearches()"
30375
30857
  [getValue]="urlOf"
30376
30858
  [getLabel]="labelOf"
@@ -30402,7 +30884,7 @@ i0.ɵɵngDeclareClassMetadata({
30402
30884
  </ListPanel>
30403
30885
 
30404
30886
  @if (paginatedRecentSearches().length > 0) {
30405
- <ListPanelFooter
30887
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()"
30406
30888
  [hasMore]="hasMore()"
30407
30889
  [config]="config"
30408
30890
  (loadMore)="loadMore()" />
@@ -30487,7 +30969,7 @@ var SavedSearchDialog = class SavedSearchDialog {
30487
30969
  [formField]="nameField"
30488
30970
  [attr.aria-label]="'searches.saved.saveName' | transloco"
30489
30971
  [attr.placeholder]="'searches.saved.saveName' | transloco"
30490
- (keydown.enter)="saveName().trim() && confirm()" />
30972
+ (keydown.enter)="$event.preventDefault(); saveName().trim() && confirm()" />
30491
30973
  </input-group>
30492
30974
  </DialogBody>
30493
30975
 
@@ -30634,7 +31116,7 @@ i0.ɵɵngDeclareClassMetadata({
30634
31116
  [formField]="nameField"
30635
31117
  [attr.aria-label]="'searches.saved.saveName' | transloco"
30636
31118
  [attr.placeholder]="'searches.saved.saveName' | transloco"
30637
- (keydown.enter)="saveName().trim() && confirm()" />
31119
+ (keydown.enter)="$event.preventDefault(); saveName().trim() && confirm()" />
30638
31120
  </input-group>
30639
31121
  </DialogBody>
30640
31122
 
@@ -30932,7 +31414,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
30932
31414
  template: `
30933
31415
  <ListPanelHeader [label]="'searches.saved.label' | transloco" [floating]="!!floating" />
30934
31416
 
30935
- <ListPanel
31417
+ <ListPanel #panel
30936
31418
  [items]="paginatedSearches()"
30937
31419
  [getValue]="urlOf"
30938
31420
  [getLabel]="labelOf"
@@ -30964,7 +31446,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
30964
31446
  </ListPanel>
30965
31447
 
30966
31448
  @if(paginatedSearches().length > 0) {
30967
- <ListPanelFooter [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31449
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()" [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
30968
31450
  }
30969
31451
  `,
30970
31452
  isInline: true,
@@ -31038,6 +31520,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31038
31520
  "getValue",
31039
31521
  "getLabel",
31040
31522
  "deleteButtonTitle",
31523
+ "deletable",
31041
31524
  "ariaLabel",
31042
31525
  "itemClass"
31043
31526
  ],
@@ -31050,7 +31533,8 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31050
31533
  inputs: [
31051
31534
  "class",
31052
31535
  "hasMore",
31053
- "config"
31536
+ "config",
31537
+ "navigationHint"
31054
31538
  ],
31055
31539
  outputs: ["loadMore"]
31056
31540
  },
@@ -31088,7 +31572,7 @@ i0.ɵɵngDeclareClassMetadata({
31088
31572
  template: `
31089
31573
  <ListPanelHeader [label]="'searches.saved.label' | transloco" [floating]="!!floating" />
31090
31574
 
31091
- <ListPanel
31575
+ <ListPanel #panel
31092
31576
  [items]="paginatedSearches()"
31093
31577
  [getValue]="urlOf"
31094
31578
  [getLabel]="labelOf"
@@ -31120,7 +31604,7 @@ i0.ɵɵngDeclareClassMetadata({
31120
31604
  </ListPanel>
31121
31605
 
31122
31606
  @if(paginatedSearches().length > 0) {
31123
- <ListPanelFooter [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31607
+ <ListPanelFooter [navigationHint]="panel.keyboardFocused()" [hasMore]="hasMore()" [config]="config" (loadMore)="loadMore()" />
31124
31608
  }
31125
31609
  `
31126
31610
  }]
@@ -31555,7 +32039,11 @@ function splitPropertyId(id) {
31555
32039
  key: id.slice(separator + 1)
31556
32040
  };
31557
32041
  }
32042
+ const FIRST_CONTROL = "button:not(:disabled), textarea, input:not([type='file']), a[href]";
31558
32043
  var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
32044
+ focusOnOpen = input(false, ...ngDevMode ? [{ debugName: "focusOnOpen" }] : /* istanbul ignore next */ []);
32045
+ host = inject(ElementRef);
32046
+ destroyRef = inject(DestroyRef);
31559
32047
  principalStore = inject(PrincipalStore);
31560
32048
  appStore = inject(AppStore);
31561
32049
  client = inject(ATOMIC_CLIENT);
@@ -31568,6 +32056,13 @@ var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
31568
32056
  const { allowChangePassword = false } = this.appStore.general()?.features || {};
31569
32057
  return allowChangePassword && useCredentials && (this.principalStore.editablePartition?.() || false);
31570
32058
  }, ...ngDevMode ? [{ debugName: "allowChangePassword" }] : /* istanbul ignore next */ []);
32059
+ constructor() {
32060
+ afterNextRender(() => {
32061
+ if (!this.focusOnOpen()) return;
32062
+ const frame = requestAnimationFrame(() => focusWhenReady(() => this.host.nativeElement.querySelector(FIRST_CONTROL), "settings-account-panel", { destroyRef: this.destroyRef }));
32063
+ this.destroyRef.onDestroy(() => cancelAnimationFrame(frame));
32064
+ });
32065
+ }
31571
32066
  resetSettings() {
31572
32067
  ResetUserSettings.call(void 0, { injector: this.injector });
31573
32068
  }
@@ -31591,6 +32086,13 @@ var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
31591
32086
  type: SettingsAccountPanelComponent,
31592
32087
  isStandalone: true,
31593
32088
  selector: "settings-account-panel",
32089
+ inputs: { focusOnOpen: {
32090
+ classPropertyName: "focusOnOpen",
32091
+ publicName: "focusOnOpen",
32092
+ isSignal: true,
32093
+ isRequired: false,
32094
+ transformFunction: null
32095
+ } },
31594
32096
  providers: [provideTranslocoScope("settings")],
31595
32097
  ngImport: i0,
31596
32098
  template: `
@@ -31773,7 +32275,16 @@ i0.ɵɵngDeclareClassMetadata({
31773
32275
  </div>
31774
32276
  `
31775
32277
  }]
31776
- }]
32278
+ }],
32279
+ ctorParameters: () => [],
32280
+ propDecorators: { focusOnOpen: [{
32281
+ type: i0.Input,
32282
+ args: [{
32283
+ isSignal: true,
32284
+ alias: "focusOnOpen",
32285
+ required: false
32286
+ }]
32287
+ }] }
31777
32288
  });
31778
32289
  const THEMES$1 = [
31779
32290
  "light",
@@ -32117,6 +32628,11 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32117
32628
  }
32118
32629
  ];
32119
32630
  panel = signal(this.call.props()?.panel ?? "account", ...ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []);
32631
+ navigated = signal(false, ...ngDevMode ? [{ debugName: "navigated" }] : /* istanbul ignore next */ []);
32632
+ navigate(id) {
32633
+ this.navigated.set(true);
32634
+ this.panel.set(id);
32635
+ }
32120
32636
  duplicate = active !== void 0;
32121
32637
  constructor() {
32122
32638
  if (active) {
@@ -32176,7 +32692,7 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32176
32692
  variant="none"
32177
32693
  class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
32178
32694
  [attr.aria-current]="panel() === item.id ? 'page' : null"
32179
- (click)="panel.set(item.id)">
32695
+ (click)="navigate(item.id)">
32180
32696
  @switch (item.id) {
32181
32697
  @case ("account") {
32182
32698
  <user-icon aria-hidden="true" />
@@ -32197,7 +32713,7 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32197
32713
  <div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
32198
32714
  @switch (panel()) {
32199
32715
  @case ("account") {
32200
- <settings-account-panel class="flex min-h-0 flex-1 flex-col" />
32716
+ <settings-account-panel class="flex min-h-0 flex-1 flex-col" [focusOnOpen]="!navigated()" />
32201
32717
  }
32202
32718
  @case ("appearance") {
32203
32719
  <settings-appearance-panel />
@@ -32270,7 +32786,8 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32270
32786
  {
32271
32787
  kind: "component",
32272
32788
  type: SettingsAccountPanelComponent,
32273
- selector: "settings-account-panel"
32789
+ selector: "settings-account-panel",
32790
+ inputs: ["focusOnOpen"]
32274
32791
  },
32275
32792
  {
32276
32793
  kind: "component",
@@ -32336,7 +32853,7 @@ i0.ɵɵngDeclareClassMetadata({
32336
32853
  variant="none"
32337
32854
  class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
32338
32855
  [attr.aria-current]="panel() === item.id ? 'page' : null"
32339
- (click)="panel.set(item.id)">
32856
+ (click)="navigate(item.id)">
32340
32857
  @switch (item.id) {
32341
32858
  @case ("account") {
32342
32859
  <user-icon aria-hidden="true" />
@@ -32357,7 +32874,7 @@ i0.ɵɵngDeclareClassMetadata({
32357
32874
  <div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
32358
32875
  @switch (panel()) {
32359
32876
  @case ("account") {
32360
- <settings-account-panel class="flex min-h-0 flex-1 flex-col" />
32877
+ <settings-account-panel class="flex min-h-0 flex-1 flex-col" [focusOnOpen]="!navigated()" />
32361
32878
  }
32362
32879
  @case ("appearance") {
32363
32880
  <settings-appearance-panel />
@@ -32712,10 +33229,12 @@ const SCOPE = "session-expiry";
32712
33229
  const SCOPE_ALIAS = "sessionExpiry";
32713
33230
  const FALLBACK = {
32714
33231
  expiringTitle: "Your session is about to expire",
32715
- expiringDescription: "It expires in about {{minutes}} min. Save your work or sign in again.",
33232
+ expiringDescription: "It expires in about {{minutes}} min. Extend your session to keep it open.",
32716
33233
  expiredTitle: "Your session has expired",
32717
33234
  expiredDescription: "Sign in again to continue.",
32718
- reload: "Sign in again"
33235
+ reload: "Sign in again",
33236
+ extend: "Extend session",
33237
+ extendedTitle: "Your session has been extended"
32719
33238
  };
32720
33239
  function provideSessionExpiry(options = {}) {
32721
33240
  const warnBeforeMs = options.warnBeforeMs ?? DEFAULT_WARN_BEFORE_MS;
@@ -32753,14 +33272,36 @@ function provideSessionExpiry(options = {}) {
32753
33272
  await settled;
32754
33273
  if (!stillDue()) return;
32755
33274
  const minutes = Math.max(1, Math.ceil((expiresAt - Date.now()) / 6e4));
32756
- const [title, description] = await Promise.all([translate("expiringTitle"), translate("expiringDescription", { minutes })]);
33275
+ const [title, description, extendLabel] = await Promise.all([
33276
+ translate("expiringTitle"),
33277
+ translate("expiringDescription", { minutes }),
33278
+ translate("extend")
33279
+ ]);
32757
33280
  if (!stillDue()) return;
32758
33281
  notify.warning(title, {
32759
33282
  description,
32760
33283
  closeButton: true,
32761
- important: true
33284
+ important: true,
33285
+ action: {
33286
+ label: extendLabel,
33287
+ onClick: () => void extend()
33288
+ }
32762
33289
  });
32763
33290
  };
33291
+ const extend = async () => {
33292
+ if (options.onExtend) return options.onExtend();
33293
+ const before = expiresAt;
33294
+ try {
33295
+ await client.api.principal.get();
33296
+ } catch {
33297
+ return;
33298
+ }
33299
+ if (before !== null && expiresAt !== null && expiresAt > before) {
33300
+ notify.success(await translate("extendedTitle"), { closeButton: true });
33301
+ return;
33302
+ }
33303
+ onReconnect();
33304
+ };
32764
33305
  const check = () => {
32765
33306
  if (expiresAt === null || warned) return;
32766
33307
  const delay = expiresAt - warnBeforeMs - Date.now();
@@ -32830,6 +33371,6 @@ function provideSessionExpiry(options = {}) {
32830
33371
  const queryNameResolver = () => {
32831
33372
  return inject(AppStore).getDefaultQuery()?.name || "";
32832
33373
  };
32833
- 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, 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, 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, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, 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, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
33374
+ 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, 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, 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, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, 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, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
32834
33375
 
32835
33376
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map