@sinequa/atomic-angular 1.7.2 → 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);
@@ -6488,6 +6578,43 @@ function injectRouteNavigation(path, showCount) {
6488
6578
  return { tabs, currentPath, searchText };
6489
6579
  }
6490
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
+
6491
6618
  /**
6492
6619
  * Directive to mark a child element for special processing.
6493
6620
  * This can be used to apply specific styles or behaviors to child elements.
@@ -8912,12 +9039,98 @@ function injectAggregationBase(refs) {
8912
9039
  const aggregationsService = inject(AggregationsService);
8913
9040
  const injector = inject(Injector);
8914
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" }] : []));
8915
9086
  const debouncedSearchText = debouncedSignal(refs.searchText, 300);
8916
9087
  const normalizedSearchText = computed(() => debouncedSearchText()
8917
9088
  .normalize("NFD")
8918
9089
  .replace(/[̀-ͯ]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
8919
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" }] : []));
8920
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
+ }
8921
9134
  getState(aggregationsStore);
8922
9135
  const { count = 0 } = queryParamsStore.getFilter({ field: refs.column() ?? undefined, name: refs.name() ?? undefined }) || {};
8923
9136
  return count;
@@ -8940,7 +9153,7 @@ function injectAggregationBase(refs) {
8940
9153
  .pipe(takeUntilDestroyed(destroyRef), switchMap(({ text, column }) => {
8941
9154
  if (text === "" || column === null)
8942
9155
  return of([]);
8943
- const q = queryParamsStore.getQuery();
9156
+ const q = resolvedQuery();
8944
9157
  return from(withFetch(() => fetchSuggestField(normalizedSearchText(), [column || ""], q), injector));
8945
9158
  }))
8946
9159
  .subscribe((result) => suggests.set(result || []));
@@ -8959,7 +9172,9 @@ function injectAggregationBase(refs) {
8959
9172
  function addCurrentFiltersToItems(aggregation) {
8960
9173
  const currentItems = aggregation?.items || [];
8961
9174
  if (!aggregation?.isTree && !aggregation?.isDistribution) {
8962
- 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 });
8963
9178
  if (currentFilters) {
8964
9179
  if (Array.isArray(currentFilters.filters) && currentFilters.filters.length) {
8965
9180
  currentFilters.filters.forEach((filter) => {
@@ -8998,34 +9213,45 @@ function injectAggregationBase(refs) {
8998
9213
  }
8999
9214
  return currentItems;
9000
9215
  }
9001
- 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) {
9002
9223
  const { name: aggName, column: field } = agg;
9003
- const syncUrl = refs.syncUrl?.() ?? true;
9004
9224
  if (appliedFilters.length > 1) {
9005
9225
  const display = appliedFilters[0].display;
9006
9226
  if (agg.isDistribution) {
9007
- queryParamsStore.updateFilter({
9227
+ return {
9008
9228
  operator: "or",
9009
9229
  filters: appliedFilters,
9010
9230
  name: aggName,
9011
9231
  field,
9012
9232
  display,
9013
- }, undefined, { syncUrl });
9014
- }
9015
- else {
9016
- const values = appliedFilters.map((f) => f.value);
9017
- queryParamsStore.updateFilter({
9018
- operator: "in",
9019
- name: aggName,
9020
- field,
9021
- values,
9022
- display,
9023
- filters: appliedFilters,
9024
- }, undefined, { syncUrl });
9233
+ };
9025
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
+ };
9026
9244
  }
9027
- else if (appliedFilters.length === 1) {
9028
- queryParamsStore.updateFilter(appliedFilters[0], undefined, { syncUrl });
9245
+ if (appliedFilters.length === 1) {
9246
+ return appliedFilters[0];
9247
+ }
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 });
9029
9255
  }
9030
9256
  else {
9031
9257
  clearFn();
@@ -9038,6 +9264,14 @@ function injectAggregationBase(refs) {
9038
9264
  aggregationsService,
9039
9265
  injector,
9040
9266
  destroyRef,
9267
+ isControlled,
9268
+ isQueryControlled,
9269
+ isDataControlled,
9270
+ isSelectionControlled,
9271
+ resolvedQuery,
9272
+ resolvedAggregationCount,
9273
+ controlledAggregation,
9274
+ selectionValues,
9041
9275
  debouncedSearchText,
9042
9276
  normalizedSearchText,
9043
9277
  suggests,
@@ -9048,6 +9282,7 @@ function injectAggregationBase(refs) {
9048
9282
  clearSearch,
9049
9283
  selectItems,
9050
9284
  addCurrentFiltersToItems,
9285
+ buildAppliedFilter,
9051
9286
  applyFilters,
9052
9287
  };
9053
9288
  }
@@ -9095,12 +9330,56 @@ class AggregationDateComponent {
9095
9330
  * @default true
9096
9331
  */
9097
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" }]));
9098
9360
  title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
9099
9361
  displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
9100
9362
  /* outputs */
9101
9363
  onSelect = output();
9102
9364
  onApply = output();
9103
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();
9104
9383
  /* collapse state */
9105
9384
  isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
9106
9385
  expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
@@ -9115,8 +9394,12 @@ class AggregationDateComponent {
9115
9394
  searchInput: this.searchInput,
9116
9395
  expanded: this.expanded,
9117
9396
  syncUrl: this.syncUrl,
9397
+ query: this.controlledQuery,
9398
+ aggregation: this.controlledAggregation,
9399
+ selection: this.controlledSelection,
9118
9400
  });
9119
9401
  /* spread from base */
9402
+ isControlled = this.base.isControlled;
9120
9403
  aggregationsService = this.base.aggregationsService;
9121
9404
  queryParamsStore = this.base.queryParamsStore;
9122
9405
  hasFilters = this.base.hasFilters;
@@ -9127,7 +9410,24 @@ class AggregationDateComponent {
9127
9410
  /* state */
9128
9411
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
9129
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;
9130
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
+ }
9131
9431
  const name = this.name();
9132
9432
  if (name !== null) {
9133
9433
  const agg = this.aggregationsService.processAggregation(name, this.column());
@@ -9163,28 +9463,75 @@ class AggregationDateComponent {
9163
9463
  this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
9164
9464
  this.lang.set(lang);
9165
9465
  });
9166
- // apply current date filter from queryParamsStore
9466
+ // apply current date filter from the selection input (controlled) or the queryParamsStore
9167
9467
  effect(() => {
9168
9468
  const agg = this.aggregation();
9169
- 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
+ }
9170
9478
  this.updateForm(this.queryParamsStore.getFilter({
9171
9479
  field: agg.column,
9172
9480
  name: agg.name
9173
9481
  }));
9174
9482
  }
9483
+ finally {
9484
+ this.seedingForm = false;
9485
+ }
9175
9486
  });
9176
9487
  this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((changes) => {
9177
9488
  this.validSelection.set(!!changes.option &&
9178
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
+ }
9179
9506
  });
9180
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
+ }
9181
9524
  select() {
9182
9525
  this.selection.set(true);
9183
9526
  }
9184
9527
  apply() {
9185
9528
  try {
9186
- const filter = this.getFormValueFilter();
9187
- 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
+ }
9188
9535
  this.selection.set(false);
9189
9536
  this.onApply.emit();
9190
9537
  }
@@ -9205,7 +9552,13 @@ class AggregationDateComponent {
9205
9552
  });
9206
9553
  const agg = this.aggregation();
9207
9554
  if (agg && notify) {
9208
- 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
+ }
9209
9562
  this.onClear.emit();
9210
9563
  }
9211
9564
  }
@@ -9223,12 +9576,22 @@ class AggregationDateComponent {
9223
9576
  this.dateRangeDialog()?.open();
9224
9577
  }
9225
9578
  onRangeSelected(range) {
9226
- console.log("Selected range:", range);
9227
9579
  this.form.patchValue({
9228
9580
  option: "custom-range",
9229
9581
  customRange: { from: range.from, to: range.to }
9230
9582
  });
9231
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
+ }
9232
9595
  }
9233
9596
  updateForm(filter) {
9234
9597
  if (!filter) {
@@ -9268,6 +9631,90 @@ class AggregationDateComponent {
9268
9631
  };
9269
9632
  this.form.setValue(formValue);
9270
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
+ }
9271
9718
  toEndOfDay(dateStr) {
9272
9719
  return `${new Date(dateStr).toLocaleDateString("en-CA", options)} 23:59:59`;
9273
9720
  }
@@ -9343,7 +9790,7 @@ class AggregationDateComponent {
9343
9790
  throw new Error("filters.filterInvalid");
9344
9791
  }
9345
9792
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9346
- 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" }] });
9347
9794
  }
9348
9795
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationDateComponent, decorators: [{
9349
9796
  type: Component,
@@ -9361,8 +9808,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9361
9808
  IconButtonComponent
9362
9809
  ], host: {
9363
9810
  class: "@container"
9364
- }, 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"] }]
9365
- }], 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"] }] } });
9366
9813
 
9367
9814
  /**
9368
9815
  * Component that allows users to select a date or a date range for filtering search results.
@@ -9371,7 +9818,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9371
9818
  */
9372
9819
  class DateComponent extends AggregationDateComponent {
9373
9820
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DateComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
9374
- 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" }] });
9375
9822
  }
9376
9823
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DateComponent, decorators: [{
9377
9824
  type: Component,
@@ -9388,7 +9835,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9388
9835
  FilterXIcon
9389
9836
  ], host: {
9390
9837
  class: "@container"
9391
- }, 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"] }]
9392
9839
  }] });
9393
9840
 
9394
9841
  class ArticleEntities {
@@ -13721,6 +14168,14 @@ class AggregationTreeComponent {
13721
14168
  collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
13722
14169
  searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
13723
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 }]));
13724
14179
  expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: (v) => {
13725
14180
  const n = numberAttribute(v);
13726
14181
  return Number.isNaN(n) ? undefined : n;
@@ -13737,13 +14192,63 @@ class AggregationTreeComponent {
13737
14192
  * @default true
13738
14193
  */
13739
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" }]));
13740
14219
  /* outputs */
13741
14220
  onSelect = output();
13742
14221
  onApply = output();
13743
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();
13744
14238
  /* view queries */
13745
14239
  virtualItems = viewChildren("virtualItem", ...(ngDevMode ? [{ debugName: "virtualItems" }] : []));
13746
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" }] : []));
13747
14252
  /* selection state */
13748
14253
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
13749
14254
  isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
@@ -13755,8 +14260,12 @@ class AggregationTreeComponent {
13755
14260
  column: this.column,
13756
14261
  searchText: this.searchText,
13757
14262
  syncUrl: this.syncUrl,
14263
+ query: this.controlledQuery,
14264
+ aggregation: this.controlledAggregation,
14265
+ selection: this.controlledSelection,
13758
14266
  });
13759
14267
  /* spread from base */
14268
+ isControlled = this.base.isControlled;
13760
14269
  aggregationsStore = this.base.aggregationsStore;
13761
14270
  queryParamsStore = this.base.queryParamsStore;
13762
14271
  appStore = this.base.appStore;
@@ -13783,6 +14292,21 @@ class AggregationTreeComponent {
13783
14292
  #measureItems = effect(() => this.virtualItems().forEach((el) => this.virtualizer.measureElement(el.nativeElement)), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
13784
14293
  linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
13785
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
+ }
13786
14310
  getState(this.aggregationsStore);
13787
14311
  const name = this.name();
13788
14312
  const column = this.column();
@@ -13813,7 +14337,10 @@ class AggregationTreeComponent {
13813
14337
  else if (agg?.items) {
13814
14338
  res = this.addCurrentFiltersToItems();
13815
14339
  }
13816
- 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}`);
13817
14344
  const sessionAgg = JSON.parse(sessionAggItem || "[]");
13818
14345
  return this.processAggregations(sessionAgg.length ? this.setSelected(res, sessionAgg) : res);
13819
14346
  }, ...(ngDevMode ? [{ debugName: "items" }] : []));
@@ -13837,13 +14364,16 @@ class AggregationTreeComponent {
13837
14364
  constructor() {
13838
14365
  effect(() => {
13839
14366
  getState(this.aggregationsStore);
14367
+ if (this.isControlled())
14368
+ return;
13840
14369
  sessionStorage.removeItem(`agg-${this.column()}`);
13841
14370
  });
13842
14371
  effect(() => {
13843
14372
  this.filters.set(this.getFilters());
13844
14373
  });
13845
14374
  this.destroyRef.onDestroy(() => {
13846
- sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
14375
+ if (!this.isControlled())
14376
+ sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
13847
14377
  if (this.selection()) {
13848
14378
  const unselect = (items) => {
13849
14379
  items.forEach((item) => {
@@ -13860,7 +14390,13 @@ class AggregationTreeComponent {
13860
14390
  clear() {
13861
14391
  const agg = this.aggregation();
13862
14392
  if (agg) {
13863
- 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
+ }
13864
14400
  const unselect = (items) => {
13865
14401
  items.forEach((item) => {
13866
14402
  item.$selected = false;
@@ -13870,7 +14406,8 @@ class AggregationTreeComponent {
13870
14406
  });
13871
14407
  };
13872
14408
  unselect(this.items());
13873
- sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
14409
+ if (!this.isControlled())
14410
+ sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
13874
14411
  this.selection.set(false);
13875
14412
  this.isAllSelected.set(false);
13876
14413
  this.onClear.emit();
@@ -13891,21 +14428,57 @@ class AggregationTreeComponent {
13891
14428
  }
13892
14429
  }
13893
14430
  apply(overrideFilters) {
13894
- sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
13895
14431
  const filters = overrideFilters || this.getFilters();
13896
14432
  const agg = this.aggregation();
13897
- 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
+ }
13898
14442
  this.searchText.set("");
13899
14443
  this.selection.set(false);
13900
14444
  this.onApply.emit();
13901
14445
  }
13902
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
+ }
13903
14459
  const q = this.queryParamsStore.getQuery();
13904
14460
  this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
13905
14461
  this.aggregationsStore.updateAggregation(aggregation);
13906
14462
  });
13907
14463
  }
13908
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
+ }
13909
14482
  const q = this.queryParamsStore.getQuery();
13910
14483
  delete q.filters;
13911
14484
  const agg = await firstValueFrom(this.aggregationsService.open(q, this.aggregation(), node));
@@ -13917,7 +14490,9 @@ class AggregationTreeComponent {
13917
14490
  this.onSelect.emit(selectedItems);
13918
14491
  this.selection.set(selectedItems.length > 0 || this.hasFilters());
13919
14492
  this.verifySelected();
13920
- 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
+ }
13921
14496
  }
13922
14497
  /* item-level methods — called from the ng-template */
13923
14498
  treeItemName(item) {
@@ -13925,6 +14500,10 @@ class AggregationTreeComponent {
13925
14500
  return typeof value === "string" ? value : `${value}`;
13926
14501
  }
13927
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
+ }
13928
14507
  const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.treeItemName(item) });
13929
14508
  if (!filters)
13930
14509
  return false;
@@ -13985,7 +14564,10 @@ class AggregationTreeComponent {
13985
14564
  const notSelectedItems = this.getFlattenTreeItems()
13986
14565
  .filter((item) => !item.$selected)
13987
14566
  .map((item) => (item.$path ? `/${item.$path}/*` : ""));
13988
- 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 || [];
13989
14571
  if (currentFilters) {
13990
14572
  const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
13991
14573
  if (filteredCurrentFilters.length > 0) {
@@ -14111,7 +14693,7 @@ class AggregationTreeComponent {
14111
14693
  return false;
14112
14694
  }
14113
14695
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14114
- 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" }] });
14115
14697
  }
14116
14698
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationTreeComponent, decorators: [{
14117
14699
  type: Component,
@@ -14129,8 +14711,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14129
14711
  AggregationPanelComponent,
14130
14712
  ], standalone: true, host: {
14131
14713
  "[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())',
14132
- }, 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"] }]
14133
- }], 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"] }] } });
14134
14716
 
14135
14717
  class AggregationListComponent {
14136
14718
  cn = cn;
@@ -14143,6 +14725,14 @@ class AggregationListComponent {
14143
14725
  collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
14144
14726
  searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
14145
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 }]));
14146
14736
  /**
14147
14737
  * When `false`, applying or clearing filters updates the store without
14148
14738
  * synchronizing the URL (no browser history entry, not shareable/restorable).
@@ -14150,12 +14740,61 @@ class AggregationListComponent {
14150
14740
  * @default true
14151
14741
  */
14152
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" }]));
14153
14766
  /* outputs */
14154
14767
  onSelect = output();
14155
14768
  onApply = output();
14156
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();
14157
14785
  /* view queries */
14158
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" }] : []));
14159
14798
  /* selection state */
14160
14799
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
14161
14800
  isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
@@ -14167,8 +14806,12 @@ class AggregationListComponent {
14167
14806
  column: this.column,
14168
14807
  searchText: this.searchText,
14169
14808
  syncUrl: this.syncUrl,
14809
+ query: this.controlledQuery,
14810
+ aggregation: this.controlledAggregation,
14811
+ selection: this.controlledSelection,
14170
14812
  });
14171
14813
  /* spread from base */
14814
+ isControlled = this.base.isControlled;
14172
14815
  aggregationsStore = this.base.aggregationsStore;
14173
14816
  queryParamsStore = this.base.queryParamsStore;
14174
14817
  appStore = this.base.appStore;
@@ -14192,6 +14835,17 @@ class AggregationListComponent {
14192
14835
  scrollElement: this.scrollElement(),
14193
14836
  }));
14194
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
+ }
14195
14849
  getState(this.aggregationsStore);
14196
14850
  const name = this.name();
14197
14851
  const column = this.column();
@@ -14253,7 +14907,13 @@ class AggregationListComponent {
14253
14907
  clear() {
14254
14908
  const agg = this.aggregation();
14255
14909
  if (agg) {
14256
- 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
+ }
14257
14917
  this.selection.set(false);
14258
14918
  this.isAllSelected.set(false);
14259
14919
  }
@@ -14278,12 +14938,34 @@ class AggregationListComponent {
14278
14938
  const agg = this.aggregation();
14279
14939
  if (!agg)
14280
14940
  return;
14281
- 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
+ }
14282
14951
  this.searchText.set("");
14283
14952
  this.selection.set(false);
14284
14953
  this.onApply.emit();
14285
14954
  }
14286
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
+ }
14287
14969
  const q = this.queryParamsStore.getQuery();
14288
14970
  this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
14289
14971
  this.aggregationsStore.updateAggregation(aggregation);
@@ -14301,6 +14983,9 @@ class AggregationListComponent {
14301
14983
  return typeof value === "string" ? value : `${value}`;
14302
14984
  }
14303
14985
  isListItemFiltered(item, field) {
14986
+ if (this.isControlled()) {
14987
+ return (this.base.selectionValues() ?? []).some((value) => value === item.value);
14988
+ }
14304
14989
  const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
14305
14990
  if (!filters)
14306
14991
  return false;
@@ -14330,7 +15015,7 @@ class AggregationListComponent {
14330
15015
  return this.base.addCurrentFiltersToItems(this.aggregation());
14331
15016
  }
14332
15017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14333
- 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" }] });
14334
15019
  }
14335
15020
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationListComponent, decorators: [{
14336
15021
  type: Component,
@@ -14346,8 +15031,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14346
15031
  AggregationPanelComponent,
14347
15032
  ], standalone: true, host: {
14348
15033
  "[class]": 'cn("block h-[inherit] max-h-[inherit]", class())',
14349
- }, 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"] }]
14350
- }], 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"] }] } });
14351
15036
 
14352
15037
  /**
14353
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.
@@ -14372,6 +15057,12 @@ class AggregationComponent {
14372
15057
  aggregationDate = viewChild(AggregationDateComponent, ...(ngDevMode ? [{ debugName: "aggregationDate" }] : []));
14373
15058
  aggregationTree = viewChild(AggregationTreeComponent, ...(ngDevMode ? [{ debugName: "aggregationTree" }] : []));
14374
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" }] : []));
14375
15066
  class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
14376
15067
  /**
14377
15068
  * The name of the <details> element. When you provide the same id, the component work as an accordion
@@ -14415,6 +15106,13 @@ class AggregationComponent {
14415
15106
  * "Undefined" and not "false" because this input overrides the custom json settings
14416
15107
  */
14417
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 }]));
14418
15116
  expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
14419
15117
  /**
14420
15118
  * Controls whether applying or clearing a filter synchronizes the URL.
@@ -14426,12 +15124,64 @@ class AggregationComponent {
14426
15124
  * @default true
14427
15125
  */
14428
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" }] : []));
14429
15164
  /**
14430
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.
14431
15166
  */
14432
15167
  onSelect = output();
14433
15168
  onApply = output();
14434
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();
14435
15185
  /**
14436
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.
14437
15187
  *
@@ -14449,6 +15199,10 @@ class AggregationComponent {
14449
15199
  selectedItems = computed(() => this.items().filter(item => item.$selected), ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
14450
15200
  /* aggregation */
14451
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;
14452
15206
  // when the aggegation store updates, we need to check if the aggregation is still valid
14453
15207
  getState(this.aggregationsStore);
14454
15208
  const agg = this.aggregationsService.getAggregation(this.name(), this.column());
@@ -14458,6 +15212,20 @@ class AggregationComponent {
14458
15212
  }
14459
15213
  return agg;
14460
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" }] : []));
14461
15229
  /**
14462
15230
  * Checks if the specified column is a date filter.
14463
15231
  *
@@ -14471,8 +15239,9 @@ class AggregationComponent {
14471
15239
  return this.appStore.isDateColumn(column);
14472
15240
  }
14473
15241
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14474
- 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: `
14475
- @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") {
14476
15245
  <AggregationDate
14477
15246
  [id]="id()"
14478
15247
  [name]="name()"
@@ -14482,11 +15251,17 @@ class AggregationComponent {
14482
15251
  [showFiltersCount]="showFiltersCount()"
14483
15252
  [searchable]="searchable()"
14484
15253
  [syncUrl]="syncUrl()"
15254
+ [query]="controlledQuery()"
15255
+ [aggregation]="controlledAggregation()"
15256
+ [selection]="controlledSelection()"
14485
15257
  [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
14486
15258
  (onApply)="onApply.emit($event)"
14487
15259
  (onClear)="onClear.emit($event)"
15260
+ (selectionChange)="selectionChange.emit($event)"
15261
+ (filtersChange)="filtersChange.emit($event)"
14488
15262
  />
14489
- } @else if (aggregation()?.isTree) {
15263
+ }
15264
+ @case ("tree") {
14490
15265
  <AggregationTree
14491
15266
  [id]="id()"
14492
15267
  [name]="name()"
@@ -14496,13 +15271,20 @@ class AggregationComponent {
14496
15271
  [showFiltersCount]="showFiltersCount()"
14497
15272
  [searchable]="searchable()"
14498
15273
  [syncUrl]="syncUrl()"
15274
+ [query]="controlledQuery()"
15275
+ [aggregation]="controlledAggregation()"
15276
+ [selection]="controlledSelection()"
15277
+ [itemTemplate]="itemDirective()?.template"
15278
+ [showCheckbox]="showCheckbox()"
14499
15279
  [expandedLevel]="expandedLevel()"
14500
15280
  (onSelect)="onSelect.emit($event)"
14501
15281
  (onApply)="onApply.emit($event)"
14502
15282
  (onClear)="onClear.emit($event)"
15283
+ (selectionChange)="selectionChange.emit($event)"
15284
+ (filtersChange)="filtersChange.emit($event)"
14503
15285
  />
14504
- }
14505
- @else {
15286
+ }
15287
+ @default {
14506
15288
  <AggregationList
14507
15289
  [id]="id()"
14508
15290
  [name]="name()"
@@ -14512,12 +15294,20 @@ class AggregationComponent {
14512
15294
  [showFiltersCount]="showFiltersCount()"
14513
15295
  [searchable]="searchable()"
14514
15296
  [syncUrl]="syncUrl()"
15297
+ [query]="controlledQuery()"
15298
+ [aggregation]="controlledAggregation()"
15299
+ [selection]="controlledSelection()"
15300
+ [itemTemplate]="itemDirective()?.template"
15301
+ [showCheckbox]="showCheckbox()"
14515
15302
  (onSelect)="onSelect.emit($event)"
14516
15303
  (onApply)="onApply.emit($event)"
14517
15304
  (onClear)="onClear.emit($event)"
15305
+ (selectionChange)="selectionChange.emit($event)"
15306
+ (filtersChange)="filtersChange.emit($event)"
14518
15307
  />
14519
- }
14520
- `, 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"] }] });
14521
15311
  }
14522
15312
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationComponent, decorators: [{
14523
15313
  type: Component,
@@ -14525,7 +15315,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14525
15315
  selector: "Aggregation, aggregation",
14526
15316
  imports: [AggregationListComponent, AggregationTreeComponent, AggregationDateComponent],
14527
15317
  template: `
14528
- @if (isDate(aggregation()?.column)) {
15318
+ @switch (resolvedType()) {
15319
+ @case ("date") {
14529
15320
  <AggregationDate
14530
15321
  [id]="id()"
14531
15322
  [name]="name()"
@@ -14535,11 +15326,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14535
15326
  [showFiltersCount]="showFiltersCount()"
14536
15327
  [searchable]="searchable()"
14537
15328
  [syncUrl]="syncUrl()"
15329
+ [query]="controlledQuery()"
15330
+ [aggregation]="controlledAggregation()"
15331
+ [selection]="controlledSelection()"
14538
15332
  [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
14539
15333
  (onApply)="onApply.emit($event)"
14540
15334
  (onClear)="onClear.emit($event)"
15335
+ (selectionChange)="selectionChange.emit($event)"
15336
+ (filtersChange)="filtersChange.emit($event)"
14541
15337
  />
14542
- } @else if (aggregation()?.isTree) {
15338
+ }
15339
+ @case ("tree") {
14543
15340
  <AggregationTree
14544
15341
  [id]="id()"
14545
15342
  [name]="name()"
@@ -14549,13 +15346,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14549
15346
  [showFiltersCount]="showFiltersCount()"
14550
15347
  [searchable]="searchable()"
14551
15348
  [syncUrl]="syncUrl()"
15349
+ [query]="controlledQuery()"
15350
+ [aggregation]="controlledAggregation()"
15351
+ [selection]="controlledSelection()"
15352
+ [itemTemplate]="itemDirective()?.template"
15353
+ [showCheckbox]="showCheckbox()"
14552
15354
  [expandedLevel]="expandedLevel()"
14553
15355
  (onSelect)="onSelect.emit($event)"
14554
15356
  (onApply)="onApply.emit($event)"
14555
15357
  (onClear)="onClear.emit($event)"
15358
+ (selectionChange)="selectionChange.emit($event)"
15359
+ (filtersChange)="filtersChange.emit($event)"
14556
15360
  />
14557
- }
14558
- @else {
15361
+ }
15362
+ @default {
14559
15363
  <AggregationList
14560
15364
  [id]="id()"
14561
15365
  [name]="name()"
@@ -14565,17 +15369,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14565
15369
  [showFiltersCount]="showFiltersCount()"
14566
15370
  [searchable]="searchable()"
14567
15371
  [syncUrl]="syncUrl()"
15372
+ [query]="controlledQuery()"
15373
+ [aggregation]="controlledAggregation()"
15374
+ [selection]="controlledSelection()"
15375
+ [itemTemplate]="itemDirective()?.template"
15376
+ [showCheckbox]="showCheckbox()"
14568
15377
  (onSelect)="onSelect.emit($event)"
14569
15378
  (onApply)="onApply.emit($event)"
14570
15379
  (onClear)="onClear.emit($event)"
15380
+ (selectionChange)="selectionChange.emit($event)"
15381
+ (filtersChange)="filtersChange.emit($event)"
14571
15382
  />
14572
- }
15383
+ }
15384
+ }
14573
15385
  `,
14574
15386
  host: {
14575
15387
  "[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
14576
15388
  }
14577
15389
  }]
14578
- }], 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"] }] } });
14579
15391
 
14580
15392
  class AsideFiltersComponent {
14581
15393
  cn = cn;
@@ -14609,7 +15421,7 @@ class AsideFiltersComponent {
14609
15421
  showFiltersCount />
14610
15422
  }
14611
15423
  </div>
14612
- `, 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"] }] });
14613
15425
  }
14614
15426
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AsideFiltersComponent, decorators: [{
14615
15427
  type: Component,
@@ -14760,7 +15572,7 @@ class FilterButtonComponent {
14760
15572
  }
14761
15573
  </PopoverContent>
14762
15574
  </Popover>
14763
- `, 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" }] });
14764
15576
  }
14765
15577
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: FilterButtonComponent, decorators: [{
14766
15578
  type: Component,
@@ -14940,7 +15752,7 @@ class MoreComponent {
14940
15752
  [name]="filter.name"
14941
15753
  [column]="filter.column" />
14942
15754
  }
14943
- `, 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" }] });
14944
15756
  }
14945
15757
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MoreComponent, decorators: [{
14946
15758
  type: Component,
@@ -16798,5 +17610,5 @@ const queryNameResolver = () => {
16798
17610
  * Generated bundle index. Do not edit.
16799
17611
  */
16800
17612
 
16801
- 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 };
16802
17614
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map