@sinequa/atomic-angular 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, EnvironmentInjector, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, resource, ChangeDetectionStrategy, ViewContainerRef, numberAttribute, viewChildren, afterRenderEffect, afterEveryRender } from '@angular/core';
3
- import { BehaviorSubject, Subscription, Subject, of, catchError, map, tap, EMPTY, throwError, filter, shareReplay, from, switchMap, firstValueFrom, fromEvent, debounceTime } from 'rxjs';
3
+ import { BehaviorSubject, Subscription, Subject, of, catchError, map, tap, EMPTY, throwError, from, filter as filter$1, shareReplay, switchMap, firstValueFrom, fromEvent, debounceTime } from 'rxjs';
4
4
  import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
5
5
  import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent, CardComponent, CardHeaderComponent, CardContentComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SquareCheckIcon, SquareMinusIcon, SquareIcon, SwitchComponent, SelectOptionDirective, DialogService, XMarkIcon, InboxIcon, SparklesIcon, FileOutputIcon, TabsComponent, TabsListComponent, TabComponent, SidebarMenuComponent, SidebarMenuItemComponent, SidebarMenuButtonComponent, TooltipDirective, FrownIcon, ChevronLeftIcon, ChevronsLeftIcon, ChevronsRightIcon, ArrowUpAzIcon, ArrowDownZaIcon, ArrowUpRightFromSquareIcon, ToggleRightIcon, ToggleLeftIcon, Separator, SheetCloseDirective, ArrowLeftIcon, SheetService, DateRangePickerDirective, DatepickerDirective, FilterIcon, FilterXIcon, IconButtonComponent, HighlighterIcon, TagsIcon, SpinnerIcon, MagnifyingGlassIcon, LoadingCircleIcon, CircleCheckIcon, PopoverComponent, BellIcon, TrashIcon, BarsIcon, CardFooterComponent, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, EyeSlashIcon, EyeIcon, BookmarkIcon, PopoverContentComponent, UserIcon, FolderIcon, DialogDescriptionComponent, VerticalDividerComponent, CommentIcon, ThumbsUpIcon, ThumbsDownIcon, DropdownDirective, SearchIcon, TriangleAlertIcon, ListFilterIcon, BreakpointObserverService, TrashCanIcon, CircleXIcon, InfoCircleIcon, HorizontalDividerComponent, HistoryIcon, StarIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, EditIcon, UndoIcon, SaveIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
6
6
  import highlightWords from 'highlight-words';
7
7
  import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
8
8
  import { withDevtools } from '@angular-architects/ngrx-toolkit';
9
9
  import { signalStore, signalStoreFeature, withState, withMethods, patchState, getState, withComputed } from '@ngrx/signals';
10
- import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, getQueryParamsFromUrl, clearSessionTokens, login, info, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, fetchQuery, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
10
+ import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, clearSessionTokens, login, info, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, fetchQuery, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
11
11
  import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
12
12
  import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
13
13
  import { HttpClient, HttpParams, httpResource, HttpResponse, HttpContextToken, HttpHeaders } from '@angular/common/http';
@@ -1365,6 +1365,64 @@ class InlineWorker {
1365
1365
  }
1366
1366
  }
1367
1367
 
1368
+ /**
1369
+ * Converts a `LegacyFilter` to the structured `Filter` model, built with the
1370
+ * `filter.*` helpers of `@sinequa/atomic`. The result is assignable as-is to
1371
+ * `query.filters` / `fetchQuery({ filters })`.
1372
+ *
1373
+ * Covers the shapes produced by the aggregation components when applying
1374
+ * filters (`toFilter`, `getFiltersForTree`, `getFormValueFilter`,
1375
+ * `buildAppliedFilter`): `in` values, `and`/`or` expression trees, `between`
1376
+ * ranges, scalar leaves (`eq`/`contains`/`gte`/`lte`/…) and the operator-less
1377
+ * `{ field, value }` form (treated as `eq`).
1378
+ *
1379
+ * Returns `undefined` for an absent filter or a degenerate shape (leaf without
1380
+ * `field`/`value`, empty combinator) — consistent with the `filter.and`/`or`
1381
+ * collapsing rules and with an absent `query.filters`.
1382
+ */
1383
+ function legacyFilterToFilter(legacy) {
1384
+ if (!legacy)
1385
+ return undefined;
1386
+ const { operator, field } = legacy;
1387
+ switch (operator) {
1388
+ case "and":
1389
+ case "or": {
1390
+ const children = (legacy.filters ?? []).map((f) => legacyFilterToFilter(f));
1391
+ return operator === "and" ? filter.and(...children) : filter.or(...children);
1392
+ }
1393
+ case "not": {
1394
+ const children = (legacy.filters ?? []).map((f) => legacyFilterToFilter(f));
1395
+ return filter.not(...children);
1396
+ }
1397
+ case "in":
1398
+ return field && legacy.values?.length ? filter.in(field, legacy.values) : undefined;
1399
+ case "between":
1400
+ return field && legacy.start !== undefined && legacy.end !== undefined
1401
+ ? filter.between(field, legacy.start, legacy.end)
1402
+ : undefined;
1403
+ case "null":
1404
+ return field ? filter.isNull(field) : undefined;
1405
+ case "notnull":
1406
+ return field ? filter.isNotNull(field) : undefined;
1407
+ case "eq":
1408
+ case "neq":
1409
+ case "gt":
1410
+ case "gte":
1411
+ case "lt":
1412
+ case "lte":
1413
+ case "like":
1414
+ case "contains":
1415
+ case "regex":
1416
+ return field && legacy.value !== undefined ? filter[operator](field, legacy.value) : undefined;
1417
+ case undefined:
1418
+ // operator-less leaf ({ field, value }) — the `toFilter` form for
1419
+ // non-string values; semantically an equality
1420
+ return field && legacy.value !== undefined ? filter.eq(field, legacy.value) : undefined;
1421
+ default:
1422
+ return undefined;
1423
+ }
1424
+ }
1425
+
1368
1426
  /**
1369
1427
  * Retrieves the query name from the current route.
1370
1428
  *
@@ -3209,11 +3267,13 @@ class AggregationsService {
3209
3267
  queryParamsStore = inject(QueryParamsStore);
3210
3268
  nonSearchableColumns = inject(NON_SEARCHABLE_COLUMNS);
3211
3269
  API_URL = `${globalConfig.backendUrl}/api/v1`;
3212
- loadMore(query, aggregation, audit) {
3270
+ loadMore(query, aggregation, audit, options) {
3213
3271
  const { app } = globalConfig;
3214
3272
  // skip cached items for computation
3215
3273
  const skip = aggregation.items?.filter((i) => !i.$cached)?.length || 0;
3216
- const count = this.appStore.getAggregationCount(query.name, aggregation.name) || 10;
3274
+ // options.count carries the page size of a controlled (non-attached) query —
3275
+ // getAggregationCount only knows queries attached to the current app
3276
+ const count = options?.count ?? (this.appStore.getAggregationCount(query.name, aggregation.name) || 10);
3217
3277
  const aggregations = { [aggregation.name]: { skip, count } };
3218
3278
  const q = { ...query, action: "aggregate", aggregations };
3219
3279
  // add audit record if available
@@ -3242,7 +3302,7 @@ class AggregationsService {
3242
3302
  info("aggregation.loadMore success - data: ", response);
3243
3303
  }));
3244
3304
  }
3245
- open(query, aggregation, item) {
3305
+ open(query, aggregation, item, options) {
3246
3306
  const value = `/${item.$path}/*`;
3247
3307
  const expression = `${aggregation.column}:${escapeExpr(value)}`;
3248
3308
  const q = {
@@ -3252,7 +3312,18 @@ class AggregationsService {
3252
3312
  open: [{ expression, aggregation: aggregation.name }]
3253
3313
  };
3254
3314
  // we dont' want to use the query params (filters) when opening nodes
3255
- return this.queryService.search(q, false).pipe(map((results) => {
3315
+ // Guard-free mode calls fetchQuery directly: QueryService.search reads the shared
3316
+ // QueryParamsStore and publishes into its app-wide `result` signal, which a
3317
+ // controlled (per-instance) aggregation must not touch.
3318
+ const results$ = options?.guardFree
3319
+ ? from(fetchQuery(q)).pipe(tap((results) => {
3320
+ results.aggregations?.forEach((agg) => {
3321
+ if (agg.isTree)
3322
+ buildPathsAndLevels(agg);
3323
+ });
3324
+ }))
3325
+ : this.queryService.search(q, false);
3326
+ return results$.pipe(map((results) => {
3256
3327
  // find the node in the tree and replace it with the new one
3257
3328
  const items = results.aggregations[0].items;
3258
3329
  // if the open call returns no items, we need to update the node to indicate it has no children
@@ -3285,6 +3356,19 @@ class AggregationsService {
3285
3356
  const agg = this.getAggregation(name, column);
3286
3357
  if (!agg)
3287
3358
  return null;
3359
+ return this.enrichAggregation(agg);
3360
+ }
3361
+ /**
3362
+ * Applies the app-level customization (custom JSON display/icon/hidden/searchable,
3363
+ * non-searchable columns) and marks the `$selected` items on the given aggregation.
3364
+ *
3365
+ * @param agg - The aggregation to enrich (mutated in place and returned).
3366
+ * @param selectionValues - Controlled selection: the selected values (list) or
3367
+ * `"/A/B/*"` paths (tree). When `undefined`, the selection is derived from the
3368
+ * shared `QueryParamsStore` filters (default behavior). An empty array means
3369
+ * "nothing selected" and skips the store read entirely.
3370
+ */
3371
+ enrichAggregation(agg, selectionValues) {
3288
3372
  const { items = [], display = agg.name, icon, hidden, expandedLevel, searchable = true } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
3289
3373
  agg.display = display;
3290
3374
  agg.icon = icon;
@@ -3298,9 +3382,15 @@ class AggregationsService {
3298
3382
  agg.searchable = false;
3299
3383
  }
3300
3384
  if (agg.items) {
3301
- // get current filters for the aggregation
3302
- const filters = this.queryParamsStore.getFilter({ field: agg.column, name: agg.name });
3303
- const flattenedValues = filters ? this.flattenFilters([filters]) : [];
3385
+ let flattenedValues;
3386
+ if (selectionValues !== undefined) {
3387
+ flattenedValues = selectionValues;
3388
+ }
3389
+ else {
3390
+ // get current filters for the aggregation
3391
+ const filters = this.queryParamsStore.getFilter({ field: agg.column, name: agg.name });
3392
+ flattenedValues = filters ? this.flattenFilters([filters]) : [];
3393
+ }
3304
3394
  if (agg.isTree) {
3305
3395
  const selectedAgg = agg.items.map((item) => this.selectNode(item, flattenedValues));
3306
3396
  const openedAgg = selectedAgg.map((item) => this.openParentNodes(item, flattenedValues));
@@ -4201,7 +4291,7 @@ class NavigationService {
4201
4291
  *
4202
4292
  * @type Observable<RouterEvent>
4203
4293
  */
4204
- navigationEnd$ = this.router.events.pipe(map((event) => event), filter((event) => event instanceof NavigationEnd), tap((event) => {
4294
+ navigationEnd$ = this.router.events.pipe(map((event) => event), filter$1((event) => event instanceof NavigationEnd), tap((event) => {
4205
4295
  const url = event.url.slice(1).split("?")[0]; // Extract route name
4206
4296
  if (url && url !== this.urlAfterNavigation) {
4207
4297
  this.auditService.notifyRouteChange(url);
@@ -5741,9 +5831,7 @@ class CollectionsDialog {
5741
5831
  closed = output();
5742
5832
  createInputElement = viewChild("createInput", ...(ngDevMode ? [{ debugName: "createInputElement" }] : []));
5743
5833
  dialogElement = viewChild(DialogComponent, ...(ngDevMode ? [{ debugName: "dialogElement" }] : []));
5744
- queryParamStore = inject(QueryParamsStore);
5745
5834
  userSettingsStore = inject(UserSettingsStore);
5746
- collection = signal(undefined, ...(ngDevMode ? [{ debugName: "collection" }] : []));
5747
5835
  article = signal([], ...(ngDevMode ? [{ debugName: "article" }] : []));
5748
5836
  newCollectionName = signal("", ...(ngDevMode ? [{ debugName: "newCollectionName" }] : []));
5749
5837
  creating = signal(false, ...(ngDevMode ? [{ debugName: "creating" }] : []));
@@ -5760,8 +5848,6 @@ class CollectionsDialog {
5760
5848
  this.article.set([article]);
5761
5849
  else
5762
5850
  this.article.set(article);
5763
- const { basket } = getState(this.queryParamStore);
5764
- this.collection.set(basket);
5765
5851
  this.dialogElement()?.showModal();
5766
5852
  }
5767
5853
  containsArticleByName(collectionName) {
@@ -5810,6 +5896,7 @@ class CollectionsDialog {
5810
5896
  // remove it
5811
5897
  const articleIds = this.article().map((a) => a.id);
5812
5898
  collection.ids = collection.ids?.filter((id) => !articleIds.includes(id)) ?? [];
5899
+ this.removedFromCollection.set(true);
5813
5900
  }
5814
5901
  else {
5815
5902
  // add it
@@ -5821,8 +5908,6 @@ class CollectionsDialog {
5821
5908
  }
5822
5909
  try {
5823
5910
  await this.userSettingsStore.updateBasket(collection, collectionIndex);
5824
- const currentCollection = this.collection();
5825
- this.removedFromCollection.set(!!currentCollection && !this.containsArticleByName(currentCollection));
5826
5911
  }
5827
5912
  catch (e) {
5828
5913
  error("Error updating collection:", e);
@@ -6493,6 +6578,43 @@ function injectRouteNavigation(path, showCount) {
6493
6578
  return { tabs, currentPath, searchText };
6494
6579
  }
6495
6580
 
6581
+ /**
6582
+ * Marker directive: project a custom template for the content/label region of
6583
+ * each aggregation item. Put it on an `<ng-template>` inside `<aggregation-list>`
6584
+ * or `<aggregation-tree>`:
6585
+ *
6586
+ * ```html
6587
+ * <aggregation-list name="Languages" column="language" [aggregation]="agg">
6588
+ * <ng-template aggregationItem let-item let-count="count" let-selected="selected">
6589
+ * <span>{{ item.display }}</span>
6590
+ * <span class="badge">{{ count }}</span>
6591
+ * </ng-template>
6592
+ * </aggregation-list>
6593
+ * ```
6594
+ *
6595
+ * When no template is projected, the component renders its default row content.
6596
+ * The `let-*` variables are typed under `strictTemplates` via
6597
+ * {@link AggregationItemDirective.ngTemplateContextGuard}; the host must import
6598
+ * this directive in its component `imports`.
6599
+ */
6600
+ class AggregationItemDirective {
6601
+ template = inject(TemplateRef);
6602
+ /**
6603
+ * Narrows the `let-*` of `<ng-template aggregationItem>` under `strictTemplates`.
6604
+ * Tree-only fields (`level`/`expanded`/`hasChildren`) are typed as optional; a tree
6605
+ * template can re-annotate them via {@link AggregationTreeItemContext} if desired.
6606
+ */
6607
+ static ngTemplateContextGuard(_dir, ctx) {
6608
+ return true;
6609
+ }
6610
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
6611
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.25", type: AggregationItemDirective, isStandalone: true, selector: "[aggregationItem]", ngImport: i0 });
6612
+ }
6613
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationItemDirective, decorators: [{
6614
+ type: Directive,
6615
+ args: [{ selector: "[aggregationItem]" }]
6616
+ }] });
6617
+
6496
6618
  /**
6497
6619
  * Directive to mark a child element for special processing.
6498
6620
  * This can be used to apply specific styles or behaviors to child elements.
@@ -8917,12 +9039,98 @@ function injectAggregationBase(refs) {
8917
9039
  const aggregationsService = inject(AggregationsService);
8918
9040
  const injector = inject(Injector);
8919
9041
  const destroyRef = inject(DestroyRef);
9042
+ /* controlled mode — each axis is controlled when its input is provided.
9043
+ Writes (store/URL/sessionStorage) are gated by the global `isControlled` flag. */
9044
+ const isQueryControlled = computed(() => refs.query?.() !== undefined, ...(ngDevMode ? [{ debugName: "isQueryControlled" }] : []));
9045
+ const isDataControlled = computed(() => refs.aggregation?.() !== undefined, ...(ngDevMode ? [{ debugName: "isDataControlled" }] : []));
9046
+ const isSelectionControlled = computed(() => refs.selection?.() !== undefined, ...(ngDevMode ? [{ debugName: "isSelectionControlled" }] : []));
9047
+ const isControlled = computed(() => isQueryControlled() || isDataControlled() || isSelectionControlled(), ...(ngDevMode ? [{ debugName: "isControlled" }] : []));
9048
+ /** The query the aggregation's own operations run against. */
9049
+ function resolvedQuery() {
9050
+ return refs.query?.() ?? queryParamsStore.getQuery();
9051
+ }
9052
+ /**
9053
+ * Load-more page size when the query axis is controlled: the host's query
9054
+ * carries its own aggregation counts (`query.aggregations[name].count`), since
9055
+ * `AppStore.getAggregationCount` only knows queries attached to the current app.
9056
+ * Returns `undefined` in default mode — the service then resolves the count
9057
+ * from the app configuration, as today.
9058
+ */
9059
+ function resolvedAggregationCount(agg) {
9060
+ const q = refs.query?.();
9061
+ if (!q)
9062
+ return undefined;
9063
+ const aggregations = q.aggregations;
9064
+ if (aggregations && !Array.isArray(aggregations)) {
9065
+ return aggregations[agg.name]?.count ?? 10;
9066
+ }
9067
+ return 10;
9068
+ }
9069
+ /**
9070
+ * Local aggregation state in controlled mode, seeded from the `aggregation`
9071
+ * input (or the shared store when only the query/selection axis is controlled).
9072
+ * `loadMore`/`open` write their results here instead of the shared store.
9073
+ *
9074
+ * The seed is deep-cloned: items are mutated in place (`$selected`, `$opened`),
9075
+ * so working on the host's (or the store's) objects would leak state across
9076
+ * instances and mutate the host's data. Any reference change of the input
9077
+ * reseeds the signal and discards local enrichments (loaded pages, opened nodes).
9078
+ */
9079
+ const controlledAggregation = linkedSignal(() => {
9080
+ if (!isControlled())
9081
+ return null;
9082
+ const source = refs.aggregation?.() ??
9083
+ (getState(aggregationsStore), aggregationsService.getAggregation(refs.name() ?? "", refs.column()));
9084
+ return source ? structuredClone(source) : null;
9085
+ }, ...(ngDevMode ? [{ debugName: "controlledAggregation" }] : []));
8920
9086
  const debouncedSearchText = debouncedSignal(refs.searchText, 300);
8921
9087
  const normalizedSearchText = computed(() => debouncedSearchText()
8922
9088
  .normalize("NFD")
8923
9089
  .replace(/[̀-ͯ]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
8924
9090
  const suggests = signal([], ...(ngDevMode ? [{ debugName: "suggests" }] : []));
9091
+ /**
9092
+ * The selected values (list) or `"/A/B/*"` paths (tree) in controlled mode.
9093
+ * `undefined` in default mode — the selection is then derived from the shared
9094
+ * `QueryParamsStore` filters. In controlled mode without a `selection` input,
9095
+ * this is an empty array: the store filters belong to the shared query, not to
9096
+ * the host's, and must never leak into a controlled instance.
9097
+ */
9098
+ const selectionValues = computed(() => {
9099
+ const sel = refs.selection?.();
9100
+ if (sel) {
9101
+ switch (sel.kind) {
9102
+ case "values":
9103
+ return sel.values;
9104
+ case "paths":
9105
+ return sel.paths;
9106
+ case "option":
9107
+ case "range":
9108
+ return [];
9109
+ }
9110
+ }
9111
+ return isControlled() ? [] : undefined;
9112
+ }, ...(ngDevMode ? [{ debugName: "selectionValues" }] : []));
9113
+ /** The controlled selection expressed in the shape `getFilter()` returns. */
9114
+ const selectionAsFilter = computed(() => {
9115
+ const sel = refs.selection?.();
9116
+ return sel?.kind === "values" && sel.values.length ? { values: sel.values } : null;
9117
+ }, ...(ngDevMode ? [{ debugName: "selectionAsFilter" }] : []));
8925
9118
  const _filterCount = computed(() => {
9119
+ if (isControlled()) {
9120
+ const sel = refs.selection?.();
9121
+ if (!sel)
9122
+ return 0;
9123
+ switch (sel.kind) {
9124
+ case "values":
9125
+ return sel.values.length;
9126
+ case "paths":
9127
+ return sel.paths.length;
9128
+ case "option":
9129
+ return sel.option ? 1 : 0;
9130
+ case "range":
9131
+ return sel.from || sel.to ? 1 : 0;
9132
+ }
9133
+ }
8926
9134
  getState(aggregationsStore);
8927
9135
  const { count = 0 } = queryParamsStore.getFilter({ field: refs.column() ?? undefined, name: refs.name() ?? undefined }) || {};
8928
9136
  return count;
@@ -8945,7 +9153,7 @@ function injectAggregationBase(refs) {
8945
9153
  .pipe(takeUntilDestroyed(destroyRef), switchMap(({ text, column }) => {
8946
9154
  if (text === "" || column === null)
8947
9155
  return of([]);
8948
- const q = queryParamsStore.getQuery();
9156
+ const q = resolvedQuery();
8949
9157
  return from(withFetch(() => fetchSuggestField(normalizedSearchText(), [column || ""], q), injector));
8950
9158
  }))
8951
9159
  .subscribe((result) => suggests.set(result || []));
@@ -8964,7 +9172,9 @@ function injectAggregationBase(refs) {
8964
9172
  function addCurrentFiltersToItems(aggregation) {
8965
9173
  const currentItems = aggregation?.items || [];
8966
9174
  if (!aggregation?.isTree && !aggregation?.isDistribution) {
8967
- const currentFilters = queryParamsStore.getFilter({ field: aggregation?.column, name: aggregation?.name });
9175
+ const currentFilters = isControlled()
9176
+ ? selectionAsFilter()
9177
+ : queryParamsStore.getFilter({ field: aggregation?.column, name: aggregation?.name });
8968
9178
  if (currentFilters) {
8969
9179
  if (Array.isArray(currentFilters.filters) && currentFilters.filters.length) {
8970
9180
  currentFilters.filters.forEach((filter) => {
@@ -9003,34 +9213,45 @@ function injectAggregationBase(refs) {
9003
9213
  }
9004
9214
  return currentItems;
9005
9215
  }
9006
- function applyFilters(appliedFilters, agg, clearFn) {
9216
+ /**
9217
+ * Aggregates the applied item filters into the single `LegacyFilter` that
9218
+ * `applyFilters` writes to the store. Also used by the controlled mode to
9219
+ * build the emitted structured filter, so both modes stay strictly
9220
+ * consistent. Returns `undefined` when nothing is applied (the clear case).
9221
+ */
9222
+ function buildAppliedFilter(appliedFilters, agg) {
9007
9223
  const { name: aggName, column: field } = agg;
9008
- const syncUrl = refs.syncUrl?.() ?? true;
9009
9224
  if (appliedFilters.length > 1) {
9010
9225
  const display = appliedFilters[0].display;
9011
9226
  if (agg.isDistribution) {
9012
- queryParamsStore.updateFilter({
9227
+ return {
9013
9228
  operator: "or",
9014
9229
  filters: appliedFilters,
9015
9230
  name: aggName,
9016
9231
  field,
9017
9232
  display,
9018
- }, undefined, { syncUrl });
9019
- }
9020
- else {
9021
- const values = appliedFilters.map((f) => f.value);
9022
- queryParamsStore.updateFilter({
9023
- operator: "in",
9024
- name: aggName,
9025
- field,
9026
- values,
9027
- display,
9028
- filters: appliedFilters,
9029
- }, undefined, { syncUrl });
9233
+ };
9030
9234
  }
9235
+ const values = appliedFilters.map((f) => f.value);
9236
+ return {
9237
+ operator: "in",
9238
+ name: aggName,
9239
+ field,
9240
+ values,
9241
+ display,
9242
+ filters: appliedFilters,
9243
+ };
9244
+ }
9245
+ if (appliedFilters.length === 1) {
9246
+ return appliedFilters[0];
9031
9247
  }
9032
- else if (appliedFilters.length === 1) {
9033
- queryParamsStore.updateFilter(appliedFilters[0], undefined, { syncUrl });
9248
+ return undefined;
9249
+ }
9250
+ function applyFilters(appliedFilters, agg, clearFn) {
9251
+ const syncUrl = refs.syncUrl?.() ?? true;
9252
+ const combined = buildAppliedFilter(appliedFilters, agg);
9253
+ if (combined) {
9254
+ queryParamsStore.updateFilter(combined, undefined, { syncUrl });
9034
9255
  }
9035
9256
  else {
9036
9257
  clearFn();
@@ -9043,6 +9264,14 @@ function injectAggregationBase(refs) {
9043
9264
  aggregationsService,
9044
9265
  injector,
9045
9266
  destroyRef,
9267
+ isControlled,
9268
+ isQueryControlled,
9269
+ isDataControlled,
9270
+ isSelectionControlled,
9271
+ resolvedQuery,
9272
+ resolvedAggregationCount,
9273
+ controlledAggregation,
9274
+ selectionValues,
9046
9275
  debouncedSearchText,
9047
9276
  normalizedSearchText,
9048
9277
  suggests,
@@ -9053,6 +9282,7 @@ function injectAggregationBase(refs) {
9053
9282
  clearSearch,
9054
9283
  selectItems,
9055
9284
  addCurrentFiltersToItems,
9285
+ buildAppliedFilter,
9056
9286
  applyFilters,
9057
9287
  };
9058
9288
  }
@@ -9100,12 +9330,56 @@ class AggregationDateComponent {
9100
9330
  * @default true
9101
9331
  */
9102
9332
  syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
9333
+ /**
9334
+ * Controlled query. When set, the component's own operations run against this
9335
+ * query instead of reading the shared `QueryParamsStore`. Use to target a query
9336
+ * not tied to the current app, without mutating the shared store or the URL.
9337
+ *
9338
+ * @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
9339
+ */
9340
+ controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
9341
+ /**
9342
+ * Controlled data. When set, the component renders this aggregation object
9343
+ * instead of reading it from the shared `AggregationsStore`.
9344
+ *
9345
+ * @default undefined — read `AggregationsStore` (current behavior)
9346
+ */
9347
+ controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
9348
+ /**
9349
+ * Controlled selection. When set, the date form is derived from this value
9350
+ * instead of the shared `QueryParamsStore` filters:
9351
+ * `{ kind: "option", option }` re-selects the date option whose aggregation
9352
+ * item `value` matches (the real filter value, never the `display`);
9353
+ * `{ kind: "range", from, to }` fills the custom range from the bounds.
9354
+ * The two kinds are mutually exclusive — a date selection is either a preset
9355
+ * or a custom range.
9356
+ *
9357
+ * @default undefined — derive from `QueryParamsStore` filters (current behavior)
9358
+ */
9359
+ controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
9103
9360
  title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
9104
9361
  displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
9105
9362
  /* outputs */
9106
9363
  onSelect = output();
9107
9364
  onApply = output();
9108
9365
  onClear = output();
9366
+ /**
9367
+ * Emits the new selection (`{ kind: "option" }` preset or `{ kind: "range" }`
9368
+ * custom range) in controlled mode. The counterpart of the `[selection]` input,
9369
+ * emitted before `filtersChange`. Date is a radio list, so this fires **live at
9370
+ * selection time** — picking a preset radio, or confirming a date in the custom
9371
+ * range picker — not on an apply click (the custom-range radio alone, with no
9372
+ * date picked, emits nothing). Also emitted on clear. In controlled mode this is
9373
+ * the only way changes surface: nothing is written to the shared stores or the URL.
9374
+ */
9375
+ selectionChange = output();
9376
+ /**
9377
+ * Emits the selection as a structured `Filter` (the exact filter the default
9378
+ * mode would write to the store), ready for `fetchQuery({ filters })`.
9379
+ * `undefined` on clear. Controlled mode only — emitted right after
9380
+ * `selectionChange` (see its live-emission timing).
9381
+ */
9382
+ filtersChange = output();
9109
9383
  /* collapse state */
9110
9384
  isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
9111
9385
  expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
@@ -9120,8 +9394,12 @@ class AggregationDateComponent {
9120
9394
  searchInput: this.searchInput,
9121
9395
  expanded: this.expanded,
9122
9396
  syncUrl: this.syncUrl,
9397
+ query: this.controlledQuery,
9398
+ aggregation: this.controlledAggregation,
9399
+ selection: this.controlledSelection,
9123
9400
  });
9124
9401
  /* spread from base */
9402
+ isControlled = this.base.isControlled;
9125
9403
  aggregationsService = this.base.aggregationsService;
9126
9404
  queryParamsStore = this.base.queryParamsStore;
9127
9405
  hasFilters = this.base.hasFilters;
@@ -9132,7 +9410,24 @@ class AggregationDateComponent {
9132
9410
  /* state */
9133
9411
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
9134
9412
  validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
9413
+ /**
9414
+ * Guards live emission while the form is seeded programmatically from the
9415
+ * `[selection]` input (see the seeding effect below). `form.setValue`/`patchValue`
9416
+ * emit `valueChanges` synchronously; without this flag a controlled host that
9417
+ * re-feeds `[selection]` from `selectionChange` would loop indefinitely.
9418
+ */
9419
+ seedingForm = false;
9135
9420
  aggregation = computed(() => {
9421
+ if (this.isControlled()) {
9422
+ const controlled = this.base.controlledAggregation();
9423
+ if (!controlled)
9424
+ return null;
9425
+ const agg = this.aggregationsService.enrichAggregation(controlled, []);
9426
+ return {
9427
+ ...agg,
9428
+ items: agg.items?.filter((item) => item.display !== "custom-range") ?? []
9429
+ };
9430
+ }
9136
9431
  const name = this.name();
9137
9432
  if (name !== null) {
9138
9433
  const agg = this.aggregationsService.processAggregation(name, this.column());
@@ -9168,28 +9463,75 @@ class AggregationDateComponent {
9168
9463
  this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
9169
9464
  this.lang.set(lang);
9170
9465
  });
9171
- // apply current date filter from queryParamsStore
9466
+ // apply current date filter from the selection input (controlled) or the queryParamsStore
9172
9467
  effect(() => {
9173
9468
  const agg = this.aggregation();
9174
- if (agg) {
9469
+ if (!agg)
9470
+ return;
9471
+ // seeding the form from an input must not re-emit through valueChanges
9472
+ this.seedingForm = true;
9473
+ try {
9474
+ if (this.isControlled()) {
9475
+ this.updateFormFromSelection(this.controlledSelection());
9476
+ return;
9477
+ }
9175
9478
  this.updateForm(this.queryParamsStore.getFilter({
9176
9479
  field: agg.column,
9177
9480
  name: agg.name
9178
9481
  }));
9179
9482
  }
9483
+ finally {
9484
+ this.seedingForm = false;
9485
+ }
9180
9486
  });
9181
9487
  this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((changes) => {
9182
9488
  this.validSelection.set(!!changes.option &&
9183
9489
  (changes.option !== "custom-range" || changes.customRange?.from !== null || changes.customRange?.to !== null));
9490
+ // controlled date is a radio list: emit the selection live, without waiting
9491
+ // for an apply click. Presets are complete as soon as the radio is picked;
9492
+ // the custom range is emitted only once a real date is chosen (see
9493
+ // `onRangeSelected`), never on toggling the "custom-range" radio, so a stale
9494
+ // range left in the form is not surfaced.
9495
+ if (!this.seedingForm &&
9496
+ this.isControlled() &&
9497
+ changes.option &&
9498
+ changes.option !== "custom-range") {
9499
+ try {
9500
+ this.emitCurrentSelection();
9501
+ }
9502
+ catch (err) {
9503
+ warn("Error emitting date selection:", err);
9504
+ }
9505
+ }
9184
9506
  });
9185
9507
  }
9508
+ /**
9509
+ * Emits the current form selection through `selectionChange` then `filtersChange`
9510
+ * (controlled mode). Shared by `apply()` and the live emission paths so both
9511
+ * produce identical payloads. Throws if the form is not a valid selection
9512
+ * (`getFormValueFilter`) — callers decide how to surface it.
9513
+ */
9514
+ emitCurrentSelection() {
9515
+ const filter = this.getFormValueFilter();
9516
+ const option = this.form.value.option;
9517
+ // a date selection is either a preset (the item's value — the real filter
9518
+ // value, robust across days) or a custom range (its bounds) — never both
9519
+ this.selectionChange.emit(option && option !== "custom-range"
9520
+ ? { kind: "option", option: this.optionToItemValue(option) ?? option }
9521
+ : this.toRangeSelection(filter));
9522
+ this.filtersChange.emit(legacyFilterToFilter(filter));
9523
+ }
9186
9524
  select() {
9187
9525
  this.selection.set(true);
9188
9526
  }
9189
9527
  apply() {
9190
9528
  try {
9191
- const filter = this.getFormValueFilter();
9192
- this.queryParamsStore.updateFilter(filter, undefined, { syncUrl: this.syncUrl() });
9529
+ if (this.isControlled()) {
9530
+ this.emitCurrentSelection();
9531
+ }
9532
+ else {
9533
+ this.queryParamsStore.updateFilter(this.getFormValueFilter(), undefined, { syncUrl: this.syncUrl() });
9534
+ }
9193
9535
  this.selection.set(false);
9194
9536
  this.onApply.emit();
9195
9537
  }
@@ -9210,7 +9552,13 @@ class AggregationDateComponent {
9210
9552
  });
9211
9553
  const agg = this.aggregation();
9212
9554
  if (agg && notify) {
9213
- this.queryParamsStore.removeFilterByName(agg.name, undefined, { syncUrl: this.syncUrl() });
9555
+ if (this.isControlled()) {
9556
+ this.selectionChange.emit({ kind: "range" });
9557
+ this.filtersChange.emit(undefined);
9558
+ }
9559
+ else {
9560
+ this.queryParamsStore.removeFilterByName(agg.name, undefined, { syncUrl: this.syncUrl() });
9561
+ }
9214
9562
  this.onClear.emit();
9215
9563
  }
9216
9564
  }
@@ -9228,12 +9576,22 @@ class AggregationDateComponent {
9228
9576
  this.dateRangeDialog()?.open();
9229
9577
  }
9230
9578
  onRangeSelected(range) {
9231
- console.log("Selected range:", range);
9232
9579
  this.form.patchValue({
9233
9580
  option: "custom-range",
9234
9581
  customRange: { from: range.from, to: range.to }
9235
9582
  });
9236
9583
  this.select();
9584
+ // a real date was picked in the picker: emit the custom range live in
9585
+ // controlled mode (the only place a custom range is emitted — never on
9586
+ // toggling the "custom-range" radio)
9587
+ if (this.isControlled()) {
9588
+ try {
9589
+ this.emitCurrentSelection();
9590
+ }
9591
+ catch (err) {
9592
+ warn("Error emitting date selection:", err);
9593
+ }
9594
+ }
9237
9595
  }
9238
9596
  updateForm(filter) {
9239
9597
  if (!filter) {
@@ -9273,6 +9631,90 @@ class AggregationDateComponent {
9273
9631
  };
9274
9632
  this.form.setValue(formValue);
9275
9633
  }
9634
+ /**
9635
+ * Derives the form from a controlled selection.
9636
+ * `{ kind: "option" }` re-selects the date option whose item `value` matches —
9637
+ * no operator/value matching, so presets whose bounds are computed relative to
9638
+ * today survive a round-trip across days; a value unknown to the aggregation
9639
+ * resets the form. `{ kind: "range" }` fills the custom range from the bounds.
9640
+ */
9641
+ updateFormFromSelection(sel) {
9642
+ if (sel?.kind === "option") {
9643
+ const code = this.itemValueToOption(sel.option);
9644
+ if (code && code !== "custom-range") {
9645
+ this.form.setValue({ option: code, customRange: { from: null, to: null } });
9646
+ }
9647
+ else {
9648
+ this.clear(false);
9649
+ }
9650
+ return;
9651
+ }
9652
+ this.updateForm(this.rangeToLegacyFilter(sel));
9653
+ }
9654
+ /**
9655
+ * The date option codes (what `form.option` holds) are the items' `display`
9656
+ * with a fallback on their `value` (see `translateAggregationToDateOptions`).
9657
+ * The controlled selection persists the item `value` — the real filter value —
9658
+ * so these two helpers convert between the option code and the item value.
9659
+ */
9660
+ optionToItemValue(option) {
9661
+ const item = this.items().find((it) => (it.display || String(it.value)) === option);
9662
+ return item ? String(item.value) : undefined;
9663
+ }
9664
+ itemValueToOption(value) {
9665
+ const item = this.items().find((it) => String(it.value) === value);
9666
+ return item ? item.display || String(item.value) : undefined;
9667
+ }
9668
+ /**
9669
+ * Converts a controlled `{ kind: "range" }` selection to the `LegacyFilter`
9670
+ * shape `updateForm` expects (which only reads `operator`/`value`/`filters`).
9671
+ * Returns `undefined` for an empty or non-range selection, which resets the form.
9672
+ */
9673
+ rangeToLegacyFilter(sel) {
9674
+ if (!sel || sel.kind !== "range")
9675
+ return undefined;
9676
+ const { from, to } = sel;
9677
+ if (from && to) {
9678
+ return {
9679
+ operator: "and",
9680
+ filters: [
9681
+ { operator: "gte", value: from },
9682
+ { operator: "lte", value: to }
9683
+ ]
9684
+ };
9685
+ }
9686
+ if (from)
9687
+ return { operator: "gte", value: from };
9688
+ if (to)
9689
+ return { operator: "lte", value: to };
9690
+ return undefined;
9691
+ }
9692
+ /**
9693
+ * Converts the applied `LegacyFilter` to a `{ kind: "range" }` selection
9694
+ * (the shape emitted through `selectionChange` in controlled mode).
9695
+ * Mirrors the operator handling of `updateForm`. Presets whose operator is not
9696
+ * a range operator (e.g. `eq`) collapse to `from = to = value`.
9697
+ */
9698
+ toRangeSelection(filter) {
9699
+ switch (filter.operator) {
9700
+ case "gte":
9701
+ return { kind: "range", from: filter.value };
9702
+ case "lte":
9703
+ return { kind: "range", to: filter.value };
9704
+ case "between":
9705
+ return { kind: "range", from: filter.start, to: filter.end };
9706
+ case "and": {
9707
+ const filters = filter.filters ?? [];
9708
+ return {
9709
+ kind: "range",
9710
+ from: filters.find((f) => f.operator === "gte")?.value,
9711
+ to: filters.find((f) => f.operator === "lte")?.value
9712
+ };
9713
+ }
9714
+ default:
9715
+ return { kind: "range", from: filter.value, to: filter.value };
9716
+ }
9717
+ }
9276
9718
  toEndOfDay(dateStr) {
9277
9719
  return `${new Date(dateStr).toLocaleDateString("en-CA", options)} 23:59:59`;
9278
9720
  }
@@ -9348,7 +9790,7 @@ class AggregationDateComponent {
9348
9790
  throw new Error("filters.filterInvalid");
9349
9791
  }
9350
9792
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9351
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationDateComponent, isStandalone: true, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "dateRangeDialog", first: true, predicate: AggregationDateRangeDialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
9793
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationDateComponent, isStandalone: true, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange", searchText: "searchTextChange" }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "dateRangeDialog", first: true, predicate: AggregationDateRangeDialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
9352
9794
  }
9353
9795
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationDateComponent, decorators: [{
9354
9796
  type: Component,
@@ -9366,8 +9808,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9366
9808
  IconButtonComponent
9367
9809
  ], host: {
9368
9810
  class: "@container"
9369
- }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
9370
- }], ctorParameters: () => [], propDecorators: { dateRangeDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateRangeDialogComponent), { isSignal: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
9811
+ }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
9812
+ }], ctorParameters: () => [], propDecorators: { dateRangeDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateRangeDialogComponent), { isSignal: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
9371
9813
 
9372
9814
  /**
9373
9815
  * Component that allows users to select a date or a date range for filtering search results.
@@ -9376,7 +9818,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9376
9818
  */
9377
9819
  class DateComponent extends AggregationDateComponent {
9378
9820
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DateComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
9379
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
9821
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
9380
9822
  }
9381
9823
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DateComponent, decorators: [{
9382
9824
  type: Component,
@@ -9393,7 +9835,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9393
9835
  FilterXIcon
9394
9836
  ], host: {
9395
9837
  class: "@container"
9396
- }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
9838
+ }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
9397
9839
  }] });
9398
9840
 
9399
9841
  class ArticleEntities {
@@ -13726,6 +14168,14 @@ class AggregationTreeComponent {
13726
14168
  collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
13727
14169
  searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
13728
14170
  showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14171
+ /**
14172
+ * Whether to render the per-node checkbox. When `false`, the checkbox is hidden
14173
+ * but selection still toggles on row click — pair it with a custom `aggregationItem`
14174
+ * template that reflects the `selected` context to convey selection.
14175
+ *
14176
+ * @default true
14177
+ */
14178
+ showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
13729
14179
  expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: (v) => {
13730
14180
  const n = numberAttribute(v);
13731
14181
  return Number.isNaN(n) ? undefined : n;
@@ -13742,13 +14192,63 @@ class AggregationTreeComponent {
13742
14192
  * @default true
13743
14193
  */
13744
14194
  syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14195
+ /**
14196
+ * Controlled query. When set, the component's own operations (load-more,
14197
+ * search-within-facet, tree-expand) run against this query instead of reading
14198
+ * the shared `QueryParamsStore`. Use to target a query not tied to the current
14199
+ * app, without mutating the shared store or the URL.
14200
+ *
14201
+ * @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
14202
+ */
14203
+ controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
14204
+ /**
14205
+ * Controlled data. When set, the component renders this aggregation object
14206
+ * instead of reading it from the shared `AggregationsStore`.
14207
+ *
14208
+ * @default undefined — read `AggregationsStore` (current behavior)
14209
+ */
14210
+ controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
14211
+ /**
14212
+ * Controlled selection. When set, item `$selected` state is derived from this
14213
+ * value (`paths` in the `"/A/B/*"` format) instead of the shared
14214
+ * `QueryParamsStore` filters.
14215
+ *
14216
+ * @default undefined — derive from `QueryParamsStore` filters (current behavior)
14217
+ */
14218
+ controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
13745
14219
  /* outputs */
13746
14220
  onSelect = output();
13747
14221
  onApply = output();
13748
14222
  onClear = output();
14223
+ /**
14224
+ * Emits the new selection (`"/A/B/*"` paths) when the user applies or clears
14225
+ * filters in controlled mode. The counterpart of the `[selection]` input —
14226
+ * emitted before `filtersChange` and `onApply`/`onClear`. In controlled mode
14227
+ * this is the only way changes surface: nothing is written to the shared
14228
+ * stores or the URL.
14229
+ */
14230
+ selectionChange = output();
14231
+ /**
14232
+ * Emits the applied selection as a structured `Filter` (the exact filter the
14233
+ * default mode would write to the store), ready for `fetchQuery({ filters })`.
14234
+ * `undefined` on clear. Controlled mode only — emitted after `selectionChange`,
14235
+ * before `onApply`/`onClear`.
14236
+ */
14237
+ filtersChange = output();
13749
14238
  /* view queries */
13750
14239
  virtualItems = viewChildren("virtualItem", ...(ngDevMode ? [{ debugName: "virtualItems" }] : []));
13751
14240
  scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
14241
+ /* optional host-provided template for the node content/label region */
14242
+ itemDirective = contentChild(AggregationItemDirective, ...(ngDevMode ? [{ debugName: "itemDirective" }] : []));
14243
+ /**
14244
+ * Item content template relayed by the `<Aggregation>` wrapper (which cannot
14245
+ * project content into this component). Also usable directly as a `TemplateRef`
14246
+ * input. A directive projected straight onto this component takes precedence.
14247
+ */
14248
+ itemTemplateInput = input(undefined, ...(ngDevMode ? [{ debugName: "itemTemplateInput", alias: "itemTemplate" }] : [{
14249
+ alias: "itemTemplate",
14250
+ }]));
14251
+ resolvedItemTemplate = computed(() => this.itemDirective()?.template ?? this.itemTemplateInput(), ...(ngDevMode ? [{ debugName: "resolvedItemTemplate" }] : []));
13752
14252
  /* selection state */
13753
14253
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
13754
14254
  isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
@@ -13760,8 +14260,12 @@ class AggregationTreeComponent {
13760
14260
  column: this.column,
13761
14261
  searchText: this.searchText,
13762
14262
  syncUrl: this.syncUrl,
14263
+ query: this.controlledQuery,
14264
+ aggregation: this.controlledAggregation,
14265
+ selection: this.controlledSelection,
13763
14266
  });
13764
14267
  /* spread from base */
14268
+ isControlled = this.base.isControlled;
13765
14269
  aggregationsStore = this.base.aggregationsStore;
13766
14270
  queryParamsStore = this.base.queryParamsStore;
13767
14271
  appStore = this.base.appStore;
@@ -13788,6 +14292,21 @@ class AggregationTreeComponent {
13788
14292
  #measureItems = effect(() => this.virtualItems().forEach((el) => this.virtualizer.measureElement(el.nativeElement)), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
13789
14293
  linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
13790
14294
  aggregation = computed(() => {
14295
+ if (this.isControlled()) {
14296
+ const agg = this.base.controlledAggregation();
14297
+ if (!agg)
14298
+ return null;
14299
+ if (!agg.isTree) {
14300
+ error("The aggregation tree component does not support list aggregations. Please use the <Aggregation /> component instead.");
14301
+ }
14302
+ const enriched = this.aggregationsService.enrichAggregation(agg, this.base.selectionValues() ?? []);
14303
+ const expandedLevel = this.expandedLevel() ?? enriched.expandedLevel;
14304
+ if (expandedLevel) {
14305
+ this.expandItems(enriched.items, expandedLevel);
14306
+ }
14307
+ enriched.searchable = this.searchable() ?? enriched.searchable;
14308
+ return enriched;
14309
+ }
13791
14310
  getState(this.aggregationsStore);
13792
14311
  const name = this.name();
13793
14312
  const column = this.column();
@@ -13818,7 +14337,10 @@ class AggregationTreeComponent {
13818
14337
  else if (agg?.items) {
13819
14338
  res = this.addCurrentFiltersToItems();
13820
14339
  }
13821
- const sessionAggItem = sessionStorage.getItem(`agg-${agg?.column}`);
14340
+ // Controlled mode never touches sessionStorage: selection persistence is the
14341
+ // host's responsibility via the `selection` input, and the `agg-${column}` key
14342
+ // is shared with any default-mode instance on the same column.
14343
+ const sessionAggItem = this.isControlled() ? null : sessionStorage.getItem(`agg-${agg?.column}`);
13822
14344
  const sessionAgg = JSON.parse(sessionAggItem || "[]");
13823
14345
  return this.processAggregations(sessionAgg.length ? this.setSelected(res, sessionAgg) : res);
13824
14346
  }, ...(ngDevMode ? [{ debugName: "items" }] : []));
@@ -13842,13 +14364,16 @@ class AggregationTreeComponent {
13842
14364
  constructor() {
13843
14365
  effect(() => {
13844
14366
  getState(this.aggregationsStore);
14367
+ if (this.isControlled())
14368
+ return;
13845
14369
  sessionStorage.removeItem(`agg-${this.column()}`);
13846
14370
  });
13847
14371
  effect(() => {
13848
14372
  this.filters.set(this.getFilters());
13849
14373
  });
13850
14374
  this.destroyRef.onDestroy(() => {
13851
- sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
14375
+ if (!this.isControlled())
14376
+ sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
13852
14377
  if (this.selection()) {
13853
14378
  const unselect = (items) => {
13854
14379
  items.forEach((item) => {
@@ -13865,7 +14390,13 @@ class AggregationTreeComponent {
13865
14390
  clear() {
13866
14391
  const agg = this.aggregation();
13867
14392
  if (agg) {
13868
- this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
14393
+ if (this.isControlled()) {
14394
+ this.selectionChange.emit({ kind: "paths", paths: [] });
14395
+ this.filtersChange.emit(undefined);
14396
+ }
14397
+ else {
14398
+ this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
14399
+ }
13869
14400
  const unselect = (items) => {
13870
14401
  items.forEach((item) => {
13871
14402
  item.$selected = false;
@@ -13875,7 +14406,8 @@ class AggregationTreeComponent {
13875
14406
  });
13876
14407
  };
13877
14408
  unselect(this.items());
13878
- sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
14409
+ if (!this.isControlled())
14410
+ sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
13879
14411
  this.selection.set(false);
13880
14412
  this.isAllSelected.set(false);
13881
14413
  this.onClear.emit();
@@ -13896,21 +14428,57 @@ class AggregationTreeComponent {
13896
14428
  }
13897
14429
  }
13898
14430
  apply(overrideFilters) {
13899
- sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
13900
14431
  const filters = overrideFilters || this.getFilters();
13901
14432
  const agg = this.aggregation();
13902
- this.base.applyFilters(filters, agg, () => this.clear());
14433
+ if (this.isControlled()) {
14434
+ const paths = filters[0]?.values ?? [];
14435
+ this.selectionChange.emit({ kind: "paths", paths });
14436
+ this.filtersChange.emit(legacyFilterToFilter(this.base.buildAppliedFilter(filters, agg)));
14437
+ }
14438
+ else {
14439
+ sessionStorage.setItem(`agg-${agg?.column}`, JSON.stringify([...this.items()]));
14440
+ this.base.applyFilters(filters, agg, () => this.clear());
14441
+ }
13903
14442
  this.searchText.set("");
13904
14443
  this.selection.set(false);
13905
14444
  this.onApply.emit();
13906
14445
  }
13907
14446
  loadMore() {
14447
+ // Controlled mode loads into the local state — never the shared AggregationsStore.
14448
+ if (this.isControlled()) {
14449
+ const agg = this.base.controlledAggregation();
14450
+ if (!agg)
14451
+ return;
14452
+ this.aggregationsService
14453
+ .loadMore(this.base.resolvedQuery(), agg, undefined, { count: this.base.resolvedAggregationCount(agg) })
14454
+ .subscribe((aggregation) => {
14455
+ this.base.controlledAggregation.set(aggregation);
14456
+ });
14457
+ return;
14458
+ }
13908
14459
  const q = this.queryParamsStore.getQuery();
13909
14460
  this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
13910
14461
  this.aggregationsStore.updateAggregation(aggregation);
13911
14462
  });
13912
14463
  }
13913
14464
  async open(node) {
14465
+ // Controlled mode expands into the local state via the guard-free fetch —
14466
+ // never the shared AggregationsStore, never QueryService's global result signal.
14467
+ if (this.isControlled()) {
14468
+ const controlled = this.base.controlledAggregation();
14469
+ if (!controlled)
14470
+ return;
14471
+ // Spread before mutating: resolvedQuery() may return the host's query object.
14472
+ const q = { ...this.base.resolvedQuery() };
14473
+ delete q.filters;
14474
+ const agg = await firstValueFrom(this.aggregationsService.open(q, controlled, node, {
14475
+ guardFree: true,
14476
+ }));
14477
+ node.$opened = true;
14478
+ // replaceInTree mutates and returns the same reference — spread so the signal notifies.
14479
+ this.base.controlledAggregation.set({ ...agg });
14480
+ return;
14481
+ }
13914
14482
  const q = this.queryParamsStore.getQuery();
13915
14483
  delete q.filters;
13916
14484
  const agg = await firstValueFrom(this.aggregationsService.open(q, this.aggregation(), node));
@@ -13922,7 +14490,9 @@ class AggregationTreeComponent {
13922
14490
  this.onSelect.emit(selectedItems);
13923
14491
  this.selection.set(selectedItems.length > 0 || this.hasFilters());
13924
14492
  this.verifySelected();
13925
- sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
14493
+ if (!this.isControlled()) {
14494
+ sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
14495
+ }
13926
14496
  }
13927
14497
  /* item-level methods — called from the ng-template */
13928
14498
  treeItemName(item) {
@@ -13930,6 +14500,10 @@ class AggregationTreeComponent {
13930
14500
  return typeof value === "string" ? value : `${value}`;
13931
14501
  }
13932
14502
  isTreeItemFiltered(item, field) {
14503
+ if (this.isControlled()) {
14504
+ const values = [item.value, `/${item.$path}/*`];
14505
+ return (this.base.selectionValues() ?? []).some((value) => values.some((v) => v === value));
14506
+ }
13933
14507
  const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.treeItemName(item) });
13934
14508
  if (!filters)
13935
14509
  return false;
@@ -13990,7 +14564,10 @@ class AggregationTreeComponent {
13990
14564
  const notSelectedItems = this.getFlattenTreeItems()
13991
14565
  .filter((item) => !item.$selected)
13992
14566
  .map((item) => (item.$path ? `/${item.$path}/*` : ""));
13993
- const currentFilters = this.queryParamsStore.getFilter({ field, name })?.values || [];
14567
+ const sel = this.controlledSelection();
14568
+ const currentFilters = this.isControlled()
14569
+ ? (sel?.kind === "paths" ? sel.paths : [])
14570
+ : this.queryParamsStore.getFilter({ field, name })?.values || [];
13994
14571
  if (currentFilters) {
13995
14572
  const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
13996
14573
  if (filteredCurrentFilters.length > 0) {
@@ -14116,7 +14693,7 @@ class AggregationTreeComponent {
14116
14693
  return false;
14117
14694
  }
14118
14695
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14119
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationTreeComponent, isStandalone: true, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\", class())" } }, viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the &lt;Aggregation /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
14696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationTreeComponent, isStandalone: true, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, itemTemplateInput: { classPropertyName: "itemTemplateInput", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\", class())" } }, queries: [{ propertyName: "itemDirective", first: true, predicate: AggregationItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the &lt;Aggregation /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: node,\r\n field: field,\r\n selected: node.$selected || node.$selectedVisually || false,\r\n count: node.count,\r\n name: (treeItemName(node) | syslang) ?? '',\r\n searchText: searchText(),\r\n level: treeItemLevel(node),\r\n expanded: node.$opened ?? false,\r\n hasChildren: node.hasChildren ?? false\r\n }\" />\r\n } @else {\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
14120
14697
  }
14121
14698
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationTreeComponent, decorators: [{
14122
14699
  type: Component,
@@ -14134,8 +14711,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14134
14711
  AggregationPanelComponent,
14135
14712
  ], standalone: true, host: {
14136
14713
  "[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())',
14137
- }, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the &lt;Aggregation /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"] }]
14138
- }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], virtualItems: [{ type: i0.ViewChildren, args: ["virtualItem", { isSignal: true }] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
14714
+ }, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the &lt;Aggregation /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: node,\r\n field: field,\r\n selected: node.$selected || node.$selectedVisually || false,\r\n count: node.count,\r\n name: (treeItemName(node) | syslang) ?? '',\r\n searchText: searchText(),\r\n level: treeItemLevel(node),\r\n expanded: node.$opened ?? false,\r\n hasChildren: node.hasChildren ?? false\r\n }\" />\r\n } @else {\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"] }]
14715
+ }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], virtualItems: [{ type: i0.ViewChildren, args: ["virtualItem", { isSignal: true }] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], itemDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AggregationItemDirective), { isSignal: true }] }], itemTemplateInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
14139
14716
 
14140
14717
  class AggregationListComponent {
14141
14718
  cn = cn;
@@ -14148,6 +14725,14 @@ class AggregationListComponent {
14148
14725
  collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
14149
14726
  searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
14150
14727
  showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14728
+ /**
14729
+ * Whether to render the per-item checkbox. When `false`, the checkbox is hidden
14730
+ * but selection still toggles on row click — pair it with a custom `aggregationItem`
14731
+ * template that reflects the `selected` context to convey selection.
14732
+ *
14733
+ * @default true
14734
+ */
14735
+ showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14151
14736
  /**
14152
14737
  * When `false`, applying or clearing filters updates the store without
14153
14738
  * synchronizing the URL (no browser history entry, not shareable/restorable).
@@ -14155,12 +14740,61 @@ class AggregationListComponent {
14155
14740
  * @default true
14156
14741
  */
14157
14742
  syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14743
+ /**
14744
+ * Controlled query. When set, the component's own operations (load-more,
14745
+ * search-within-facet) run against this query instead of reading the shared
14746
+ * `QueryParamsStore`. Use to target a query not tied to the current app,
14747
+ * without mutating the shared store or the URL.
14748
+ *
14749
+ * @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
14750
+ */
14751
+ controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
14752
+ /**
14753
+ * Controlled data. When set, the component renders this aggregation object
14754
+ * instead of reading it from the shared `AggregationsStore`.
14755
+ *
14756
+ * @default undefined — read `AggregationsStore` (current behavior)
14757
+ */
14758
+ controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
14759
+ /**
14760
+ * Controlled selection. When set, item `$selected` state is derived from this
14761
+ * value instead of the shared `QueryParamsStore` filters.
14762
+ *
14763
+ * @default undefined — derive from `QueryParamsStore` filters (current behavior)
14764
+ */
14765
+ controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
14158
14766
  /* outputs */
14159
14767
  onSelect = output();
14160
14768
  onApply = output();
14161
14769
  onClear = output();
14770
+ /**
14771
+ * Emits the new selection (item values) when the user applies or clears
14772
+ * filters in controlled mode. The counterpart of the `[selection]` input —
14773
+ * emitted before `filtersChange` and `onApply`/`onClear`. In controlled mode
14774
+ * this is the only way changes surface: nothing is written to the shared
14775
+ * stores or the URL.
14776
+ */
14777
+ selectionChange = output();
14778
+ /**
14779
+ * Emits the applied selection as a structured `Filter` (the exact filter the
14780
+ * default mode would write to the store), ready for `fetchQuery({ filters })`.
14781
+ * `undefined` on clear. Controlled mode only — emitted after `selectionChange`,
14782
+ * before `onApply`/`onClear`.
14783
+ */
14784
+ filtersChange = output();
14162
14785
  /* view queries */
14163
14786
  scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
14787
+ /* optional host-provided template for the item content/label region */
14788
+ itemDirective = contentChild(AggregationItemDirective, ...(ngDevMode ? [{ debugName: "itemDirective" }] : []));
14789
+ /**
14790
+ * Item content template relayed by the `<Aggregation>` wrapper (which cannot
14791
+ * project content into this component). Also usable directly as a `TemplateRef`
14792
+ * input. A directive projected straight onto this component takes precedence.
14793
+ */
14794
+ itemTemplateInput = input(undefined, ...(ngDevMode ? [{ debugName: "itemTemplateInput", alias: "itemTemplate" }] : [{
14795
+ alias: "itemTemplate",
14796
+ }]));
14797
+ resolvedItemTemplate = computed(() => this.itemDirective()?.template ?? this.itemTemplateInput(), ...(ngDevMode ? [{ debugName: "resolvedItemTemplate" }] : []));
14164
14798
  /* selection state */
14165
14799
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
14166
14800
  isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
@@ -14172,8 +14806,12 @@ class AggregationListComponent {
14172
14806
  column: this.column,
14173
14807
  searchText: this.searchText,
14174
14808
  syncUrl: this.syncUrl,
14809
+ query: this.controlledQuery,
14810
+ aggregation: this.controlledAggregation,
14811
+ selection: this.controlledSelection,
14175
14812
  });
14176
14813
  /* spread from base */
14814
+ isControlled = this.base.isControlled;
14177
14815
  aggregationsStore = this.base.aggregationsStore;
14178
14816
  queryParamsStore = this.base.queryParamsStore;
14179
14817
  appStore = this.base.appStore;
@@ -14197,6 +14835,17 @@ class AggregationListComponent {
14197
14835
  scrollElement: this.scrollElement(),
14198
14836
  }));
14199
14837
  aggregation = computed(() => {
14838
+ if (this.isControlled()) {
14839
+ const agg = this.base.controlledAggregation();
14840
+ if (!agg)
14841
+ return null;
14842
+ if (agg.isTree) {
14843
+ error("The aggregation component does not support tree aggregations. Please use the <AggregationTree /> component instead.");
14844
+ }
14845
+ const enriched = this.aggregationsService.enrichAggregation(agg, this.base.selectionValues() ?? []);
14846
+ enriched.searchable = this.searchable() ?? enriched.searchable;
14847
+ return enriched;
14848
+ }
14200
14849
  getState(this.aggregationsStore);
14201
14850
  const name = this.name();
14202
14851
  const column = this.column();
@@ -14258,7 +14907,13 @@ class AggregationListComponent {
14258
14907
  clear() {
14259
14908
  const agg = this.aggregation();
14260
14909
  if (agg) {
14261
- this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
14910
+ if (this.isControlled()) {
14911
+ this.selectionChange.emit({ kind: "values", values: [] });
14912
+ this.filtersChange.emit(undefined);
14913
+ }
14914
+ else {
14915
+ this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
14916
+ }
14262
14917
  this.selection.set(false);
14263
14918
  this.isAllSelected.set(false);
14264
14919
  }
@@ -14283,12 +14938,34 @@ class AggregationListComponent {
14283
14938
  const agg = this.aggregation();
14284
14939
  if (!agg)
14285
14940
  return;
14286
- this.base.applyFilters(this.getFilters(), agg, () => this.clear());
14941
+ if (this.isControlled()) {
14942
+ const values = [...this.items(), ...this.searchedItems()]
14943
+ .filter((item) => item.$selected)
14944
+ .map((item) => String(item.value));
14945
+ this.selectionChange.emit({ kind: "values", values: Array.from(new Set(values)) });
14946
+ this.filtersChange.emit(legacyFilterToFilter(this.base.buildAppliedFilter(this.getFilters(), agg)));
14947
+ }
14948
+ else {
14949
+ this.base.applyFilters(this.getFilters(), agg, () => this.clear());
14950
+ }
14287
14951
  this.searchText.set("");
14288
14952
  this.selection.set(false);
14289
14953
  this.onApply.emit();
14290
14954
  }
14291
14955
  loadMore() {
14956
+ // Controlled mode loads into the local state — never the shared AggregationsStore.
14957
+ if (this.isControlled()) {
14958
+ const agg = this.base.controlledAggregation();
14959
+ if (!agg)
14960
+ return;
14961
+ this.aggregationsService
14962
+ .loadMore(this.base.resolvedQuery(), agg, undefined, { count: this.base.resolvedAggregationCount(agg) })
14963
+ .subscribe((aggregation) => {
14964
+ this.base.controlledAggregation.set(aggregation);
14965
+ this.cdr.detectChanges();
14966
+ });
14967
+ return;
14968
+ }
14292
14969
  const q = this.queryParamsStore.getQuery();
14293
14970
  this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
14294
14971
  this.aggregationsStore.updateAggregation(aggregation);
@@ -14306,6 +14983,9 @@ class AggregationListComponent {
14306
14983
  return typeof value === "string" ? value : `${value}`;
14307
14984
  }
14308
14985
  isListItemFiltered(item, field) {
14986
+ if (this.isControlled()) {
14987
+ return (this.base.selectionValues() ?? []).some((value) => value === item.value);
14988
+ }
14309
14989
  const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
14310
14990
  if (!filters)
14311
14991
  return false;
@@ -14335,7 +15015,7 @@ class AggregationListComponent {
14335
15015
  return this.base.addCurrentFiltersToItems(this.aggregation());
14336
15016
  }
14337
15017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14338
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationListComponent, isStandalone: true, selector: "AggregationList, aggregation-list, aggregationlist", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class())" } }, viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the &lt;AggregationTree /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
15018
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationListComponent, isStandalone: true, selector: "AggregationList, aggregation-list, aggregationlist", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, itemTemplateInput: { classPropertyName: "itemTemplateInput", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class())" } }, queries: [{ propertyName: "itemDirective", first: true, predicate: AggregationItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the &lt;AggregationTree /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: field,\r\n selected: item.$selected ?? false,\r\n count: item.count,\r\n name: (listItemName(item) | syslang) ?? '',\r\n searchText: searchText()\r\n }\" />\r\n } @else {\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
14339
15019
  }
14340
15020
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationListComponent, decorators: [{
14341
15021
  type: Component,
@@ -14351,8 +15031,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14351
15031
  AggregationPanelComponent,
14352
15032
  ], standalone: true, host: {
14353
15033
  "[class]": 'cn("block h-[inherit] max-h-[inherit]", class())',
14354
- }, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the &lt;AggregationTree /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"] }]
14355
- }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
15034
+ }, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the &lt;AggregationTree /&gt; component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: field,\r\n selected: item.$selected ?? false,\r\n count: item.count,\r\n name: (listItemName(item) | syslang) ?? '',\r\n searchText: searchText()\r\n }\" />\r\n } @else {\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"] }]
15035
+ }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], itemDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AggregationItemDirective), { isSignal: true }] }], itemTemplateInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
14356
15036
 
14357
15037
  /**
14358
15038
  * The `AggregationComponent` is responsible for rendering the appropriate aggregation component based on the type of aggregation (date, tree, or list). It uses the `AggregationsService` to retrieve the aggregation data and determines which component to display based on the column type and whether it is a tree structure. The component also provides inputs for configuring its behavior, such as collapsibility, searchability, and showing filter counts.
@@ -14377,6 +15057,12 @@ class AggregationComponent {
14377
15057
  aggregationDate = viewChild(AggregationDateComponent, ...(ngDevMode ? [{ debugName: "aggregationDate" }] : []));
14378
15058
  aggregationTree = viewChild(AggregationTreeComponent, ...(ngDevMode ? [{ debugName: "aggregationTree" }] : []));
14379
15059
  aggregationList = viewChild(AggregationListComponent, ...(ngDevMode ? [{ debugName: "aggregationList" }] : []));
15060
+ /**
15061
+ * Host-projected `<ng-template aggregationItem>`, relayed to the active list/tree
15062
+ * sub-component (the wrapper has no `<ng-content>`, so the sub-component's own
15063
+ * `contentChild` cannot see it). See {@link AggregationItemDirective}.
15064
+ */
15065
+ itemDirective = contentChild(AggregationItemDirective, ...(ngDevMode ? [{ debugName: "itemDirective" }] : []));
14380
15066
  class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
14381
15067
  /**
14382
15068
  * The name of the <details> element. When you provide the same id, the component work as an accordion
@@ -14420,6 +15106,13 @@ class AggregationComponent {
14420
15106
  * "Undefined" and not "false" because this input overrides the custom json settings
14421
15107
  */
14422
15108
  searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
15109
+ /**
15110
+ * Whether to render the per-item checkbox (list/tree). When `false`, the checkbox
15111
+ * is hidden but selection still toggles on row click. Ignored by the date variant.
15112
+ *
15113
+ * @default true
15114
+ */
15115
+ showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14423
15116
  expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
14424
15117
  /**
14425
15118
  * Controls whether applying or clearing a filter synchronizes the URL.
@@ -14431,12 +15124,64 @@ class AggregationComponent {
14431
15124
  * @default true
14432
15125
  */
14433
15126
  syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
15127
+ /**
15128
+ * Controlled query. When set, the aggregation's own operations (load-more,
15129
+ * search-within-facet, tree-expand) run against this query instead of reading
15130
+ * the shared `QueryParamsStore`. Use to target a query not tied to the current
15131
+ * app, without mutating the shared store or the URL.
15132
+ *
15133
+ * @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
15134
+ */
15135
+ controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
15136
+ /**
15137
+ * Controlled data. When set, the component renders this aggregation object
15138
+ * (name/column/items) instead of reading it from the shared `AggregationsStore`.
15139
+ *
15140
+ * @default undefined — read `AggregationsStore` (current behavior)
15141
+ */
15142
+ controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
15143
+ /**
15144
+ * Controlled selection. When set, item `$selected` state is derived from this
15145
+ * value (list: item values; tree: `"/A/B/*"` paths; date: the range) instead
15146
+ * of the shared `QueryParamsStore` filters.
15147
+ *
15148
+ * @default undefined — derive from `QueryParamsStore` filters (current behavior)
15149
+ */
15150
+ controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
15151
+ /**
15152
+ * Which sub-component renders the aggregation.
15153
+ *
15154
+ * `"auto"` (default) keeps the current heuristic: a date column of the app
15155
+ * (`AppStore.isDateColumn`) renders `<AggregationDate>`, a tree aggregation
15156
+ * renders `<AggregationTree>`, anything else `<AggregationList>`. Set an
15157
+ * explicit value in controlled mode when the heuristic cannot apply — e.g. a
15158
+ * date facet of a query not attached to the current app, whose column is
15159
+ * unknown to the app's column map.
15160
+ *
15161
+ * @default "auto"
15162
+ */
15163
+ type = input("auto", ...(ngDevMode ? [{ debugName: "type" }] : []));
14434
15164
  /**
14435
15165
  * An output event that emits an array of selected `AggregationItem` objects when the user selects items in the aggregation component. This allows parent components to listen for selection changes and react accordingly, such as applying filters or updating the UI based on the selected items.
14436
15166
  */
14437
15167
  onSelect = output();
14438
15168
  onApply = output();
14439
15169
  onClear = output();
15170
+ /**
15171
+ * Emits the new selection when the user applies or clears filters in
15172
+ * controlled mode (list: item values; tree: `"/A/B/*"` paths; date: option or
15173
+ * range). The counterpart of the `[selection]` input — emitted before
15174
+ * `filtersChange` and `onApply`/`onClear`. In controlled mode this is the only
15175
+ * way changes surface: nothing is written to the shared stores or the URL.
15176
+ */
15177
+ selectionChange = output();
15178
+ /**
15179
+ * Emits the applied selection as a structured `Filter` (the exact filter the
15180
+ * default mode would write to the store), ready for `fetchQuery({ filters })`.
15181
+ * `undefined` on clear. Controlled mode only — emitted after `selectionChange`,
15182
+ * before `onApply`/`onClear`.
15183
+ */
15184
+ filtersChange = output();
14440
15185
  /**
14441
15186
  * A computed property that retrieves the items to be displayed in the aggregation component. It checks for the presence of date, tree, or list aggregation components and returns their respective items. If none of these components are present, it returns an empty array. This allows the component to dynamically display the appropriate items based on the type of aggregation being rendered.
14442
15187
  *
@@ -14454,6 +15199,10 @@ class AggregationComponent {
14454
15199
  selectedItems = computed(() => this.items().filter(item => item.$selected), ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
14455
15200
  /* aggregation */
14456
15201
  aggregation = computed(() => {
15202
+ // controlled mode: the input is the data source — the store is not read
15203
+ const controlled = this.controlledAggregation();
15204
+ if (controlled)
15205
+ return controlled;
14457
15206
  // when the aggegation store updates, we need to check if the aggregation is still valid
14458
15207
  getState(this.aggregationsStore);
14459
15208
  const agg = this.aggregationsService.getAggregation(this.name(), this.column());
@@ -14463,6 +15212,20 @@ class AggregationComponent {
14463
15212
  }
14464
15213
  return agg;
14465
15214
  }, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
15215
+ /**
15216
+ * The sub-component to render: the explicit `type` input, or the current
15217
+ * date/tree/list heuristic when `type` is `"auto"`.
15218
+ */
15219
+ resolvedType = computed(() => {
15220
+ const type = this.type();
15221
+ if (type !== "auto")
15222
+ return type;
15223
+ if (this.isDate(this.aggregation()?.column))
15224
+ return "date";
15225
+ if (this.aggregation()?.isTree)
15226
+ return "tree";
15227
+ return "list";
15228
+ }, ...(ngDevMode ? [{ debugName: "resolvedType" }] : []));
14466
15229
  /**
14467
15230
  * Checks if the specified column is a date filter.
14468
15231
  *
@@ -14476,8 +15239,9 @@ class AggregationComponent {
14476
15239
  return this.appStore.isDateColumn(column);
14477
15240
  }
14478
15241
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14479
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationComponent, isStandalone: true, selector: "Aggregation, aggregation", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, viewQueries: [{ propertyName: "aggregationDate", first: true, predicate: AggregationDateComponent, descendants: true, isSignal: true }, { propertyName: "aggregationTree", first: true, predicate: AggregationTreeComponent, descendants: true, isSignal: true }, { propertyName: "aggregationList", first: true, predicate: AggregationListComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
14480
- @if (isDate(aggregation()?.column)) {
15242
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationComponent, isStandalone: true, selector: "Aggregation, aggregation", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, queries: [{ propertyName: "itemDirective", first: true, predicate: AggregationItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "aggregationDate", first: true, predicate: AggregationDateComponent, descendants: true, isSignal: true }, { propertyName: "aggregationTree", first: true, predicate: AggregationTreeComponent, descendants: true, isSignal: true }, { propertyName: "aggregationList", first: true, predicate: AggregationListComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
15243
+ @switch (resolvedType()) {
15244
+ @case ("date") {
14481
15245
  <AggregationDate
14482
15246
  [id]="id()"
14483
15247
  [name]="name()"
@@ -14487,11 +15251,17 @@ class AggregationComponent {
14487
15251
  [showFiltersCount]="showFiltersCount()"
14488
15252
  [searchable]="searchable()"
14489
15253
  [syncUrl]="syncUrl()"
15254
+ [query]="controlledQuery()"
15255
+ [aggregation]="controlledAggregation()"
15256
+ [selection]="controlledSelection()"
14490
15257
  [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
14491
15258
  (onApply)="onApply.emit($event)"
14492
15259
  (onClear)="onClear.emit($event)"
15260
+ (selectionChange)="selectionChange.emit($event)"
15261
+ (filtersChange)="filtersChange.emit($event)"
14493
15262
  />
14494
- } @else if (aggregation()?.isTree) {
15263
+ }
15264
+ @case ("tree") {
14495
15265
  <AggregationTree
14496
15266
  [id]="id()"
14497
15267
  [name]="name()"
@@ -14501,13 +15271,20 @@ class AggregationComponent {
14501
15271
  [showFiltersCount]="showFiltersCount()"
14502
15272
  [searchable]="searchable()"
14503
15273
  [syncUrl]="syncUrl()"
15274
+ [query]="controlledQuery()"
15275
+ [aggregation]="controlledAggregation()"
15276
+ [selection]="controlledSelection()"
15277
+ [itemTemplate]="itemDirective()?.template"
15278
+ [showCheckbox]="showCheckbox()"
14504
15279
  [expandedLevel]="expandedLevel()"
14505
15280
  (onSelect)="onSelect.emit($event)"
14506
15281
  (onApply)="onApply.emit($event)"
14507
15282
  (onClear)="onClear.emit($event)"
15283
+ (selectionChange)="selectionChange.emit($event)"
15284
+ (filtersChange)="filtersChange.emit($event)"
14508
15285
  />
14509
- }
14510
- @else {
15286
+ }
15287
+ @default {
14511
15288
  <AggregationList
14512
15289
  [id]="id()"
14513
15290
  [name]="name()"
@@ -14517,12 +15294,20 @@ class AggregationComponent {
14517
15294
  [showFiltersCount]="showFiltersCount()"
14518
15295
  [searchable]="searchable()"
14519
15296
  [syncUrl]="syncUrl()"
15297
+ [query]="controlledQuery()"
15298
+ [aggregation]="controlledAggregation()"
15299
+ [selection]="controlledSelection()"
15300
+ [itemTemplate]="itemDirective()?.template"
15301
+ [showCheckbox]="showCheckbox()"
14520
15302
  (onSelect)="onSelect.emit($event)"
14521
15303
  (onApply)="onApply.emit($event)"
14522
15304
  (onClear)="onClear.emit($event)"
15305
+ (selectionChange)="selectionChange.emit($event)"
15306
+ (filtersChange)="filtersChange.emit($event)"
14523
15307
  />
14524
- }
14525
- `, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "syncUrl", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }, { kind: "component", type: AggregationTreeComponent, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "expandedLevel", "syncUrl", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }, { kind: "component", type: AggregationDateComponent, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: ["name", "column", "id", "collapsible", "collapsed", "searchable", "showFiltersCount", "syncUrl", "title", "displayEmptyDistributionIntervals", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }] });
15308
+ }
15309
+ }
15310
+ `, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "showCheckbox", "syncUrl", "query", "aggregation", "selection", "itemTemplate", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }, { kind: "component", type: AggregationTreeComponent, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "itemTemplate", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }, { kind: "component", type: AggregationDateComponent, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: ["name", "column", "id", "collapsible", "collapsed", "searchable", "showFiltersCount", "syncUrl", "query", "aggregation", "selection", "title", "displayEmptyDistributionIntervals", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }] });
14526
15311
  }
14527
15312
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationComponent, decorators: [{
14528
15313
  type: Component,
@@ -14530,7 +15315,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14530
15315
  selector: "Aggregation, aggregation",
14531
15316
  imports: [AggregationListComponent, AggregationTreeComponent, AggregationDateComponent],
14532
15317
  template: `
14533
- @if (isDate(aggregation()?.column)) {
15318
+ @switch (resolvedType()) {
15319
+ @case ("date") {
14534
15320
  <AggregationDate
14535
15321
  [id]="id()"
14536
15322
  [name]="name()"
@@ -14540,11 +15326,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14540
15326
  [showFiltersCount]="showFiltersCount()"
14541
15327
  [searchable]="searchable()"
14542
15328
  [syncUrl]="syncUrl()"
15329
+ [query]="controlledQuery()"
15330
+ [aggregation]="controlledAggregation()"
15331
+ [selection]="controlledSelection()"
14543
15332
  [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
14544
15333
  (onApply)="onApply.emit($event)"
14545
15334
  (onClear)="onClear.emit($event)"
15335
+ (selectionChange)="selectionChange.emit($event)"
15336
+ (filtersChange)="filtersChange.emit($event)"
14546
15337
  />
14547
- } @else if (aggregation()?.isTree) {
15338
+ }
15339
+ @case ("tree") {
14548
15340
  <AggregationTree
14549
15341
  [id]="id()"
14550
15342
  [name]="name()"
@@ -14554,13 +15346,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14554
15346
  [showFiltersCount]="showFiltersCount()"
14555
15347
  [searchable]="searchable()"
14556
15348
  [syncUrl]="syncUrl()"
15349
+ [query]="controlledQuery()"
15350
+ [aggregation]="controlledAggregation()"
15351
+ [selection]="controlledSelection()"
15352
+ [itemTemplate]="itemDirective()?.template"
15353
+ [showCheckbox]="showCheckbox()"
14557
15354
  [expandedLevel]="expandedLevel()"
14558
15355
  (onSelect)="onSelect.emit($event)"
14559
15356
  (onApply)="onApply.emit($event)"
14560
15357
  (onClear)="onClear.emit($event)"
15358
+ (selectionChange)="selectionChange.emit($event)"
15359
+ (filtersChange)="filtersChange.emit($event)"
14561
15360
  />
14562
- }
14563
- @else {
15361
+ }
15362
+ @default {
14564
15363
  <AggregationList
14565
15364
  [id]="id()"
14566
15365
  [name]="name()"
@@ -14570,17 +15369,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14570
15369
  [showFiltersCount]="showFiltersCount()"
14571
15370
  [searchable]="searchable()"
14572
15371
  [syncUrl]="syncUrl()"
15372
+ [query]="controlledQuery()"
15373
+ [aggregation]="controlledAggregation()"
15374
+ [selection]="controlledSelection()"
15375
+ [itemTemplate]="itemDirective()?.template"
15376
+ [showCheckbox]="showCheckbox()"
14573
15377
  (onSelect)="onSelect.emit($event)"
14574
15378
  (onApply)="onApply.emit($event)"
14575
15379
  (onClear)="onClear.emit($event)"
15380
+ (selectionChange)="selectionChange.emit($event)"
15381
+ (filtersChange)="filtersChange.emit($event)"
14576
15382
  />
14577
- }
15383
+ }
15384
+ }
14578
15385
  `,
14579
15386
  host: {
14580
15387
  "[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
14581
15388
  }
14582
15389
  }]
14583
- }], propDecorators: { aggregationDate: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateComponent), { isSignal: true }] }], aggregationTree: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationTreeComponent), { isSignal: true }] }], aggregationList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationListComponent), { isSignal: true }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }] } });
15390
+ }], propDecorators: { aggregationDate: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateComponent), { isSignal: true }] }], aggregationTree: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationTreeComponent), { isSignal: true }] }], aggregationList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationListComponent), { isSignal: true }] }], itemDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AggregationItemDirective), { isSignal: true }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }] } });
14584
15391
 
14585
15392
  class AsideFiltersComponent {
14586
15393
  cn = cn;
@@ -14614,7 +15421,7 @@ class AsideFiltersComponent {
14614
15421
  showFiltersCount />
14615
15422
  }
14616
15423
  </div>
14617
- `, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel", "syncUrl"], outputs: ["onSelect", "onApply", "onClear"] }] });
15424
+ `, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }] });
14618
15425
  }
14619
15426
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AsideFiltersComponent, decorators: [{
14620
15427
  type: Component,
@@ -14765,7 +15572,7 @@ class FilterButtonComponent {
14765
15572
  }
14766
15573
  </PopoverContent>
14767
15574
  </Popover>
14768
- `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel", "syncUrl"], outputs: ["onSelect", "onApply", "onClear"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size", "aria-label"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: OperatorPipe, name: "operator" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
15575
+ `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size", "aria-label"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: OperatorPipe, name: "operator" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
14769
15576
  }
14770
15577
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: FilterButtonComponent, decorators: [{
14771
15578
  type: Component,
@@ -14945,7 +15752,7 @@ class MoreComponent {
14945
15752
  [name]="filter.name"
14946
15753
  [column]="filter.column" />
14947
15754
  }
14948
- `, isInline: true, styles: [":host{scrollbar-width:none}\n"], dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel", "syncUrl"], outputs: ["onSelect", "onApply", "onClear"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
15755
+ `, isInline: true, styles: [":host{scrollbar-width:none}\n"], dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14949
15756
  }
14950
15757
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MoreComponent, decorators: [{
14951
15758
  type: Component,
@@ -15276,7 +16083,7 @@ class FiltersBarComponent {
15276
16083
  @if (currentBasket()) {
15277
16084
  <button
15278
16085
  variant="destructive"
15279
- class="max-w-40 size-9"
16086
+ class="max-w-40 h-9"
15280
16087
  role="listitem"
15281
16088
  [title]="'filters.clearBasket' | transloco"
15282
16089
  (click)="clearBasket()"
@@ -15348,7 +16155,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15348
16155
  @if (currentBasket()) {
15349
16156
  <button
15350
16157
  variant="destructive"
15351
- class="max-w-40 size-9"
16158
+ class="max-w-40 h-9"
15352
16159
  role="listitem"
15353
16160
  [title]="'filters.clearBasket' | transloco"
15354
16161
  (click)="clearBasket()"
@@ -16803,5 +17610,5 @@ const queryNameResolver = () => {
16803
17610
  * Generated bundle index. Do not edit.
16804
17611
  */
16805
17612
 
16806
- export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, 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, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeatureFlagsDialogComponent, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
17613
+ export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, 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, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeatureFlagsDialogComponent, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, legacyFilterToFilter, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
16807
17614
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map