@sinequa/atomic-angular 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ 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, 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';
10
+ import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, info, clearSessionTokens, login, 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';
@@ -1295,6 +1295,40 @@ const PrincipalStore = signalStore({ providedIn: 'root' }, withDevtools('Princip
1295
1295
  };
1296
1296
  }));
1297
1297
 
1298
+ /** The sign-out confirmation route. Fixed, like the `logout` path `AuthPageComponent` reads. */
1299
+ const LOGOUT_PATH = "/logout";
1300
+ /** Leading slash, no query string, no fragment — what `Router.url` and `loginPath` must agree on. */
1301
+ function normalize(url) {
1302
+ const path = url.split("?")[0].split("#")[0];
1303
+ return path.startsWith("/") ? path : `/${path}`;
1304
+ }
1305
+ /** Whether `path` is `base` or one of its children — `/login/sso` yes, `/loginer` no. */
1306
+ function isUnder(path, base) {
1307
+ return path === base || path.startsWith(`${base}/`);
1308
+ }
1309
+ /**
1310
+ * Whether `url` is one of the authentication screens: the configured `loginPath` (defaulting to
1311
+ * `/login`) or `/logout`.
1312
+ *
1313
+ * Two things follow from it, and both were bugs before it existed:
1314
+ *
1315
+ * - **Nothing redirects to the login screen from one of these routes.** A request still trailing
1316
+ * behind a logout answers 401; recovering from it navigated to `loginPath` and replaced the
1317
+ * "signed out" confirmation the user was reading with the login form they had just left.
1318
+ * - **No `returnUrl` points at one of them.** Signing in only to be returned to `/logout` is a
1319
+ * loop with a friendly face.
1320
+ *
1321
+ * Comparisons are on the path alone, so `/login?mode=changepassword` counts, and by segment, so a
1322
+ * route merely starting with the same letters does not. Query params matter to the screen, never to
1323
+ * the question being asked here.
1324
+ */
1325
+ function isAuthRoute(url, loginPath) {
1326
+ if (!url)
1327
+ return false;
1328
+ const path = normalize(url);
1329
+ return isUnder(path, normalize(loginPath ?? "/login")) || isUnder(path, LOGOUT_PATH);
1330
+ }
1331
+
1298
1332
  /**
1299
1333
  * Creates a debounced signal that updates its value after a specified timeout.
1300
1334
  *
@@ -1661,13 +1695,29 @@ async function signIn() {
1661
1695
  function __resetSignInMutexForTests() {
1662
1696
  pendingSignIn = null;
1663
1697
  }
1698
+ /**
1699
+ * Sends the user to the login screen — unless they are already on an authentication screen.
1700
+ *
1701
+ * That exception is the whole point of routing every redirect through here. A request still in
1702
+ * flight when the user signs out answers 401 on the deleted cookie, and recovering from it landed
1703
+ * on this line: it replaced the "signed out" confirmation with the login form the user had just
1704
+ * left, carrying `returnUrl=/logout` — so signing back in returned them to the logout page.
1705
+ */
1706
+ function redirectToLogin(router, loginPath, returnUrl) {
1707
+ const path = loginPath ?? "/login";
1708
+ if (isAuthRoute(router.url, path)) {
1709
+ info("Already on an authentication screen — not redirecting to the login form");
1710
+ return;
1711
+ }
1712
+ router.navigate([path], { queryParams: { returnUrl } });
1713
+ }
1664
1714
  async function performSignIn(router, lastUrlAfterNavigation) {
1665
1715
  const { loginPath, authMode } = globalConfig;
1666
1716
  // Always clear authentication tokens first to clear any existing session
1667
1717
  clearSessionTokens();
1668
1718
  // Credentials: show the login form.
1669
1719
  if (authMode?.kind === "credentials") {
1670
- router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
1720
+ redirectToLogin(router, loginPath, lastUrlAfterNavigation);
1671
1721
  return false; // prevent further execution
1672
1722
  }
1673
1723
  // SSO: the browser/proxy owns authentication. A 401 landing here means the (cookie) session is
@@ -1690,7 +1740,7 @@ async function performSignIn(router, lastUrlAfterNavigation) {
1690
1740
  // so we don't touch the router. Otherwise (unknown resolved to credentials, or bearer failed)
1691
1741
  // show the login form.
1692
1742
  if (authMode?.kind !== "oauth" && authMode?.kind !== "saml") {
1693
- router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
1743
+ redirectToLogin(router, loginPath, lastUrlAfterNavigation);
1694
1744
  }
1695
1745
  return false;
1696
1746
  }
@@ -1699,7 +1749,7 @@ async function performSignIn(router, lastUrlAfterNavigation) {
1699
1749
  // A 401 is recoverable: the user just needs to authenticate → show the login form.
1700
1750
  if (err?.status === 401) {
1701
1751
  error("Unauthorized access - please check your credentials:", err?.errorMessage);
1702
- router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
1752
+ redirectToLogin(router, loginPath, lastUrlAfterNavigation);
1703
1753
  return false;
1704
1754
  }
1705
1755
  // Any other error (e.g. a misconfigured OAuth/SAML provider → 5xx) is fatal and not something
@@ -3769,7 +3819,11 @@ function AuthGuard() {
3769
3819
  const router = inject(Router);
3770
3820
  const principalStore = inject(PrincipalStore);
3771
3821
  const { loginPath, authMode } = globalConfig;
3772
- if (state.url.startsWith("/login"))
3822
+ // The authentication screens let themselves through: guarding the login form against not being
3823
+ // logged in redirects it to itself, and guarding `/logout` sends the user who just signed out
3824
+ // back to the form they left. `/logout` used to be missed — the test was a literal `/login` —
3825
+ // which only stayed invisible because the applications did not guard that route.
3826
+ if (isAuthRoute(state.url, loginPath))
3773
3827
  return true;
3774
3828
  // If the user is not authenticated, navigate to the login page.
3775
3829
  // The login page handles every authentication method (credentials, OAuth, SAML).
@@ -4395,22 +4449,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
4395
4449
  }]
4396
4450
  }] });
4397
4451
 
4452
+ /**
4453
+ * The route an audit event names: the path, without its leading slash, query string or fragment.
4454
+ * `/search?q=irsn#p2` and `/search` are the same route — arriving on one from the other is a new
4455
+ * query, not a navigation.
4456
+ */
4457
+ function routeNameOf(url) {
4458
+ return url.slice(1).split(/[?#]/)[0];
4459
+ }
4398
4460
  class NavigationService {
4399
4461
  router = inject(Router);
4400
4462
  auditService = inject(AuditService);
4401
- // Tracks the previous NavigationEnd url, used only to dedupe audit route-change notifications
4402
- // in navigationEnd$ below. Kept separate from urlAfterNavigation so that getter stays correct
4463
+ // The route of the previous navigation, used only to dedupe audit route-change notifications in
4464
+ // navigationEnd$ below. Kept separate from urlAfterNavigation so that getter stays correct
4403
4465
  // regardless of whether anything subscribes to navigationEnd$.
4404
- previousNavigationUrl = null;
4466
+ //
4467
+ // It used to hold the full url (`/search?q=irsn`) while the comparison was against the bare route
4468
+ // name (`search`): the two could never be equal, so the dedupe never fired and every navigation
4469
+ // was audited — including the query-param changes that make up most of a search session.
4470
+ previousRouteName = null;
4405
4471
  /**
4406
4472
  * The URL of the current navigation, used to redirect the user after login.
4407
4473
  *
4408
4474
  * Sourced directly from `Router.url`, which Angular keeps up to date on every navigation with no
4409
4475
  * subscription required — unlike a field updated as a side effect of `navigationEnd$` below,
4410
4476
  * which only happens for whoever actually subscribes to it.
4477
+ *
4478
+ * The authentication screens are never returned: sending someone back to `/logout` after they
4479
+ * signed in defeats the sign-in, and back to `/login` loops. The exclusion used to be a literal
4480
+ * `!== "/login"`, which let through `/logout`, `/login?mode=changepassword` — same screen, one
4481
+ * query param — and any application whose `loginPath` is not spelled `/login`.
4411
4482
  */
4412
4483
  get urlAfterNavigation() {
4413
- return this.router.url !== "/login" ? this.router.url : null;
4484
+ const url = this.router.url;
4485
+ return isAuthRoute(url, globalConfig.loginPath) ? null : url;
4414
4486
  }
4415
4487
  /**
4416
4488
  * Observable that emits events of type `NavigationEnd` from the Angular Router.
@@ -4418,21 +4490,23 @@ class NavigationService {
4418
4490
  * This observable performs the following operations:
4419
4491
  * - Maps all router events to `RouterEvent`.
4420
4492
  * - Filters the events to only include instances of `NavigationEnd`.
4421
- * - Taps into the event stream to extract the route name from the URL and notify the audit service of route changes,
4422
- * excluding duplicate navigations.
4493
+ * - Taps into the event stream to notify the audit service when the route itself changes — a new
4494
+ * query, a filter or another page of results stays on the same route and is not one.
4423
4495
  * - Shares the replayed value with a buffer size of 1 to ensure subscribers receive the latest emitted value.
4424
4496
  *
4425
4497
  * @type Observable<RouterEvent>
4426
4498
  */
4427
- navigationEnd$ = this.router.events.pipe(map((event) => event), filter$1((event) => event instanceof NavigationEnd), tap((event) => {
4428
- const url = event.url.slice(1).split("?")[0]; // Extract route name
4429
- if (url && url !== this.previousNavigationUrl) {
4430
- this.auditService.notifyRouteChange(url);
4431
- }
4432
- }), tap((event) => {
4433
- if (event.url !== "/login") {
4434
- this.previousNavigationUrl = event.url;
4435
- }
4499
+ navigationEnd$ = this.router.events.pipe(map(event => event), filter$1((event) => event instanceof NavigationEnd), tap(event => {
4500
+ const routeName = routeNameOf(event.url);
4501
+ // The empty name is the application root, which has never been audited.
4502
+ if (routeName && routeName !== this.previousRouteName) {
4503
+ this.auditService.notifyRouteChange(routeName);
4504
+ }
4505
+ // Every route is remembered, the authentication screens included. Skipping them would swallow
4506
+ // the return: leaving `/search` for `/login` and coming back is two route changes, and the
4507
+ // second one would compare against a `search` the user has meanwhile left. (The exclusion that
4508
+ // used to sit here never ran — the dedupe above it could not fire.)
4509
+ this.previousRouteName = routeName;
4436
4510
  }), shareReplay(1));
4437
4511
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4438
4512
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: NavigationService, providedIn: "root" });
@@ -4622,18 +4696,26 @@ class PreviewService {
4622
4696
  // reset preview data
4623
4697
  this.previewData = undefined;
4624
4698
  this.events.set("loading");
4625
- return this.http
4626
- .post(`${this.API_URL}/preview`, body)
4627
- .pipe(tap((data) => this.setPreviewData(data)), tap(() => this.events.set("loaded")));
4699
+ return this.http.post(`${this.API_URL}/preview`, body).pipe(tap((data) => this.setPreviewData(data)), tap(() => this.events.set("loaded")));
4628
4700
  }
4629
4701
  /**
4630
4702
  * Closes the preview with the specified ID and updates the audit log.
4631
- *
4632
- * @param id - The ID of the preview to close.
4633
- * @param query - The partial query object used to retrieve the preview detail.
4634
- */
4703
+ *
4704
+ * @param id - The ID of the preview to close.
4705
+ * @param query - The partial query object used to retrieve the preview detail.
4706
+ */
4635
4707
  close(id, query) {
4636
4708
  this.passageOffset.set(undefined);
4709
+ // There is nothing to report to a session that is already closed. `close()` runs from the
4710
+ // preview's teardown, and a logout triggers exactly that on its way out of the page: the event
4711
+ // reached the server on a cookie that had just been deleted and answered 401. Nothing recovers
4712
+ // from it on this line, so this is only about not making a request that cannot succeed.
4713
+ //
4714
+ // The guard is here rather than in `AuditService.notify()`, which has the same one: routing this
4715
+ // call through the service would drop the `sessionid` and `url` that the free `Audit.notify`
4716
+ // adds to the detail, and the server reads them.
4717
+ if (!isAuthenticated())
4718
+ return;
4637
4719
  const detail = this.getAuditPreviewDetail(id, query);
4638
4720
  const auditEvent = {
4639
4721
  type: AuditEventType.Preview_Close,
@@ -4685,9 +4767,7 @@ class PreviewService {
4685
4767
  setPreviewData(data) {
4686
4768
  this.previewData = data;
4687
4769
  if (data) {
4688
- this.highlightCategory = data.highlightsPerCategory?.["matchingpassages"]?.values.length
4689
- ? "matchingpassages"
4690
- : "extractslocations";
4770
+ this.highlightCategory = data.highlightsPerCategory?.["matchingpassages"]?.values.length ? "matchingpassages" : "extractslocations";
4691
4771
  }
4692
4772
  }
4693
4773
  /**
@@ -4729,8 +4809,8 @@ class PreviewService {
4729
4809
  // NB: positionInCategories[entity] is a zero-based index, so the first occurrence is 0.
4730
4810
  // Use a presence check (not truthiness) to avoid dropping that first occurrence.
4731
4811
  const entitySearched = this.previewData.highlightsPerLocation
4732
- .filter((item) => item.positionInCategories[entity] !== undefined && item.values.some((v) => v === value))
4733
- .map((e) => ({ id: e.positionInCategories[entity] }));
4812
+ .filter(item => item.positionInCategories[entity] !== undefined && item.values.some(v => v === value))
4813
+ .map(e => ({ id: e.positionInCategories[entity] }));
4734
4814
  const id = entitySearched[index]?.id;
4735
4815
  return id;
4736
4816
  }
@@ -4790,17 +4870,14 @@ class PreviewService {
4790
4870
  * @param entities - A boolean flag indicating whether to include entities highlights.
4791
4871
  */
4792
4872
  toggle(extracts, entities) {
4793
- const extractsHighlights = extracts ? this.highlights.filter((h) => this.extracts.includes(h.name)) : [];
4794
- const entitiesHighlights = entities ? this.highlights.filter((h) => this.entities.includes(h.name)) : [];
4873
+ const extractsHighlights = extracts ? this.highlights.filter(h => this.extracts.includes(h.name)) : [];
4874
+ const entitiesHighlights = entities ? this.highlights.filter(h => this.entities.includes(h.name)) : [];
4795
4875
  const highlights = [...extractsHighlights, ...entitiesHighlights];
4796
4876
  this.sendMessage({ action: "highlight", highlights });
4797
4877
  }
4798
4878
  getAuditPreviewDetail(id, q) {
4799
4879
  const results = this.queryService.result();
4800
- const queryLanguage = results?.queryAnalysis?.queryLanguage ||
4801
- q?.questionLanguage ||
4802
- (q.name && this.appStore.getQueryByName(q.name)?.questionLanguage) ||
4803
- undefined;
4880
+ const queryLanguage = results?.queryAnalysis?.queryLanguage || q?.questionLanguage || (q.name && this.appStore.getQueryByName(q.name)?.questionLanguage) || undefined;
4804
4881
  // if selection store contains the same id, use the record
4805
4882
  // otherwise use the preview data record
4806
4883
  // this will be helpful when reading the ranking of the document
@@ -4846,8 +4923,8 @@ class PreviewService {
4846
4923
  }));
4847
4924
  // then sanitize the text and remove empty extracts
4848
4925
  const _extracts = extractslocations
4849
- .filter((item) => item.text.trim().length > 0)
4850
- .map((item) => ({
4926
+ .filter(item => item.text.trim().length > 0)
4927
+ .map(item => ({
4851
4928
  ...item,
4852
4929
  text: this.sanitizer.bypassSecurityTrustHtml(item.text)
4853
4930
  }));
@@ -4865,7 +4942,7 @@ class PreviewService {
4865
4942
  return;
4866
4943
  // update extracts
4867
4944
  // remove "extracts" values from the highlights configuration
4868
- const entities = previewConfig.highlights.split(",").filter((h) => !this.extracts.includes(h));
4945
+ const entities = previewConfig.highlights.split(",").filter(h => !this.extracts.includes(h));
4869
4946
  // merge the entities with the default highlights without duplicates
4870
4947
  this.entities = [...new Set([...this.entities, ...entities])];
4871
4948
  }
@@ -4875,11 +4952,11 @@ class PreviewService {
4875
4952
  this.sendMessage({ action: "goto-page", page });
4876
4953
  }
4877
4954
  nextPage() {
4878
- this.currentPage.update((n) => n + 1);
4955
+ this.currentPage.update(n => n + 1);
4879
4956
  this.sendMessage({ action: "next-page" });
4880
4957
  }
4881
4958
  prevPage() {
4882
- this.currentPage.update((n) => n - 1);
4959
+ this.currentPage.update(n => n - 1);
4883
4960
  this.sendMessage({ action: "prev-page" });
4884
4961
  }
4885
4962
  firstPage() {
@@ -5610,16 +5687,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
5610
5687
  args: [{
5611
5688
  selector: "document-locator, DocumentLocator",
5612
5689
  standalone: true,
5613
- imports: [
5614
- EllipsisIcon,
5615
- ChevronRightIcon,
5616
- SourceComponent,
5617
- ButtonComponent,
5618
- MenuComponent,
5619
- MenuContentComponent,
5620
- MenuItemComponent,
5621
- LinkComponent
5622
- ],
5690
+ imports: [EllipsisIcon, ChevronRightIcon, SourceComponent, ButtonComponent, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent],
5623
5691
  template: `
5624
5692
  <!-- Renders all segment hidden to user to compute width -->
5625
5693
  <div #shadowRender class="pointer-events-none invisible absolute left-0 top-0 -z-10 flex gap-1">
@@ -6753,7 +6821,7 @@ class AggregationItemDirective {
6753
6821
  * Tree-only fields (`level`/`expanded`/`hasChildren`) are typed as optional; a tree
6754
6822
  * template can re-annotate them via {@link AggregationTreeItemContext} if desired.
6755
6823
  */
6756
- static ngTemplateContextGuard(_dir, ctx) {
6824
+ static ngTemplateContextGuard(_dir, _ctx) {
6757
6825
  return true;
6758
6826
  }
6759
6827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -12602,7 +12670,7 @@ class OverrideUserDialogComponent {
12602
12670
  const fullName = this.principalStore.fullName();
12603
12671
  notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
12604
12672
  })
12605
- .catch((err) => {
12673
+ .catch(err => {
12606
12674
  // `err.message` already carries the backend's specific reason (e.g. "The user XYZ is
12607
12675
  // disabled") when @sinequa/atomic's UnauthorizedError is thrown with one — surface it
12608
12676
  // instead of a generic string so admins know why the override was rejected.
@@ -14415,6 +14483,18 @@ class AggregationTreeComponent {
14415
14483
  * @default true
14416
14484
  */
14417
14485
  showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14486
+ /**
14487
+ * Whether every selection gesture — row click, checkbox click, Enter on the
14488
+ * checkbox — applies the filter immediately, instead of only revealing the
14489
+ * Apply button.
14490
+ *
14491
+ * Independent of the `features.quickFilter` customization flag: `applyOnSelect`
14492
+ * applies even when `quickFilter` is off, and leaves the label-click behavior
14493
+ * untouched when it is off itself.
14494
+ *
14495
+ * @default false
14496
+ */
14497
+ applyOnSelect = input(false, ...(ngDevMode ? [{ debugName: "applyOnSelect", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14418
14498
  expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: (v) => {
14419
14499
  const n = numberAttribute(v);
14420
14500
  return Number.isNaN(n) ? undefined : n;
@@ -14422,7 +14502,7 @@ class AggregationTreeComponent {
14422
14502
  transform: (v) => {
14423
14503
  const n = numberAttribute(v);
14424
14504
  return Number.isNaN(n) ? undefined : n;
14425
- },
14505
+ }
14426
14506
  }]));
14427
14507
  /**
14428
14508
  * When `false`, applying or clearing filters updates the store without
@@ -14485,12 +14565,19 @@ class AggregationTreeComponent {
14485
14565
  * input. A directive projected straight onto this component takes precedence.
14486
14566
  */
14487
14567
  itemTemplateInput = input(undefined, ...(ngDevMode ? [{ debugName: "itemTemplateInput", alias: "itemTemplate" }] : [{
14488
- alias: "itemTemplate",
14568
+ alias: "itemTemplate"
14489
14569
  }]));
14490
14570
  resolvedItemTemplate = computed(() => this.itemDirective()?.template ?? this.itemTemplateInput(), ...(ngDevMode ? [{ debugName: "resolvedItemTemplate" }] : []));
14491
14571
  /* selection state */
14492
14572
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
14493
14573
  isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
14574
+ /**
14575
+ * Paths of the nodes the user expanded, so they can be reopened after a refresh
14576
+ * wipes `$opened`. Plain field, not a signal: nothing renders from it.
14577
+ */
14578
+ openedPaths = new Set();
14579
+ /** Re-entrancy guard: `open()` writes to the store, which re-runs the restore effect. */
14580
+ isRestoringOpenedNodes = false;
14494
14581
  /* search state */
14495
14582
  searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
14496
14583
  /* composable — injects stores/services, wires search effects, provides shared methods */
@@ -14501,7 +14588,7 @@ class AggregationTreeComponent {
14501
14588
  syncUrl: this.syncUrl,
14502
14589
  query: this.controlledQuery,
14503
14590
  aggregation: this.controlledAggregation,
14504
- selection: this.controlledSelection,
14591
+ selection: this.controlledSelection
14505
14592
  });
14506
14593
  /* spread from base */
14507
14594
  isControlled = this.base.isControlled;
@@ -14526,9 +14613,8 @@ class AggregationTreeComponent {
14526
14613
  virtualizer = injectVirtualizer(() => ({
14527
14614
  count: this.items().length,
14528
14615
  estimateSize: () => 32,
14529
- scrollElement: this.scrollElement(),
14616
+ scrollElement: this.scrollElement()
14530
14617
  }));
14531
- #measureItems = effect(() => this.virtualItems().forEach((el) => this.virtualizer.measureElement(el.nativeElement)), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
14532
14618
  linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
14533
14619
  aggregation = computed(() => {
14534
14620
  if (this.isControlled()) {
@@ -14590,23 +14676,34 @@ class AggregationTreeComponent {
14590
14676
  if (this.aggregation()?.isTree) {
14591
14677
  return suggestionsToTreeAggregationNodes(this.suggests(), this.searchText());
14592
14678
  }
14593
- return this.suggests()?.map((suggest) => ({
14679
+ return this.suggests()?.map(suggest => ({
14594
14680
  name: this.name(),
14595
14681
  value: suggest.normalized || suggest.display || "",
14596
14682
  display: suggest.display,
14597
14683
  column: suggest.category,
14598
14684
  count: Number(suggest.frequency),
14599
14685
  $selected: false,
14600
- items: [],
14686
+ items: []
14601
14687
  }));
14602
14688
  }, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
14603
14689
  constructor() {
14690
+ // Feed the virtualizer the real height of each rendered row.
14691
+ effect(() => this.virtualItems().forEach(el => this.virtualizer.measureElement(el.nativeElement)));
14604
14692
  effect(() => {
14605
14693
  getState(this.aggregationsStore);
14606
14694
  if (this.isControlled())
14607
14695
  return;
14608
14696
  sessionStorage.removeItem(`agg-${this.column()}`);
14609
14697
  });
14698
+ // A new search replaces the aggregation objects wholesale, dropping `$opened` and any
14699
+ // lazily-loaded children. Reopen what the user had expanded. Controlled mode keeps its
14700
+ // own local aggregation, so nothing is lost there.
14701
+ effect(() => {
14702
+ getState(this.aggregationsStore);
14703
+ if (this.isControlled())
14704
+ return;
14705
+ void this.restoreOpenedNodes();
14706
+ });
14610
14707
  effect(() => {
14611
14708
  this.filters.set(this.getFilters());
14612
14709
  });
@@ -14615,7 +14712,7 @@ class AggregationTreeComponent {
14615
14712
  sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
14616
14713
  if (this.selection()) {
14617
14714
  const unselect = (items) => {
14618
- items.forEach((item) => {
14715
+ items.forEach(item => {
14619
14716
  item.$selected = false;
14620
14717
  item.$selectedVisually = false;
14621
14718
  if (item.items)
@@ -14637,7 +14734,7 @@ class AggregationTreeComponent {
14637
14734
  this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
14638
14735
  }
14639
14736
  const unselect = (items) => {
14640
- items.forEach((item) => {
14737
+ items.forEach(item => {
14641
14738
  item.$selected = false;
14642
14739
  item.$selectedVisually = false;
14643
14740
  if (item.items)
@@ -14666,7 +14763,12 @@ class AggregationTreeComponent {
14666
14763
  this.isAllSelected.set(false);
14667
14764
  }
14668
14765
  }
14669
- apply(overrideFilters) {
14766
+ /**
14767
+ * @param opts.keepSearchText Leave the search-within-facet text in place. Used by
14768
+ * `applyOnSelect`, where the user is expected to tick several values in a row and
14769
+ * would otherwise lose the search context on the first one.
14770
+ */
14771
+ apply(overrideFilters, opts) {
14670
14772
  const filters = overrideFilters || this.getFilters();
14671
14773
  const agg = this.aggregation();
14672
14774
  if (this.isControlled()) {
@@ -14678,7 +14780,8 @@ class AggregationTreeComponent {
14678
14780
  sessionStorage.setItem(`agg-${agg?.column}`, JSON.stringify([...this.items()]));
14679
14781
  this.base.applyFilters(filters, agg, () => this.clear());
14680
14782
  }
14681
- this.searchText.set("");
14783
+ if (!opts?.keepSearchText)
14784
+ this.searchText.set("");
14682
14785
  this.selection.set(false);
14683
14786
  this.onApply.emit();
14684
14787
  }
@@ -14690,13 +14793,13 @@ class AggregationTreeComponent {
14690
14793
  return;
14691
14794
  this.aggregationsService
14692
14795
  .loadMore(this.base.resolvedQuery(), agg, undefined, { count: this.base.resolvedAggregationCount(agg) })
14693
- .subscribe((aggregation) => {
14796
+ .subscribe(aggregation => {
14694
14797
  this.base.controlledAggregation.set(aggregation);
14695
14798
  });
14696
14799
  return;
14697
14800
  }
14698
14801
  const q = this.queryParamsStore.getQuery();
14699
- this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
14802
+ this.aggregationsService.loadMore(q, this.aggregation()).subscribe(aggregation => {
14700
14803
  this.aggregationsStore.updateAggregation(aggregation);
14701
14804
  });
14702
14805
  }
@@ -14711,7 +14814,7 @@ class AggregationTreeComponent {
14711
14814
  const q = { ...this.base.resolvedQuery() };
14712
14815
  delete q.filters;
14713
14816
  const agg = await firstValueFrom(this.aggregationsService.open(q, controlled, node, {
14714
- guardFree: true,
14817
+ guardFree: true
14715
14818
  }));
14716
14819
  node.$opened = true;
14717
14820
  // replaceInTree mutates and returns the same reference — spread so the signal notifies.
@@ -14722,10 +14825,54 @@ class AggregationTreeComponent {
14722
14825
  delete q.filters;
14723
14826
  const agg = await firstValueFrom(this.aggregationsService.open(q, this.aggregation(), node));
14724
14827
  node.$opened = true;
14828
+ this.trackOpened(node, true);
14725
14829
  this.aggregationsStore.updateAggregation(agg);
14726
14830
  }
14831
+ /**
14832
+ * Reopen the nodes the user had expanded, after a search replaced the aggregation.
14833
+ *
14834
+ * Shallowest first, sequentially: a deep node only exists in the tree once its parent
14835
+ * has been reopened. Nodes whose children were lazily loaded come back empty, so they
14836
+ * are re-fetched through `open()` — which is why this cannot be a plain synchronous pass.
14837
+ */
14838
+ async restoreOpenedNodes() {
14839
+ if (this.isRestoringOpenedNodes || this.openedPaths.size === 0)
14840
+ return;
14841
+ if (!untracked(() => this.aggregation())?.isTree)
14842
+ return;
14843
+ const paths = [...this.openedPaths].sort((a, b) => a.split("/").length - b.split("/").length);
14844
+ this.isRestoringOpenedNodes = true;
14845
+ try {
14846
+ for (const path of paths) {
14847
+ // Re-read after each open(): the store hands back a new aggregation object.
14848
+ const items = untracked(() => this.aggregation())?.items;
14849
+ const node = this.findNodeByPath(items, path);
14850
+ if (!node || node.$opened)
14851
+ continue;
14852
+ if (node.items?.length)
14853
+ node.$opened = true;
14854
+ else if (node.hasChildren)
14855
+ await this.open(node);
14856
+ }
14857
+ }
14858
+ finally {
14859
+ this.isRestoringOpenedNodes = false;
14860
+ }
14861
+ }
14862
+ findNodeByPath(items, path) {
14863
+ if (!items?.length)
14864
+ return null;
14865
+ let found = null;
14866
+ this.traverse(items, (_lineage, node) => {
14867
+ if (node.$path !== path)
14868
+ return false;
14869
+ found = node;
14870
+ return true;
14871
+ });
14872
+ return found;
14873
+ }
14727
14874
  select() {
14728
- const selectedItems = this.getFlattenTreeItems().filter((item) => item.$selected);
14875
+ const selectedItems = this.getFlattenTreeItems().filter(item => item.$selected);
14729
14876
  this.onSelect.emit(selectedItems);
14730
14877
  this.selection.set(selectedItems.length > 0 || this.hasFilters());
14731
14878
  this.verifySelected();
@@ -14741,14 +14888,13 @@ class AggregationTreeComponent {
14741
14888
  isTreeItemFiltered(item, field) {
14742
14889
  if (this.isControlled()) {
14743
14890
  const values = [item.value, `/${item.$path}/*`];
14744
- return (this.base.selectionValues() ?? []).some((value) => values.some((v) => v === value));
14891
+ return (this.base.selectionValues() ?? []).some(value => values.some(v => v === value));
14745
14892
  }
14746
14893
  const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.treeItemName(item) });
14747
14894
  if (!filters)
14748
14895
  return false;
14749
14896
  const values = [item.value, `/${item.$path}/*`];
14750
- return (values.some((v) => v === filters.value) ||
14751
- !!(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
14897
+ return values.some(v => v === filters.value) || !!(filters.values?.length && filters.values.some((value) => values.some(v => v === value)));
14752
14898
  }
14753
14899
  treeItemLevel(item) {
14754
14900
  const level = (item.$level ?? 0) - 1 + (!item.hasChildren ? 1 : 0);
@@ -14758,6 +14904,11 @@ class AggregationTreeComponent {
14758
14904
  return parentPath.concat(`/${item.$path}/*`);
14759
14905
  }
14760
14906
  selectTreeItem(node, parent, e, updateChildren = false) {
14907
+ // Selection only — never apply from here. `apply()` belongs to the interaction
14908
+ // entry points below, so that `selectAll`/`unselectAll` and the controlled mode
14909
+ // keep their own semantics.
14910
+ // The stopImmediatePropagation() below is also what guarantees a label click
14911
+ // never additionally reaches the row handler (no double apply). Do not remove it.
14761
14912
  e?.stopImmediatePropagation();
14762
14913
  const selected = !node.$selected && !node.$selectedVisually;
14763
14914
  node.$selected = selected;
@@ -14773,19 +14924,46 @@ class AggregationTreeComponent {
14773
14924
  e.stopImmediatePropagation();
14774
14925
  if (node.items && node.$opened) {
14775
14926
  node.$opened = false;
14927
+ this.trackOpened(node, false);
14776
14928
  return;
14777
14929
  }
14778
14930
  if (node.items && !node.$opened) {
14779
14931
  node.$opened = true;
14932
+ this.trackOpened(node, true);
14780
14933
  return;
14781
14934
  }
14782
14935
  this.open(node);
14783
14936
  }
14937
+ /** Remember (or forget) an expanded node, keyed by its full `$path`. */
14938
+ trackOpened(node, opened) {
14939
+ if (!node.$path)
14940
+ return;
14941
+ if (opened)
14942
+ this.openedPaths.add(node.$path);
14943
+ else
14944
+ this.openedPaths.delete(node.$path);
14945
+ }
14946
+ /**
14947
+ * Single decision point for the item interaction paths: select, then apply only
14948
+ * when the caller says so. Never call `apply()` from anywhere else in that path.
14949
+ */
14950
+ commitTreeSelection(node, parent, event, shouldApply) {
14951
+ this.selectTreeItem(node, parent, event, true);
14952
+ if (shouldApply)
14953
+ this.apply(undefined, { keepSearchText: true });
14954
+ }
14955
+ /** Row click, and checkbox click/Enter — the "any selection gesture" surface. */
14956
+ onTreeItemRowClick(node, parent, event) {
14957
+ this.commitTreeSelection(node, parent, event, this.applyOnSelect());
14958
+ }
14959
+ /**
14960
+ * Label click. `quickFilter` is the app-wide label-only shortcut; `applyOnSelect`
14961
+ * covers the label too. With neither, the click bubbles up to the row handler.
14962
+ */
14784
14963
  onTreeItemTextClick(node, parent, event) {
14785
- if (this.quickFilter()) {
14786
- this.selectTreeItem(node, parent, event, true);
14787
- this.apply();
14788
- }
14964
+ if (!this.quickFilter() && !this.applyOnSelect())
14965
+ return;
14966
+ this.commitTreeSelection(node, parent, event, true);
14789
14967
  }
14790
14968
  getFilters() {
14791
14969
  if (this.aggregation()?.isTree) {
@@ -14798,17 +14976,15 @@ class AggregationTreeComponent {
14798
14976
  if (!name || !field)
14799
14977
  return [];
14800
14978
  const items = this.getFlattenTreeItems()
14801
- .filter((item) => item.$selected)
14802
- .map((item) => (item.$path ? `/${item.$path}/*` : ""));
14979
+ .filter(item => item.$selected)
14980
+ .map(item => (item.$path ? `/${item.$path}/*` : ""));
14803
14981
  const notSelectedItems = this.getFlattenTreeItems()
14804
- .filter((item) => !item.$selected)
14805
- .map((item) => (item.$path ? `/${item.$path}/*` : ""));
14982
+ .filter(item => !item.$selected)
14983
+ .map(item => (item.$path ? `/${item.$path}/*` : ""));
14806
14984
  const sel = this.controlledSelection();
14807
- const currentFilters = this.isControlled()
14808
- ? (sel?.kind === "paths" ? sel.paths : [])
14809
- : this.queryParamsStore.getFilter({ field, name })?.values || [];
14985
+ const currentFilters = this.isControlled() ? (sel?.kind === "paths" ? sel.paths : []) : this.queryParamsStore.getFilter({ field, name })?.values || [];
14810
14986
  if (currentFilters) {
14811
- const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
14987
+ const filteredCurrentFilters = currentFilters.filter(filter => !notSelectedItems.includes(filter));
14812
14988
  if (filteredCurrentFilters.length > 0) {
14813
14989
  items.push(...filteredCurrentFilters);
14814
14990
  }
@@ -14819,30 +14995,27 @@ class AggregationTreeComponent {
14819
14995
  return [{ operator: "in", name, field, values: uniqueItems, display: uniqueItems[0] }];
14820
14996
  }
14821
14997
  getFiltersForList() {
14822
- const items = this.addCurrentFiltersToItems().filter((item) => item.$selected);
14823
- const searchedItems = this.searchedItems().filter((item) => item.$selected);
14998
+ const items = this.addCurrentFiltersToItems().filter(item => item.$selected);
14999
+ const searchedItems = this.searchedItems().filter(item => item.$selected);
14824
15000
  const currentItems = [...items, ...searchedItems];
14825
15001
  const { column, name, isDistribution = false } = this.aggregation() || {};
14826
- return currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
15002
+ return currentItems.map(item => this.aggregationsService.toFilter(item, column, name, isDistribution));
14827
15003
  }
14828
15004
  getFlattenTreeItems() {
14829
15005
  const flattenItems = (items) => items.reduce((flat, item) => flat.concat(item, item.items ? flattenItems(item.items) : []), []);
14830
15006
  const searchedItemsFiltered = (this.searchedItems() || []).filter((item) => "items" in item);
14831
- return [
14832
- ...flattenItems(searchedItemsFiltered),
14833
- ...flattenItems(this.aggregation()?.items || []),
14834
- ];
15007
+ return [...flattenItems(searchedItemsFiltered), ...flattenItems(this.aggregation()?.items || [])];
14835
15008
  }
14836
15009
  addCurrentFiltersToItems() {
14837
15010
  return this.base.addCurrentFiltersToItems(this.aggregation());
14838
15011
  }
14839
15012
  verifySelected() {
14840
- const someItemsUnselected = (items) => items.some((item) => !item.$selected || (item.items?.length && someItemsUnselected(item.items)));
15013
+ const someItemsUnselected = (items) => items.some(item => !item.$selected || (item.items?.length && someItemsUnselected(item.items)));
14841
15014
  this.isAllSelected.set(!someItemsUnselected(this.items()));
14842
15015
  }
14843
15016
  setSelected(items, savedItems) {
14844
- return items.map((item) => {
14845
- const savedItem = savedItems.find((i) => i.value === item.value);
15017
+ return items.map(item => {
15018
+ const savedItem = savedItems.find(i => i.value === item.value);
14846
15019
  if (savedItem) {
14847
15020
  item.$selected = savedItem.$selected;
14848
15021
  item.$selectedVisually = savedItem.$selectedVisually;
@@ -14856,14 +15029,14 @@ class AggregationTreeComponent {
14856
15029
  processAggregations(items) {
14857
15030
  if (!this.linkChildren())
14858
15031
  return items;
14859
- items.forEach((item) => {
15032
+ items.forEach(item => {
14860
15033
  if (item.items?.length)
14861
15034
  this.selectVisually(item.items, item.$selected || false);
14862
15035
  });
14863
15036
  return items;
14864
15037
  }
14865
15038
  selectVisually(items, parentSelected) {
14866
- items.forEach((item) => {
15039
+ items.forEach(item => {
14867
15040
  item.$selectedVisually = parentSelected;
14868
15041
  if (item.items?.length)
14869
15042
  this.selectVisually(item.items, item.$selected || item.$selectedVisually);
@@ -14872,7 +15045,7 @@ class AggregationTreeComponent {
14872
15045
  selectTreeItemChildren(items, select) {
14873
15046
  if (!this.linkChildren() || !items?.length)
14874
15047
  return;
14875
- items.forEach((item) => {
15048
+ items.forEach(item => {
14876
15049
  item.$selectedVisually = select;
14877
15050
  if (select)
14878
15051
  item.$selected = false;
@@ -14881,15 +15054,15 @@ class AggregationTreeComponent {
14881
15054
  });
14882
15055
  }
14883
15056
  handleTreeChildSelect(parent, child) {
14884
- if (this.linkChildren() && !child.$selected && parent.items.some((i) => i.$selectedVisually)) {
14885
- parent.items.forEach((i) => {
15057
+ if (this.linkChildren() && !child.$selected && parent.items.some(i => i.$selectedVisually)) {
15058
+ parent.items.forEach(i => {
14886
15059
  if (i !== child) {
14887
15060
  i.$selectedVisually = false;
14888
15061
  i.$selected = true;
14889
15062
  }
14890
15063
  });
14891
15064
  }
14892
- if (this.linkChildren() && parent.items.some((i) => !i.$selectedVisually && !i.$selected)) {
15065
+ if (this.linkChildren() && parent.items.some(i => !i.$selectedVisually && !i.$selected)) {
14893
15066
  parent.$selected = false;
14894
15067
  parent.$selectedVisually = false;
14895
15068
  }
@@ -14932,7 +15105,7 @@ class AggregationTreeComponent {
14932
15105
  return false;
14933
15106
  }
14934
15107
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14935
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.26", 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 <!-- Relay the host's `label` projection into the panel's `label` slot. When the\r\n host projects nothing, the local <ng-content> falls back to the icon + display\r\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\r\n <ng-container ngProjectAs=\"label\">\r\n <ng-content select=\"label\">\r\n @let labelIcon = aggregation()?.icon;\r\n @if (labelIcon) {\r\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n </ng-container>\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, true)\"\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" }] });
15108
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.26", 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 }, applyOnSelect: { classPropertyName: "applyOnSelect", publicName: "applyOnSelect", 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) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1\" />\n The aggregationTree component does not support list aggregations. Please use\n the &lt;Aggregation /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\n <div\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"tree\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n <div\n class=\"absolute top-0 left-0 w-full\"\n [style.transform]=\"\n 'translateY(' +\n (virtualizer.getVirtualItems()[0]\n ? virtualizer.getVirtualItems()[0].start\n : 0) +\n 'px)'\n \">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n <div #virtualItem [attr.data-index]=\"vItem.index\">\n <ng-container\n [ngTemplateOutlet]=\"treeItemTpl\"\n [ngTemplateOutletContext]=\"{\n node: item,\n path: [],\n field: aggregation()?.column,\n tpl: treeItemTpl,\n parent: null\n }\">\n </ng-container>\n </div>\n }\n </div>\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template\n #treeItemTpl\n let-node=\"node\"\n let-path=\"path\"\n let-field=\"field\"\n let-tpl=\"tpl\"\n let-parent=\"parent\">\n <div\n role=\"treeitem\"\n [style.--level]=\"treeItemLevel(node)\"\n [attr.aria-selected]=\"node.$selected ?? false\"\n [attr.aria-expanded]=\"\n node.hasChildren !== false ? (node.$opened ?? false) : null\n \"\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\n <a\n [attr.aria-label]=\"treeItemName(node) | syslang\"\n [class]=\"\n cn(\n 'flex grow items-center gap-2 p-1 leading-7',\n node.count === 0 && 'disabled pointer-events-none',\n applyOnSelect() && 'cursor-pointer',\n (node.$selected || node.$selectedVisually) && ''\n )\n \"\n (click)=\"onTreeItemRowClick(node, parent, $event)\">\n <button\n variant=\"none\"\n [iconOnly]=\"true\"\n (click)=\"toggleTreeNode($event, node)\"\n class=\"transition-transform ease-in hover:scale-125\"\n aria-label=\"Open\">\n <ChevronRight\n [class]=\"\n cn(\n 'size-4 translate-x-1',\n node.$opened && 'rotate-90',\n !node.hasChildren && 'hidden'\n )\n \"\n width=\"16\"\n height=\"16\" />\n </button>\n @if (showCheckbox()) {\n <input\n type=\"checkbox\"\n role=\"checkbox\"\n value=\"{{ node.value }}\"\n [attr.disabled]=\"node.count === 0 ? true : null\"\n [attr.aria-disabled]=\"node.count === 0\"\n (keydown.enter)=\"onTreeItemRowClick(node, parent, $event)\"\n [checked]=\"node.$selected || node.$selectedVisually\" />\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: field,\n selected: node.$selected || node.$selectedVisually || false,\n count: node.count,\n name: (treeItemName(node) | syslang) ?? '',\n searchText: searchText(),\n level: treeItemLevel(node),\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n } @else {\n @let icon = node.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 break-all text-ellipsis',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isTreeItemFiltered(node, field)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n (treeItemName(node) | syslang)\n : (treeItemName(node) | syslang)\n \"\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\n @for (\n chunk of (treeItemName(node) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n </a>\n @if (node.hasChildren && node.$opened) {\n <div role=\"group\">\n @for (child of node.items; track $index) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n node: child,\n path: treeChildrenPath(node, path),\n field: field,\n tpl: tpl,\n parent: node\n }\">\n </ng-container>\n }\n </div>\n }\n </div>\n</ng-template>\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" }] });
14936
15109
  }
14937
15110
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationTreeComponent, decorators: [{
14938
15111
  type: Component,
@@ -14947,11 +15120,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
14947
15120
  FaIconComponent,
14948
15121
  TriangleAlertIcon,
14949
15122
  HighlightWordPipe,
14950
- AggregationPanelComponent,
15123
+ AggregationPanelComponent
14951
15124
  ], standalone: true, host: {
14952
- "[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())',
14953
- }, 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 <!-- Relay the host's `label` projection into the panel's `label` slot. When the\r\n host projects nothing, the local <ng-content> falls back to the icon + display\r\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\r\n <ng-container ngProjectAs=\"label\">\r\n <ng-content select=\"label\">\r\n @let labelIcon = aggregation()?.icon;\r\n @if (labelIcon) {\r\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n </ng-container>\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, true)\"\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"] }]
14954
- }], 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"] }] } });
15125
+ "[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())'
15126
+ }, template: "@if (!aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1\" />\n The aggregationTree component does not support list aggregations. Please use\n the &lt;Aggregation /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\n <div\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"tree\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n <div\n class=\"absolute top-0 left-0 w-full\"\n [style.transform]=\"\n 'translateY(' +\n (virtualizer.getVirtualItems()[0]\n ? virtualizer.getVirtualItems()[0].start\n : 0) +\n 'px)'\n \">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n <div #virtualItem [attr.data-index]=\"vItem.index\">\n <ng-container\n [ngTemplateOutlet]=\"treeItemTpl\"\n [ngTemplateOutletContext]=\"{\n node: item,\n path: [],\n field: aggregation()?.column,\n tpl: treeItemTpl,\n parent: null\n }\">\n </ng-container>\n </div>\n }\n </div>\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template\n #treeItemTpl\n let-node=\"node\"\n let-path=\"path\"\n let-field=\"field\"\n let-tpl=\"tpl\"\n let-parent=\"parent\">\n <div\n role=\"treeitem\"\n [style.--level]=\"treeItemLevel(node)\"\n [attr.aria-selected]=\"node.$selected ?? false\"\n [attr.aria-expanded]=\"\n node.hasChildren !== false ? (node.$opened ?? false) : null\n \"\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\n <a\n [attr.aria-label]=\"treeItemName(node) | syslang\"\n [class]=\"\n cn(\n 'flex grow items-center gap-2 p-1 leading-7',\n node.count === 0 && 'disabled pointer-events-none',\n applyOnSelect() && 'cursor-pointer',\n (node.$selected || node.$selectedVisually) && ''\n )\n \"\n (click)=\"onTreeItemRowClick(node, parent, $event)\">\n <button\n variant=\"none\"\n [iconOnly]=\"true\"\n (click)=\"toggleTreeNode($event, node)\"\n class=\"transition-transform ease-in hover:scale-125\"\n aria-label=\"Open\">\n <ChevronRight\n [class]=\"\n cn(\n 'size-4 translate-x-1',\n node.$opened && 'rotate-90',\n !node.hasChildren && 'hidden'\n )\n \"\n width=\"16\"\n height=\"16\" />\n </button>\n @if (showCheckbox()) {\n <input\n type=\"checkbox\"\n role=\"checkbox\"\n value=\"{{ node.value }}\"\n [attr.disabled]=\"node.count === 0 ? true : null\"\n [attr.aria-disabled]=\"node.count === 0\"\n (keydown.enter)=\"onTreeItemRowClick(node, parent, $event)\"\n [checked]=\"node.$selected || node.$selectedVisually\" />\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: field,\n selected: node.$selected || node.$selectedVisually || false,\n count: node.count,\n name: (treeItemName(node) | syslang) ?? '',\n searchText: searchText(),\n level: treeItemLevel(node),\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n } @else {\n @let icon = node.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 break-all text-ellipsis',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isTreeItemFiltered(node, field)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n (treeItemName(node) | syslang)\n : (treeItemName(node) | syslang)\n \"\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\n @for (\n chunk of (treeItemName(node) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n </a>\n @if (node.hasChildren && node.$opened) {\n <div role=\"group\">\n @for (child of node.items; track $index) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n node: child,\n path: treeChildrenPath(node, path),\n field: field,\n tpl: tpl,\n parent: node\n }\">\n </ng-container>\n }\n </div>\n }\n </div>\n</ng-template>\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"] }]
15127
+ }], 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 }] }], applyOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyOnSelect", 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"] }] } });
14955
15128
 
14956
15129
  class AggregationListComponent {
14957
15130
  cn = cn;
@@ -14972,6 +15145,18 @@ class AggregationListComponent {
14972
15145
  * @default true
14973
15146
  */
14974
15147
  showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
15148
+ /**
15149
+ * Whether every selection gesture — row click, checkbox click, Enter on the
15150
+ * checkbox — applies the filter immediately, instead of only revealing the
15151
+ * Apply button.
15152
+ *
15153
+ * Independent of the `features.quickFilter` customization flag: `applyOnSelect`
15154
+ * applies even when `quickFilter` is off, and leaves the label-click behavior
15155
+ * untouched when it is off itself.
15156
+ *
15157
+ * @default false
15158
+ */
15159
+ applyOnSelect = input(false, ...(ngDevMode ? [{ debugName: "applyOnSelect", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
14975
15160
  /**
14976
15161
  * When `false`, applying or clearing filters updates the store without
14977
15162
  * synchronizing the URL (no browser history entry, not shareable/restorable).
@@ -15031,7 +15216,7 @@ class AggregationListComponent {
15031
15216
  * input. A directive projected straight onto this component takes precedence.
15032
15217
  */
15033
15218
  itemTemplateInput = input(undefined, ...(ngDevMode ? [{ debugName: "itemTemplateInput", alias: "itemTemplate" }] : [{
15034
- alias: "itemTemplate",
15219
+ alias: "itemTemplate"
15035
15220
  }]));
15036
15221
  resolvedItemTemplate = computed(() => this.itemDirective()?.template ?? this.itemTemplateInput(), ...(ngDevMode ? [{ debugName: "resolvedItemTemplate" }] : []));
15037
15222
  /* selection state */
@@ -15053,8 +15238,8 @@ class AggregationListComponent {
15053
15238
  */
15054
15239
  isAllSelected = computed(() => {
15055
15240
  this.pendingSelection();
15056
- const selectable = this.items().filter((item) => item.count > 0);
15057
- return selectable.length > 0 && selectable.every((item) => item.$selected);
15241
+ const selectable = this.items().filter(item => item.count > 0);
15242
+ return selectable.length > 0 && selectable.every(item => item.$selected);
15058
15243
  }, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
15059
15244
  /* search state */
15060
15245
  searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
@@ -15066,7 +15251,7 @@ class AggregationListComponent {
15066
15251
  syncUrl: this.syncUrl,
15067
15252
  query: this.controlledQuery,
15068
15253
  aggregation: this.controlledAggregation,
15069
- selection: this.controlledSelection,
15254
+ selection: this.controlledSelection
15070
15255
  });
15071
15256
  /* spread from base */
15072
15257
  isControlled = this.base.isControlled;
@@ -15090,7 +15275,7 @@ class AggregationListComponent {
15090
15275
  virtualizer = injectVirtualizer(() => ({
15091
15276
  count: this.items().length,
15092
15277
  estimateSize: () => 32,
15093
- scrollElement: this.scrollElement(),
15278
+ scrollElement: this.scrollElement()
15094
15279
  }));
15095
15280
  aggregation = computed(() => {
15096
15281
  if (this.isControlled()) {
@@ -15130,7 +15315,9 @@ class AggregationListComponent {
15130
15315
  // Reset $selected so items removed from the filter are deselected.
15131
15316
  // Then return a new array reference so Angular's signal equality check
15132
15317
  // detects the change and re-renders all component instances.
15133
- agg.items.forEach(item => { item.$selected = false; });
15318
+ agg.items.forEach(item => {
15319
+ item.$selected = false;
15320
+ });
15134
15321
  const result = [...this.addCurrentFiltersToItems()];
15135
15322
  // Re-apply pending (checked but not-yet-applied) selections so they survive this
15136
15323
  // recompute — e.g. after "Load more" replaces/extends the item list.
@@ -15153,7 +15340,7 @@ class AggregationListComponent {
15153
15340
  // Untracked: keep the click-time mutation authoritative (see items()); pending is only
15154
15341
  // re-applied when suggests() legitimately recomputes the searched list.
15155
15342
  const pending = untracked(() => this.pendingSelection());
15156
- return this.suggests()?.map((suggest) => {
15343
+ return this.suggests()?.map(suggest => {
15157
15344
  const column = this.appStore.getColumn(suggest.category);
15158
15345
  const field = column?.name ?? suggest.category;
15159
15346
  const item = {
@@ -15162,7 +15349,7 @@ class AggregationListComponent {
15162
15349
  display: suggest.display,
15163
15350
  column: field,
15164
15351
  count: Number(suggest.frequency),
15165
- $selected: false,
15352
+ $selected: false
15166
15353
  };
15167
15354
  if (column?.eType === EngineType.bool) {
15168
15355
  item.value = Boolean(item.value);
@@ -15178,7 +15365,7 @@ class AggregationListComponent {
15178
15365
  constructor() {
15179
15366
  this.destroyRef.onDestroy(() => {
15180
15367
  if (this.selection()) {
15181
- this.aggregation()?.items?.forEach((item) => {
15368
+ this.aggregation()?.items?.forEach(item => {
15182
15369
  item.$selected = undefined;
15183
15370
  });
15184
15371
  }
@@ -15206,8 +15393,10 @@ class AggregationListComponent {
15206
15393
  // Only selectable (count > 0) items get checked by `selectItems`; mirror that in the
15207
15394
  // pending intents so a disabled item is not re-checked on the next items() recompute.
15208
15395
  const pending = new Map(this.pendingSelection());
15209
- this.items().forEach(item => { if (item.count > 0)
15210
- pending.set(item.value, true); });
15396
+ this.items().forEach(item => {
15397
+ if (item.count > 0)
15398
+ pending.set(item.value, true);
15399
+ });
15211
15400
  this.pendingSelection.set(pending);
15212
15401
  this.selection.set(true);
15213
15402
  }
@@ -15223,21 +15412,25 @@ class AggregationListComponent {
15223
15412
  this.select();
15224
15413
  }
15225
15414
  }
15226
- apply() {
15415
+ /**
15416
+ * @param opts.keepSearchText Leave the search-within-facet text in place. Used by
15417
+ * `applyOnSelect`, where the user is expected to tick several values in a row and
15418
+ * would otherwise lose the search context on the first one.
15419
+ */
15420
+ apply(opts) {
15227
15421
  const agg = this.aggregation();
15228
15422
  if (!agg)
15229
15423
  return;
15230
15424
  if (this.isControlled()) {
15231
- const values = [...this.items(), ...this.searchedItems()]
15232
- .filter((item) => item.$selected)
15233
- .map((item) => String(item.value));
15425
+ const values = [...this.items(), ...this.searchedItems()].filter(item => item.$selected).map(item => String(item.value));
15234
15426
  this.selectionChange.emit({ kind: "values", values: Array.from(new Set(values)) });
15235
15427
  this.filtersChange.emit(legacyFilterToFilter(this.base.buildAppliedFilter(this.getFilters(), agg)));
15236
15428
  }
15237
15429
  else {
15238
15430
  this.base.applyFilters(this.getFilters(), agg, () => this.clear());
15239
15431
  }
15240
- this.searchText.set("");
15432
+ if (!opts?.keepSearchText)
15433
+ this.searchText.set("");
15241
15434
  this.selection.set(false);
15242
15435
  // Applied selections now live in the filters; clear pending so it doesn't double-apply.
15243
15436
  this.pendingSelection.set(new Map());
@@ -15251,20 +15444,20 @@ class AggregationListComponent {
15251
15444
  return;
15252
15445
  this.aggregationsService
15253
15446
  .loadMore(this.base.resolvedQuery(), agg, undefined, { count: this.base.resolvedAggregationCount(agg) })
15254
- .subscribe((aggregation) => {
15447
+ .subscribe(aggregation => {
15255
15448
  this.base.controlledAggregation.set(aggregation);
15256
15449
  this.cdr.detectChanges();
15257
15450
  });
15258
15451
  return;
15259
15452
  }
15260
15453
  const q = this.queryParamsStore.getQuery();
15261
- this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
15454
+ this.aggregationsService.loadMore(q, this.aggregation()).subscribe(aggregation => {
15262
15455
  this.aggregationsStore.updateAggregation(aggregation);
15263
15456
  this.cdr.detectChanges();
15264
15457
  });
15265
15458
  }
15266
15459
  select() {
15267
- const selectedItems = this.items().filter((item) => item.$selected);
15460
+ const selectedItems = this.items().filter(item => item.$selected);
15268
15461
  this.onSelect.emit(selectedItems);
15269
15462
  this.selection.set(selectedItems.length > 0 || this.hasFilters());
15270
15463
  }
@@ -15275,16 +15468,20 @@ class AggregationListComponent {
15275
15468
  }
15276
15469
  isListItemFiltered(item, field) {
15277
15470
  if (this.isControlled()) {
15278
- return (this.base.selectionValues() ?? []).some((value) => value === item.value);
15471
+ return (this.base.selectionValues() ?? []).some(value => value === item.value);
15279
15472
  }
15280
15473
  const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
15281
15474
  if (!filters)
15282
15475
  return false;
15283
15476
  const values = [item.value];
15284
- return (values.some((v) => v === filters.value) ||
15285
- !!(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
15477
+ return values.some(v => v === filters.value) || !!(filters.values?.length && filters.values.some((value) => values.some(v => v === value)));
15286
15478
  }
15287
15479
  selectListItem(item, e) {
15480
+ // Selection only — never apply from here. `apply()` belongs to the interaction
15481
+ // entry points below, so that `selectAll`/`unselectAll` and the controlled mode
15482
+ // keep their own semantics.
15483
+ // The stopImmediatePropagation() below is also what guarantees a label click
15484
+ // never additionally reaches the row handler (no double apply). Do not remove it.
15288
15485
  e?.stopImmediatePropagation();
15289
15486
  item.$selected = !item.$selected;
15290
15487
  // Track the pending intent by value so it survives an items() recompute (ES-32085).
@@ -15295,24 +15492,43 @@ class AggregationListComponent {
15295
15492
  this.pendingSelection.set(pending);
15296
15493
  this.select();
15297
15494
  }
15495
+ /**
15496
+ * Single decision point for the item interaction paths: select, then apply only
15497
+ * when the caller says so. Never call `apply()` from anywhere else in that path.
15498
+ */
15499
+ commitListSelection(item, event, shouldApply) {
15500
+ this.selectListItem(item, event);
15501
+ if (shouldApply)
15502
+ this.apply({ keepSearchText: true });
15503
+ }
15504
+ /** Row click, and checkbox click/Enter — the "any selection gesture" surface. */
15505
+ onListItemRowClick(item, event) {
15506
+ this.commitListSelection(item, event, this.applyOnSelect());
15507
+ }
15508
+ /**
15509
+ * Label click. `quickFilter` is the app-wide label-only shortcut; `applyOnSelect`
15510
+ * covers the label too. With neither, the click bubbles up to the row handler.
15511
+ */
15298
15512
  onListItemTextClick(item, event) {
15299
- if (this.quickFilter()) {
15300
- this.selectListItem(item, event);
15301
- this.apply();
15302
- }
15513
+ if (!this.quickFilter() && !this.applyOnSelect())
15514
+ return;
15515
+ this.commitListSelection(item, event, true);
15303
15516
  }
15304
15517
  getFilters() {
15305
- const items = this.items().filter((item) => item.$selected);
15306
- const searchedItems = this.searchedItems().filter((item) => item.$selected);
15307
- const currentItems = [...items, ...searchedItems];
15518
+ const items = this.items().filter(item => item.$selected);
15519
+ const searchedItems = this.searchedItems().filter(item => item.$selected);
15520
+ // While a search-within-facet is active, items() IS searchedItems() (same reference),
15521
+ // so a naive concat counts every selected item twice — producing values: ["pdf", "pdf"].
15522
+ // Deduplicate by value; the last occurrence wins, they are the same object anyway.
15523
+ const currentItems = Array.from(new Map([...items, ...searchedItems].map(item => [item.value, item])).values());
15308
15524
  const { column, name, isDistribution = false } = this.aggregation() || {};
15309
- return currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
15525
+ return currentItems.map(item => this.aggregationsService.toFilter(item, column, name, isDistribution));
15310
15526
  }
15311
15527
  addCurrentFiltersToItems() {
15312
15528
  return this.base.addCurrentFiltersToItems(this.aggregation());
15313
15529
  }
15314
15530
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15315
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.26", 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 <!-- Relay the host's `label` projection into the panel's `label` slot. When the\r\n host projects nothing, the local <ng-content> falls back to the icon + display\r\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\r\n <ng-container ngProjectAs=\"label\">\r\n <ng-content select=\"label\">\r\n @let labelIcon = aggregation()?.icon;\r\n @if (labelIcon) {\r\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n </ng-container>\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" }] });
15531
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.26", 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 }, applyOnSelect: { classPropertyName: "applyOnSelect", publicName: "applyOnSelect", 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) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1\" />\n The aggregation component no longer supports tree aggregations. Please use\n the &lt;AggregationTree /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\n <div\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n <div\n class=\"absolute w-full\"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\"\n [attr.aria-selected]=\"item.$selected ?? false\"\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n 'flex grow items-center gap-2 p-1 leading-7',\n item.count === 0 && 'disabled pointer-events-none',\n applyOnSelect() && 'cursor-pointer'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <input\n type=\"checkbox\"\n role=\"checkbox\"\n value=\"{{ item.value }}\"\n [attr.disabled]=\"item.count === 0 ? true : null\"\n [attr.aria-disabled]=\"item.count === 0\"\n (keydown.enter)=\"onListItemRowClick(item, $event)\"\n [checked]=\"item.$selected\" />\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = item.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 break-all text-ellipsis',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isListItemFiltered(item, field)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n (listItemName(item) | syslang)\n : (listItemName(item) | syslang)\n \"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n", 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" }] });
15316
15532
  }
15317
15533
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationListComponent, decorators: [{
15318
15534
  type: Component,
@@ -15325,11 +15541,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
15325
15541
  FaIconComponent,
15326
15542
  TriangleAlertIcon,
15327
15543
  HighlightWordPipe,
15328
- AggregationPanelComponent,
15544
+ AggregationPanelComponent
15329
15545
  ], standalone: true, host: {
15330
- "[class]": 'cn("block h-[inherit] max-h-[inherit]", class())',
15331
- }, 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 <!-- Relay the host's `label` projection into the panel's `label` slot. When the\r\n host projects nothing, the local <ng-content> falls back to the icon + display\r\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\r\n <ng-container ngProjectAs=\"label\">\r\n <ng-content select=\"label\">\r\n @let labelIcon = aggregation()?.icon;\r\n @if (labelIcon) {\r\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n </ng-container>\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"] }]
15332
- }], 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"] }] } });
15546
+ "[class]": 'cn("block h-[inherit] max-h-[inherit]", class())'
15547
+ }, template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1\" />\n The aggregation component no longer supports tree aggregations. Please use\n the &lt;AggregationTree /&gt; component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\n <div\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n <div\n class=\"absolute w-full\"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\"\n [attr.aria-selected]=\"item.$selected ?? false\"\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n 'flex grow items-center gap-2 p-1 leading-7',\n item.count === 0 && 'disabled pointer-events-none',\n applyOnSelect() && 'cursor-pointer'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <input\n type=\"checkbox\"\n role=\"checkbox\"\n value=\"{{ item.value }}\"\n [attr.disabled]=\"item.count === 0 ? true : null\"\n [attr.aria-disabled]=\"item.count === 0\"\n (keydown.enter)=\"onListItemRowClick(item, $event)\"\n [checked]=\"item.$selected\" />\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = item.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 break-all text-ellipsis',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isListItemFiltered(item, field)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n (listItemName(item) | syslang)\n : (listItemName(item) | syslang)\n \"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n", 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"] }]
15548
+ }], 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 }] }], applyOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyOnSelect", 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"] }] } });
15333
15549
 
15334
15550
  /**
15335
15551
  * 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.
@@ -15410,6 +15626,18 @@ class AggregationComponent {
15410
15626
  * @default true
15411
15627
  */
15412
15628
  showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
15629
+ /**
15630
+ * **List/tree only.** Whether every selection gesture — row click, checkbox click,
15631
+ * Enter on the checkbox — applies the filter immediately, instead of only revealing
15632
+ * the Apply button.
15633
+ *
15634
+ * Independent of the `features.quickFilter` customization flag, which governs the
15635
+ * label click only. Ignored by the date variant, which is a radio list: use
15636
+ * `emitOn="selection"` there for the equivalent behavior.
15637
+ *
15638
+ * @default false
15639
+ */
15640
+ applyOnSelect = input(false, ...(ngDevMode ? [{ debugName: "applyOnSelect", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
15413
15641
  expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
15414
15642
  /**
15415
15643
  * Controls whether applying or clearing a filter synchronizes the URL.
@@ -15452,6 +15680,9 @@ class AggregationComponent {
15452
15680
  * emissions to a visible Apply button, matching the list/tree select-then-Apply
15453
15681
  * flow. Ignored outside controlled mode.
15454
15682
  *
15683
+ * The list/tree counterpart of `emitOn="selection"` is `applyOnSelect` — do not
15684
+ * use `emitOn` on those variants, it has no effect there.
15685
+ *
15455
15686
  * @default 'selection'
15456
15687
  */
15457
15688
  emitOn = input("selection", ...(ngDevMode ? [{ debugName: "emitOn" }] : []));
@@ -15494,10 +15725,7 @@ class AggregationComponent {
15494
15725
  *
15495
15726
  * @returns An array of items to be displayed in the aggregation component.
15496
15727
  */
15497
- items = computed(() => this.aggregationDate()?.items() ??
15498
- this.aggregationTree()?.items() ??
15499
- this.aggregationList()?.items() ??
15500
- [], ...(ngDevMode ? [{ debugName: "items" }] : []));
15728
+ items = computed(() => this.aggregationDate()?.items() ?? this.aggregationTree()?.items() ?? this.aggregationList()?.items() ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
15501
15729
  /**
15502
15730
  * A computed property that filters the items to include only those that are currently selected. It uses the `items` computed property to retrieve all items and then applies a filter to return only those with the `$selected` property set to true. This allows the component to easily access and manage the selected items for further processing, such as applying filters or updating the UI based on user selections.
15503
15731
  *
@@ -15546,7 +15774,7 @@ class AggregationComponent {
15546
15774
  return this.appStore.isDateColumn(column);
15547
15775
  }
15548
15776
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15549
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.26", 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 }, emitOn: { classPropertyName: "emitOn", publicName: "emitOn", 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: `
15777
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.26", 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 }, applyOnSelect: { classPropertyName: "applyOnSelect", publicName: "applyOnSelect", 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 }, emitOn: { classPropertyName: "emitOn", publicName: "emitOn", 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: `
15550
15778
  @switch (resolvedType()) {
15551
15779
  @case ("date") {
15552
15780
  <AggregationDate
@@ -15584,6 +15812,7 @@ class AggregationComponent {
15584
15812
  [selection]="controlledSelection()"
15585
15813
  [itemTemplate]="itemDirective()?.template"
15586
15814
  [showCheckbox]="showCheckbox()"
15815
+ [applyOnSelect]="applyOnSelect()"
15587
15816
  [expandedLevel]="expandedLevel()"
15588
15817
  (onSelect)="onSelect.emit($event)"
15589
15818
  (onApply)="onApply.emit($event)"
@@ -15607,6 +15836,7 @@ class AggregationComponent {
15607
15836
  [selection]="controlledSelection()"
15608
15837
  [itemTemplate]="itemDirective()?.template"
15609
15838
  [showCheckbox]="showCheckbox()"
15839
+ [applyOnSelect]="applyOnSelect()"
15610
15840
  (onSelect)="onSelect.emit($event)"
15611
15841
  (onApply)="onApply.emit($event)"
15612
15842
  (onClear)="onClear.emit($event)"
@@ -15615,7 +15845,7 @@ class AggregationComponent {
15615
15845
  />
15616
15846
  }
15617
15847
  }
15618
- `, 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", "emitOn", "title", "displayEmptyDistributionIntervals", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }] });
15848
+ `, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "showCheckbox", "applyOnSelect", "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", "applyOnSelect", "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", "emitOn", "title", "displayEmptyDistributionIntervals", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }] });
15619
15849
  }
15620
15850
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AggregationComponent, decorators: [{
15621
15851
  type: Component,
@@ -15660,6 +15890,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
15660
15890
  [selection]="controlledSelection()"
15661
15891
  [itemTemplate]="itemDirective()?.template"
15662
15892
  [showCheckbox]="showCheckbox()"
15893
+ [applyOnSelect]="applyOnSelect()"
15663
15894
  [expandedLevel]="expandedLevel()"
15664
15895
  (onSelect)="onSelect.emit($event)"
15665
15896
  (onApply)="onApply.emit($event)"
@@ -15683,6 +15914,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
15683
15914
  [selection]="controlledSelection()"
15684
15915
  [itemTemplate]="itemDirective()?.template"
15685
15916
  [showCheckbox]="showCheckbox()"
15917
+ [applyOnSelect]="applyOnSelect()"
15686
15918
  (onSelect)="onSelect.emit($event)"
15687
15919
  (onApply)="onApply.emit($event)"
15688
15920
  (onClear)="onClear.emit($event)"
@@ -15696,7 +15928,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
15696
15928
  "[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
15697
15929
  }
15698
15930
  }]
15699
- }], 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 }] }], emitOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "emitOn", 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"] }] } });
15931
+ }], 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 }] }], applyOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyOnSelect", 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 }] }], emitOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "emitOn", 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"] }] } });
15700
15932
 
15701
15933
  class AsideFiltersComponent {
15702
15934
  cn = cn;
@@ -15757,7 +15989,7 @@ class AsideFiltersComponent {
15757
15989
  showFiltersCount />
15758
15990
  }
15759
15991
  </div>
15760
- `, 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", "emitOn", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }] });
15992
+ `, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "applyOnSelect", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "emitOn", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }] });
15761
15993
  }
15762
15994
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: AsideFiltersComponent, decorators: [{
15763
15995
  type: Component,
@@ -15908,7 +16140,7 @@ class FilterButtonComponent {
15908
16140
  }
15909
16141
  </PopoverContent>
15910
16142
  </Popover>
15911
- `, 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", "emitOn", "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" }] });
16143
+ `, 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", "applyOnSelect", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "emitOn", "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" }] });
15912
16144
  }
15913
16145
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: FilterButtonComponent, decorators: [{
15914
16146
  type: Component,
@@ -16023,7 +16255,7 @@ class MoreComponent {
16023
16255
  [name]="filter.name"
16024
16256
  [column]="filter.column" />
16025
16257
  }
16026
- `, 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", "emitOn", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
16258
+ `, 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", "applyOnSelect", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "emitOn", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
16027
16259
  }
16028
16260
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: MoreComponent, decorators: [{
16029
16261
  type: Component,
@@ -17881,5 +18113,5 @@ const queryNameResolver = () => {
17881
18113
  * Generated bundle index. Do not edit.
17882
18114
  */
17883
18115
 
17884
- 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, __resetSignInMutexForTests, 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, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
18116
+ 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, __resetSignInMutexForTests, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, isAuthRoute, legacyFilterToFilter, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
17885
18117
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map