@sinequa/atomic-angular 0.3.33 → 0.4.19-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,21 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, resource, ViewContainerRef, numberAttribute, viewChildren, afterEveryRender } from '@angular/core';
2
+ import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, resource, ViewContainerRef, viewChildren, numberAttribute, afterEveryRender } from '@angular/core';
3
3
  import { BehaviorSubject, Subscription, catchError, EMPTY, firstValueFrom, map, Subject, of, tap, throwError, filter, shareReplay, fromEvent, debounceTime, from, switchMap } from 'rxjs';
4
4
  import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
5
- import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SwitchComponent, SelectOptionDirective, DialogService, TabsComponent, TabsListComponent, TabComponent, ChevronLeftIconComponent, ChevronsLeftIconComponent, ChevronsRightIconComponent, Separator, SheetCloseDirective, SheetService, LoadingCircleIconComponent, CircleCheckIconComponent, PopoverComponent, HorizontalDividerComponent, CardComponent, CardHeaderComponent, CardContentComponent, CardFooterComponent, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, PopoverContentComponent, BookmarkIcon, UserIcon, TrashIcon, FolderIcon, VerticalDividerComponent, ButtonGroup, SearchIcon, FilterIcon, DateRangePickerDirective, BreakpointObserverService, FlagEnglishIconComponent, FlagFrenchIconComponent, EditIcon, UndoIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
5
+ import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SwitchComponent, SelectOptionDirective, DialogService, TabsComponent, TabsListComponent, TabComponent, ChevronLeftIconComponent, ChevronsLeftIconComponent, ChevronsRightIconComponent, Separator, SheetCloseDirective, SheetService, ButtonGroup, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, SearchIcon, FilterIcon, DateRangePickerDirective, LoadingCircleIconComponent, CircleCheckIconComponent, PopoverComponent, HorizontalDividerComponent, CardComponent, CardHeaderComponent, CardContentComponent, CardFooterComponent, PopoverContentComponent, BookmarkIcon, UserIcon, TrashIcon, FolderIcon, VerticalDividerComponent, BreakpointObserverService, FlagEnglishIconComponent, FlagFrenchIconComponent, EditIcon, UndoIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
6
6
  import highlightWords from 'highlight-words';
7
7
  import { ActivatedRoute, Router, NavigationEnd, RouterLink, 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 { globalConfig, EngineType, extraColumns, sysLang, getQueryParamsFromUrl, logout, login, info, warn, notify, error, setGlobalConfig, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, buildPathsAndLevels, escapeExpr, isAuthenticated, isExpired, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, fetchQuery, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, debug, fetchSimilarDocuments, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, fetchSuggestField, labels, fetchLabels, guid, getRelativeDate, createUserProfile, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
11
- import { HttpClient, HttpParams, httpResource, HttpResponse, HttpHeaders } from '@angular/common/http';
10
+ import { globalConfig, EngineType, extraColumns, sysLang, getQueryParamsFromUrl, logout, login, info, warn, notify, error, setGlobalConfig, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, buildPathsAndLevels, escapeExpr, isAuthenticated, isExpired, debug, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, fetchQuery, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
11
+ import { HttpClient, HttpParams, httpResource, HttpResponse, HttpHeaders, HttpContextToken } from '@angular/common/http';
12
12
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
13
13
  import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
14
14
  import { Title, DomSanitizer } from '@angular/platform-browser';
15
15
  import { cva } from 'class-variance-authority';
16
16
  import * as i1 from '@angular/forms';
17
17
  import { FormsModule, NonNullableFormBuilder, ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
18
+ import { injectVirtualizer } from '@tanstack/angular-virtual';
18
19
  import * as i1$1 from '@angular/cdk/drag-drop';
19
20
  import { DragDropModule } from '@angular/cdk/drag-drop';
20
21
  import * as i2 from '@angular/cdk/a11y';
@@ -321,7 +322,7 @@ function withAppFeatures() {
321
322
  }
322
323
  });
323
324
  // Also include columns from the default index "_"
324
- const schema = app.indexes._.columns;
325
+ const schema = app.indexes._?.columns;
325
326
  if (schema) {
326
327
  Object.keys(schema).forEach((schemaKey) => {
327
328
  const col = schema[schemaKey.toLocaleLowerCase()];
@@ -335,29 +336,29 @@ function withAppFeatures() {
335
336
  });
336
337
  }
337
338
  });
339
+ // Include columns from all queries
340
+ Object.keys(app.queries).forEach((queryKey) => {
341
+ const query = app.queries[queryKey];
342
+ if (query.columnsInfo?.columns) {
343
+ Object.values(query.columnsInfo.columns).forEach((c) => {
344
+ const col = schema[c.name.toLocaleLowerCase()];
345
+ if (!col)
346
+ return;
347
+ columnMap[c.name.toLocaleLowerCase()] = col;
348
+ // Add aliases to the map
349
+ if (c.aliases) {
350
+ c.aliases
351
+ .split(/[,;|]/)
352
+ .map((a) => a.trim())
353
+ .forEach((alias) => {
354
+ columnMap[alias.toLocaleLowerCase()] = col;
355
+ });
356
+ }
357
+ });
358
+ }
359
+ });
360
+ patchState(store, { columnMap });
338
361
  }
339
- // Include columns from all queries
340
- Object.keys(app.queries).forEach((queryKey) => {
341
- const query = app.queries[queryKey];
342
- if (query.columnsInfo?.columns) {
343
- Object.values(query.columnsInfo.columns).forEach((c) => {
344
- const col = schema[c.name.toLocaleLowerCase()];
345
- if (!col)
346
- return;
347
- columnMap[c.name.toLocaleLowerCase()] = col;
348
- // Add aliases to the map
349
- if (c.aliases) {
350
- c.aliases
351
- .split(/[,;|]/)
352
- .map((a) => a.trim())
353
- .forEach((alias) => {
354
- columnMap[alias.toLocaleLowerCase()] = col;
355
- });
356
- }
357
- });
358
- }
359
- });
360
- patchState(store, { columnMap });
361
362
  })));
362
363
  },
363
364
  /**
@@ -373,7 +374,7 @@ function withAppFeatures() {
373
374
  /**
374
375
  * Updates the application state with the provided CCApp object.
375
376
  *
376
- * @param {CCApp} app - The application object containing the new state values.
377
+ * @param {Partial<CCAppState>} app - The application object containing the new state values.
377
378
  */
378
379
  update(app) {
379
380
  patchState(store, (state) => {
@@ -631,7 +632,8 @@ function withAppFeatures() {
631
632
  sources: computed(() => parseCustomJson("sources", {})),
632
633
  filters: computed(() => parseCustomJson("filters", [])),
633
634
  general: computed(() => parseCustomJson("general", {})),
634
- assistants: computed(() => parseCustomJson("assistants", {}))
635
+ assistants: computed(() => parseCustomJson("assistants", {})),
636
+ agents: computed(() => parseCustomJson("agents", {}))
635
637
  };
636
638
  }), withMethods((store) => ({
637
639
  /**
@@ -737,6 +739,12 @@ function withAppFeatures() {
737
739
  if (assistants === undefined || Object.keys(assistants).length === 0)
738
740
  return false;
739
741
  return !!assistants[assistantName]?.defaultValues?.service_id;
742
+ },
743
+ isAgentAllowed(agentName) {
744
+ const agents = store.agents();
745
+ if (agents === undefined || Object.keys(agents).length === 0)
746
+ return false;
747
+ return !!agents[agentName]?.defaultAgent;
740
748
  }
741
749
  })));
742
750
  }
@@ -1105,7 +1113,7 @@ class DropdownInputComponent {
1105
1113
  }
1106
1114
  </DropdownContent>
1107
1115
  </Dropdown>
1108
- `, isInline: true, dependencies: [{ kind: "component", type: DropdownComponent, selector: "dropdown, Dropdown", inputs: ["disabled"] }, { kind: "component", type: DropdownListComponent, selector: "dropdown-list, DropdownList", inputs: ["items"], outputs: ["onClick"] }, { kind: "directive", type: DropdownContentComponent, selector: "dropdown-content, dropdowncontent, DropdownContent", inputs: ["class", "position", "strategy", "offset"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
1116
+ `, isInline: true, dependencies: [{ kind: "component", type: DropdownComponent, selector: "dropdown, Dropdown", inputs: ["disabled"] }, { kind: "component", type: DropdownListComponent, selector: "dropdown-list, DropdownList", inputs: ["items"], outputs: ["onClick"] }, { kind: "directive", type: DropdownContentComponent, selector: "dropdown-content, dropdowncontent, DropdownContent", inputs: ["class", "position", "strategy", "offset"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
1109
1117
  }
1110
1118
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DropdownInputComponent, decorators: [{
1111
1119
  type: Component,
@@ -1221,82 +1229,81 @@ function withExtractsFeatures() {
1221
1229
  })));
1222
1230
  }
1223
1231
 
1224
- class PrincipalService {
1225
- http = inject(HttpClient);
1226
- API_URL = `${globalConfig.backendUrl}/api/v1`;
1227
- /**
1228
- * Retrieves the principal information from the server.
1229
- *
1230
- * @returns Observable<Principal> An observable that emits the principal information.
1231
- *
1232
- * @remarks
1233
- * This method sends a GET request to the API endpoint to fetch the principal data.
1234
- * It includes query parameters to specify the action and to indicate that no authentication is required.
1235
- * In case of an error, it logs the error to the console and returns an empty observable.
1236
- *
1237
- * @example
1238
- * ```typescript
1239
- * principalService.getPrincipal().subscribe(principal => {
1240
- * console.log(principal);
1241
- * });
1242
- * ```
1243
- */
1244
- getPrincipal() {
1245
- const params = new HttpParams().set('action', 'get');
1246
- params.append('noAuthentication', 'true');
1247
- return this.http.get(this.API_URL + '/principal', { params }).pipe(catchError(error => {
1248
- console.error('PrincipalService.getPrincipal failure - error: ', error);
1249
- return EMPTY;
1250
- }));
1232
+ const initialPrincipal = {
1233
+ id: "",
1234
+ id2: "",
1235
+ id3: "",
1236
+ id4: "",
1237
+ id5: "",
1238
+ email: "",
1239
+ name: "",
1240
+ longName: "",
1241
+ userId: "",
1242
+ fullName: "",
1243
+ isAdministrator: false,
1244
+ isDelegatedAdmin: false,
1245
+ description: "",
1246
+ param1: "",
1247
+ param2: "",
1248
+ param3: "",
1249
+ param4: "",
1250
+ param5: "",
1251
+ param6: "",
1252
+ param7: "",
1253
+ param8: "",
1254
+ param9: "",
1255
+ param10: "",
1256
+ userOverrideActive: false,
1257
+ state: 'initial'
1258
+ };
1259
+ const PrincipalStore = signalStore({ providedIn: 'root' }, withDevtools('Principal'), withState(initialPrincipal), withComputed((store) => ({
1260
+ allowUserOverride: computed(() => store.isAdministrator() && !store.userOverrideActive()),
1261
+ isOverridingUser: computed(() => store.userOverrideActive()),
1262
+ initials: computed(() => {
1263
+ const fullName = store.fullName?.();
1264
+ if (!fullName)
1265
+ return '';
1266
+ return fullName
1267
+ .split(' ')
1268
+ .map(word => word[0]?.toUpperCase())
1269
+ .join('');
1270
+ }),
1271
+ })), withMethods((store) => ({
1272
+ /**
1273
+ * @deprecated use getState(PrincipalStore))
1274
+ */
1275
+ principal() {
1276
+ const { userOverrideActive: _, ...rest } = getState(store);
1277
+ // this block cannot be put in a computed due to dynamic construction object with signals, which is not supported in computed.
1278
+ // using a method allows us to construct the principal object on each call, ensuring that the signals are properly evaluated and returned as values.
1279
+ const principal = Object.entries(rest).reduce((acc, [k, v]) => {
1280
+ acc[k] = v;
1281
+ return acc;
1282
+ }, {});
1283
+ return principal;
1251
1284
  }
1252
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PrincipalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1253
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PrincipalService, providedIn: 'root' });
1254
- }
1255
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PrincipalService, decorators: [{
1256
- type: Injectable,
1257
- args: [{
1258
- providedIn: 'root'
1259
- }]
1260
- }] });
1261
-
1262
- const PrincipalStore = signalStore({ providedIn: 'root' }, withDevtools('Principal'), withState({ principal: {}, userOverrideActive: false }), withComputed(({ principal, userOverrideActive }) => ({
1263
- allowUserOverride: computed(() => principal().isAdministrator && userOverrideActive() === false),
1264
- isOverridingUser: computed(() => userOverrideActive() === true)
1265
1285
  })), withPrincipalFeatures());
1266
- /**
1267
- * Enhances the store with principal-related features.
1268
- *
1269
- * This function integrates state, methods, and computed properties related to the principal.
1270
- *
1271
- * @returns A feature that can be added to a signal store.
1272
- *
1273
- * @feature
1274
- * - State:
1275
- * - `principal`: An object representing the principal.
1276
- * - `userOverrideActive`: A boolean indicating if user override is active.
1277
- *
1278
- * - Methods:
1279
- * - `initialize()`: Initializes the principal state by fetching the principal data from the `PrincipalService`.
1280
- *
1281
- * - Computed Properties:
1282
- * - `allowUserOverride`: A computed boolean indicating if user override is allowed based on the principal's administrator status and the `userOverrideActive` state.
1283
- * - `isOverridingUser`: A computed boolean indicating if the user override is currently active.
1284
- */
1285
1286
  function withPrincipalFeatures() {
1286
- return signalStoreFeature(withMethods((store, principalService = inject(PrincipalService)) => ({
1287
- /**
1288
- * Initializes the principal store by fetching the principal data from the principal service.
1289
- * It patches the store with the fetched principal data and a user override active flag from the global configuration.
1290
- *
1291
- * @returns A promise that resolves when the principal data has been fetched and the store has been patched.
1292
- */
1293
- initialize() {
1294
- return firstValueFrom(principalService.getPrincipal().pipe(map(principal => {
1295
- const { userOverrideActive = false } = globalConfig;
1296
- patchState(store, { principal, userOverrideActive });
1297
- })));
1298
- }
1299
- })));
1287
+ return signalStoreFeature(withMethods((store) => {
1288
+ const http = inject(HttpClient);
1289
+ const API_URL = `${globalConfig.backendUrl}/api/v1`;
1290
+ return {
1291
+ initialize() {
1292
+ patchState(store, { state: 'loading' });
1293
+ const params = new HttpParams()
1294
+ .set('action', 'get')
1295
+ .set('noAuthentication', 'true');
1296
+ return firstValueFrom(http.get(`${API_URL}/principal`, { params }).pipe(catchError((error) => {
1297
+ console.error('Principal fetch failed', error);
1298
+ patchState(store, { state: 'error' });
1299
+ throw error;
1300
+ }), map((principal) => {
1301
+ const { userOverrideActive = false } = globalConfig;
1302
+ patchState(store, { ...initialPrincipal, ...principal, userOverrideActive, state: 'loaded' });
1303
+ })));
1304
+ }
1305
+ };
1306
+ }));
1300
1307
  }
1301
1308
 
1302
1309
  /**
@@ -1403,7 +1410,7 @@ function getQueryNameFromRoute() {
1403
1410
  const { t, n } = route.snapshot.queryParams;
1404
1411
  // ! This is a workaround for the case where the queryName is not defined in the route data
1405
1412
  // "search" is the parent route of the search results, and "t" is the query parameter for the tab
1406
- const { queryName } = routes.config.filter(p => p.path === 'search')[0].children?.find(route => route.path === t)?.data || n || {};
1413
+ const { queryName } = routes.config.filter(p => p.path === 'search')[0]?.children?.find(route => route.path === t)?.data || n || {};
1407
1414
  if (queryName)
1408
1415
  return queryName;
1409
1416
  // fallback to queryName from route data
@@ -1766,7 +1773,7 @@ function withQueryParamsFeatures() {
1766
1773
  /**
1767
1774
  * Updates the filter in the store's state.
1768
1775
  *
1769
- * @param filter - The filter to be updated. If the filter is `undefined`, the state remains unchanged.
1776
+ * @param newFilter - The filter to be updated. If the filter is `undefined`, the state remains unchanged.
1770
1777
  *
1771
1778
  * The function performs the following operations:
1772
1779
  * - Adds the filter to the state if it doesn't already exist and has values.
@@ -1780,66 +1787,75 @@ function withQueryParamsFeatures() {
1780
1787
  * - If the filter's operator is not 'between' and `filters`, `value`, and `values` are all `undefined`, the filter is removed.
1781
1788
  * - If the filter already exists, its values are updated.
1782
1789
  */
1783
- updateFilter(filter, audit) {
1790
+ updateFilter(newFilter, audit) {
1784
1791
  patchState(store, (state) => {
1785
- if (filter === undefined)
1792
+ if (newFilter === undefined)
1786
1793
  return state;
1787
- const allFilters = state.filters || [];
1794
+ // is filter contains values, check if the operator is set, otherwise set operator to "in" by default
1795
+ if (newFilter.values && !newFilter.operator) {
1796
+ newFilter.operator = "in";
1797
+ }
1798
+ const currentActiveFilters = state.filters || [];
1788
1799
  let newState;
1789
- const existingFilter = allFilters.find((f) => f.field === filter.field);
1800
+ const filterFound = currentActiveFilters.find((f) => f.field === newFilter.field);
1790
1801
  // if the filter has no name (might be coming from a metadata appliance)
1791
- if (!filter.name || !existingFilter?.name) {
1802
+ if (!newFilter.name || !filterFound?.name) {
1792
1803
  // cancel if a filter is found for the current field which contains the value we want to add
1793
- if (existingFilter &&
1794
- filter.value &&
1795
- (existingFilter.value?.toLowerCase() === filter.value?.toLowerCase() ||
1796
- existingFilter.values?.some((v) => v?.toLowerCase() === filter.value?.toLowerCase())))
1804
+ if (filterFound &&
1805
+ newFilter.value &&
1806
+ (filterFound.value?.toLowerCase() === newFilter.value?.toLowerCase() ||
1807
+ filterFound.values?.some((v) => v?.toLowerCase() === newFilter.value?.toLowerCase()))) {
1797
1808
  return state;
1798
- if (existingFilter) {
1799
- // if filter found for the field without the value we want to add, merge the filters and values
1800
- const values = (existingFilter.values || []).concat(filter.values || []);
1801
- if (filter.value)
1802
- values.push(filter.value);
1803
- if (existingFilter.value)
1804
- values.push(existingFilter.value);
1809
+ }
1810
+ if (filterFound) {
1811
+ // if filter found for the field without a value property we want to add, merge the filters and values
1812
+ // but merge values without duplicates
1813
+ const values = Array.from(new Set([...(filterFound.values || []), ...(newFilter.values || [])].filter(Boolean)));
1814
+ // add the new value if it exists and is not already in the existing values
1815
+ if (newFilter.value)
1816
+ values.push(newFilter.value);
1817
+ if (filterFound.value)
1818
+ values.push(filterFound.value);
1805
1819
  const filterToApply = {
1806
- field: filter.field,
1820
+ field: newFilter.field,
1807
1821
  values,
1808
- name: existingFilter.name || filter.name
1822
+ name: filterFound.name || newFilter.name,
1823
+ operator: newFilter.operator || filterFound.operator || "in"
1809
1824
  };
1810
- const existingIndex = allFilters.findIndex((f) => f.field === filter.field);
1811
- const filters = allFilters.toSpliced(existingIndex, 1, filterToApply);
1825
+ // find the index of the existing filter for the field and replace it with the merged filter
1826
+ const existingIndex = currentActiveFilters.findIndex((f) => f.field === newFilter.field);
1827
+ const filters = currentActiveFilters.toSpliced(existingIndex, 1, filterToApply);
1812
1828
  newState = { ...state, filters };
1813
1829
  }
1814
1830
  else {
1815
1831
  // Add filter if it doesn't exist and has values
1816
- if (!existingFilter) {
1817
- newState = { ...state, filters: [...(allFilters || []), filter] };
1832
+ if (!filterFound) {
1833
+ newState = { ...state, filters: [...(currentActiveFilters || []), newFilter] };
1818
1834
  }
1819
1835
  }
1820
1836
  }
1821
1837
  else {
1822
1838
  // search filter by name not by field because many filters can have the same field but different names
1823
- const existing = allFilters.findIndex((f) => f.name === filter.name);
1839
+ const existing = currentActiveFilters.findIndex((f) => f.name === newFilter.name);
1824
1840
  // Add filter if it doesn't exist and has values
1825
1841
  if (existing === -1) {
1826
- newState = { ...state, filters: [...(allFilters || []), filter] };
1842
+ newState = { ...state, filters: [...(currentActiveFilters || []), newFilter] };
1827
1843
  }
1828
1844
  if (existing >= 0) {
1829
1845
  // remove filter if between operator is selected but no values are selected
1830
- if (filter.operator === "between" && filter.start === undefined && filter.end === undefined) {
1831
- newState = { ...state, filters: allFilters.filter((f) => f.name !== filter.name) };
1846
+ if (newFilter.operator === "between" && newFilter.start === undefined && newFilter.end === undefined) {
1847
+ newState = { ...state, filters: currentActiveFilters.filter((f) => f.name !== newFilter.name) };
1832
1848
  }
1833
1849
  // Remove filter if no values are selected
1834
- if (filter.operator !== "between" &&
1835
- filter.filters === undefined &&
1836
- filter.value === undefined &&
1837
- filter.values === undefined) {
1838
- newState = { ...state, filters: (allFilters || []).filter((f) => f?.name !== filter.name) };
1850
+ if (newFilter.operator !== "between" &&
1851
+ newFilter.filters === undefined &&
1852
+ newFilter.value === undefined &&
1853
+ newFilter.values === undefined) {
1854
+ newState = { ...state, filters: (currentActiveFilters || []).filter((f) => f?.name !== newFilter.name) };
1839
1855
  }
1840
1856
  // Update filter values
1841
1857
  if (existing >= 0) {
1842
- const filters = allFilters.toSpliced(existing, 1, filter);
1858
+ const filters = currentActiveFilters.toSpliced(existing, 1, newFilter);
1843
1859
  newState = { ...state, filters };
1844
1860
  }
1845
1861
  }
@@ -1992,7 +2008,8 @@ function withQueryParamsFeatures() {
1992
2008
  let { name } = getState(store);
1993
2009
  if (!name) {
1994
2010
  const routeData = route.snapshot.firstChild?.children[0]?.data;
1995
- name = routeData ? routeData["queryName"] : appStore.getDefaultQuery()?.name || "";
2011
+ const queryName = routeData?.["queryName"];
2012
+ name = queryName || appStore.getDefaultQuery()?.name || "";
1996
2013
  }
1997
2014
  let text = queryText;
1998
2015
  // remove concepts filters from the query to add them in the query expression
@@ -2811,6 +2828,7 @@ class QueryService {
2811
2828
  queryParamsStore = inject(QueryParamsStore);
2812
2829
  transloco = inject(TranslocoService);
2813
2830
  API_URL = `${globalConfig.backendUrl}/api/v1`;
2831
+ API_V2_URL = `${globalConfig.backendUrl}/api/v2`;
2814
2832
  // Represents the last result of a search operation with getResult().
2815
2833
  result = signal({}, ...(ngDevMode ? [{ debugName: "result" }] : []));
2816
2834
  audit;
@@ -2968,6 +2986,34 @@ class QueryService {
2968
2986
  };
2969
2987
  this.queryParamsStore.patch({ page }, audit);
2970
2988
  }
2989
+ /**
2990
+ * Get the pages associated to some text locations.
2991
+ *
2992
+ * @param docs - all documents offsets we want to get the pages from.
2993
+ * @returns An Observable that emits the found pages.
2994
+ */
2995
+ getDocPages(docs) {
2996
+ const queryName = this.appStore.getDefaultQuery()?.name || "_default";
2997
+ const appName = globalConfig.app;
2998
+ return this.http.post(`${this.API_V2_URL}/app/${appName}/query/${queryName}/doc-pages`, { docs }).pipe(map(({ items }) => items), catchError(err => {
2999
+ error('queryService.getDocPages failure - error: ', err);
3000
+ return [];
3001
+ }));
3002
+ }
3003
+ /**
3004
+ * Get the page associated to a single text location.
3005
+ *
3006
+ * @param id - the document id.
3007
+ * @param offset - offset from the passage location.
3008
+ * @param length - length from the passage location.
3009
+ * @returns An Observable that emits the found page.
3010
+ */
3011
+ getDocPage(id, offset, length) {
3012
+ return this.getDocPages([{ id, textLocations: [{ offset, length }] }]).pipe(map((response) => response[0].pages[0].pageNumber), catchError(err => {
3013
+ error('queryService.getDocPage failure - error: ', err);
3014
+ return [];
3015
+ }));
3016
+ }
2971
3017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: QueryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2972
3018
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: QueryService, providedIn: "root" });
2973
3019
  }
@@ -3066,11 +3112,14 @@ class AggregationsService {
3066
3112
  const agg = this.getAggregation(name, column);
3067
3113
  if (!agg)
3068
3114
  return null;
3069
- const { items = [], display = agg.name, icon, hidden, searchable = true } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
3115
+ const { items = [], display = agg.name, icon, hidden, expandedLevel, searchable = true } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
3070
3116
  agg.display = display;
3071
3117
  agg.icon = icon;
3072
3118
  agg.hidden = hidden;
3073
3119
  agg.searchable = searchable;
3120
+ if (agg.isTree && expandedLevel) {
3121
+ agg.expandedLevel = expandedLevel;
3122
+ }
3074
3123
  // if the aggregation's column is a "concepts", disable the search
3075
3124
  if (this.nonSearchableColumns.some((column) => column === agg.column)) {
3076
3125
  agg.searchable = false;
@@ -3283,6 +3332,7 @@ function AuthGuard() {
3283
3332
  const { loginPath, useCredentials, useSSO } = globalConfig;
3284
3333
  if (state.url.startsWith("/login"))
3285
3334
  return true;
3335
+ // If the user is not authenticated, navigate to the login page or loading page based on the authentication method
3286
3336
  if (!isAuthenticated() && !useSSO) {
3287
3337
  if (useCredentials) {
3288
3338
  router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
@@ -3292,80 +3342,41 @@ function AuthGuard() {
3292
3342
  }
3293
3343
  return false;
3294
3344
  }
3345
+ // If the user is authenticated, initialize the principal store if it's in the initial state
3295
3346
  try {
3296
- if (!principalStore.principal?.()) {
3347
+ if (principalStore.state() === "initial") {
3297
3348
  await principalStore.initialize();
3298
3349
  }
3299
3350
  }
3300
3351
  catch {
3352
+ error("Failed to initialize PrincipalStore. Redirecting to login.");
3353
+ // TODO: we might want to navigate to an error page instead of the login page in SSO mode, as the issue is not related to authentication but rather to fetching user details
3301
3354
  router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3302
3355
  return false;
3303
3356
  }
3304
- const p = principalStore.principal?.();
3305
- if (!p) {
3357
+ // check if the "princiapl" infos are loaded, if not navigate to the login page
3358
+ const { passwordExpirationDate, editablePartition, name, state: principalState } = getState(principalStore);
3359
+ if (principalState !== "loaded") {
3360
+ info("PrincipalStore state is still initial after initialization attempt. Redirecting to login.");
3306
3361
  router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3307
3362
  return false;
3308
3363
  }
3309
- const exp = p.passwordExpirationDate;
3310
- const editable = !!p.editablePartition;
3311
- if (editable && exp && isExpired(exp)) {
3312
- sessionStorage.setItem("passwordExpiredFlow", "true");
3313
- const username = (p.name || "").trim();
3314
- router.navigate(["/login"], {
3315
- queryParams: { mode: "changepassword", alert: "passwordExpired", username, returnUrl: state.url }
3316
- });
3317
- return false;
3318
- }
3319
- return true;
3320
- };
3321
- }
3322
-
3323
- /**
3324
- * Password-expiry guard: blocks navigation when the password is expired
3325
- * and redirects to the change-password .
3326
- */
3327
- function PasswordExpiryGuard() {
3328
- return async (_, state) => {
3329
- const router = inject(Router);
3330
- const principalStore = inject(PrincipalStore);
3331
- const { loginPath, useCredentials, useSSO } = globalConfig;
3332
- if (!isAuthenticated() && !useSSO) {
3333
- if (useCredentials) {
3334
- router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3335
- }
3336
- else {
3337
- router.navigate(["loading"], { queryParams: { returnUrl: state.url } });
3338
- }
3339
- return false;
3340
- }
3341
- try {
3342
- if (!principalStore.principal?.()) {
3343
- await principalStore.initialize();
3364
+ // check if the password is expired and if the partition is editable
3365
+ // changing password is only possible when user use credentials to auhtenticate
3366
+ // only in credentials mode
3367
+ if (useCredentials) {
3368
+ const exp = passwordExpirationDate;
3369
+ const editable = !!editablePartition;
3370
+ if (editable && exp && isExpired(exp)) {
3371
+ sessionStorage.setItem("passwordExpiredFlow", "true");
3372
+ const username = (name || "").trim();
3373
+ router.navigate(["/login"], {
3374
+ queryParams: { mode: "changepassword", alert: "passwordExpired", username, returnUrl: state.url }
3375
+ });
3376
+ return false;
3344
3377
  }
3345
3378
  }
3346
- catch {
3347
- router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3348
- return false;
3349
- }
3350
- const p = principalStore.principal?.();
3351
- if (!p) {
3352
- router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3353
- return false;
3354
- }
3355
- const exp = p.passwordExpirationDate;
3356
- const editable = !!p.editablePartition;
3357
- if (!editable || !exp) {
3358
- router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3359
- return false;
3360
- }
3361
- if (isExpired(exp)) {
3362
- sessionStorage.setItem("passwordExpiredFlow", "true");
3363
- const username = (p.name || "").trim();
3364
- router.navigate(["/login"], {
3365
- queryParams: { mode: "changepassword", alert: "passwordExpired", username, returnUrl: state.url }
3366
- });
3367
- return false;
3368
- }
3379
+ debug("User is authenticated and principal store is initialized. Access granted.");
3369
3380
  return true;
3370
3381
  };
3371
3382
  }
@@ -3547,7 +3558,7 @@ class ApplicationService {
3547
3558
  const searchPath = this.router.config.find((route) => route.path === "search") || {
3548
3559
  path: "search",
3549
3560
  component: this.components.find((c) => c.path === "search")?.component || this.defaultLayoutComponent(),
3550
- canActivate: [AuthGuard(), PasswordExpiryGuard()],
3561
+ canActivate: [AuthGuard()],
3551
3562
  children: []
3552
3563
  };
3553
3564
  const wildcardPath = searchPath.children?.find((route) => route.path === "**") || {
@@ -4018,6 +4029,7 @@ class PreviewService {
4018
4029
  highlights = inject(HIGHLIGHTS);
4019
4030
  previewData;
4020
4031
  iframe;
4032
+ passageOffset = signal(undefined, ...(ngDevMode ? [{ debugName: "passageOffset" }] : []));
4021
4033
  highlightCategory = "extractslocations";
4022
4034
  extracts = ["matchlocations", "extractslocations", "matchingpassages"];
4023
4035
  entities = ["company", "geo", "person"];
@@ -4135,11 +4147,12 @@ class PreviewService {
4135
4147
  }
4136
4148
  /**
4137
4149
  * Closes the preview with the specified ID and updates the audit log.
4138
- *
4139
- * @param id - The ID of the preview to close.
4140
- * @param query - The partial query object used to retrieve the preview detail.
4141
- */
4150
+ *
4151
+ * @param id - The ID of the preview to close.
4152
+ * @param query - The partial query object used to retrieve the preview detail.
4153
+ */
4142
4154
  close(id, query) {
4155
+ this.passageOffset.set(undefined);
4143
4156
  const detail = this.getAuditPreviewDetail(id, query);
4144
4157
  const auditEvent = {
4145
4158
  type: "Preview_Close" /* AuditEventType.Preview_Close */,
@@ -4390,6 +4403,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4390
4403
  }]
4391
4404
  }], ctorParameters: () => [{ type: i0.DestroyRef }] });
4392
4405
 
4406
+ /**
4407
+ * @deprecated This service is deprecated and should not be used directly. Please use the PrincipalStore instead.
4408
+ */
4409
+ class PrincipalService {
4410
+ http = inject(HttpClient);
4411
+ API_URL = `${globalConfig.backendUrl}/api/v1`;
4412
+ /**
4413
+ * Retrieves the principal information from the server.
4414
+ *
4415
+ * @returns Observable<Principal> An observable that emits the principal information.
4416
+ *
4417
+ * @remarks
4418
+ * This method sends a GET request to the API endpoint to fetch the principal data.
4419
+ * It includes query parameters to specify the action and to indicate that no authentication is required.
4420
+ * In case of an error, it logs the error to the console and returns an empty observable.
4421
+ *
4422
+ * @example
4423
+ * ```typescript
4424
+ * principalService.getPrincipal().subscribe(principal => {
4425
+ * console.log(principal);
4426
+ * });
4427
+ * ```
4428
+ */
4429
+ getPrincipal() {
4430
+ const params = new HttpParams().set('action', 'get');
4431
+ params.append('noAuthentication', 'true');
4432
+ return this.http.get(this.API_URL + '/principal', { params }).pipe(catchError(error => {
4433
+ console.error('PrincipalService.getPrincipal failure - error: ', error);
4434
+ return EMPTY;
4435
+ }));
4436
+ }
4437
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PrincipalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4438
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PrincipalService, providedIn: 'root' });
4439
+ }
4440
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PrincipalService, decorators: [{
4441
+ type: Injectable,
4442
+ args: [{
4443
+ providedIn: 'root'
4444
+ }]
4445
+ }] });
4446
+
4393
4447
  const SAVED_SEARCHES_MAX_STORAGE = 100;
4394
4448
  class SavedSearchesService {
4395
4449
  userSettingsStore = inject(UserSettingsStore);
@@ -5271,7 +5325,7 @@ class LoadingComponent {
5271
5325
  }
5272
5326
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: LoadingComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
5273
5327
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: LoadingComponent, isStandalone: true, selector: "app-wait", ngImport: i0, template: `
5274
- <div class="flex h-[100dvh] w-full items-center justify-center">
5328
+ <div class="flex h-dvh w-full items-center justify-center">
5275
5329
  <div class="flex flex-col items-center space-y-4">
5276
5330
  <span class="loader"></span>
5277
5331
  </div>
@@ -5281,7 +5335,7 @@ class LoadingComponent {
5281
5335
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: LoadingComponent, decorators: [{
5282
5336
  type: Component,
5283
5337
  args: [{ selector: 'app-wait', standalone: true, imports: [], template: `
5284
- <div class="flex h-[100dvh] w-full items-center justify-center">
5338
+ <div class="flex h-dvh w-full items-center justify-center">
5285
5339
  <div class="flex flex-col items-center space-y-4">
5286
5340
  <span class="loader"></span>
5287
5341
  </div>
@@ -5522,7 +5576,7 @@ class CollectionsDialog {
5522
5576
  </DialogFooter>
5523
5577
  </DialogContent>
5524
5578
  </dialog>
5525
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
5579
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
5526
5580
  }
5527
5581
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CollectionsDialog, decorators: [{
5528
5582
  type: Component,
@@ -5730,7 +5784,7 @@ class ExportDialog {
5730
5784
  destroyRef = inject(DestroyRef);
5731
5785
  open(selection) {
5732
5786
  const app = getState(this.appStore);
5733
- const webservice = app.queryExport;
5787
+ const webservice = app.queryExport.toLocaleLowerCase();
5734
5788
  let queryExport;
5735
5789
  if (selection && selection.length > 0)
5736
5790
  this.selection.set(selection);
@@ -7339,7 +7393,7 @@ class PreviewNavigator {
7339
7393
  [disabled]="isLast()">
7340
7394
  <chevrons-right />
7341
7395
  </button>
7342
- `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: ChevronLeftIconComponent, selector: "chevron-left, ChevronLeft, chevronleft", inputs: ["class"] }, { kind: "component", type: ChevronsLeftIconComponent, selector: "chevrons-left, ChevronsLeft, chevronsleft", inputs: ["class"] }, { kind: "component", type: ChevronsRightIconComponent, selector: "chevrons-right, ChevronsRight, chevronsright", inputs: ["class"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }] });
7396
+ `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: ChevronLeftIconComponent, selector: "chevron-left, ChevronLeft, chevronleft", inputs: ["class"] }, { kind: "component", type: ChevronsLeftIconComponent, selector: "chevrons-left, ChevronsLeft, chevronsleft", inputs: ["class"] }, { kind: "component", type: ChevronsRightIconComponent, selector: "chevrons-right, ChevronsRight, chevronsright", inputs: ["class"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }] });
7343
7397
  }
7344
7398
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PreviewNavigator, decorators: [{
7345
7399
  type: Component,
@@ -8023,7 +8077,7 @@ class AdvancedFiltersComponent {
8023
8077
  return res;
8024
8078
  }
8025
8079
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AdvancedFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8026
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AdvancedFiltersComponent, isStandalone: true, selector: "advanced-filters", host: { classAttribute: "contents" }, providers: [SyslangPipe, TranslocoPipe, provideTranslocoScope("drawers")], ngImport: i0, template: "<div class=\"flex h-full flex-col overflow-auto\">\n <sheet-navbar class=\"border-foreground/10 block border-b pb-2\">\n <button [attr.title]=\"'drawers.search' | transloco\" (click)=\"onSearch()\">\n {{ 'drawers.search' | transloco }}\n </button>\n </sheet-navbar>\n\n <div class=\"flex h-full grow flex-col overflow-auto p-4\">\n <section class=\"flex flex-col gap-4\" [formGroup]=\"form\">\n <!-- FIND IN -->\n <h1 class=\"text-xl font-bold\">{{ 'drawers.findInContent' | transloco }}</h1>\n <div class=\"flex items-center gap-4\" formGroupName=\"content\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInContent' | transloco }}</span>\n <select\n id=\"content-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input\n [variant]=\"inputVariant()\"\n id=\"content-value\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [placeholder]=\"getPlaceholder('content.operator')\"\n formControlName=\"value\" />\n </div>\n @if (enableFieldedSearch()) {\n <div class=\"flex items-center gap-4\" formGroupName=\"title\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInTitle' | transloco }}</span>\n <select\n id=\"title-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input id=\"title-value\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [placeholder]=\"getPlaceholder('title.operator')\" formControlName=\"value\" />\n </div>\n }\n\n <!-- TABS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.inScope' | transloco }}&nbsp;&quot;{{ currentTab() }}&quot;</h1>\n\n <Tabs>\n <TabsList variant=\"ghost\">\n @for (tab of tabs(); track $index) {\n <Tab class=\"w-fit\" [value]=\"tab.path\" [active]=\"tab.path === currentTab()\" (click)=\"onTabChange(tab.path)\">\n {{ tab.display | syslang | transloco }}\n </Tab>\n }\n </TabsList>\n </Tabs>\n\n <!-- FILTERS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.applyFilters' | transloco }}</h1>\n @for (filter of filters(); track $index) {\n <DropdownInput\n [suggestions]=\"suggestions()\"\n [selected]=\"getItems(filter.column)\"\n [label]=\"filter.display || filter.alias | syslang | transloco\"\n [placeholder]=\"'drawers.startTyping' | transloco\"\n [noResultLabel]=\"'drawers.noResult' | transloco\"\n (onFocus)=\"setFilterFocus($event, filter)\"\n (onKeyUp)=\"onInputTyping($event)\"\n (removeItem)=\"removeItem($event, filter)\"\n (addItem)=\"addItem($event, filter)\" />\n } @empty {\n {{ 'drawers.noFilters' | transloco }}\n }\n </section>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: DropdownInputComponent, selector: "dropdown-input, DropdownInput", inputs: ["label", "placeholder", "noResultLabel", "suggestions", "selected"], outputs: ["onFocus", "onKeyUp", "removeItem", "addItem"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "component", type: SheetNavbarComponent, selector: "SheetNavbar, sheetnavbar, sheet-navbar" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
8080
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AdvancedFiltersComponent, isStandalone: true, selector: "advanced-filters", host: { classAttribute: "contents" }, providers: [SyslangPipe, TranslocoPipe, provideTranslocoScope("drawers")], ngImport: i0, template: "<div class=\"flex h-full flex-col overflow-auto\">\n <sheet-navbar class=\"border-foreground/10 block border-b pb-2\">\n <button [attr.title]=\"'drawers.search' | transloco\" (click)=\"onSearch()\">\n {{ 'drawers.search' | transloco }}\n </button>\n </sheet-navbar>\n\n <div class=\"flex h-full grow flex-col overflow-auto p-4\">\n <section class=\"flex flex-col gap-4\" [formGroup]=\"form\">\n <!-- FIND IN -->\n <h1 class=\"text-xl font-bold\">{{ 'drawers.findInContent' | transloco }}</h1>\n <div class=\"flex items-center gap-4\" formGroupName=\"content\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInContent' | transloco }}</span>\n <select\n id=\"content-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input\n [variant]=\"inputVariant()\"\n id=\"content-value\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [placeholder]=\"getPlaceholder('content.operator')\"\n formControlName=\"value\" />\n </div>\n @if (enableFieldedSearch()) {\n <div class=\"flex items-center gap-4\" formGroupName=\"title\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInTitle' | transloco }}</span>\n <select\n id=\"title-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input id=\"title-value\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [placeholder]=\"getPlaceholder('title.operator')\" formControlName=\"value\" />\n </div>\n }\n\n <!-- TABS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.inScope' | transloco }}&nbsp;&quot;{{ currentTab() }}&quot;</h1>\n\n <Tabs>\n <TabsList variant=\"ghost\">\n @for (tab of tabs(); track $index) {\n <Tab class=\"w-fit\" [value]=\"tab.path\" [active]=\"tab.path === currentTab()\" (click)=\"onTabChange(tab.path)\">\n {{ tab.display | syslang | transloco }}\n </Tab>\n }\n </TabsList>\n </Tabs>\n\n <!-- FILTERS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.applyFilters' | transloco }}</h1>\n @for (filter of filters(); track $index) {\n <DropdownInput\n [suggestions]=\"suggestions()\"\n [selected]=\"getItems(filter.column)\"\n [label]=\"filter.display || filter.alias | syslang | transloco\"\n [placeholder]=\"'drawers.startTyping' | transloco\"\n [noResultLabel]=\"'drawers.noResult' | transloco\"\n (onFocus)=\"setFilterFocus($event, filter)\"\n (onKeyUp)=\"onInputTyping($event)\"\n (removeItem)=\"removeItem($event, filter)\"\n (addItem)=\"addItem($event, filter)\" />\n } @empty {\n {{ 'drawers.noFilters' | transloco }}\n }\n </section>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: DropdownInputComponent, selector: "dropdown-input, DropdownInput", inputs: ["label", "placeholder", "noResultLabel", "suggestions", "selected"], outputs: ["onFocus", "onKeyUp", "removeItem", "addItem"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "component", type: SheetNavbarComponent, selector: "SheetNavbar, sheetnavbar, sheet-navbar" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
8027
8081
  }
8028
8082
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AdvancedFiltersComponent, decorators: [{
8029
8083
  type: Component,
@@ -8043,57 +8097,777 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8043
8097
  }, template: "<div class=\"flex h-full flex-col overflow-auto\">\n <sheet-navbar class=\"border-foreground/10 block border-b pb-2\">\n <button [attr.title]=\"'drawers.search' | transloco\" (click)=\"onSearch()\">\n {{ 'drawers.search' | transloco }}\n </button>\n </sheet-navbar>\n\n <div class=\"flex h-full grow flex-col overflow-auto p-4\">\n <section class=\"flex flex-col gap-4\" [formGroup]=\"form\">\n <!-- FIND IN -->\n <h1 class=\"text-xl font-bold\">{{ 'drawers.findInContent' | transloco }}</h1>\n <div class=\"flex items-center gap-4\" formGroupName=\"content\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInContent' | transloco }}</span>\n <select\n id=\"content-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input\n [variant]=\"inputVariant()\"\n id=\"content-value\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [placeholder]=\"getPlaceholder('content.operator')\"\n formControlName=\"value\" />\n </div>\n @if (enableFieldedSearch()) {\n <div class=\"flex items-center gap-4\" formGroupName=\"title\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInTitle' | transloco }}</span>\n <select\n id=\"title-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input id=\"title-value\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [placeholder]=\"getPlaceholder('title.operator')\" formControlName=\"value\" />\n </div>\n }\n\n <!-- TABS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.inScope' | transloco }}&nbsp;&quot;{{ currentTab() }}&quot;</h1>\n\n <Tabs>\n <TabsList variant=\"ghost\">\n @for (tab of tabs(); track $index) {\n <Tab class=\"w-fit\" [value]=\"tab.path\" [active]=\"tab.path === currentTab()\" (click)=\"onTabChange(tab.path)\">\n {{ tab.display | syslang | transloco }}\n </Tab>\n }\n </TabsList>\n </Tabs>\n\n <!-- FILTERS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.applyFilters' | transloco }}</h1>\n @for (filter of filters(); track $index) {\n <DropdownInput\n [suggestions]=\"suggestions()\"\n [selected]=\"getItems(filter.column)\"\n [label]=\"filter.display || filter.alias | syslang | transloco\"\n [placeholder]=\"'drawers.startTyping' | transloco\"\n [noResultLabel]=\"'drawers.noResult' | transloco\"\n (onFocus)=\"setFilterFocus($event, filter)\"\n (onKeyUp)=\"onInputTyping($event)\"\n (removeItem)=\"removeItem($event, filter)\"\n (addItem)=\"addItem($event, filter)\" />\n } @empty {\n {{ 'drawers.noFilters' | transloco }}\n }\n </section>\n </div>\n</div>\n" }]
8044
8098
  }], ctorParameters: () => [] });
8045
8099
 
8046
- class ArticleEntities {
8100
+ /**
8101
+ * Injection token that indicates whether custom date ranges are allowed.
8102
+ *
8103
+ * @remarks
8104
+ * This token is used to configure the date component to allow users to select custom date ranges.
8105
+ *
8106
+ * @example
8107
+ * ```typescript
8108
+ * providers: [
8109
+ * { provide: FILTER_DATE_ALLOW_CUSTOM_RANGE, useValue: false }
8110
+ * ]
8111
+ * ```
8112
+ *
8113
+ * @public
8114
+ */
8115
+ const FILTER_DATE_ALLOW_CUSTOM_RANGE = new InjectionToken("date allow custom range", {
8116
+ factory: () => true
8117
+ });
8118
+
8119
+ class AggregationListItemComponent {
8047
8120
  cn = cn;
8048
- appStore = inject(AppStore);
8049
- previewService = inject(PreviewService);
8050
- article = input.required(...(ngDevMode ? [{ debugName: "article" }] : []));
8051
- previewHighlights = computed(() => {
8052
- const highlights = this.appStore.getWebServiceByType("preview")?.highlights
8053
- ?.split(",")
8054
- .filter((h) => h !== "extractslocations" && h !== "matchlocations" && h !== "matchingpassages");
8055
- return highlights?.map((highlight) => ({
8056
- name: highlight,
8057
- entity: highlight,
8058
- metadata: this.getMetadata(this.article(), highlight)
8059
- }));
8060
- }, ...(ngDevMode ? [{ debugName: "previewHighlights" }] : []));
8061
- getMetadata(article, entity) {
8062
- const m = article[entity];
8063
- return m ?? [];
8121
+ get disabled() {
8122
+ return this.node().count === 0 ? "disabled" : null;
8064
8123
  }
8065
- navigation = signal(undefined, ...(ngDevMode ? [{ debugName: "navigation" }] : []));
8066
- hovering = signal(undefined, ...(ngDevMode ? [{ debugName: "hovering" }] : []));
8067
- hoverIndex = computed(() => this.navigation()?.value === this.hovering() ? (this.navigation()?.index ?? 0) : 0, ...(ngDevMode ? [{ debugName: "hoverIndex" }] : []));
8068
- scrollTo(type, index, usePassageHighlighter = false) {
8069
- // if previewData contains "matchingpassages" then we use the passage highlighter otherwise we use "extractslocations"
8070
- const category = type || this.previewService.highlightCategory;
8071
- this.previewService.events.set("scrollTo");
8072
- this.previewService.sendMessage({ action: "select", id: `${category}_${index}`, usePassageHighlighter });
8124
+ onSelect = output();
8125
+ onOpen = output();
8126
+ onFilter = output();
8127
+ node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
8128
+ field = input(...(ngDevMode ? [undefined, { debugName: "field" }] : []));
8129
+ appStore = inject(AppStore);
8130
+ queryParamsStore = inject(QueryParamsStore);
8131
+ searchText = inject(AggregationListComponent).searchText;
8132
+ // is the count of items displayed, default to false
8133
+ showCount = computed(() => this.appStore.general()?.features?.showAggregationItemCount ?? false, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
8134
+ quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
8135
+ linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
8136
+ isFiltered = computed(() => {
8137
+ const filters = this.queryParamsStore.getFilter({ field: this.field(), name: this.name() });
8138
+ if (!filters)
8139
+ return false;
8140
+ const values = [this.node().value]; // to also consider the treepath value
8141
+ return (values.some((v) => v === filters.value) ||
8142
+ (filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
8143
+ }, ...(ngDevMode ? [{ debugName: "isFiltered" }] : []));
8144
+ name = computed(() => {
8145
+ const value = this.node().display || this.node().value;
8146
+ return typeof value === "string" ? value : `${value}`;
8147
+ }, ...(ngDevMode ? [{ debugName: "name" }] : []));
8148
+ select(item, e) {
8149
+ e?.stopImmediatePropagation();
8150
+ const selected = !item.$selected;
8151
+ item.$selected = selected;
8152
+ this.onSelect.emit(item);
8073
8153
  }
8074
- navigate(entity, data, direction) {
8075
- if (!data.count || data.count <= 0) {
8076
- warn(`Invalid count for entity ${entity}:`, data.count);
8077
- return;
8078
- }
8079
- const currentNavigation = this.navigation();
8080
- const isCurrentValue = currentNavigation?.value === data.value;
8081
- let newIndex;
8082
- if (direction === "next") {
8083
- newIndex = isCurrentValue && currentNavigation.index < data.count ? currentNavigation.index + 1 : 1;
8084
- }
8085
- else {
8086
- newIndex = isCurrentValue && currentNavigation.index > 1 ? currentNavigation.index - 1 : data.count;
8154
+ onTextClick(event) {
8155
+ if (this.quickFilter()) {
8156
+ this.select(this.node(), event);
8157
+ this.onFilter.emit();
8087
8158
  }
8088
- this.navigation.set({
8089
- value: data.value,
8090
- index: newIndex
8091
- });
8092
- const id = this.previewService.getEntityId(entity, data.value, newIndex - 1);
8093
- if (id !== undefined) {
8094
- this.scrollTo(entity, id);
8095
- debug(`${direction} navigation:`, {
8096
- id,
8159
+ }
8160
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8161
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AggregationListItemComponent, isStandalone: true, selector: "aggregation-list-item, AggregationListItem, aggregationlistitem", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onOpen: "onOpen", onFilter: "onFilter" }, host: { properties: { "attr.disabled": "this.disabled" } }, ngImport: i0, template: "<a\r\n role=\"listitem\"\r\n [attr.aria-selected]=\"node().$selected\"\r\n [attr.aria-label]=\"name() | 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 text-neutral-300',\r\n node().$selected &&\r\n 'bg-primary-100 text-primary dark:text-primary-foreground'\r\n )\r\n \"\r\n (click)=\"select(node(), $event)\">\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)=\"select(node(), $event)\"\r\n [checked]=\"node().$selected\" />\r\n\r\n @if (node().icon) {\r\n <i\r\n class=\"fa-fw {{ node().icon }} self-center justify-self-center\"\r\n aria-hidden=\"true\"></i>\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 ? ((isFiltered() ? 'filters.removeFilter' : 'filters.addFilter')\r\n | transloco) +\r\n ': ' +\r\n (name() | syslang)\r\n : (name() | syslang)\r\n \"\r\n (click)=\"onTextClick($event)\">\r\n @for (\r\n chunk of (name() | syslang) ?? \"\" | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node().count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node().count\r\n }}</span>\r\n }\r\n</a>\r\n", styles: [":host{display:block;-webkit-user-select:none;user-select:none}:host a{padding-left:var(--agg-tree-indent, .5rem)}a{line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
8162
+ }
8163
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationListItemComponent, decorators: [{
8164
+ type: Component,
8165
+ args: [{ selector: "aggregation-list-item, AggregationListItem, aggregationlistitem", standalone: true, imports: [HighlightWordPipe, ListItemComponent, SyslangPipe, TranslocoPipe], template: "<a\r\n role=\"listitem\"\r\n [attr.aria-selected]=\"node().$selected\"\r\n [attr.aria-label]=\"name() | 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 text-neutral-300',\r\n node().$selected &&\r\n 'bg-primary-100 text-primary dark:text-primary-foreground'\r\n )\r\n \"\r\n (click)=\"select(node(), $event)\">\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)=\"select(node(), $event)\"\r\n [checked]=\"node().$selected\" />\r\n\r\n @if (node().icon) {\r\n <i\r\n class=\"fa-fw {{ node().icon }} self-center justify-self-center\"\r\n aria-hidden=\"true\"></i>\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 ? ((isFiltered() ? 'filters.removeFilter' : 'filters.addFilter')\r\n | transloco) +\r\n ': ' +\r\n (name() | syslang)\r\n : (name() | syslang)\r\n \"\r\n (click)=\"onTextClick($event)\">\r\n @for (\r\n chunk of (name() | syslang) ?? \"\" | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node().count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node().count\r\n }}</span>\r\n }\r\n</a>\r\n", styles: [":host{display:block;-webkit-user-select:none;user-select:none}:host a{padding-left:var(--agg-tree-indent, .5rem)}a{line-height:var(--agg-item-height, inherit)}\n"] }]
8166
+ }], propDecorators: { disabled: [{
8167
+ type: HostBinding,
8168
+ args: ["attr.disabled"]
8169
+ }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onOpen: [{ type: i0.Output, args: ["onOpen"] }], onFilter: [{ type: i0.Output, args: ["onFilter"] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }] } });
8170
+
8171
+ class AggregationListComponent {
8172
+ cn = cn;
8173
+ cdr = inject(ChangeDetectorRef);
8174
+ /* virtualizer */
8175
+ scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
8176
+ virtualizer = injectVirtualizer(() => ({
8177
+ count: this.items().length,
8178
+ estimateSize: () => 32,
8179
+ scrollElement: this.scrollElement()
8180
+ }));
8181
+ searchInput = viewChild("searchInput", ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
8182
+ /* stores */
8183
+ aggregationsStore = inject(AggregationsStore);
8184
+ queryParamsStore = inject(QueryParamsStore);
8185
+ appStore = inject(AppStore);
8186
+ /* services */
8187
+ aggregationsService = inject(AggregationsService);
8188
+ el = inject(ElementRef);
8189
+ injector = inject(Injector);
8190
+ class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
8191
+ /**
8192
+ * The name of the <details> element. When you provide the same id, the component work as an accordion
8193
+ * @defaultValue null
8194
+ */
8195
+ id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
8196
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
8197
+ column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
8198
+ onSelect = output();
8199
+ /**
8200
+ * Determines whether the aggregation component can be collapsed or expanded.
8201
+ * When true, the component will display collapse/expand controls allowing users
8202
+ * to show or hide the aggregation content.
8203
+ *
8204
+ * @default false
8205
+ */
8206
+ collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
8207
+ /**
8208
+ * Controls whether the aggregation component is in a collapsed state.
8209
+ * When true, the component will be visually collapsed/hidden.
8210
+ * When false, the component will be expanded/visible.
8211
+ *
8212
+ * @default false
8213
+ */
8214
+ collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
8215
+ /**
8216
+ * A computed signal that tracks the collapsed state of the component.
8217
+ * This signal is linked to the `collapsed()` signal and automatically updates
8218
+ * when the collapsed state changes.
8219
+ */
8220
+ isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
8221
+ /**
8222
+ * Computed property that returns an empty string when the component is not collapsed,
8223
+ * or null when the component is collapsed. This is typically used to control
8224
+ * expansion state in UI components with conditional rendering or styling.
8225
+ *
8226
+ * @returns empty string if not collapsed, null if collapsed
8227
+ */
8228
+ expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
8229
+ /**
8230
+ * A boolean flag indicating whether the component is searchable.
8231
+ * This property is initialized to `undefined` by default.
8232
+ * "Undefined" and not "false" because this input overrides the custom json settings
8233
+ */
8234
+ searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
8235
+ selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
8236
+ /**
8237
+ * A boolean flag indicating whether we want to see the filters count when some is applied
8238
+ * This property is initialized to `false` by default.
8239
+ */
8240
+ showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
8241
+ /* aggregation */
8242
+ aggregation = computed(() => {
8243
+ // when the aggegation store updates, we need to check if the aggregation is still valid
8244
+ getState(this.aggregationsStore);
8245
+ const name = this.name();
8246
+ const column = this.column();
8247
+ if (name !== null) {
8248
+ const agg = this.aggregationsService.processAggregation(name, column);
8249
+ if (agg) {
8250
+ if (agg.isTree) {
8251
+ error("The aggregation component does not support tree aggregations. Please use the <AggregationTree /> component instead.");
8252
+ }
8253
+ // overrides "searchable" properties with the input if any
8254
+ agg.searchable = this.searchable() ?? agg.searchable;
8255
+ return agg;
8256
+ }
8257
+ }
8258
+ return null;
8259
+ }, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
8260
+ /* items of the aggregation */
8261
+ items = computed(() => {
8262
+ // when the aggegation store updates, we need to check if the aggregation is still valid
8263
+ getState(this.aggregationsStore);
8264
+ const agg = this.aggregation();
8265
+ const searchedItems = this.searchedItems();
8266
+ if (searchedItems.length > 0) {
8267
+ return searchedItems;
8268
+ }
8269
+ else if (agg?.items) {
8270
+ const res = this.addCurrentFiltersToItems();
8271
+ return res;
8272
+ }
8273
+ return [];
8274
+ }, ...(ngDevMode ? [{ debugName: "items" }] : []));
8275
+ /**
8276
+ * Computed signal that determines whether the items collection is empty.
8277
+ * @returns True if the items array has no elements, false otherwise.
8278
+ */
8279
+ isEmpty = computed(() => this.items().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
8280
+ /**
8281
+ * A computed property that determines whether there are active filters
8282
+ * for the current aggregation column.
8283
+ *
8284
+ * if True, the clear button is shown.
8285
+ *
8286
+ * @returns {boolean} `true` if the filter count for the aggregation column is greater than 0, otherwise `false`.
8287
+ */
8288
+ hasFilters = computed(() => {
8289
+ const { count = 0 } = this.queryParamsStore.getFilter({ field: this.aggregation()?.column, name: this.aggregation()?.name }) || {};
8290
+ return count > 0;
8291
+ }, ...(ngDevMode ? [{ debugName: "hasFilters" }] : []));
8292
+ /**
8293
+ * A computed property that returns the number of items of this aggregation applied in the active filters
8294
+ *
8295
+ * if more than 0 and the showCount input is set as True, the count number is shown.
8296
+ *
8297
+ * @returns {number} the filters count.
8298
+ */
8299
+ filtersCount = computed(() => {
8300
+ const { count = 0 } = this.queryParamsStore.getFilter({ field: this.aggregation()?.column, name: this.aggregation()?.name }) || {};
8301
+ return count;
8302
+ }, ...(ngDevMode ? [{ debugName: "filtersCount" }] : []));
8303
+ isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
8304
+ /* search feature */
8305
+ searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
8306
+ debouncedSearchText = debouncedSignal(this.searchText, 300);
8307
+ normalizedSearchText = computed(() => this.debouncedSearchText()
8308
+ .normalize("NFD")
8309
+ .replace(/[\u0300-\u036f]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
8310
+ /* suggestions */
8311
+ suggests = signal([], ...(ngDevMode ? [{ debugName: "suggests" }] : []));
8312
+ /* searched items */
8313
+ searchedItems = computed(() => {
8314
+ if (!this.suggests())
8315
+ return [];
8316
+ // if the aggregation is not a tree, we return the suggestions as is
8317
+ return this.suggests()?.map(suggest => {
8318
+ const column = this.appStore.getColumn(suggest.category);
8319
+ const item = {
8320
+ name: this.name(),
8321
+ value: suggest.normalized || suggest.display || "",
8322
+ display: suggest.display,
8323
+ column: column?.name ?? suggest.category,
8324
+ count: Number(suggest.frequency),
8325
+ $selected: false
8326
+ };
8327
+ // if the column is of type boolean, we need to convert the value to a boolean
8328
+ if (column?.eType === EngineType.bool) {
8329
+ item.value = Boolean(item.value);
8330
+ }
8331
+ return item;
8332
+ });
8333
+ }, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
8334
+ query;
8335
+ filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
8336
+ constructor() {
8337
+ this.query = buildQuery();
8338
+ effect(() => {
8339
+ // focus the search input when expanded
8340
+ if (this.searchInput()?.nativeElement && this.expanded() !== null) {
8341
+ setTimeout(() => {
8342
+ this.searchInput()?.nativeElement.focus();
8343
+ }, 0);
8344
+ }
8345
+ });
8346
+ effect(async () => {
8347
+ if (this.debouncedSearchText() === "" || this.aggregation() === null) {
8348
+ this.suggests.set([]);
8349
+ return;
8350
+ }
8351
+ const suggests = (await withFetch(() => fetchSuggestField(this.normalizedSearchText(), [this.aggregation()?.column || ""]), this.injector)) || [];
8352
+ this.suggests.set(suggests);
8353
+ });
8354
+ }
8355
+ /**
8356
+ * Clears the current filter for the aggregation column.
8357
+ *
8358
+ * This method updates the filter in the `queryParamsStore` by setting the display value
8359
+ * of the current aggregation column to an empty string.
8360
+ */
8361
+ clear() {
8362
+ const agg = this.aggregation();
8363
+ if (agg) {
8364
+ this.queryParamsStore.removeFilterByName(agg.name, agg.column);
8365
+ this.selection.set(false);
8366
+ this.isAllSelected.set(false);
8367
+ }
8368
+ this.onSelect.emit([]);
8369
+ }
8370
+ /**
8371
+ * Select all filters for the aggregation column.
8372
+ */
8373
+ selectAll() {
8374
+ if (this.items().length) {
8375
+ this.selectItems(this.items(), true);
8376
+ this.selection.set(true);
8377
+ this.isAllSelected.set(true);
8378
+ }
8379
+ }
8380
+ /**
8381
+ * Unselect all filters for the aggregation column.
8382
+ */
8383
+ unselectAll() {
8384
+ if (this.items().length) {
8385
+ this.selectItems(this.items(), false);
8386
+ this.selection.set(true);
8387
+ this.isAllSelected.set(false);
8388
+ }
8389
+ }
8390
+ /**
8391
+ * Applies the current filters to the query parameters store.
8392
+ *
8393
+ * - If there are multiple filters, they are wrapped in an "or" filter.
8394
+ * - If the aggregation is not a distribution, the filters are merged into a single filter with an "in" operator.
8395
+ * - If there is only one filter, it is directly applied.
8396
+ * - If there are no filters, the current filters are cleared.
8397
+ *
8398
+ * After applying the filters, the search text is reset.
8399
+ */
8400
+ apply() {
8401
+ const filters = this.getFilters();
8402
+ const agg = this.aggregation();
8403
+ if (!agg)
8404
+ return;
8405
+ const { name, column: field } = agg;
8406
+ // if filters length > 1, we need to wrap them in an "or" filter
8407
+ if (filters.length > 1) {
8408
+ const display = filters[0].display;
8409
+ // if aggregation not a distribution, we need to merge the filters into a single filter with an in operator
8410
+ // with the values of the filters
8411
+ if (this.aggregation()?.isDistribution) {
8412
+ this.queryParamsStore.updateFilter({
8413
+ operator: "or",
8414
+ filters,
8415
+ name,
8416
+ field,
8417
+ display
8418
+ });
8419
+ }
8420
+ else {
8421
+ const values = filters.map(filter => filter.value);
8422
+ this.queryParamsStore.updateFilter({
8423
+ operator: "in",
8424
+ name,
8425
+ field,
8426
+ values,
8427
+ display,
8428
+ filters
8429
+ });
8430
+ }
8431
+ }
8432
+ else if (filters.length === 1) {
8433
+ this.queryParamsStore.updateFilter(filters[0]);
8434
+ }
8435
+ else {
8436
+ this.clear();
8437
+ }
8438
+ this.searchText.set("");
8439
+ this.selection.set(false);
8440
+ }
8441
+ loadMore() {
8442
+ const q = this.queryParamsStore.getQuery();
8443
+ this.aggregationsService.loadMore(q, this.aggregation()).subscribe(aggregation => {
8444
+ this.aggregationsStore.updateAggregation(aggregation);
8445
+ this.cdr.detectChanges();
8446
+ });
8447
+ }
8448
+ /**
8449
+ * Updates the selected state of the given item in the aggregation list.
8450
+ *
8451
+ * @param item - The item to be selected or deselected.
8452
+ *
8453
+ * This method iterates through the items in the aggregation list and updates
8454
+ * the `$selected` property of the item that matches the value of the given item.
8455
+ *
8456
+ * If the item is selected, the selection count is incremented by 1.
8457
+ * If the item is deselected, the selection count is decremented by 1.
8458
+ */
8459
+ select() {
8460
+ this.onSelect.emit(this.items().filter(item => item.$selected));
8461
+ this.selection.set(true);
8462
+ }
8463
+ /**
8464
+ * Updates the collapsed status on header click if the component is collapsible.
8465
+ */
8466
+ onHeaderClick(event) {
8467
+ event.preventDefault();
8468
+ const isDate = this.aggregationsService.appStore.isDateColumn(this.aggregation()?.column || "");
8469
+ // prevent header click if no items are present
8470
+ if (!isDate && this.isEmpty()) {
8471
+ return;
8472
+ }
8473
+ if (this.collapsible()) {
8474
+ this.isCollapsed.update(value => !value);
8475
+ }
8476
+ }
8477
+ /**
8478
+ * Retrieves a list of filters based on the selected items.
8479
+ *
8480
+ * This method filters the items to include only those that are selected,
8481
+ * and then maps each selected item to a filter using the `toFilter` method.
8482
+ *
8483
+ * @returns {LegacyFilter[]} An array of filters corresponding to the selected items.
8484
+ */
8485
+ getFilters() {
8486
+ const items = this.addCurrentFiltersToItems().filter(item => item.$selected);
8487
+ const searchedItems = this.searchedItems().filter(item => item.$selected);
8488
+ const currentItems = [...items, ...searchedItems];
8489
+ const { column, name, isDistribution = false } = this.aggregation() || {};
8490
+ const selectedItems = currentItems.map(item => this.aggregationsService.toFilter(item, column, name, isDistribution));
8491
+ return selectedItems;
8492
+ }
8493
+ addCurrentFiltersToItems() {
8494
+ const aggItems = (this.aggregation()?.items) || [];
8495
+ // add the current filters to the current items only if they are not already present
8496
+ if (!this.aggregation()?.isTree && (!this.aggregation()?.isDistribution || this.aggregation()?.isDistribution === false)) {
8497
+ // get all active filters for the current aggregation/column
8498
+ const activeFilters = this.queryParamsStore.getFilter({
8499
+ field: this.aggregation()?.column,
8500
+ name: this.aggregation()?.name
8501
+ });
8502
+ // if there are active filters, we need to add them to the current items
8503
+ if (activeFilters) {
8504
+ // multiples filters
8505
+ if (activeFilters.filters) {
8506
+ activeFilters.filters.forEach((filter) => {
8507
+ // check if the filter is already present in the current items
8508
+ // if not, add it to the current items
8509
+ const found = aggItems.find(item => item.value && item.value.toLocaleString().toLocaleLowerCase() === filter.value?.toLocaleLowerCase());
8510
+ if (!found) {
8511
+ // add it to the current items
8512
+ aggItems.unshift({
8513
+ value: filter.value,
8514
+ display: filter.display,
8515
+ $selected: true
8516
+ });
8517
+ }
8518
+ });
8519
+ }
8520
+ else {
8521
+ // single filter
8522
+ const found = aggItems.find(item => item.value?.toString().toLocaleLowerCase() === activeFilters.value?.toLocaleLowerCase());
8523
+ if (!found) {
8524
+ // add it to the current items
8525
+ aggItems.push({
8526
+ value: activeFilters.value,
8527
+ display: activeFilters.display,
8528
+ $selected: true
8529
+ });
8530
+ }
8531
+ }
8532
+ }
8533
+ }
8534
+ return aggItems;
8535
+ }
8536
+ /**
8537
+ * Update the $selected property to the selected parameter to all items
8538
+ *
8539
+ * @param items the items to apply to
8540
+ * @param selected the selected status
8541
+ */
8542
+ selectItems(items, selected) {
8543
+ items.forEach(item => {
8544
+ // don't select disabled items
8545
+ if (item.count > 0) {
8546
+ item.$selected = selected;
8547
+ }
8548
+ });
8549
+ }
8550
+ onToggle(event) {
8551
+ const e = event;
8552
+ this.isCollapsed.set(e.newState === "closed");
8553
+ }
8554
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8555
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", 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 }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <i class=\"fa-fw fas fa-triangle-exclamation mr-1\"></i>\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<details\r\n [attr.open]=\"expanded()\"\r\n [attr.name]=\"id()\"\r\n class=\"group space-y-2\"\r\n (toggle)=\"onToggle($event)\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\r\n [class.text-muted-foreground]=\"isEmpty()\"\r\n class=\"m-0 mt-1 flex h-8 w-full items-center gap-1 pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <!-- count -->\r\n <Badge size=\"xs\" class=\"ml-1 pb-0.5\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n <ButtonGroup>\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.clearFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n @if (selection()) {\r\n <button\r\n variant=\"primary\"\r\n size=\"xs\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); apply()\"\r\n class=\"h-4 px-1\">\r\n <FilterIcon class=\"size-4\" />\r\n {{ \"filters.apply\" | transloco }}\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.unselectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.unselectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <i class=\"fa-fw far fa-check-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.unselectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n } @else {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.selectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.selectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <i class=\"fa-fw far fa-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.selectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n </ButtonGroup>\r\n }\r\n\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n @if (aggregation()?.searchable && items().length) {\r\n <InputGroup class=\"group/item mt-1\">\r\n <input\r\n #searchInput\r\n input-group\r\n id=\"aggregation-input-{{ column() }}\"\r\n type=\"text\"\r\n [attr.placeholder]=\"'search' | transloco\"\r\n [(ngModel)]=\"searchText\"\r\n class=\"mt-1\" />\r\n <InputGroupAddon>\r\n <SearchIcon\r\n class=\"text-foreground size-4 rotate-0 transition-[rotate] duration-500 group-focus-within/item:rotate-90\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n }\r\n\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-[calc(var(--height,100%)-100px)] w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"list\"\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=\"listitem\">\r\n <AggregationListItem\r\n [node]=\"item\"\r\n [field]=\"aggregation()?.column\"\r\n (onSelect)=\"select()\"\r\n (onFilter)=\"apply()\" />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (aggregation()?.$hasMore && this.searchedItems().length === 0) {\r\n <button\r\n variant=\"link\"\r\n class=\"mt-1 flex w-full justify-center\"\r\n [attr.aria-label]=\"'loadMore' | transloco\"\r\n (click)=\"loadMore()\">\r\n {{ \"loadMore\" | transloco }}\r\n </button>\r\n }\r\n</details>\r\n", styles: ["AggregationItem:has(+AggregationItem){margin-bottom:var(--agg-item-gap, 0)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: AggregationListItemComponent, selector: "aggregation-list-item, AggregationListItem, aggregationlistitem", inputs: ["node", "field"], outputs: ["onSelect", "onOpen", "onFilter"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "directive", type: ButtonGroup, selector: "button-group, ButtonGroup", inputs: ["class", "orientation"] }, { kind: "directive", type: InputGroupInput, selector: "input[input-group]", inputs: ["class", "type", "placeholder", "disabled"] }, { kind: "directive", type: InputGroupComponent, selector: "input-group, inputgroup, InputGroup", inputs: ["class"] }, { kind: "directive", type: InputGroupAddonComponent, selector: "input-group-addon, inputgroupaddon, InputGroupAddon", inputs: ["class", "align"] }, { kind: "component", type: SearchIcon, selector: "SearchIcon", inputs: ["class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
8556
+ }
8557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationListComponent, decorators: [{
8558
+ type: Component,
8559
+ args: [{ selector: "AggregationList, aggregation-list, aggregationlist", imports: [
8560
+ FormsModule,
8561
+ ReactiveFormsModule,
8562
+ ButtonComponent,
8563
+ AggregationListItemComponent,
8564
+ SyslangPipe,
8565
+ TranslocoPipe,
8566
+ BadgeComponent,
8567
+ ChevronRightIcon,
8568
+ ButtonGroup,
8569
+ InputGroupInput,
8570
+ InputGroupComponent,
8571
+ InputGroupAddonComponent,
8572
+ SearchIcon,
8573
+ FilterIcon,
8574
+ AggregationListItemComponent
8575
+ ], standalone: true, host: {
8576
+ "[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
8577
+ }, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <i class=\"fa-fw fas fa-triangle-exclamation mr-1\"></i>\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<details\r\n [attr.open]=\"expanded()\"\r\n [attr.name]=\"id()\"\r\n class=\"group space-y-2\"\r\n (toggle)=\"onToggle($event)\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\r\n [class.text-muted-foreground]=\"isEmpty()\"\r\n class=\"m-0 mt-1 flex h-8 w-full items-center gap-1 pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <!-- count -->\r\n <Badge size=\"xs\" class=\"ml-1 pb-0.5\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n <ButtonGroup>\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.clearFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n @if (selection()) {\r\n <button\r\n variant=\"primary\"\r\n size=\"xs\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); apply()\"\r\n class=\"h-4 px-1\">\r\n <FilterIcon class=\"size-4\" />\r\n {{ \"filters.apply\" | transloco }}\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.unselectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.unselectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <i class=\"fa-fw far fa-check-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.unselectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n } @else {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.selectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.selectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <i class=\"fa-fw far fa-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.selectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n </ButtonGroup>\r\n }\r\n\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n @if (aggregation()?.searchable && items().length) {\r\n <InputGroup class=\"group/item mt-1\">\r\n <input\r\n #searchInput\r\n input-group\r\n id=\"aggregation-input-{{ column() }}\"\r\n type=\"text\"\r\n [attr.placeholder]=\"'search' | transloco\"\r\n [(ngModel)]=\"searchText\"\r\n class=\"mt-1\" />\r\n <InputGroupAddon>\r\n <SearchIcon\r\n class=\"text-foreground size-4 rotate-0 transition-[rotate] duration-500 group-focus-within/item:rotate-90\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n }\r\n\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-[calc(var(--height,100%)-100px)] w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"list\"\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=\"listitem\">\r\n <AggregationListItem\r\n [node]=\"item\"\r\n [field]=\"aggregation()?.column\"\r\n (onSelect)=\"select()\"\r\n (onFilter)=\"apply()\" />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (aggregation()?.$hasMore && this.searchedItems().length === 0) {\r\n <button\r\n variant=\"link\"\r\n class=\"mt-1 flex w-full justify-center\"\r\n [attr.aria-label]=\"'loadMore' | transloco\"\r\n (click)=\"loadMore()\">\r\n {{ \"loadMore\" | transloco }}\r\n </button>\r\n }\r\n</details>\r\n", styles: ["AggregationItem:has(+AggregationItem){margin-bottom:var(--agg-item-gap, 0)}\n"] }]
8578
+ }], ctorParameters: () => [], propDecorators: { scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ["searchInput", { 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 }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], 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 }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
8579
+
8580
+ class AggregationDateComponent extends AggregationListComponent {
8581
+ destroyRef;
8582
+ datepicker = viewChild(DateRangePickerDirective, ...(ngDevMode ? [{ debugName: "datepicker" }] : []));
8583
+ title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
8584
+ displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
8585
+ allowCustomRange = inject(FILTER_DATE_ALLOW_CUSTOM_RANGE);
8586
+ transloco = inject(TranslocoService);
8587
+ dateOptions = computed(() => translateAggregationToDateOptions(this.aggregation(), this.displayEmptyDistributionIntervals()), ...(ngDevMode ? [{ debugName: "dateOptions" }] : []));
8588
+ form = new FormGroup({
8589
+ option: new FormControl(null),
8590
+ customRange: new FormGroup({
8591
+ from: new FormControl(null),
8592
+ to: new FormControl(null)
8593
+ })
8594
+ });
8595
+ today = new Date();
8596
+ lang = signal(this.transloco.getActiveLang(), ...(ngDevMode ? [{ debugName: "lang" }] : []));
8597
+ validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
8598
+ constructor(destroyRef) {
8599
+ super();
8600
+ this.destroyRef = destroyRef;
8601
+ this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
8602
+ this.lang.set(lang);
8603
+ this.datepicker()?.setOptions({ language: lang });
8604
+ });
8605
+ const abortController = new AbortController();
8606
+ addEventListener("changeDate", this.onDateChange.bind(this), {
8607
+ signal: abortController.signal
8608
+ });
8609
+ // apply current date filter from queryParamsStore
8610
+ effect(() => {
8611
+ this.updateForm(this.queryParamsStore.getFilter({
8612
+ field: this.aggregation()?.column,
8613
+ name: this.aggregation()?.name
8614
+ }));
8615
+ });
8616
+ this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((changes) => {
8617
+ this.validSelection.set(!!changes.option &&
8618
+ (changes.option !== "custom-range" || changes.customRange?.from !== null || changes.customRange?.to !== null));
8619
+ });
8620
+ destroyRef.onDestroy(() => {
8621
+ abortController.abort();
8622
+ });
8623
+ }
8624
+ aggregation = computed(() => {
8625
+ const name = this.name();
8626
+ if (name !== null) {
8627
+ const agg = this.aggregationsService.processAggregation(name, this.column());
8628
+ return {
8629
+ ...agg,
8630
+ items: agg?.items?.filter((item) => item.display !== "custom-range") ?? []
8631
+ };
8632
+ }
8633
+ return null;
8634
+ }, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
8635
+ apply() {
8636
+ try {
8637
+ const filter = this.getFormValueFilter();
8638
+ this.queryParamsStore.updateFilter(filter);
8639
+ this.selection.set(false);
8640
+ }
8641
+ catch (err) {
8642
+ warn("Error applying date filter:", err);
8643
+ if (err instanceof Error) {
8644
+ notify.error(this.transloco.translate(err.message));
8645
+ }
8646
+ }
8647
+ }
8648
+ clear(notify = true) {
8649
+ this.form.setValue({
8650
+ option: null,
8651
+ customRange: {
8652
+ from: null,
8653
+ to: null
8654
+ }
8655
+ });
8656
+ const agg = this.aggregation();
8657
+ if (agg && notify) {
8658
+ this.queryParamsStore.removeFilterByName(agg.name);
8659
+ }
8660
+ }
8661
+ updateForm(filter) {
8662
+ if (!filter) {
8663
+ this.clear(false);
8664
+ return;
8665
+ }
8666
+ const { operator, value } = filter;
8667
+ const code = this.dateOptions().find((option) => option.operator === operator && option.value === value)
8668
+ ?.display ?? "custom-range";
8669
+ let from = null, to = null;
8670
+ if (code === "custom-range") {
8671
+ switch (operator) {
8672
+ case "lte":
8673
+ to = filter.value;
8674
+ break;
8675
+ case "gte":
8676
+ from = filter.value;
8677
+ break;
8678
+ case "between":
8679
+ from = filter.start;
8680
+ to = filter.end;
8681
+ break;
8682
+ }
8683
+ }
8684
+ const formValue = {
8685
+ option: code,
8686
+ customRange: {
8687
+ from: code === "custom-range" ? (from ?? null) : null,
8688
+ to: code === "custom-range" ? (to ?? null) : null
8689
+ }
8690
+ };
8691
+ this.form.setValue(formValue);
8692
+ // Update the date range picker
8693
+ const datepicker = this.datepicker();
8694
+ if (datepicker) {
8695
+ const start = new Date(formValue.customRange.from || "");
8696
+ const end = new Date(formValue.customRange.to || "");
8697
+ setTimeout(() => {
8698
+ datepicker.rangeDatepicker?.setDates(start, end);
8699
+ }, 1000);
8700
+ }
8701
+ }
8702
+ getFormValueFilter() {
8703
+ const value = this.form.value;
8704
+ // value.option is null
8705
+ if (!value.option) {
8706
+ throw new Error("filters.selectionInvalid");
8707
+ }
8708
+ if (value.option !== "custom-range") {
8709
+ const dateOption = this.dateOptions().find((option) => option.display === value.option);
8710
+ const aggregation = this.aggregation();
8711
+ if (!aggregation) {
8712
+ throw new Error("filters.aggregationNotFound");
8713
+ }
8714
+ return {
8715
+ name: aggregation.name,
8716
+ operator: dateOption?.operator || "eq",
8717
+ field: aggregation.column,
8718
+ display: dateOption?.display ?? "",
8719
+ filters: dateOption?.filters,
8720
+ value: dateOption?.value
8721
+ };
8722
+ }
8723
+ else if (value.customRange) {
8724
+ // if to is null, operator is gte
8725
+ // if from is null, operator is lte
8726
+ // if both are not null, operator is between
8727
+ const aggregation = this.aggregation();
8728
+ if (!aggregation) {
8729
+ throw new Error("filters.aggregationNotFound");
8730
+ }
8731
+ const filter = {
8732
+ name: aggregation.name,
8733
+ field: aggregation.column,
8734
+ display: value.option
8735
+ };
8736
+ if (value.customRange.from && value.customRange.to) {
8737
+ filter.operator = "between";
8738
+ filter.start = value.customRange.from;
8739
+ filter.end = value.customRange.to;
8740
+ }
8741
+ else if (value.customRange.from) {
8742
+ filter.operator = "gte";
8743
+ filter.value = value.customRange.from;
8744
+ }
8745
+ else if (value.customRange.to) {
8746
+ filter.operator = "lte";
8747
+ filter.value = value.customRange.to;
8748
+ }
8749
+ else {
8750
+ throw new Error("filters.customRangeInvalid");
8751
+ }
8752
+ return filter;
8753
+ }
8754
+ throw new Error("filters.filterInvalid");
8755
+ }
8756
+ onDateChange() {
8757
+ // Get the selected dates
8758
+ const [start, end] = this.datepicker()?.getDates() || [];
8759
+ // If both start and end are not selected, reset the datepicker options
8760
+ if (!start && !end) {
8761
+ this.datepicker()?.setOptions({
8762
+ minDate: undefined,
8763
+ maxDate: this.today
8764
+ });
8765
+ }
8766
+ // Update the form with the selected dates
8767
+ if (start && end) {
8768
+ this.form.patchValue({
8769
+ customRange: {
8770
+ from: start.toISOString(),
8771
+ to: end.toISOString()
8772
+ }
8773
+ });
8774
+ }
8775
+ }
8776
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationDateComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
8777
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AggregationDateComponent, isStandalone: true, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "datepicker", first: true, predicate: DateRangePickerDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <i class=\"fa-fw far fa-filter\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[calc(var(--height,100%)-100px)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"custom range\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n #radiorange\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-custom-range\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n\r\n <div\r\n class=\"@container flex grow gap-1 p-1 @max-[340px]:flex-wrap\"\r\n daterangepicker\r\n datepicker-buttons\r\n [datepicker-language]=\"lang()\"\r\n [datepicker-min]=\"form.get('customRange.from')?.value || undefined\"\r\n [datepicker-max]=\"\r\n form.get('customRange.to')?.value ?? today.toISOString()\r\n \"\r\n (click)=\"radiorange.click()\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10\">{{\r\n \"from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-end\" class=\"min-w-10 text-right\">{{\r\n \"to\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: DateRangePickerDirective, selector: "[daterangepicker]", inputs: ["datepicker-title", "datepicker-autohide", "datepicker-clear", "datepicker-today", "datepicker-buttons", "datepicker-today-highlight", "datepicker-language", "datepicker-format", "datepicker-max", "datepicker-min", "datepicker-orientation"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
8778
+ }
8779
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationDateComponent, decorators: [{
8780
+ type: Component,
8781
+ args: [{ selector: "aggregation-date, AggregationDate, aggregationdate", standalone: true, providers: [provideTranslocoScope("filters")], imports: [
8782
+ InputComponent,
8783
+ ButtonComponent,
8784
+ ListItemComponent,
8785
+ ReactiveFormsModule,
8786
+ TranslocoPipe,
8787
+ SyslangPipe,
8788
+ DateRangePickerDirective,
8789
+ ChevronRightIcon
8790
+ ], host: {
8791
+ class: "@container"
8792
+ }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <i class=\"fa-fw far fa-filter\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[calc(var(--height,100%)-100px)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"custom range\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n #radiorange\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-custom-range\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n\r\n <div\r\n class=\"@container flex grow gap-1 p-1 @max-[340px]:flex-wrap\"\r\n daterangepicker\r\n datepicker-buttons\r\n [datepicker-language]=\"lang()\"\r\n [datepicker-min]=\"form.get('customRange.from')?.value || undefined\"\r\n [datepicker-max]=\"\r\n form.get('customRange.to')?.value ?? today.toISOString()\r\n \"\r\n (click)=\"radiorange.click()\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10\">{{\r\n \"from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-end\" class=\"min-w-10 text-right\">{{\r\n \"to\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
8793
+ }], ctorParameters: () => [{ type: i0.DestroyRef }], propDecorators: { datepicker: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DateRangePickerDirective), { isSignal: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }] } });
8794
+
8795
+ /**
8796
+ * Component that allows users to select a date or a date range for filtering search results.
8797
+ *
8798
+ * @deprecated since version 4. Use `AggregationDateComponent` instead.
8799
+ */
8800
+ class DateComponent extends AggregationDateComponent {
8801
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DateComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8802
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <i class=\"fa-fw far fa-filter\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[calc(var(--height,100%)-100px)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"custom range\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n #radiorange\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-custom-range\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n\r\n <div\r\n class=\"@container flex grow gap-1 p-1 @max-[340px]:flex-wrap\"\r\n daterangepicker\r\n datepicker-buttons\r\n [datepicker-language]=\"lang()\"\r\n [datepicker-min]=\"form.get('customRange.from')?.value || undefined\"\r\n [datepicker-max]=\"\r\n form.get('customRange.to')?.value ?? today.toISOString()\r\n \"\r\n (click)=\"radiorange.click()\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10\">{{\r\n \"from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-end\" class=\"min-w-10 text-right\">{{\r\n \"to\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: DateRangePickerDirective, selector: "[daterangepicker]", inputs: ["datepicker-title", "datepicker-autohide", "datepicker-clear", "datepicker-today", "datepicker-buttons", "datepicker-today-highlight", "datepicker-language", "datepicker-format", "datepicker-max", "datepicker-min", "datepicker-orientation"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
8803
+ }
8804
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DateComponent, decorators: [{
8805
+ type: Component,
8806
+ args: [{ selector: "date-filter,DateFilter", standalone: true, providers: [provideTranslocoScope("filters")], imports: [
8807
+ InputComponent,
8808
+ ButtonComponent,
8809
+ ListItemComponent,
8810
+ ReactiveFormsModule,
8811
+ TranslocoPipe,
8812
+ SyslangPipe,
8813
+ DateRangePickerDirective,
8814
+ ChevronRightIcon
8815
+ ], host: {
8816
+ class: "@container"
8817
+ }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <i class=\"fa-fw far fa-filter\" aria-hidden=\"true\"></i>\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </button>\r\n }\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[calc(var(--height,100%)-100px)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"custom range\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n #radiorange\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-custom-range\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n\r\n <div\r\n class=\"@container flex grow gap-1 p-1 @max-[340px]:flex-wrap\"\r\n daterangepicker\r\n datepicker-buttons\r\n [datepicker-language]=\"lang()\"\r\n [datepicker-min]=\"form.get('customRange.from')?.value || undefined\"\r\n [datepicker-max]=\"\r\n form.get('customRange.to')?.value ?? today.toISOString()\r\n \"\r\n (click)=\"radiorange.click()\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10\">{{\r\n \"from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-end\" class=\"min-w-10 text-right\">{{\r\n \"to\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n class=\"h-8 min-w-[13ch]\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
8818
+ }] });
8819
+
8820
+ class ArticleEntities {
8821
+ cn = cn;
8822
+ appStore = inject(AppStore);
8823
+ previewService = inject(PreviewService);
8824
+ article = input.required(...(ngDevMode ? [{ debugName: "article" }] : []));
8825
+ previewHighlights = computed(() => {
8826
+ const highlights = this.appStore.getWebServiceByType("preview")?.highlights
8827
+ ?.split(",")
8828
+ .filter((h) => h !== "extractslocations" && h !== "matchlocations" && h !== "matchingpassages");
8829
+ return highlights?.map((highlight) => ({
8830
+ name: highlight,
8831
+ entity: highlight,
8832
+ metadata: this.getMetadata(this.article(), highlight)
8833
+ }));
8834
+ }, ...(ngDevMode ? [{ debugName: "previewHighlights" }] : []));
8835
+ getMetadata(article, entity) {
8836
+ const m = article[entity];
8837
+ return m ?? [];
8838
+ }
8839
+ navigation = signal(undefined, ...(ngDevMode ? [{ debugName: "navigation" }] : []));
8840
+ hovering = signal(undefined, ...(ngDevMode ? [{ debugName: "hovering" }] : []));
8841
+ hoverIndex = computed(() => this.navigation()?.value === this.hovering() ? (this.navigation()?.index ?? 0) : 0, ...(ngDevMode ? [{ debugName: "hoverIndex" }] : []));
8842
+ scrollTo(type, index, usePassageHighlighter = false) {
8843
+ // if previewData contains "matchingpassages" then we use the passage highlighter otherwise we use "extractslocations"
8844
+ const category = type || this.previewService.highlightCategory;
8845
+ this.previewService.events.set("scrollTo");
8846
+ this.previewService.sendMessage({ action: "select", id: `${category}_${index}`, usePassageHighlighter });
8847
+ }
8848
+ navigate(entity, data, direction) {
8849
+ if (!data.count || data.count <= 0) {
8850
+ warn(`Invalid count for entity ${entity}:`, data.count);
8851
+ return;
8852
+ }
8853
+ const currentNavigation = this.navigation();
8854
+ const isCurrentValue = currentNavigation?.value === data.value;
8855
+ let newIndex;
8856
+ if (direction === "next") {
8857
+ newIndex = isCurrentValue && currentNavigation.index < data.count ? currentNavigation.index + 1 : 1;
8858
+ }
8859
+ else {
8860
+ newIndex = isCurrentValue && currentNavigation.index > 1 ? currentNavigation.index - 1 : data.count;
8861
+ }
8862
+ this.navigation.set({
8863
+ value: data.value,
8864
+ index: newIndex
8865
+ });
8866
+ const id = this.previewService.getEntityId(entity, data.value, newIndex - 1);
8867
+ if (id !== undefined) {
8868
+ this.scrollTo(entity, id);
8869
+ debug(`${direction} navigation:`, {
8870
+ id,
8097
8871
  entity,
8098
8872
  value: data.value,
8099
8873
  index: newIndex,
@@ -8798,7 +9572,7 @@ class AlertDialog {
8798
9572
  </DialogFooter>
8799
9573
  </DialogContent>
8800
9574
  </dialog>
8801
- `, isInline: true, styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "component", type: LoadingCircleIconComponent, selector: "loading-circle, LoadingCircle, loadingcircle", inputs: ["class"] }, { kind: "component", type: CircleCheckIconComponent, selector: "circle-check, CircleCheck, circlecheck", inputs: ["class"] }, { kind: "directive", type: SelectOptionDirective, selector: "option", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
9575
+ `, isInline: true, styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "component", type: LoadingCircleIconComponent, selector: "loading-circle, LoadingCircle, loadingcircle", inputs: ["class"] }, { kind: "component", type: CircleCheckIconComponent, selector: "circle-check, CircleCheck, circlecheck", inputs: ["class"] }, { kind: "directive", type: SelectOptionDirective, selector: "option", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
8802
9576
  }
8803
9577
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AlertDialog, decorators: [{
8804
9578
  type: Component,
@@ -8987,6 +9761,7 @@ class ChangePasswordComponent {
8987
9761
  username = input(null, ...(ngDevMode ? [{ debugName: "username" }] : []));
8988
9762
  alert = input(undefined, ...(ngDevMode ? [{ debugName: "alert" }] : []));
8989
9763
  redirectAfterSuccess = input(true, ...(ngDevMode ? [{ debugName: "redirectAfterSuccess" }] : []));
9764
+ redirectAfterCancel = input(true, ...(ngDevMode ? [{ debugName: "redirectAfterCancel" }] : []));
8990
9765
  transloco = inject(TranslocoService);
8991
9766
  router = inject(Router);
8992
9767
  principalStore = inject(PrincipalStore);
@@ -9010,8 +9785,8 @@ class ChangePasswordComponent {
9010
9785
  if (fromInput?.trim())
9011
9786
  return fromInput.trim();
9012
9787
  if (isAuthenticated()) {
9013
- const p = this.principalStore.principal?.();
9014
- const name = p?.name?.trim();
9788
+ const p = getState(this.principalStore);
9789
+ const name = p.name.trim();
9015
9790
  if (name)
9016
9791
  return name;
9017
9792
  }
@@ -9046,7 +9821,6 @@ class ChangePasswordComponent {
9046
9821
  if (!username) {
9047
9822
  notify.info(this.transloco.translate("login.loginAfterPasswordChangeMissingUser"), { duration: 3000 });
9048
9823
  this.router.navigate(["/login"]);
9049
- this.success.emit();
9050
9824
  return;
9051
9825
  }
9052
9826
  await this.clearLocalAuthState();
@@ -9087,10 +9861,12 @@ class ChangePasswordComponent {
9087
9861
  this.pending.set(false);
9088
9862
  this.errorMsg.set(null);
9089
9863
  this.cancel.emit();
9090
- this.location.back();
9864
+ if (this.redirectAfterCancel()) {
9865
+ this.location.back();
9866
+ }
9091
9867
  }
9092
9868
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ChangePasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9093
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ChangePasswordComponent, isStandalone: true, selector: "change-password, ChangePassword, changepassword", inputs: { username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null }, alert: { classPropertyName: "alert", publicName: "alert", isSignal: true, isRequired: false, transformFunction: null }, redirectAfterSuccess: { classPropertyName: "redirectAfterSuccess", publicName: "redirectAfterSuccess", isSignal: true, isRequired: false, transformFunction: null }, currentPassword: { classPropertyName: "currentPassword", publicName: "currentPassword", isSignal: true, isRequired: false, transformFunction: null }, newPassword: { classPropertyName: "newPassword", publicName: "newPassword", isSignal: true, isRequired: false, transformFunction: null }, confirmPassword: { classPropertyName: "confirmPassword", publicName: "confirmPassword", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { success: "success", cancel: "cancel", currentPassword: "currentPasswordChange", newPassword: "newPasswordChange", confirmPassword: "confirmPasswordChange" }, providers: [provideTranslocoScope("login")], ngImport: i0, template: `
9869
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ChangePasswordComponent, isStandalone: true, selector: "change-password, ChangePassword, changepassword", inputs: { username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null }, alert: { classPropertyName: "alert", publicName: "alert", isSignal: true, isRequired: false, transformFunction: null }, redirectAfterSuccess: { classPropertyName: "redirectAfterSuccess", publicName: "redirectAfterSuccess", isSignal: true, isRequired: false, transformFunction: null }, redirectAfterCancel: { classPropertyName: "redirectAfterCancel", publicName: "redirectAfterCancel", isSignal: true, isRequired: false, transformFunction: null }, currentPassword: { classPropertyName: "currentPassword", publicName: "currentPassword", isSignal: true, isRequired: false, transformFunction: null }, newPassword: { classPropertyName: "newPassword", publicName: "newPassword", isSignal: true, isRequired: false, transformFunction: null }, confirmPassword: { classPropertyName: "confirmPassword", publicName: "confirmPassword", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { success: "success", cancel: "cancel", currentPassword: "currentPasswordChange", newPassword: "newPasswordChange", confirmPassword: "confirmPasswordChange" }, providers: [provideTranslocoScope("login")], ngImport: i0, template: `
9094
9870
  <Card hover="no" cdkTrapFocus cdkTrapFocusAutoCapture="true" class="bg-card rounded-xl shadow-sm">
9095
9871
  <CardHeader class="flex flex-col items-center gap-3">
9096
9872
  <img class="h-12 content-(--logo-large)" alt="logo" />
@@ -9198,14 +9974,11 @@ class ChangePasswordComponent {
9198
9974
  </button>
9199
9975
  </CardFooter>
9200
9976
  </Card>
9201
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "directive", type: CardFooterComponent, selector: ".card-footer, card-footer, CardFooter, cardfooter", inputs: ["class"] }, { kind: "directive", type: InputGroupInput, selector: "input[input-group]", inputs: ["class", "type", "placeholder", "disabled"] }, { kind: "directive", type: InputGroupComponent, selector: "input-group, inputgroup, InputGroup", inputs: ["class"] }, { kind: "directive", type: InputGroupAddonComponent, selector: "input-group-addon, inputgroupaddon, InputGroupAddon", inputs: ["class", "align"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
9977
+ `, isInline: true, styles: ["input::-ms-reveal{display:none}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "directive", type: CardFooterComponent, selector: ".card-footer, card-footer, CardFooter, cardfooter", inputs: ["class"] }, { kind: "directive", type: InputGroupInput, selector: "input[input-group]", inputs: ["class", "type", "placeholder", "disabled"] }, { kind: "directive", type: InputGroupComponent, selector: "input-group, inputgroup, InputGroup", inputs: ["class"] }, { kind: "directive", type: InputGroupAddonComponent, selector: "input-group-addon, inputgroupaddon, InputGroupAddon", inputs: ["class", "align"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
9202
9978
  }
9203
9979
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ChangePasswordComponent, decorators: [{
9204
9980
  type: Component,
9205
- args: [{
9206
- selector: "change-password, ChangePassword, changepassword",
9207
- providers: [provideTranslocoScope("login")],
9208
- imports: [
9981
+ args: [{ selector: "change-password, ChangePassword, changepassword", providers: [provideTranslocoScope("login")], imports: [
9209
9982
  FormsModule,
9210
9983
  A11yModule,
9211
9984
  TranslocoPipe,
@@ -9217,8 +9990,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9217
9990
  InputGroupInput,
9218
9991
  InputGroupComponent,
9219
9992
  InputGroupAddonComponent
9220
- ],
9221
- template: `
9993
+ ], template: `
9222
9994
  <Card hover="no" cdkTrapFocus cdkTrapFocusAutoCapture="true" class="bg-card rounded-xl shadow-sm">
9223
9995
  <CardHeader class="flex flex-col items-center gap-3">
9224
9996
  <img class="h-12 content-(--logo-large)" alt="logo" />
@@ -9326,9 +10098,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9326
10098
  </button>
9327
10099
  </CardFooter>
9328
10100
  </Card>
9329
- `
9330
- }]
9331
- }], propDecorators: { success: [{ type: i0.Output, args: ["success"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], username: [{ type: i0.Input, args: [{ isSignal: true, alias: "username", required: false }] }], alert: [{ type: i0.Input, args: [{ isSignal: true, alias: "alert", required: false }] }], redirectAfterSuccess: [{ type: i0.Input, args: [{ isSignal: true, alias: "redirectAfterSuccess", required: false }] }], currentPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPassword", required: false }] }, { type: i0.Output, args: ["currentPasswordChange"] }], newPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "newPassword", required: false }] }, { type: i0.Output, args: ["newPasswordChange"] }], confirmPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmPassword", required: false }] }, { type: i0.Output, args: ["confirmPasswordChange"] }] } });
10101
+ `, styles: ["input::-ms-reveal{display:none}\n"] }]
10102
+ }], propDecorators: { success: [{ type: i0.Output, args: ["success"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], username: [{ type: i0.Input, args: [{ isSignal: true, alias: "username", required: false }] }], alert: [{ type: i0.Input, args: [{ isSignal: true, alias: "alert", required: false }] }], redirectAfterSuccess: [{ type: i0.Input, args: [{ isSignal: true, alias: "redirectAfterSuccess", required: false }] }], redirectAfterCancel: [{ type: i0.Input, args: [{ isSignal: true, alias: "redirectAfterCancel", required: false }] }], currentPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPassword", required: false }] }, { type: i0.Output, args: ["currentPasswordChange"] }], newPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "newPassword", required: false }] }, { type: i0.Output, args: ["newPasswordChange"] }], confirmPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmPassword", required: false }] }, { type: i0.Output, args: ["confirmPasswordChange"] }] } });
9332
10103
 
9333
10104
  class ForgotPasswordComponent {
9334
10105
  cancel = output();
@@ -9400,7 +10171,7 @@ class ForgotPasswordComponent {
9400
10171
 
9401
10172
  </CardFooter>
9402
10173
  </Card>
9403
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "directive", type: CardFooterComponent, selector: ".card-footer, card-footer, CardFooter, cardfooter", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
10174
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "directive", type: CardFooterComponent, selector: ".card-footer, card-footer, CardFooter, cardfooter", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
9404
10175
  }
9405
10176
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ForgotPasswordComponent, decorators: [{
9406
10177
  type: Component,
@@ -9475,55 +10246,40 @@ class SignInComponent {
9475
10246
  username: this.username(),
9476
10247
  password: this.password()
9477
10248
  }), ...(ngDevMode ? [{ debugName: "credentials" }] : []));
9478
- isValid = computed(() => !!this.credentials().username?.length && !!this.credentials().password?.length, ...(ngDevMode ? [{ debugName: "isValid" }] : []));
9479
- authenticated = signal(isAuthenticated(), ...(ngDevMode ? [{ debugName: "authenticated" }] : []));
9480
- user = signal(null, ...(ngDevMode ? [{ debugName: "user" }] : []));
9481
- returnUrl = signal(null, ...(ngDevMode ? [{ debugName: "returnUrl" }] : []));
10249
+ isValid = computed(() => !!this.credentials().username?.length &&
10250
+ !!this.credentials().password?.length, ...(ngDevMode ? [{ debugName: "isValid" }] : []));
9482
10251
  router = inject(Router);
9483
10252
  route = inject(ActivatedRoute);
9484
- principalService = inject(PrincipalService);
9485
10253
  applicationService = inject(ApplicationService);
9486
10254
  principalStore = inject(PrincipalStore);
9487
10255
  transloco = inject(TranslocoService);
10256
+ authenticated = signal(isAuthenticated(), ...(ngDevMode ? [{ debugName: "authenticated" }] : []));
10257
+ user = signal(getState(this.principalStore), ...(ngDevMode ? [{ debugName: "user" }] : []));
10258
+ expiresSoonNotified = signal(false, ...(ngDevMode ? [{ debugName: "expiresSoonNotified" }] : []));
9488
10259
  constructor(destroyRef) {
9489
10260
  this.destroyRef = destroyRef;
9490
10261
  effect(() => {
9491
- if (this.authenticated()) {
9492
- this.principalService
9493
- .getPrincipal()
9494
- .pipe(takeUntilDestroyed(this.destroyRef))
9495
- .subscribe((principal) => this.user.set(principal));
9496
- }
9497
- });
9498
- effect(() => {
9499
- const principal = this.user();
9500
- if (this.authenticated() && principal) {
9501
- this.checkPasswordExpiresSoon(principal);
9502
- }
9503
- });
9504
- effect(() => {
9505
- if (this.returnUrl() !== null) {
9506
- const [url] = this.returnUrl() || ["/"];
9507
- this.router.navigateByUrl(url);
10262
+ const principal = getState(this.principalStore);
10263
+ if (this.authenticated() && principal && !this.expiresSoonNotified()) {
10264
+ this.checkPasswordExpiresSoon();
9508
10265
  }
9509
10266
  });
9510
10267
  fromEvent(window, "authenticated")
9511
10268
  .pipe(takeUntilDestroyed(this.destroyRef))
9512
- .subscribe((event) => {
10269
+ .subscribe(event => {
9513
10270
  this.authenticated.set(event.detail.authenticated);
9514
10271
  const url = this.route.snapshot.queryParams["returnUrl"] || null;
9515
- if (url !== null)
9516
- this.returnUrl.set([url]);
9517
- if (isAuthenticated()) {
10272
+ if (url !== null) {
9518
10273
  this.router.navigateByUrl(url ?? "/");
9519
10274
  }
9520
10275
  });
9521
10276
  }
9522
- checkPasswordExpiresSoon(principal) {
9523
- if (!principal?.editablePartition || !principal.passwordExpirationDate) {
10277
+ checkPasswordExpiresSoon() {
10278
+ const { editablePartition, passwordExpirationDate } = getState(this.principalStore);
10279
+ if (!editablePartition || !passwordExpirationDate) {
9524
10280
  return;
9525
10281
  }
9526
- if (expiresSoon(principal.passwordExpirationDate, 7)) {
10282
+ if (expiresSoon(passwordExpirationDate, 7)) {
9527
10283
  notify.warning(this.transloco.translate("login.passwordExpiresSoon"), {
9528
10284
  duration: Number.POSITIVE_INFINITY,
9529
10285
  action: {
@@ -9538,17 +10294,16 @@ class SignInComponent {
9538
10294
  }
9539
10295
  }
9540
10296
  });
10297
+ this.expiresSoonNotified.set(true);
9541
10298
  }
9542
10299
  }
9543
10300
  async handleLogout() {
9544
10301
  await logout();
9545
10302
  this.authenticated.set(false);
9546
- this.user.set(null);
9547
- this.returnUrl.set(null);
9548
10303
  this.router.navigate(["/login"]);
9549
10304
  }
9550
10305
  async handleLogin() {
9551
- login().catch((error) => {
10306
+ login().catch(error => {
9552
10307
  warn("An error occurred while logging in", error);
9553
10308
  this.router.navigate(["error"]);
9554
10309
  });
@@ -9562,14 +10317,14 @@ class SignInComponent {
9562
10317
  return;
9563
10318
  const { createRoutes = false } = globalConfig;
9564
10319
  await this.applicationService.initialize(createRoutes);
9565
- const p = this.principalStore.principal?.();
9566
- this.checkPasswordExpiresSoon(p ?? null);
10320
+ this.checkPasswordExpiresSoon();
9567
10321
  const url = this.route.snapshot.queryParams["returnUrl"] || "/";
9568
10322
  this.router.navigateByUrl(url);
9569
10323
  }
9570
10324
  catch (err) {
9571
10325
  const { status, errorMessage } = err;
9572
- if (status === 401 && errorMessage?.toLowerCase().includes("password expired")) {
10326
+ if (status === 401 &&
10327
+ errorMessage?.toLowerCase().includes("password expired")) {
9573
10328
  sessionStorage.setItem("passwordExpiredFlow", "true");
9574
10329
  this.router.navigate(["/login"], {
9575
10330
  queryParams: {
@@ -9582,94 +10337,82 @@ class SignInComponent {
9582
10337
  }
9583
10338
  notify.error("Login", { description: errorMessage });
9584
10339
  }
9585
- }
9586
- handleBack() {
9587
- this.authenticated.set(false);
9588
- this.user.set(null);
9589
- this.returnUrl.set(null);
9590
- this.router.navigate(["/"]);
9591
- }
9592
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SignInComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
9593
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: SignInComponent, isStandalone: true, selector: "signIn, signin, sign-in", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null }, password: { classPropertyName: "password", publicName: "password", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { forgotPassword: "forgotPassword", username: "usernameChange", password: "passwordChange" }, host: { properties: { "class": "cn('grid h-dvh w-full place-content-center', class())" } }, providers: [provideTranslocoScope("login")], ngImport: i0, template: `
9594
- <Card
9595
- hover="no"
9596
- cdkTrapFocus
9597
- cdkTrapFocusAutoCapture="true"
9598
- class="bg-card rounded-xl shadow-sm"
9599
- >
9600
- @if (authenticated()) {
9601
- <CardHeader class="flex flex-row items-start gap-4">
9602
- <img class="content-(--logo-large) h-12" alt="logo" />
9603
- <div class="space-y-1">
9604
- <h3 class="text-2xl font-semibold leading-tight">
9605
- {{
9606
- 'login.welcomeBack' | transloco : { name: user()?.fullName ?? '' }
9607
- }}
9608
- </h3>
9609
- <p class="text-muted-foreground text-sm">
9610
- {{ 'login.youAreLoggedIn' | transloco }}
9611
- </p>
9612
- </div>
9613
- </CardHeader>
9614
-
9615
- <CardFooter class="grid grid-cols-2 gap-3">
9616
- <button type="button" (click)="handleLogout()">
9617
- {{ 'login.disconnect' | transloco }}
9618
- </button>
9619
- <button type="button" (click)="handleBack()">{{ 'login.back' | transloco }}</button>
9620
- </CardFooter>
9621
- } @else {
9622
- <CardHeader class="flex flex-col items-center gap-3 text-center">
9623
- <img class="content-(--logo-large) h-12" alt="logo" />
9624
- </CardHeader>
9625
-
9626
- <CardContent class="grid gap-4">
9627
- @if (!config.autoOAuthProvider && !config.autoSAMLProvider) {
9628
- <div class="grid gap-2">
9629
- <label class="text-sm font-medium" for="username">{{
9630
- 'login.username' | transloco
9631
- }}</label>
9632
- <input
9633
- id="username"
9634
- type="text"
9635
- required
9636
- [(ngModel)]="username"
9637
- (keydown.enter)="handleLoginWithCredentials()"
9638
- />
9639
- </div>
10340
+ }
10341
+ handleBack() {
10342
+ this.authenticated.set(false);
10343
+ this.router.navigate(["/"]);
10344
+ }
10345
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SignInComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
10346
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: SignInComponent, isStandalone: true, selector: "signIn, signin, sign-in", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null }, password: { classPropertyName: "password", publicName: "password", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { forgotPassword: "forgotPassword", username: "usernameChange", password: "passwordChange" }, host: { properties: { "class": "cn('grid h-dvh w-full place-content-center', class())" } }, providers: [provideTranslocoScope("login")], ngImport: i0, template: `
10347
+ @if (!authenticated()) {
10348
+ <Card
10349
+ hover="no"
10350
+ cdkTrapFocus
10351
+ cdkTrapFocusAutoCapture="true"
10352
+ class="bg-card rounded-xl shadow-sm">
10353
+ <CardHeader class="flex flex-col items-center gap-3 text-center">
10354
+ <img class="h-12 content-(--logo-large)" alt="logo" />
10355
+ </CardHeader>
10356
+
10357
+ <CardContent class="grid gap-4">
10358
+ @let useCredentials =
10359
+ !config.autoOAuthProvider && !config.autoSAMLProvider;
10360
+ @if (useCredentials) {
10361
+ <!-- authentication using credentials -->
10362
+ <div class="grid gap-2">
10363
+ <label class="text-sm font-medium" for="username">{{
10364
+ "login.username" | transloco
10365
+ }}</label>
10366
+ <input
10367
+ id="username"
10368
+ type="text"
10369
+ required
10370
+ [(ngModel)]="username"
10371
+ (keydown.enter)="handleLoginWithCredentials()" />
10372
+ </div>
9640
10373
 
9641
10374
  <div class="grid gap-2">
9642
- <label class="text-sm font-medium" for="password">{{ 'login.password' | transloco }}</label>
9643
- <input id="password" type="password" required [(ngModel)]="password" (keydown.enter)="handleLoginWithCredentials()" />
10375
+ <label class="text-sm font-medium" for="password">{{
10376
+ "login.password" | transloco
10377
+ }}</label>
10378
+ <input
10379
+ id="password"
10380
+ type="password"
10381
+ required
10382
+ [(ngModel)]="password"
10383
+ (keydown.enter)="handleLoginWithCredentials()" />
9644
10384
  </div>
9645
10385
 
9646
- <span
9647
- class="cursor-pointer text-xs text-muted-foreground hover:underline justify-self-start"
9648
- role="button"
9649
- tabindex="0"
10386
+ <span
10387
+ class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
10388
+ role="button"
10389
+ tabindex="0"
9650
10390
  (click)="forgotPassword.emit()"
9651
- (keydown.enter)="forgotPassword.emit()">
9652
- {{ 'login.forgotPassword' | transloco }}
9653
- </span>
9654
- <button [disabled]="!isValid()" (click)="handleLoginWithCredentials()">
9655
- {{ 'login.connect' | transloco }}
10391
+ (keydown.enter)="forgotPassword.emit()">
10392
+ {{ "login.forgotPassword" | transloco }}
10393
+ </span>
10394
+ <button
10395
+ [disabled]="!isValid()"
10396
+ (click)="handleLoginWithCredentials()">
10397
+ {{ "login.connect" | transloco }}
9656
10398
  </button>
9657
-
9658
- } @else {
10399
+ }
10400
+ @else {
10401
+ <!-- authentication using OAuth or SAML provider -->
9659
10402
  <button (click)="handleLogin()">
9660
- {{ 'login.SignInWith' | transloco: { provider: config.autoOAuthProvider ? 'OAuth' : 'SAML' } }}
10403
+ {{ "login.SignInWith" | transloco : { provider: config.autoOAuthProvider ? "OAuth" : "SAML" } }}
9661
10404
  </button>
9662
10405
  }
9663
10406
  </CardContent>
9664
- }
9665
- </Card>
9666
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "directive", type: CardFooterComponent, selector: ".card-footer, card-footer, CardFooter, cardfooter", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
10407
+ </Card>
10408
+ } @else {
10409
+ <app-wait />
10410
+ }
10411
+ `, isInline: true, styles: ["input{background-color:var(--background)}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "component", type: LoadingComponent, selector: "app-wait" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
9667
10412
  }
9668
10413
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SignInComponent, decorators: [{
9669
10414
  type: Component,
9670
- args: [{
9671
- selector: "signIn, signin, sign-in",
9672
- imports: [
10415
+ args: [{ selector: "signIn, signin, sign-in", imports: [
9673
10416
  RouterModule,
9674
10417
  FormsModule,
9675
10418
  A11yModule,
@@ -9679,87 +10422,75 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9679
10422
  CardComponent,
9680
10423
  CardHeaderComponent,
9681
10424
  CardContentComponent,
9682
- CardFooterComponent
9683
- ],
9684
- providers: [provideTranslocoScope("login")],
9685
- template: `
9686
- <Card
9687
- hover="no"
9688
- cdkTrapFocus
9689
- cdkTrapFocusAutoCapture="true"
9690
- class="bg-card rounded-xl shadow-sm"
9691
- >
9692
- @if (authenticated()) {
9693
- <CardHeader class="flex flex-row items-start gap-4">
9694
- <img class="content-(--logo-large) h-12" alt="logo" />
9695
- <div class="space-y-1">
9696
- <h3 class="text-2xl font-semibold leading-tight">
9697
- {{
9698
- 'login.welcomeBack' | transloco : { name: user()?.fullName ?? '' }
9699
- }}
9700
- </h3>
9701
- <p class="text-muted-foreground text-sm">
9702
- {{ 'login.youAreLoggedIn' | transloco }}
9703
- </p>
9704
- </div>
9705
- </CardHeader>
9706
-
9707
- <CardFooter class="grid grid-cols-2 gap-3">
9708
- <button type="button" (click)="handleLogout()">
9709
- {{ 'login.disconnect' | transloco }}
9710
- </button>
9711
- <button type="button" (click)="handleBack()">{{ 'login.back' | transloco }}</button>
9712
- </CardFooter>
9713
- } @else {
9714
- <CardHeader class="flex flex-col items-center gap-3 text-center">
9715
- <img class="content-(--logo-large) h-12" alt="logo" />
9716
- </CardHeader>
9717
-
9718
- <CardContent class="grid gap-4">
9719
- @if (!config.autoOAuthProvider && !config.autoSAMLProvider) {
9720
- <div class="grid gap-2">
9721
- <label class="text-sm font-medium" for="username">{{
9722
- 'login.username' | transloco
9723
- }}</label>
9724
- <input
9725
- id="username"
9726
- type="text"
9727
- required
9728
- [(ngModel)]="username"
9729
- (keydown.enter)="handleLoginWithCredentials()"
9730
- />
9731
- </div>
10425
+ LoadingComponent
10426
+ ], providers: [provideTranslocoScope("login")], template: `
10427
+ @if (!authenticated()) {
10428
+ <Card
10429
+ hover="no"
10430
+ cdkTrapFocus
10431
+ cdkTrapFocusAutoCapture="true"
10432
+ class="bg-card rounded-xl shadow-sm">
10433
+ <CardHeader class="flex flex-col items-center gap-3 text-center">
10434
+ <img class="h-12 content-(--logo-large)" alt="logo" />
10435
+ </CardHeader>
10436
+
10437
+ <CardContent class="grid gap-4">
10438
+ @let useCredentials =
10439
+ !config.autoOAuthProvider && !config.autoSAMLProvider;
10440
+ @if (useCredentials) {
10441
+ <!-- authentication using credentials -->
10442
+ <div class="grid gap-2">
10443
+ <label class="text-sm font-medium" for="username">{{
10444
+ "login.username" | transloco
10445
+ }}</label>
10446
+ <input
10447
+ id="username"
10448
+ type="text"
10449
+ required
10450
+ [(ngModel)]="username"
10451
+ (keydown.enter)="handleLoginWithCredentials()" />
10452
+ </div>
9732
10453
 
9733
10454
  <div class="grid gap-2">
9734
- <label class="text-sm font-medium" for="password">{{ 'login.password' | transloco }}</label>
9735
- <input id="password" type="password" required [(ngModel)]="password" (keydown.enter)="handleLoginWithCredentials()" />
10455
+ <label class="text-sm font-medium" for="password">{{
10456
+ "login.password" | transloco
10457
+ }}</label>
10458
+ <input
10459
+ id="password"
10460
+ type="password"
10461
+ required
10462
+ [(ngModel)]="password"
10463
+ (keydown.enter)="handleLoginWithCredentials()" />
9736
10464
  </div>
9737
10465
 
9738
- <span
9739
- class="cursor-pointer text-xs text-muted-foreground hover:underline justify-self-start"
9740
- role="button"
9741
- tabindex="0"
10466
+ <span
10467
+ class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
10468
+ role="button"
10469
+ tabindex="0"
9742
10470
  (click)="forgotPassword.emit()"
9743
- (keydown.enter)="forgotPassword.emit()">
9744
- {{ 'login.forgotPassword' | transloco }}
9745
- </span>
9746
- <button [disabled]="!isValid()" (click)="handleLoginWithCredentials()">
9747
- {{ 'login.connect' | transloco }}
10471
+ (keydown.enter)="forgotPassword.emit()">
10472
+ {{ "login.forgotPassword" | transloco }}
10473
+ </span>
10474
+ <button
10475
+ [disabled]="!isValid()"
10476
+ (click)="handleLoginWithCredentials()">
10477
+ {{ "login.connect" | transloco }}
9748
10478
  </button>
9749
-
9750
- } @else {
10479
+ }
10480
+ @else {
10481
+ <!-- authentication using OAuth or SAML provider -->
9751
10482
  <button (click)="handleLogin()">
9752
- {{ 'login.SignInWith' | transloco: { provider: config.autoOAuthProvider ? 'OAuth' : 'SAML' } }}
10483
+ {{ "login.SignInWith" | transloco : { provider: config.autoOAuthProvider ? "OAuth" : "SAML" } }}
9753
10484
  </button>
9754
10485
  }
9755
10486
  </CardContent>
9756
- }
9757
- </Card>
9758
- `,
9759
- host: {
10487
+ </Card>
10488
+ } @else {
10489
+ <app-wait />
10490
+ }
10491
+ `, host: {
9760
10492
  "[class]": "cn('grid h-dvh w-full place-content-center', class())"
9761
- }
9762
- }]
10493
+ }, styles: ["input{background-color:var(--background)}\n"] }]
9763
10494
  }], ctorParameters: () => [{ type: i0.DestroyRef }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], forgotPassword: [{ type: i0.Output, args: ["forgotPassword"] }], username: [{ type: i0.Input, args: [{ isSignal: true, alias: "username", required: false }] }, { type: i0.Output, args: ["usernameChange"] }], password: [{ type: i0.Input, args: [{ isSignal: true, alias: "password", required: false }] }, { type: i0.Output, args: ["passwordChange"] }] } });
9764
10495
 
9765
10496
  class AuthPageComponent {
@@ -9801,7 +10532,7 @@ class AuthPageComponent {
9801
10532
  />
9802
10533
  }
9803
10534
  </div>
9804
- `, isInline: true, dependencies: [{ kind: "component", type: SignInComponent, selector: "signIn, signin, sign-in", inputs: ["class", "username", "password"], outputs: ["forgotPassword", "usernameChange", "passwordChange"] }, { kind: "component", type: ChangePasswordComponent, selector: "change-password, ChangePassword, changepassword", inputs: ["username", "alert", "redirectAfterSuccess", "currentPassword", "newPassword", "confirmPassword"], outputs: ["success", "cancel", "currentPasswordChange", "newPasswordChange", "confirmPasswordChange"] }, { kind: "component", type: ForgotPasswordComponent, selector: "forgot-password, ForgotPassword, forgotpassword", inputs: ["userName"], outputs: ["cancel", "success", "userNameChange"] }] });
10535
+ `, isInline: true, dependencies: [{ kind: "component", type: SignInComponent, selector: "signIn, signin, sign-in", inputs: ["class", "username", "password"], outputs: ["forgotPassword", "usernameChange", "passwordChange"] }, { kind: "component", type: ChangePasswordComponent, selector: "change-password, ChangePassword, changepassword", inputs: ["username", "alert", "redirectAfterSuccess", "redirectAfterCancel", "currentPassword", "newPassword", "confirmPassword"], outputs: ["success", "cancel", "currentPasswordChange", "newPasswordChange", "confirmPasswordChange"] }, { kind: "component", type: ForgotPasswordComponent, selector: "forgot-password, ForgotPassword, forgotpassword", inputs: ["userName"], outputs: ["cancel", "success", "userNameChange"] }] });
9805
10536
  }
9806
10537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthPageComponent, decorators: [{
9807
10538
  type: Component,
@@ -10154,7 +10885,7 @@ class OverrideUserDialogComponent {
10154
10885
  this.appService
10155
10886
  .initialize(createRoutes)
10156
10887
  .then(() => {
10157
- const { fullName } = getState(this.principalStore).principal;
10888
+ const fullName = this.principalStore.fullName();
10158
10889
  notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
10159
10890
  })
10160
10891
  .catch((err) => {
@@ -10170,12 +10901,11 @@ class OverrideUserDialogComponent {
10170
10901
  this.appService
10171
10902
  .initialize(createRoutes)
10172
10903
  .then(() => {
10173
- const { fullName } = getState(this.principalStore).principal;
10904
+ const fullName = this.principalStore.fullName();
10174
10905
  notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
10175
10906
  })
10176
10907
  .catch((err) => {
10177
10908
  error("An error occured while overriding (initialize)", err);
10178
- notify.error("An error occured while overriding (initialize)", { duration: 2000 });
10179
10909
  setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
10180
10910
  });
10181
10911
  }
@@ -10234,7 +10964,7 @@ class OverrideUserDialogComponent {
10234
10964
  </DialogFooter>
10235
10965
  </DialogContent>
10236
10966
  </dialog>
10237
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
10967
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
10238
10968
  }
10239
10969
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: OverrideUserDialogComponent, decorators: [{
10240
10970
  type: Component,
@@ -10970,7 +11700,7 @@ class DrawerAdvancedFiltersComponent extends DrawerComponent {
10970
11700
  return res;
10971
11701
  }
10972
11702
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DrawerAdvancedFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10973
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DrawerAdvancedFiltersComponent, isStandalone: true, selector: "advanced-filters", host: { classAttribute: "fixed bg-background grid h-full top-0 -right-full justify-end" }, providers: [DrawerService, SyslangPipe, TranslocoPipe, provideTranslocoScope("drawers")], usesInheritance: true, ngImport: i0, template: "<div (click)=\"drawer.toggleExtension()\" (keydown.escape)=\"drawer.toggleExtension()\" [attr.aria-hidden]=\"true\"></div>\n\n<div class=\"flex h-full flex-col overflow-auto\">\n <DrawerNavbar class=\"border-foreground/10 block border-b\">\n <button [attr.title]=\"'drawers.search' | transloco\" (click)=\"onSearch()\">\n {{ 'drawers.search' | transloco }}\n </button>\n </DrawerNavbar>\n\n <div class=\"flex h-full grow flex-col overflow-auto\">\n <section class=\"flex flex-col gap-4 p-6\" [formGroup]=\"form\">\n <!-- FIND IN -->\n <h1 class=\"text-xl font-bold\">{{ 'drawers.findInContent' | transloco }}</h1>\n <div class=\"flex items-center gap-4\" formGroupName=\"content\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInContent' | transloco }}</span>\n <select\n id=\"content-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input\n [variant]=\"inputVariant()\"\n id=\"content-value\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [placeholder]=\"getPlaceholder('content.operator')\"\n formControlName=\"value\" />\n </div>\n @if (enableFieldedSearch()) {\n <div class=\"flex items-center gap-4\" formGroupName=\"title\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInTitle' | transloco }}</span>\n <select\n id=\"title-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input id=\"title-value\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [placeholder]=\"getPlaceholder('title.operator')\" formControlName=\"value\" />\n </div>\n }\n\n <!-- TABS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.inScope' | transloco }}&nbsp;&quot;{{ currentTab() }}&quot;</h1>\n\n <Tabs>\n <TabsList variant=\"ghost\">\n @for (tab of tabs(); track $index) {\n <Tab class=\"w-fit\" [value]=\"tab.path\" [active]=\"tab.path === currentTab()\" (click)=\"onTabChange(tab.path)\">\n {{ tab.display | syslang | transloco }}\n </Tab>\n }\n </TabsList>\n </Tabs>\n\n <!-- FILTERS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.applyFilters' | transloco }}</h1>\n @for (filter of filters(); track $index) {\n <DropdownInput\n [suggestions]=\"suggestions()\"\n [selected]=\"getItems(filter.column)\"\n [label]=\"filter.display || filter.alias | syslang | transloco\"\n [placeholder]=\"'drawers.startTyping' | transloco\"\n [noResultLabel]=\"'drawers.noResult' | transloco\"\n (onFocus)=\"setFilterFocus($event, filter)\"\n (onKeyUp)=\"onInputTyping($event)\"\n (removeItem)=\"removeItem($event, filter)\"\n (addItem)=\"addItem($event, filter)\" />\n } @empty {\n {{ 'drawers.noFilters' | transloco }}\n }\n </section>\n </div>\n</div>\n", styles: [":host{--drawer-width: 46;--drawer-subdrawer-width: 400px;width:calc(100vw / 100 * var(--drawer-width) + var(--drawer-subdrawer-width));z-index:var(--z-drawer);grid-template-columns:0 1fr var(--drawer-subdrawer-width);transition:right .3s ease-in-out,transform .3s ease-in-out}:host[drawer-opened=true]{right:calc(var(--drawer-subdrawer-width) * -1);box-shadow:var(--drawer-box-shadow)}:host[drawer-extended=true]{width:100vw;right:calc(var(--spacing) * 0);grid-template-columns:1fr calc(var(--drawer-width) * 1%) var(--drawer-subdrawer-width);box-shadow:unset}:is() .dropdown{width:100%}:is() .dropdown-content{width:50%}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: DrawerNavbarComponent, selector: "DrawerNavbar, drawernavbar" }, { kind: "component", type: DropdownInputComponent, selector: "dropdown-input, DropdownInput", inputs: ["label", "placeholder", "noResultLabel", "suggestions", "selected"], outputs: ["onFocus", "onKeyUp", "removeItem", "addItem"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
11703
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DrawerAdvancedFiltersComponent, isStandalone: true, selector: "advanced-filters", host: { classAttribute: "fixed bg-background grid h-full top-0 -right-full justify-end" }, providers: [DrawerService, SyslangPipe, TranslocoPipe, provideTranslocoScope("drawers")], usesInheritance: true, ngImport: i0, template: "<div (click)=\"drawer.toggleExtension()\" (keydown.escape)=\"drawer.toggleExtension()\" [attr.aria-hidden]=\"true\"></div>\n\n<div class=\"flex h-full flex-col overflow-auto\">\n <DrawerNavbar class=\"border-foreground/10 block border-b\">\n <button [attr.title]=\"'drawers.search' | transloco\" (click)=\"onSearch()\">\n {{ 'drawers.search' | transloco }}\n </button>\n </DrawerNavbar>\n\n <div class=\"flex h-full grow flex-col overflow-auto\">\n <section class=\"flex flex-col gap-4 p-6\" [formGroup]=\"form\">\n <!-- FIND IN -->\n <h1 class=\"text-xl font-bold\">{{ 'drawers.findInContent' | transloco }}</h1>\n <div class=\"flex items-center gap-4\" formGroupName=\"content\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInContent' | transloco }}</span>\n <select\n id=\"content-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input\n [variant]=\"inputVariant()\"\n id=\"content-value\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [placeholder]=\"getPlaceholder('content.operator')\"\n formControlName=\"value\" />\n </div>\n @if (enableFieldedSearch()) {\n <div class=\"flex items-center gap-4\" formGroupName=\"title\">\n <span class=\"w-1/3 font-semibold\">{{ 'drawers.findInTitle' | transloco }}</span>\n <select\n id=\"title-operator\"\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 w-full rounded-md border px-2 hover:outline focus:outline\"\n formControlName=\"operator\">\n @for (data of selectData; track $index) {\n <option [value]=\"data.operator\">{{ 'drawers.' + data.display | transloco }}</option>\n }\n </select>\n <input id=\"title-value\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [placeholder]=\"getPlaceholder('title.operator')\" formControlName=\"value\" />\n </div>\n }\n\n <!-- TABS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.inScope' | transloco }}&nbsp;&quot;{{ currentTab() }}&quot;</h1>\n\n <Tabs>\n <TabsList variant=\"ghost\">\n @for (tab of tabs(); track $index) {\n <Tab class=\"w-fit\" [value]=\"tab.path\" [active]=\"tab.path === currentTab()\" (click)=\"onTabChange(tab.path)\">\n {{ tab.display | syslang | transloco }}\n </Tab>\n }\n </TabsList>\n </Tabs>\n\n <!-- FILTERS -->\n <h1 class=\"mt-4 text-xl font-bold\">{{ 'drawers.applyFilters' | transloco }}</h1>\n @for (filter of filters(); track $index) {\n <DropdownInput\n [suggestions]=\"suggestions()\"\n [selected]=\"getItems(filter.column)\"\n [label]=\"filter.display || filter.alias | syslang | transloco\"\n [placeholder]=\"'drawers.startTyping' | transloco\"\n [noResultLabel]=\"'drawers.noResult' | transloco\"\n (onFocus)=\"setFilterFocus($event, filter)\"\n (onKeyUp)=\"onInputTyping($event)\"\n (removeItem)=\"removeItem($event, filter)\"\n (addItem)=\"addItem($event, filter)\" />\n } @empty {\n {{ 'drawers.noFilters' | transloco }}\n }\n </section>\n </div>\n</div>\n", styles: [":host{--drawer-width: 46;--drawer-subdrawer-width: 400px;width:calc(100vw / 100 * var(--drawer-width) + var(--drawer-subdrawer-width));z-index:var(--z-drawer);grid-template-columns:0 1fr var(--drawer-subdrawer-width);transition:right .3s ease-in-out,transform .3s ease-in-out}:host[drawer-opened=true]{right:calc(var(--drawer-subdrawer-width) * -1);box-shadow:var(--drawer-box-shadow)}:host[drawer-extended=true]{width:100vw;right:calc(var(--spacing) * 0);grid-template-columns:1fr calc(var(--drawer-width) * 1%) var(--drawer-subdrawer-width);box-shadow:unset}:is() .dropdown{width:100%}:is() .dropdown-content{width:50%}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: DrawerNavbarComponent, selector: "DrawerNavbar, drawernavbar" }, { kind: "component", type: DropdownInputComponent, selector: "dropdown-input, DropdownInput", inputs: ["label", "placeholder", "noResultLabel", "suggestions", "selected"], outputs: ["onFocus", "onKeyUp", "removeItem", "addItem"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
10974
11704
  }
10975
11705
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DrawerAdvancedFiltersComponent, decorators: [{
10976
11706
  type: Component,
@@ -11868,7 +12598,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
11868
12598
  args: [{ selector: 'feedback, Feedback', standalone: true, imports: [ButtonComponent, MenuComponent, MenuContentComponent, MenuItemComponent, TranslocoPipe, FeedbackDialogComponent], providers: [provideTranslocoScope('feedback')], template: "<menu>\n <button class=\"border border-foreground/18\" variant=\"outline\" title=\"{{ 'feedback.button' | transloco }}\">\n <i class=\"fa-fw far fa-commenting\" aria-hidden=\"true\"></i>\n {{ 'feedback.label' | transloco }}\n <i class=\"fa-fw fas fa-xmark ms-2\" (click)=\"close($event)\" aria-hidden=\"true\"></i>\n </button>\n\n <MenuContent>\n @if (!disliked()) {\n <menuitem (click)=\"like()\" [attr.aria-label]=\"'feedback.like' | transloco\">\n @if (liked()) {\n <i class=\"fa-fw fas fa-thumbs-up\"></i> {{ 'feedback.liked' | transloco }}\n } @else {\n <i class=\"fa-fw far fa-thumbs-up\"></i> {{ 'feedback.like' | transloco }}\n }\n </menuitem>\n }\n @if (!liked()) {\n <menuitem (click)=\"dislike()\" [attr.aria-label]=\"'feedback.dislike' | transloco\">\n @if (disliked()) {\n <i class=\"fa-fw fas fa-thumbs-down\"></i> {{ 'feedback.disliked' | transloco }}\n } @else {\n <i class=\"fa-fw far fa-thumbs-down\"></i> {{ 'feedback.dislike' | transloco }}\n }\n </menuitem>\n }\n @for (menu of menus; track $index) {\n <menuitem (click)=\"openFeedbackDialog(menu.type)\" [attr.aria-label]=\"'feedback.' + menu.type + '.title' | transloco\">\n <i class=\"fa-fw {{ menu.icon }}\"></i> {{ 'feedback.' + menu.type + '.title' | transloco }}\n </menuitem>\n }\n </MenuContent>\n</menu>\n\n<feedback-dialog />\n" }]
11869
12599
  }], propDecorators: { onClose: [{ type: i0.Output, args: ["onClose"] }], feedbackDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FeedbackDialogComponent), { isSignal: true }] }], pages: [{ type: i0.Input, args: [{ isSignal: true, alias: "pages", required: true }] }] } });
11870
12600
 
11871
- class AggregationItemComponent {
12601
+ class AggregationTreeItemComponent {
11872
12602
  cn = cn;
11873
12603
  get disabled() {
11874
12604
  return this.node().count === 0 ? "disabled" : null;
@@ -11881,7 +12611,7 @@ class AggregationItemComponent {
11881
12611
  field = input(...(ngDevMode ? [undefined, { debugName: "field" }] : []));
11882
12612
  appStore = inject(AppStore);
11883
12613
  queryParamsStore = inject(QueryParamsStore);
11884
- searchText = inject(AggregationComponent).searchText;
12614
+ searchText = inject(AggregationTreeComponent).searchText;
11885
12615
  // is the count of items displayed, default to false
11886
12616
  showCount = computed(() => this.appStore.general()?.features?.showAggregationItemCount ?? false, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
11887
12617
  quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
@@ -11951,10 +12681,9 @@ class AggregationItemComponent {
11951
12681
  }
11952
12682
  }
11953
12683
  onChildSelect(item) {
11954
- this.onSelect.emit(undefined);
11955
12684
  // if some items are selected visually, this means all these items got selected visually and if one
11956
12685
  // got changed we need to change their selection status
11957
- if (item && this.linkChildren() && this.node().items.some((i) => i.$selectedVisually)) {
12686
+ if (item && this.linkChildren() && !item.$selected && this.node().items.some((i) => i.$selectedVisually)) {
11958
12687
  this.node().items.forEach((i) => {
11959
12688
  if (i !== item) {
11960
12689
  i.$selectedVisually = false;
@@ -11966,20 +12695,31 @@ class AggregationItemComponent {
11966
12695
  this.node().$selected = false;
11967
12696
  this.node().$selectedVisually = false;
11968
12697
  }
12698
+ this.onSelect.emit(this.node());
11969
12699
  }
11970
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11971
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AggregationItemComponent, isStandalone: true, selector: "aggregation-item, AggregationItem, aggregationitem", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: true, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onOpen: "onOpen", onFilter: "onFilter" }, host: { properties: { "attr.disabled": "this.disabled" } }, ngImport: i0, template: "<a\n role=\"listitem\"\n [attr.aria-selected]=\"node().$selected || node().$selectedVisually\"\n [attr.aria-label]=\"name() | syslang\"\n [style.--level]=\"level()\"\n [class]=\"\n cn(\n 'flex grow items-center gap-2 p-1 leading-7',\n node().count === 0 && 'disabled pointer-events-none text-neutral-300',\n (node().$selected || node().$selectedVisually) && 'bg-primary-100 text-primary'\n )\n \"\n (click)=\"select(node(), $event, true)\">\n <!-- chrevron is visible only if the node has children -->\n <button (click)=\"open($event, node())\" class=\"transition-transform ease-in hover:scale-125\" aria-label=\"Open\">\n <ChevronRight [class]=\"cn('size-4 translate-x-1', node().$opened && 'rotate-90', !node().hasChildren && 'hidden')\" width=\"16\" height=\"16\" />\n </button>\n\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)=\"select(node(), $event)\"\n [checked]=\"node().$selected || node().$selectedVisually\" />\n\n @if (node().icon) {\n <i class=\"fa-fw {{ node().icon }} self-center justify-self-center\" aria-hidden=\"true\"></i>\n }\n <span\n [class]=\"cn('line-clamp-1 text-ellipsis break-all', quickFilter() && 'hover:underline')\"\n [title]=\"quickFilter() ? ((isFiltered() ? 'filters.removeFilter' : 'filters.addFilter') | transloco) + ': ' +(name() | syslang) : (name() | syslang)\"\n (click)=\"onTextClick($event)\">\n @for (chunk of (name() | syslang) ?? '' | highlightWord: searchText() : 10; track $index) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{ chunk.text }}</span>\n }\n </span>\n @if(showCount() && node().count > 0) {\n <span class=\"ml-auto empty:hidden px-1 text-xs\" aria-hidden=\"true\">{{ node().count }}</span>\n }\n</a>\n\n@if (node().hasChildren && node().$opened) {\n @for (item of node().items; track $index) {\n <AggregationItem\n [node]=\"item\"\n [path]=\"childrenPath()\"\n [field]=\"field()\"\n (onOpen)=\"onOpen.emit($event)\"\n (onFilter)=\"onFilter.emit()\"\n (onSelect)=\"onChildSelect($event)\" />\n }\n}\n", styles: [":host{display:block;-webkit-user-select:none;user-select:none}:host a{padding-left:calc((var(--agg-tree-indent, .5rem) * var(--level)))}a{line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "component", type: AggregationItemComponent, selector: "aggregation-item, AggregationItem, aggregationitem", inputs: ["node", "path", "field"], outputs: ["onSelect", "onOpen", "onFilter"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
12700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationTreeItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12701
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AggregationTreeItemComponent, isStandalone: true, selector: "aggregation-tree-item, AggregationTreeItem, aggregationtreeitem", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: true, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onOpen: "onOpen", onFilter: "onFilter" }, host: { properties: { "attr.disabled": "this.disabled" } }, ngImport: i0, template: "<a\r\n role=\"listitem\"\r\n [attr.aria-selected]=\"node().$selected || node().$selectedVisually\"\r\n [attr.aria-label]=\"name() | syslang\"\r\n [style.--level]=\"level()\"\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 text-neutral-300',\r\n (node().$selected || node().$selectedVisually) &&\r\n 'bg-primary-100 text-primary dark:text-primary-foreground'\r\n )\r\n \"\r\n (click)=\"select(node(), $event, true)\">\r\n <!-- chrevron is visible only if the node has children -->\r\n <button\r\n (click)=\"open($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\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)=\"select(node(), $event)\"\r\n [checked]=\"node().$selected || node().$selectedVisually\" />\r\n\r\n @if (node().icon) {\r\n <i\r\n class=\"fa-fw {{ node().icon }} self-center justify-self-center\"\r\n aria-hidden=\"true\"></i>\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 ? ((isFiltered() ? 'filters.removeFilter' : 'filters.addFilter')\r\n | transloco) +\r\n ': ' +\r\n (name() | syslang)\r\n : (name() | syslang)\r\n \"\r\n (click)=\"onTextClick($event)\">\r\n @for (\r\n chunk of (name() | syslang) ?? \"\" | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node().count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node().count\r\n }}</span>\r\n }\r\n</a>\r\n\r\n@if (node().hasChildren && node().$opened) {\r\n @for (item of node().items; track $index) {\r\n <AggregationTreeItem\r\n [node]=\"item\"\r\n [path]=\"childrenPath()\"\r\n [field]=\"field()\"\r\n (onOpen)=\"onOpen.emit($event)\"\r\n (onFilter)=\"onFilter.emit()\"\r\n (onSelect)=\"onChildSelect($event)\" />\r\n }\r\n}\r\n", styles: [":host{display:block;-webkit-user-select:none;user-select:none}:host a{padding-left:calc((var(--agg-tree-indent, .5rem) * var(--level)))}a{line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "component", type: AggregationTreeItemComponent, selector: "aggregation-tree-item, AggregationTreeItem, aggregationtreeitem", inputs: ["node", "path", "field"], outputs: ["onSelect", "onOpen", "onFilter"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
11972
12702
  }
11973
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationItemComponent, decorators: [{
12703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationTreeItemComponent, decorators: [{
11974
12704
  type: Component,
11975
- args: [{ selector: "aggregation-item, AggregationItem, aggregationitem", standalone: true, imports: [HighlightWordPipe, ListItemComponent, SyslangPipe, ChevronRightIcon, TranslocoPipe], template: "<a\n role=\"listitem\"\n [attr.aria-selected]=\"node().$selected || node().$selectedVisually\"\n [attr.aria-label]=\"name() | syslang\"\n [style.--level]=\"level()\"\n [class]=\"\n cn(\n 'flex grow items-center gap-2 p-1 leading-7',\n node().count === 0 && 'disabled pointer-events-none text-neutral-300',\n (node().$selected || node().$selectedVisually) && 'bg-primary-100 text-primary'\n )\n \"\n (click)=\"select(node(), $event, true)\">\n <!-- chrevron is visible only if the node has children -->\n <button (click)=\"open($event, node())\" class=\"transition-transform ease-in hover:scale-125\" aria-label=\"Open\">\n <ChevronRight [class]=\"cn('size-4 translate-x-1', node().$opened && 'rotate-90', !node().hasChildren && 'hidden')\" width=\"16\" height=\"16\" />\n </button>\n\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)=\"select(node(), $event)\"\n [checked]=\"node().$selected || node().$selectedVisually\" />\n\n @if (node().icon) {\n <i class=\"fa-fw {{ node().icon }} self-center justify-self-center\" aria-hidden=\"true\"></i>\n }\n <span\n [class]=\"cn('line-clamp-1 text-ellipsis break-all', quickFilter() && 'hover:underline')\"\n [title]=\"quickFilter() ? ((isFiltered() ? 'filters.removeFilter' : 'filters.addFilter') | transloco) + ': ' +(name() | syslang) : (name() | syslang)\"\n (click)=\"onTextClick($event)\">\n @for (chunk of (name() | syslang) ?? '' | highlightWord: searchText() : 10; track $index) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{ chunk.text }}</span>\n }\n </span>\n @if(showCount() && node().count > 0) {\n <span class=\"ml-auto empty:hidden px-1 text-xs\" aria-hidden=\"true\">{{ node().count }}</span>\n }\n</a>\n\n@if (node().hasChildren && node().$opened) {\n @for (item of node().items; track $index) {\n <AggregationItem\n [node]=\"item\"\n [path]=\"childrenPath()\"\n [field]=\"field()\"\n (onOpen)=\"onOpen.emit($event)\"\n (onFilter)=\"onFilter.emit()\"\n (onSelect)=\"onChildSelect($event)\" />\n }\n}\n", styles: [":host{display:block;-webkit-user-select:none;user-select:none}:host a{padding-left:calc((var(--agg-tree-indent, .5rem) * var(--level)))}a{line-height:var(--agg-item-height, inherit)}\n"] }]
12705
+ args: [{ selector: "aggregation-tree-item, AggregationTreeItem, aggregationtreeitem", standalone: true, imports: [HighlightWordPipe, ListItemComponent, SyslangPipe, ChevronRightIcon, TranslocoPipe], template: "<a\r\n role=\"listitem\"\r\n [attr.aria-selected]=\"node().$selected || node().$selectedVisually\"\r\n [attr.aria-label]=\"name() | syslang\"\r\n [style.--level]=\"level()\"\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 text-neutral-300',\r\n (node().$selected || node().$selectedVisually) &&\r\n 'bg-primary-100 text-primary dark:text-primary-foreground'\r\n )\r\n \"\r\n (click)=\"select(node(), $event, true)\">\r\n <!-- chrevron is visible only if the node has children -->\r\n <button\r\n (click)=\"open($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\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)=\"select(node(), $event)\"\r\n [checked]=\"node().$selected || node().$selectedVisually\" />\r\n\r\n @if (node().icon) {\r\n <i\r\n class=\"fa-fw {{ node().icon }} self-center justify-self-center\"\r\n aria-hidden=\"true\"></i>\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 ? ((isFiltered() ? 'filters.removeFilter' : 'filters.addFilter')\r\n | transloco) +\r\n ': ' +\r\n (name() | syslang)\r\n : (name() | syslang)\r\n \"\r\n (click)=\"onTextClick($event)\">\r\n @for (\r\n chunk of (name() | syslang) ?? \"\" | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node().count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node().count\r\n }}</span>\r\n }\r\n</a>\r\n\r\n@if (node().hasChildren && node().$opened) {\r\n @for (item of node().items; track $index) {\r\n <AggregationTreeItem\r\n [node]=\"item\"\r\n [path]=\"childrenPath()\"\r\n [field]=\"field()\"\r\n (onOpen)=\"onOpen.emit($event)\"\r\n (onFilter)=\"onFilter.emit()\"\r\n (onSelect)=\"onChildSelect($event)\" />\r\n }\r\n}\r\n", styles: [":host{display:block;-webkit-user-select:none;user-select:none}:host a{padding-left:calc((var(--agg-tree-indent, .5rem) * var(--level)))}a{line-height:var(--agg-item-height, inherit)}\n"] }]
11976
12706
  }], propDecorators: { disabled: [{
11977
12707
  type: HostBinding,
11978
12708
  args: ["attr.disabled"]
11979
12709
  }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onOpen: [{ type: i0.Output, args: ["onOpen"] }], onFilter: [{ type: i0.Output, args: ["onFilter"] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: true }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }] } });
11980
12710
 
11981
- class AggregationComponent {
12711
+ class AggregationTreeComponent {
11982
12712
  cn = cn;
12713
+ virtualItems = viewChildren('virtualItem', ...(ngDevMode ? [{ debugName: "virtualItems" }] : []));
12714
+ scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
12715
+ virtualizer = injectVirtualizer(() => ({
12716
+ count: this.items().length,
12717
+ estimateSize: () => 32,
12718
+ scrollElement: this.scrollElement()
12719
+ }));
12720
+ #measureItems = effect(() => this.virtualItems().forEach((el) => {
12721
+ this.virtualizer.measureElement(el.nativeElement);
12722
+ }), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
11983
12723
  searchInput = viewChild("searchInput", ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
11984
12724
  /* stores */
11985
12725
  aggregationsStore = inject(AggregationsStore);
@@ -11997,6 +12737,7 @@ class AggregationComponent {
11997
12737
  id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
11998
12738
  name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
11999
12739
  column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
12740
+ expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
12000
12741
  onSelect = output();
12001
12742
  /**
12002
12743
  * Determines whether the aggregation component can be collapsed or expanded.
@@ -12005,7 +12746,7 @@ class AggregationComponent {
12005
12746
  *
12006
12747
  * @default false
12007
12748
  */
12008
- collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12749
+ collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
12009
12750
  /**
12010
12751
  * Controls whether the aggregation component is in a collapsed state.
12011
12752
  * When true, the component will be visually collapsed/hidden.
@@ -12013,7 +12754,7 @@ class AggregationComponent {
12013
12754
  *
12014
12755
  * @default false
12015
12756
  */
12016
- collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12757
+ collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
12017
12758
  /**
12018
12759
  * A computed signal that tracks the collapsed state of the component.
12019
12760
  * This signal is linked to the `collapsed()` signal and automatically updates
@@ -12027,13 +12768,13 @@ class AggregationComponent {
12027
12768
  *
12028
12769
  * @returns Empty string if not collapsed, null if collapsed
12029
12770
  */
12030
- expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
12771
+ expanded = computed(() => (this.isCollapsed() ? null : ''), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
12031
12772
  /**
12032
12773
  * A boolean flag indicating whether the component is searchable.
12033
12774
  * This property is initialized to `undefined` by default.
12034
12775
  * "Undefined" and not "false" because this input overrides the custom json settings
12035
12776
  */
12036
- searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12777
+ searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
12037
12778
  selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
12038
12779
  /**
12039
12780
  * A boolean flag indicating whether we want to see the filters count when some is applied
@@ -12049,6 +12790,12 @@ class AggregationComponent {
12049
12790
  if (name !== null) {
12050
12791
  const agg = this.aggregationsService.processAggregation(name, column);
12051
12792
  if (agg) {
12793
+ if (!agg.isTree) {
12794
+ error("The aggregation tree component does not support list aggregations. Please use the <Aggregation /> component instead.");
12795
+ }
12796
+ // overrides "expandedLevel" from custom JSON file
12797
+ const expandedLevel = this.expandedLevel() ?? agg.expandedLevel ?? 0;
12798
+ this.expandItems(agg.items, expandedLevel);
12052
12799
  // overrides "searchable" properties with the input if any
12053
12800
  agg.searchable = this.searchable() ?? agg.searchable;
12054
12801
  return agg;
@@ -12121,11 +12868,11 @@ class AggregationComponent {
12121
12868
  }
12122
12869
  // if the aggregation is not a tree, we return the suggestions as is
12123
12870
  return this.suggests()?.map((suggest) => ({
12124
- name: suggest.category,
12871
+ name: this.name(),
12125
12872
  value: suggest.normalized || suggest.display || "",
12126
12873
  display: suggest.display,
12127
- column: suggest.id,
12128
- count: 1,
12874
+ column: suggest.category,
12875
+ count: Number(suggest.frequency),
12129
12876
  $selected: false,
12130
12877
  items: []
12131
12878
  }));
@@ -12196,10 +12943,16 @@ class AggregationComponent {
12196
12943
  const agg = this.aggregation();
12197
12944
  if (agg) {
12198
12945
  this.queryParamsStore.removeFilterByName(agg.name, agg.column);
12199
- this.items().forEach((item) => {
12200
- item.$selected = false;
12201
- });
12202
- sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
12946
+ const unselect = (items) => {
12947
+ items.forEach((item) => {
12948
+ item.$selected = false;
12949
+ item.$selectedVisually = false;
12950
+ if (item.items)
12951
+ unselect(item.items);
12952
+ });
12953
+ };
12954
+ unselect(this.items());
12955
+ sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
12203
12956
  this.selection.set(false);
12204
12957
  this.isAllSelected.set(false);
12205
12958
  }
@@ -12298,7 +13051,7 @@ class AggregationComponent {
12298
13051
  * If the item is deselected, the selection count is decremented by 1.
12299
13052
  */
12300
13053
  select() {
12301
- this.onSelect.emit(true);
13054
+ this.onSelect.emit(this.getFlattenTreeItems().filter(item => item.$selected));
12302
13055
  this.selection.set(true);
12303
13056
  this.verifySelected();
12304
13057
  sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
@@ -12399,7 +13152,9 @@ class AggregationComponent {
12399
13152
  return flat.concat(item, item.items ? flattenItems(item.items) : []);
12400
13153
  }, []);
12401
13154
  };
12402
- const searchItems = flattenItems(this.searchedItems() || []);
13155
+ // we need to flatten both the searched items and the current items to get all the items in the tree
13156
+ const searchedItemsFiltered = (this.searchedItems() || []).filter((item) => "items" in item);
13157
+ const searchItems = flattenItems(searchedItemsFiltered);
12403
13158
  const items = flattenItems(this.aggregation()?.items || []);
12404
13159
  const flattenedTreeItems = [...searchItems, ...items];
12405
13160
  return flattenedTreeItems;
@@ -12491,287 +13246,312 @@ class AggregationComponent {
12491
13246
  if (savedItem) {
12492
13247
  item.$selected = savedItem.$selected;
12493
13248
  item.$selectedVisually = savedItem.$selectedVisually;
12494
- if (item.items?.length && savedItem.items?.length) {
12495
- item.items = this.setSelected(item.items, savedItem.items);
12496
- }
12497
- }
12498
- return item;
12499
- });
12500
- }
12501
- processAggregations(items) {
12502
- if (!this.linkChildren())
12503
- return items;
12504
- items.forEach((item) => {
12505
- if (item.items?.length) {
12506
- this.selectVisually(item.items, item.$selected || false);
12507
- }
12508
- });
12509
- return items;
12510
- }
12511
- selectVisually(items, parentSelected) {
12512
- items.forEach((item) => {
12513
- item.$selectedVisually = parentSelected;
12514
- if (item.items?.length)
12515
- this.selectVisually(item.items, item.$selected || item.$selectedVisually);
12516
- });
12517
- }
12518
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12519
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", 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 }, 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 }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <summary [class.cursor-pointer]=\"collapsible() && !isEmpty()\" [class.text-muted-foreground]=\"isEmpty()\"\n class=\"m-0 mt-1 flex gap-1 h-8 w-full select-none items-center pl-1 font-semibold\" (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @if (aggregation()?.icon) {\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\n }\n <span class=\"grow\">{{ aggregation()?.display | syslang | transloco }}</span>\n </ng-content>\n\n @if (showFiltersCount() && filtersCount() > 0) {\n <!-- count -->\n <Badge size=\"xs\" class=\"ml-1 pb-0.5\">\n {{ filtersCount() }}\n </Badge>\n }\n <!-- apply filter block -->\n @if (!isCollapsed()) {\n <ButtonGroup>\n @if (hasFilters()) {\n <button variant=\"outline\" size=\"icon\" class=\"size-6\" [attr.title]=\"'filters.clearFilters' | transloco\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\" (click)=\"$event.stopPropagation(); clear()\">\n <i class=\"fa-fw far fa-filter-circle-xmark\"></i>\n </button>\n }\n @if (selection()) {\n <button variant=\"primary\" size=\"xs\" [attr.title]=\"'filters.applyFilters' | transloco\"\n [attr.aria-label]=\"'filters.applyFilters' | transloco\" (click)=\"$event.stopPropagation(); apply()\" class=\"px-1 h-4\">\n <FilterIcon class=\"size-4\"/>\n {{ 'filters.apply' | transloco }}\n </button>\n }\n\n <!-- select / unselect all -->\n @if (isAllSelected()) {\n <button variant=\"outline\" size=\"icon\" class=\"size-6\" [attr.title]=\"'filters.unselectAllFilters' | transloco\"\n [attr.aria-label]=\"'filters.unselectAllFilters' | transloco\" (click)=\"$event.stopPropagation(); unselectAll()\">\n <i class=\"fa-fw far fa-check-square\"></i>\n </button>\n } @else {\n <button variant=\"outline\" size=\"icon\" class=\"size-6\" [attr.title]=\"'filters.selectAllFilters' | transloco\"\n [attr.aria-label]=\"'filters.selectAllFilters' | transloco\" (click)=\"$event.stopPropagation(); selectAll()\">\n <i class=\"fa-fw far fa-square\"></i>\n </button>\n }\n </ButtonGroup>\n }\n\n @if (collapsible()) {\n <button variant=\"none\" title=\"Open/Close\"\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevronright />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n @if (aggregation()?.searchable && items().length) {\n <InputGroup class=\"group/item mt-1\">\n <input #searchInput input-group id=\"aggregation-input-{{column()}}\" type=\"text\" [attr.placeholder]=\"'search' | transloco\" [(ngModel)]=\"searchText\" class=\"mt-1\"/>\n <InputGroupAddon>\n <SearchIcon class=\"text-foreground size-4 rotate-0 transition-[rotate] duration-500 group-focus-within/item:rotate-90\" />\n </InputGroupAddon>\n </InputGroup>\n }\n\n <ul class=\"scrollbar-thin max-h-[calc(var(--height,100%)-100px)] snap-y snap-start overflow-auto\" role=\"list\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (item of items(); track $index) {\n <AggregationItem [node]=\"item\" [path]=\"[]\" [field]=\"aggregation()?.column\" (onSelect)=\"select()\"\n (onOpen)=\"open($event)\" (onFilter)=\"apply()\" />\n }\n </ul>\n @if (aggregation()?.$hasMore && this.searchedItems().length === 0) {\n <button variant=\"link\" class=\"mt-1 flex w-full justify-center\" [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadMore()\">\n {{ 'loadMore' | transloco }}\n </button>\n }\n</details>\n", styles: ["AggregationItem:has(+AggregationItem){margin-bottom:var(--agg-item-gap, 0)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: AggregationItemComponent, selector: "aggregation-item, AggregationItem, aggregationitem", inputs: ["node", "path", "field"], outputs: ["onSelect", "onOpen", "onFilter"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "directive", type: ButtonGroup, selector: "button-group, ButtonGroup", inputs: ["class", "orientation"] }, { kind: "directive", type: InputGroupInput, selector: "input[input-group]", inputs: ["class", "type", "placeholder", "disabled"] }, { kind: "directive", type: InputGroupComponent, selector: "input-group, inputgroup, InputGroup", inputs: ["class"] }, { kind: "directive", type: InputGroupAddonComponent, selector: "input-group-addon, inputgroupaddon, InputGroupAddon", inputs: ["class", "align"] }, { kind: "component", type: SearchIcon, selector: "SearchIcon", inputs: ["class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
12520
- }
12521
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationComponent, decorators: [{
12522
- type: Component,
12523
- args: [{ selector: "Aggregation, aggregation", imports: [
12524
- FormsModule,
12525
- ReactiveFormsModule,
12526
- ButtonComponent,
12527
- AggregationItemComponent,
12528
- SyslangPipe,
12529
- TranslocoPipe,
12530
- BadgeComponent,
12531
- ChevronRightIcon,
12532
- ButtonGroup,
12533
- InputGroupInput,
12534
- InputGroupComponent,
12535
- InputGroupAddonComponent,
12536
- SearchIcon,
12537
- FilterIcon
12538
- ], standalone: true, host: {
12539
- "[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
12540
- }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <summary [class.cursor-pointer]=\"collapsible() && !isEmpty()\" [class.text-muted-foreground]=\"isEmpty()\"\n class=\"m-0 mt-1 flex gap-1 h-8 w-full select-none items-center pl-1 font-semibold\" (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @if (aggregation()?.icon) {\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\n }\n <span class=\"grow\">{{ aggregation()?.display | syslang | transloco }}</span>\n </ng-content>\n\n @if (showFiltersCount() && filtersCount() > 0) {\n <!-- count -->\n <Badge size=\"xs\" class=\"ml-1 pb-0.5\">\n {{ filtersCount() }}\n </Badge>\n }\n <!-- apply filter block -->\n @if (!isCollapsed()) {\n <ButtonGroup>\n @if (hasFilters()) {\n <button variant=\"outline\" size=\"icon\" class=\"size-6\" [attr.title]=\"'filters.clearFilters' | transloco\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\" (click)=\"$event.stopPropagation(); clear()\">\n <i class=\"fa-fw far fa-filter-circle-xmark\"></i>\n </button>\n }\n @if (selection()) {\n <button variant=\"primary\" size=\"xs\" [attr.title]=\"'filters.applyFilters' | transloco\"\n [attr.aria-label]=\"'filters.applyFilters' | transloco\" (click)=\"$event.stopPropagation(); apply()\" class=\"px-1 h-4\">\n <FilterIcon class=\"size-4\"/>\n {{ 'filters.apply' | transloco }}\n </button>\n }\n\n <!-- select / unselect all -->\n @if (isAllSelected()) {\n <button variant=\"outline\" size=\"icon\" class=\"size-6\" [attr.title]=\"'filters.unselectAllFilters' | transloco\"\n [attr.aria-label]=\"'filters.unselectAllFilters' | transloco\" (click)=\"$event.stopPropagation(); unselectAll()\">\n <i class=\"fa-fw far fa-check-square\"></i>\n </button>\n } @else {\n <button variant=\"outline\" size=\"icon\" class=\"size-6\" [attr.title]=\"'filters.selectAllFilters' | transloco\"\n [attr.aria-label]=\"'filters.selectAllFilters' | transloco\" (click)=\"$event.stopPropagation(); selectAll()\">\n <i class=\"fa-fw far fa-square\"></i>\n </button>\n }\n </ButtonGroup>\n }\n\n @if (collapsible()) {\n <button variant=\"none\" title=\"Open/Close\"\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevronright />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n @if (aggregation()?.searchable && items().length) {\n <InputGroup class=\"group/item mt-1\">\n <input #searchInput input-group id=\"aggregation-input-{{column()}}\" type=\"text\" [attr.placeholder]=\"'search' | transloco\" [(ngModel)]=\"searchText\" class=\"mt-1\"/>\n <InputGroupAddon>\n <SearchIcon class=\"text-foreground size-4 rotate-0 transition-[rotate] duration-500 group-focus-within/item:rotate-90\" />\n </InputGroupAddon>\n </InputGroup>\n }\n\n <ul class=\"scrollbar-thin max-h-[calc(var(--height,100%)-100px)] snap-y snap-start overflow-auto\" role=\"list\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (item of items(); track $index) {\n <AggregationItem [node]=\"item\" [path]=\"[]\" [field]=\"aggregation()?.column\" (onSelect)=\"select()\"\n (onOpen)=\"open($event)\" (onFilter)=\"apply()\" />\n }\n </ul>\n @if (aggregation()?.$hasMore && this.searchedItems().length === 0) {\n <button variant=\"link\" class=\"mt-1 flex w-full justify-center\" [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadMore()\">\n {{ 'loadMore' | transloco }}\n </button>\n }\n</details>\n", styles: ["AggregationItem:has(+AggregationItem){margin-bottom:var(--agg-item-gap, 0)}\n"] }]
12541
- }], ctorParameters: () => [], propDecorators: { searchInput: [{ type: i0.ViewChild, args: ["searchInput", { 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 }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], 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 }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
12542
-
12543
- /**
12544
- * Injection token that indicates whether custom date ranges are allowed.
12545
- *
12546
- * @remarks
12547
- * This token is used to configure the date component to allow users to select custom date ranges.
12548
- *
12549
- * @example
12550
- * ```typescript
12551
- * providers: [
12552
- * { provide: ALLOW_CUSTOM_RANGE, useValue: false }
12553
- * ]
12554
- * ```
12555
- *
12556
- * @public
12557
- */
12558
- const FILTER_DATE_ALLOW_CUSTOM_RANGE = new InjectionToken("date allow custom range", {
12559
- factory: () => true
12560
- });
12561
- class DateComponent extends AggregationComponent {
12562
- destroyRef;
12563
- datepicker = viewChild(DateRangePickerDirective, ...(ngDevMode ? [{ debugName: "datepicker" }] : []));
12564
- title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
12565
- displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
12566
- allowCustomRange = inject(FILTER_DATE_ALLOW_CUSTOM_RANGE);
12567
- transloco = inject(TranslocoService);
12568
- dateOptions = computed(() => translateAggregationToDateOptions(this.aggregation(), this.displayEmptyDistributionIntervals()), ...(ngDevMode ? [{ debugName: "dateOptions" }] : []));
12569
- form = new FormGroup({
12570
- option: new FormControl(null),
12571
- customRange: new FormGroup({
12572
- from: new FormControl(null),
12573
- to: new FormControl(null)
12574
- })
12575
- });
12576
- today = new Date();
12577
- lang = signal(this.transloco.getActiveLang(), ...(ngDevMode ? [{ debugName: "lang" }] : []));
12578
- validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
12579
- constructor(destroyRef) {
12580
- super();
12581
- this.destroyRef = destroyRef;
12582
- this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
12583
- this.lang.set(lang);
12584
- this.datepicker()?.setOptions({ language: lang });
12585
- });
12586
- const abortController = new AbortController();
12587
- addEventListener("changeDate", this.onDateChange.bind(this), {
12588
- signal: abortController.signal
12589
- });
12590
- // apply current date filter from queryParamsStore
12591
- effect(() => {
12592
- this.updateForm(this.queryParamsStore.getFilter({
12593
- field: this.aggregation()?.column,
12594
- name: this.aggregation()?.name
12595
- }));
12596
- });
12597
- this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((changes) => {
12598
- this.validSelection.set(!!changes.option &&
12599
- (changes.option !== "custom-range" || changes.customRange?.from !== null || changes.customRange?.to !== null));
12600
- });
12601
- destroyRef.onDestroy(() => {
12602
- abortController.abort();
12603
- });
12604
- }
12605
- aggregation = computed(() => {
12606
- const name = this.name();
12607
- if (name !== null) {
12608
- const agg = this.aggregationsService.processAggregation(name, this.column());
12609
- return {
12610
- ...agg,
12611
- items: agg?.items?.filter((item) => item.display !== "custom-range") ?? []
12612
- };
12613
- }
12614
- return null;
12615
- }, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
12616
- apply() {
12617
- try {
12618
- const filter = this.getFormValueFilter();
12619
- this.queryParamsStore.updateFilter(filter);
12620
- this.selection.set(false);
12621
- }
12622
- catch (err) {
12623
- warn("Error applying date filter:", err);
12624
- if (err instanceof Error) {
12625
- notify.error(this.transloco.translate(err.message));
12626
- }
12627
- }
12628
- }
12629
- clear(notify = true) {
12630
- this.form.setValue({
12631
- option: null,
12632
- customRange: {
12633
- from: null,
12634
- to: null
12635
- }
12636
- });
12637
- const agg = this.aggregation();
12638
- if (agg && notify) {
12639
- this.queryParamsStore.removeFilterByName(agg.name);
12640
- }
12641
- }
12642
- updateForm(filter) {
12643
- if (!filter) {
12644
- this.clear(false);
12645
- return;
12646
- }
12647
- const { operator, value } = filter;
12648
- const code = this.dateOptions().find((option) => option.operator === operator && option.value === value)
12649
- ?.display ?? "custom-range";
12650
- let from = null, to = null;
12651
- if (code === "custom-range") {
12652
- switch (operator) {
12653
- case "lte":
12654
- to = filter.value;
12655
- break;
12656
- case "gte":
12657
- from = filter.value;
12658
- break;
12659
- case "between":
12660
- from = filter.start;
12661
- to = filter.end;
12662
- break;
12663
- }
12664
- }
12665
- const formValue = {
12666
- option: code,
12667
- customRange: {
12668
- from: code === "custom-range" ? (from ?? null) : null,
12669
- to: code === "custom-range" ? (to ?? null) : null
12670
- }
12671
- };
12672
- this.form.setValue(formValue);
12673
- // Update the date range picker
12674
- const datepicker = this.datepicker();
12675
- if (datepicker) {
12676
- const start = new Date(formValue.customRange.from || "");
12677
- const end = new Date(formValue.customRange.to || "");
12678
- setTimeout(() => {
12679
- datepicker.rangeDatepicker?.setDates(start, end);
12680
- }, 1000);
12681
- }
12682
- }
12683
- getFormValueFilter() {
12684
- const value = this.form.value;
12685
- // value.option is null
12686
- if (!value.option) {
12687
- throw new Error("filters.selectionInvalid");
12688
- }
12689
- if (value.option !== "custom-range") {
12690
- const dateOption = this.dateOptions().find((option) => option.display === value.option);
12691
- const aggregation = this.aggregation();
12692
- if (!aggregation) {
12693
- throw new Error("filters.aggregationNotFound");
13249
+ if (item.items?.length && savedItem.items?.length) {
13250
+ item.items = this.setSelected(item.items, savedItem.items);
13251
+ }
12694
13252
  }
12695
- return {
12696
- name: aggregation.name,
12697
- operator: dateOption?.operator || "eq",
12698
- field: aggregation.column,
12699
- display: dateOption?.display ?? "",
12700
- filters: dateOption?.filters,
12701
- value: dateOption?.value
12702
- };
12703
- }
12704
- else if (value.customRange) {
12705
- // if to is null, operator is gte
12706
- // if from is null, operator is lte
12707
- // if both are not null, operator is between
12708
- const aggregation = this.aggregation();
12709
- if (!aggregation) {
12710
- throw new Error("filters.aggregationNotFound");
13253
+ return item;
13254
+ });
13255
+ }
13256
+ processAggregations(items) {
13257
+ if (!this.linkChildren())
13258
+ return items;
13259
+ items.forEach((item) => {
13260
+ if (item.items?.length) {
13261
+ this.selectVisually(item.items, item.$selected || false);
12711
13262
  }
12712
- const filter = {
12713
- name: aggregation.name,
12714
- field: aggregation.column,
12715
- display: value.option
12716
- };
12717
- if (value.customRange.from && value.customRange.to) {
12718
- filter.operator = "between";
12719
- filter.start = value.customRange.from;
12720
- filter.end = value.customRange.to;
13263
+ });
13264
+ return items;
13265
+ }
13266
+ selectVisually(items, parentSelected) {
13267
+ items.forEach((item) => {
13268
+ item.$selectedVisually = parentSelected;
13269
+ if (item.items?.length)
13270
+ this.selectVisually(item.items, item.$selected || item.$selectedVisually);
13271
+ });
13272
+ }
13273
+ onToggle(event) {
13274
+ const e = event;
13275
+ this.isCollapsed.set(e.newState === "closed");
13276
+ }
13277
+ expandItems(items, expandedLevel) {
13278
+ this.traverse(items, (lineage, node, level) => {
13279
+ if (!node.$opened && node.items?.length >= 0 && level < expandedLevel) {
13280
+ node.$opened = true;
12721
13281
  }
12722
- else if (value.customRange.from) {
12723
- filter.operator = "gte";
12724
- filter.value = value.customRange.from;
13282
+ if (node.$opened && level >= expandedLevel) {
13283
+ node.$opened = false;
12725
13284
  }
12726
- else if (value.customRange.to) {
12727
- filter.operator = "lte";
12728
- filter.value = value.customRange.to;
13285
+ return false;
13286
+ });
13287
+ }
13288
+ /**
13289
+ * Traverses a tree structure, executing a callback function at every node
13290
+ * @param nodes the nodes to traverse
13291
+ * @param callback the callback function
13292
+ */
13293
+ traverse(nodes, callback) {
13294
+ if (!nodes || nodes.length === 0) {
13295
+ return false;
13296
+ }
13297
+ if (!callback) {
13298
+ return false;
13299
+ }
13300
+ const lineage = [];
13301
+ const stack = [];
13302
+ let _i = nodes.length;
13303
+ while (_i--) {
13304
+ stack.push(nodes[_i]);
13305
+ }
13306
+ while (stack.length) {
13307
+ const node = stack.pop();
13308
+ if (!node) {
13309
+ lineage.pop();
12729
13310
  }
12730
13311
  else {
12731
- throw new Error("filters.customRangeInvalid");
13312
+ lineage.push(node);
13313
+ if (callback(lineage, node, lineage.length - 1)) {
13314
+ return true;
13315
+ }
13316
+ stack.push(undefined);
13317
+ if (node.items && node.items.length > 0) {
13318
+ _i = node.items.length;
13319
+ while (_i--) {
13320
+ stack.push(node.items[_i]);
13321
+ }
13322
+ }
12732
13323
  }
12733
- return filter;
12734
13324
  }
12735
- throw new Error("filters.filterInvalid");
13325
+ return false;
12736
13326
  }
12737
- onDateChange() {
12738
- // Get the selected dates
12739
- const [start, end] = this.datepicker()?.getDates() || [];
12740
- // If both start and end are not selected, reset the datepicker options
12741
- if (!start && !end) {
12742
- this.datepicker()?.setOptions({
12743
- minDate: undefined,
12744
- maxDate: this.today
12745
- });
13327
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13328
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", 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 }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\",class())" } }, viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <i class=\"fa-fw fas fa-triangle-exclamation mr-1\"></i>\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<details\r\n [attr.open]=\"expanded()\"\r\n [attr.name]=\"id()\"\r\n class=\"group space-y-2\"\r\n (toggle)=\"onToggle($event)\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\r\n [class.text-muted-foreground]=\"isEmpty()\"\r\n class=\"m-0 mt-1 flex h-8 w-full items-center gap-1 pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <!-- count -->\r\n <Badge size=\"xs\" class=\"ml-1 pb-0.5\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n <ButtonGroup>\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.clearFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n @if (selection()) {\r\n <button\r\n variant=\"primary\"\r\n size=\"xs\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); apply()\"\r\n class=\"h-4 px-1\">\r\n <FilterIcon class=\"size-4\" />\r\n <span class=\"sr-only\">{{ \"filters.apply\" | transloco }}</span>\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.unselectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.unselectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <i class=\"fa-fw far fa-check-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.unselectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n } @else {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.selectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.selectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <i class=\"fa-fw far fa-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.selectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n </ButtonGroup>\r\n }\r\n\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n @if (aggregation()?.searchable && items().length) {\r\n <InputGroup class=\"group/item mt-1\">\r\n <input\r\n #searchInput\r\n input-group\r\n id=\"aggregation-input-{{ column() }}\"\r\n type=\"text\"\r\n [attr.placeholder]=\"'search' | transloco\"\r\n [(ngModel)]=\"searchText\"\r\n class=\"mt-1\" />\r\n <InputGroupAddon>\r\n <SearchIcon\r\n class=\"text-foreground size-4 rotate-0 transition-[rotate] duration-500 group-focus-within/item:rotate-90\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n }\r\n\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-[calc(var(--height,100%)-100px)] w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"list\"\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 role=\"listitem\">\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 <AggregationTreeItem\r\n [node]=\"item\"\r\n [path]=\"[]\"\r\n [field]=\"aggregation()?.column\"\r\n (onSelect)=\"select()\"\r\n (onOpen)=\"open($event)\"\r\n (onFilter)=\"apply()\" />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n @if (aggregation()?.$hasMore && this.searchedItems().length === 0) {\r\n <button\r\n variant=\"link\"\r\n class=\"mt-1 flex w-full justify-center\"\r\n [attr.aria-label]=\"'loadMore' | transloco\"\r\n (click)=\"loadMore()\">\r\n {{ \"loadMore\" | transloco }}\r\n </button>\r\n }\r\n</details>\r\n", styles: ["AggregationTreeItem:has(+AggregationTreeItem){margin-bottom:var(--agg-item-gap, 0)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: AggregationTreeItemComponent, selector: "aggregation-tree-item, AggregationTreeItem, aggregationtreeitem", inputs: ["node", "path", "field"], outputs: ["onSelect", "onOpen", "onFilter"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "directive", type: ButtonGroup, selector: "button-group, ButtonGroup", inputs: ["class", "orientation"] }, { kind: "directive", type: InputGroupInput, selector: "input[input-group]", inputs: ["class", "type", "placeholder", "disabled"] }, { kind: "directive", type: InputGroupComponent, selector: "input-group, inputgroup, InputGroup", inputs: ["class"] }, { kind: "directive", type: InputGroupAddonComponent, selector: "input-group-addon, inputgroupaddon, InputGroupAddon", inputs: ["class", "align"] }, { kind: "component", type: SearchIcon, selector: "SearchIcon", inputs: ["class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
13329
+ }
13330
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationTreeComponent, decorators: [{
13331
+ type: Component,
13332
+ args: [{ selector: "AggregationTree, aggregation-tree, aggregationtree", imports: [
13333
+ FormsModule,
13334
+ ReactiveFormsModule,
13335
+ ButtonComponent,
13336
+ AggregationTreeItemComponent,
13337
+ SyslangPipe,
13338
+ TranslocoPipe,
13339
+ BadgeComponent,
13340
+ ChevronRightIcon,
13341
+ ButtonGroup,
13342
+ InputGroupInput,
13343
+ InputGroupComponent,
13344
+ InputGroupAddonComponent,
13345
+ SearchIcon,
13346
+ FilterIcon
13347
+ ], standalone: true, host: {
13348
+ "[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]",class())'
13349
+ }, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <i class=\"fa-fw fas fa-triangle-exclamation mr-1\"></i>\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<details\r\n [attr.open]=\"expanded()\"\r\n [attr.name]=\"id()\"\r\n class=\"group space-y-2\"\r\n (toggle)=\"onToggle($event)\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\r\n [class.text-muted-foreground]=\"isEmpty()\"\r\n class=\"m-0 mt-1 flex h-8 w-full items-center gap-1 pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @if (aggregation()?.icon) {\r\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <!-- count -->\r\n <Badge size=\"xs\" class=\"ml-1 pb-0.5\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n <ButtonGroup>\r\n @if (hasFilters()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <i class=\"fa-fw far fa-filter-circle-xmark\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.clearFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n @if (selection()) {\r\n <button\r\n variant=\"primary\"\r\n size=\"xs\"\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); apply()\"\r\n class=\"h-4 px-1\">\r\n <FilterIcon class=\"size-4\" />\r\n <span class=\"sr-only\">{{ \"filters.apply\" | transloco }}</span>\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.unselectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.unselectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <i class=\"fa-fw far fa-check-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.unselectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n } @else {\r\n <button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n class=\"size-6\"\r\n [attr.title]=\"'filters.selectAllFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.selectAllFilters' | transloco\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <i class=\"fa-fw far fa-square\"></i>\r\n <span class=\"sr-only\">{{\r\n \"filters.selectAllFilters\" | transloco\r\n }}</span>\r\n </button>\r\n }\r\n </ButtonGroup>\r\n }\r\n\r\n @if (collapsible()) {\r\n <button\r\n variant=\"none\"\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n @if (aggregation()?.searchable && items().length) {\r\n <InputGroup class=\"group/item mt-1\">\r\n <input\r\n #searchInput\r\n input-group\r\n id=\"aggregation-input-{{ column() }}\"\r\n type=\"text\"\r\n [attr.placeholder]=\"'search' | transloco\"\r\n [(ngModel)]=\"searchText\"\r\n class=\"mt-1\" />\r\n <InputGroupAddon>\r\n <SearchIcon\r\n class=\"text-foreground size-4 rotate-0 transition-[rotate] duration-500 group-focus-within/item:rotate-90\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n }\r\n\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-[calc(var(--height,100%)-100px)] w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"list\"\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 role=\"listitem\">\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 <AggregationTreeItem\r\n [node]=\"item\"\r\n [path]=\"[]\"\r\n [field]=\"aggregation()?.column\"\r\n (onSelect)=\"select()\"\r\n (onOpen)=\"open($event)\"\r\n (onFilter)=\"apply()\" />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n @if (aggregation()?.$hasMore && this.searchedItems().length === 0) {\r\n <button\r\n variant=\"link\"\r\n class=\"mt-1 flex w-full justify-center\"\r\n [attr.aria-label]=\"'loadMore' | transloco\"\r\n (click)=\"loadMore()\">\r\n {{ \"loadMore\" | transloco }}\r\n </button>\r\n }\r\n</details>\r\n", styles: ["AggregationTreeItem:has(+AggregationTreeItem){margin-bottom:var(--agg-item-gap, 0)}\n"] }]
13350
+ }], ctorParameters: () => [], propDecorators: { virtualItems: [{ type: i0.ViewChildren, args: ['virtualItem', { isSignal: true }] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ["searchInput", { 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 }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], 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 }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
13351
+
13352
+ /**
13353
+ * 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.
13354
+ *
13355
+ *
13356
+ * @example
13357
+ * <Aggregation
13358
+ * name="myAggregation"
13359
+ * column="myColumn"
13360
+ * [collapsible]="true"
13361
+ * [collapsed]="false"
13362
+ * [showFiltersCount]="true"
13363
+ * [searchable]="true"
13364
+ * (onSelect)="handleSelect($event)"
13365
+ * />
13366
+ */
13367
+ class AggregationComponent {
13368
+ cn = cn;
13369
+ appStore = inject(AppStore);
13370
+ aggregationsStore = inject(AggregationsStore);
13371
+ aggregationsService = inject(AggregationsService);
13372
+ aggregationDate = viewChild(AggregationDateComponent, ...(ngDevMode ? [{ debugName: "aggregationDate" }] : []));
13373
+ aggregationTree = viewChild(AggregationTreeComponent, ...(ngDevMode ? [{ debugName: "aggregationTree" }] : []));
13374
+ aggregationList = viewChild(AggregationListComponent, ...(ngDevMode ? [{ debugName: "aggregationList" }] : []));
13375
+ class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
13376
+ /**
13377
+ * The name of the <details> element. When you provide the same id, the component work as an accordion
13378
+ * @defaultValue null
13379
+ */
13380
+ id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
13381
+ /**
13382
+ * The name of the aggregation. This is a required input and must be provided for the component to function correctly.
13383
+ * @defaultValue undefined
13384
+ */
13385
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
13386
+ /**
13387
+ * The column associated with the aggregation. This is a required input and must be provided for the component to function correctly.
13388
+ * @defaultValue undefined
13389
+ */
13390
+ column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
13391
+ /**
13392
+ * A boolean flag indicating whether we want to see the filters count when some is applied
13393
+ * This property is initialized to `false` by default.
13394
+ */
13395
+ showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
13396
+ /**
13397
+ * Determines whether the aggregation component can be collapsed or expanded.
13398
+ * When true, the component will display collapse/expand controls allowing users
13399
+ * to show or hide the aggregation content.
13400
+ *
13401
+ * @default false
13402
+ */
13403
+ collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
13404
+ /**
13405
+ * Controls whether the aggregation component is in a collapsed state.
13406
+ * When true, the component will be visually collapsed/hidden.
13407
+ * When false, the component will be expanded/visible.
13408
+ *
13409
+ * @default false
13410
+ */
13411
+ collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
13412
+ /**
13413
+ * A boolean flag indicating whether the component is searchable.
13414
+ * This property is initialized to `undefined` by default.
13415
+ * "Undefined" and not "false" because this input overrides the custom json settings
13416
+ */
13417
+ searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
13418
+ expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
13419
+ /**
13420
+ * An output event that emits an array of selected `AggregationItem` objects when the user selects items in the aggregation component. This allows parent components to listen for selection changes and react accordingly, such as applying filters or updating the UI based on the selected items.
13421
+ */
13422
+ onSelect = output();
13423
+ /**
13424
+ * A computed property that retrieves the items to be displayed in the aggregation component. It checks for the presence of date, tree, or list aggregation components and returns their respective items. If none of these components are present, it returns an empty array. This allows the component to dynamically display the appropriate items based on the type of aggregation being rendered.
13425
+ *
13426
+ * @returns An array of items to be displayed in the aggregation component.
13427
+ */
13428
+ items = computed(() => this.aggregationDate()?.items() ??
13429
+ this.aggregationTree()?.items() ??
13430
+ this.aggregationList()?.items() ??
13431
+ [], ...(ngDevMode ? [{ debugName: "items" }] : []));
13432
+ /**
13433
+ * 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.
13434
+ *
13435
+ * @returns An array of selected items from the aggregation component.
13436
+ */
13437
+ selectedItems = computed(() => this.items().filter(item => item.$selected), ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
13438
+ /* aggregation */
13439
+ aggregation = computed(() => {
13440
+ // when the aggegation store updates, we need to check if the aggregation is still valid
13441
+ getState(this.aggregationsStore);
13442
+ const agg = this.aggregationsService.getAggregation(this.name(), this.column());
13443
+ if (!agg) {
13444
+ warn(`Aggregation with name ${this.name()} and column ${this.column()} not found`);
13445
+ return undefined;
12746
13446
  }
12747
- // Update the form with the selected dates
12748
- if (start && end) {
12749
- this.form.patchValue({
12750
- customRange: {
12751
- from: start.toISOString(),
12752
- to: end.toISOString()
12753
- }
12754
- });
13447
+ return agg;
13448
+ }, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
13449
+ /**
13450
+ * Checks if the specified column is a date filter.
13451
+ *
13452
+ * @param column The column name to check.
13453
+ * @returns True if the column is a date filter, false otherwise.
13454
+ */
13455
+ isDate(column) {
13456
+ if (!column) {
13457
+ return false;
12755
13458
  }
13459
+ return this.appStore.isDateColumn(column);
12756
13460
  }
12757
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DateComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
12758
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "datepicker", first: true, predicate: DateRangePickerDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <summary\n [class.cursor-pointer]=\"collapsible()\"\n class=\"m-0 flex h-8 w-full select-none items-center pl-1 font-semibold\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @if (aggregation()?.icon) {\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\n }\n <span class=\"grow\">{{ aggregation()?.display | syslang | transloco }}</span>\n </ng-content>\n\n @if (hasFilters()) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n [attr.title]=\"'filters.clearFilters' | transloco\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\"\n (keydown.enter)=\"clear()\">\n <i class=\"fa-fw far fa-filter-circle-xmark\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (selection() && validSelection()) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n [attr.title]=\"'filters.applyFilters' | transloco\"\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\n (click)=\"apply()\"\n (keydown.enter)=\"apply()\">\n <i class=\"fa-fw far fa-filter\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (collapsible()) {\n <button variant=\"none\" title=\"Open/Close\" class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevronright />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n <form [formGroup]=\"form\">\n <ul class=\"flex flex-col gap-1 pt-2 scrollbar-thin max-h-[calc(var(--height,100%)-100px)] snap-y snap-start overflow-auto\" role=\"list\">\n @for (option of dateOptions(); track $index) {\n <li\n role=\"listitem\"\n tabindex=\"0\"\n (click)=\"radio.click()\"\n [attr.aria-label]=\"option.display | syslang | transloco\"\n [class]=\"\n cn(\n 'flex p-0 px-2 leading-7',\n form.get('option')?.value === option.display && 'bg-accent',\n option.hidden && 'hidden',\n option.disabled && 'disabled pointer-events-none text-neutral-300'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <input\n #radio\n type=\"radio\"\n formControlName=\"option\"\n id=\"date-filter-{{ option.display }}\"\n [attr.disabled]=\"option.disabled ? true : null\"\n [attr.aria-disabled]=\"option.disabled\"\n (click)=\"select()\"\n value=\"{{ option.display }}\" />\n\n <label for=\"date-filter-{{ option.display }}\" class=\"grow cursor-pointer p-1\">\n {{ option.display | syslang | transloco }}\n </label>\n </li>\n }\n\n @if (allowCustomRange) {\n <li role=\"listitem\" aria-label=\"custom range\" class=\"flex px-2 leading-7\" [class.select]=\"form.get('option')?.value === 'custom-range'\">\n <input #radiorange type=\"radio\" formControlName=\"option\" id=\"date-filter-custom-range\" value=\"custom-range\" (click)=\"select()\" />\n\n <div\n class=\"flex @max-[340px]:flex-wrap grow gap-1 p-1 @container\"\n daterangepicker\n datepicker-buttons\n [datepicker-language]=\"lang()\"\n [datepicker-min]=\"form.get('customRange.from')?.value || undefined\"\n [datepicker-max]=\"form.get('customRange.to')?.value ?? today.toISOString()\"\n (click)=\"radiorange.click()\">\n <div class=\"flex gap-1\">\n <label for=\"datepicker-range-start\" class=\"min-w-10\">{{ 'from' | transloco }}</label>\n <input id=\"datepicker-range-start\" name=\"start\" type=\"text\" class=\"h-8 min-w-[13ch]\" />\n </div>\n <div class=\"flex gap-1\">\n <label for=\"datepicker-range-end\" class=\"text-right min-w-10\">{{ 'to' | transloco }}</label>\n <input id=\"datepicker-range-end\" name=\"end\" type=\"text\" class=\"h-8 min-w-[13ch]\" />\n </div>\n </div>\n </li>\n }\n </ul>\n </form>\n</details>\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: DateRangePickerDirective, selector: "[daterangepicker]", inputs: ["datepicker-title", "datepicker-autohide", "datepicker-clear", "datepicker-today", "datepicker-buttons", "datepicker-today-highlight", "datepicker-language", "datepicker-format", "datepicker-max", "datepicker-min", "datepicker-orientation"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
13461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13462
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AggregationComponent, isStandalone: true, selector: "Aggregation, aggregation", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, viewQueries: [{ propertyName: "aggregationDate", first: true, predicate: AggregationDateComponent, descendants: true, isSignal: true }, { propertyName: "aggregationTree", first: true, predicate: AggregationTreeComponent, descendants: true, isSignal: true }, { propertyName: "aggregationList", first: true, predicate: AggregationListComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
13463
+ @if (isDate(aggregation()?.column)) {
13464
+ <AggregationDate
13465
+ class="max-w-80"
13466
+ [id]="id()"
13467
+ [name]="name()"
13468
+ [column]="column()"
13469
+ [collapsed]="collapsed()"
13470
+ [collapsible]="collapsible()"
13471
+ [showFiltersCount]="showFiltersCount()"
13472
+ [searchable]="searchable()"
13473
+ [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
13474
+ />
13475
+ } @else if (aggregation()?.isTree) {
13476
+ <AggregationTree
13477
+ class="max-w-80"
13478
+ [id]="id()"
13479
+ [name]="name()"
13480
+ [column]="column()"
13481
+ [collapsed]="collapsed()"
13482
+ [collapsible]="collapsible()"
13483
+ [showFiltersCount]="showFiltersCount()"
13484
+ [searchable]="searchable()"
13485
+ [expandedLevel]="expandedLevel()"
13486
+ (onSelect)="onSelect.emit($event)"
13487
+ />
13488
+ }
13489
+ @else {
13490
+ <AggregationList
13491
+ class="w-60"
13492
+ [id]="id()"
13493
+ [name]="name()"
13494
+ [column]="column()"
13495
+ [collapsed]="collapsed()"
13496
+ [collapsible]="collapsible()"
13497
+ [showFiltersCount]="showFiltersCount()"
13498
+ [searchable]="searchable()"
13499
+ (onSelect)="onSelect.emit($event)"
13500
+ />
13501
+ }
13502
+ `, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "searchTextChange"] }, { kind: "component", type: AggregationTreeComponent, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: ["class", "id", "name", "column", "expandedLevel", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "searchTextChange"] }, { kind: "component", type: AggregationDateComponent, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: ["title", "displayEmptyDistributionIntervals"] }] });
12759
13503
  }
12760
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DateComponent, decorators: [{
13504
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AggregationComponent, decorators: [{
12761
13505
  type: Component,
12762
- args: [{ selector: "date-filter,DateFilter", standalone: true, providers: [provideTranslocoScope("filters")], imports: [
12763
- InputComponent,
12764
- ButtonComponent,
12765
- ListItemComponent,
12766
- ReactiveFormsModule,
12767
- TranslocoPipe,
12768
- SyslangPipe,
12769
- DateRangePickerDirective,
12770
- ChevronRightIcon
12771
- ], host: {
12772
- class: "@container"
12773
- }, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <summary\n [class.cursor-pointer]=\"collapsible()\"\n class=\"m-0 flex h-8 w-full select-none items-center pl-1 font-semibold\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @if (aggregation()?.icon) {\n <i class=\"fa-fw {{ aggregation()?.icon }} mr-1\" aria-hidden=\"true\"></i>\n }\n <span class=\"grow\">{{ aggregation()?.display | syslang | transloco }}</span>\n </ng-content>\n\n @if (hasFilters()) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n [attr.title]=\"'filters.clearFilters' | transloco\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\"\n (keydown.enter)=\"clear()\">\n <i class=\"fa-fw far fa-filter-circle-xmark\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (selection() && validSelection()) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n [attr.title]=\"'filters.applyFilters' | transloco\"\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\n (click)=\"apply()\"\n (keydown.enter)=\"apply()\">\n <i class=\"fa-fw far fa-filter\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (collapsible()) {\n <button variant=\"none\" title=\"Open/Close\" class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevronright />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n <form [formGroup]=\"form\">\n <ul class=\"flex flex-col gap-1 pt-2 scrollbar-thin max-h-[calc(var(--height,100%)-100px)] snap-y snap-start overflow-auto\" role=\"list\">\n @for (option of dateOptions(); track $index) {\n <li\n role=\"listitem\"\n tabindex=\"0\"\n (click)=\"radio.click()\"\n [attr.aria-label]=\"option.display | syslang | transloco\"\n [class]=\"\n cn(\n 'flex p-0 px-2 leading-7',\n form.get('option')?.value === option.display && 'bg-accent',\n option.hidden && 'hidden',\n option.disabled && 'disabled pointer-events-none text-neutral-300'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <input\n #radio\n type=\"radio\"\n formControlName=\"option\"\n id=\"date-filter-{{ option.display }}\"\n [attr.disabled]=\"option.disabled ? true : null\"\n [attr.aria-disabled]=\"option.disabled\"\n (click)=\"select()\"\n value=\"{{ option.display }}\" />\n\n <label for=\"date-filter-{{ option.display }}\" class=\"grow cursor-pointer p-1\">\n {{ option.display | syslang | transloco }}\n </label>\n </li>\n }\n\n @if (allowCustomRange) {\n <li role=\"listitem\" aria-label=\"custom range\" class=\"flex px-2 leading-7\" [class.select]=\"form.get('option')?.value === 'custom-range'\">\n <input #radiorange type=\"radio\" formControlName=\"option\" id=\"date-filter-custom-range\" value=\"custom-range\" (click)=\"select()\" />\n\n <div\n class=\"flex @max-[340px]:flex-wrap grow gap-1 p-1 @container\"\n daterangepicker\n datepicker-buttons\n [datepicker-language]=\"lang()\"\n [datepicker-min]=\"form.get('customRange.from')?.value || undefined\"\n [datepicker-max]=\"form.get('customRange.to')?.value ?? today.toISOString()\"\n (click)=\"radiorange.click()\">\n <div class=\"flex gap-1\">\n <label for=\"datepicker-range-start\" class=\"min-w-10\">{{ 'from' | transloco }}</label>\n <input id=\"datepicker-range-start\" name=\"start\" type=\"text\" class=\"h-8 min-w-[13ch]\" />\n </div>\n <div class=\"flex gap-1\">\n <label for=\"datepicker-range-end\" class=\"text-right min-w-10\">{{ 'to' | transloco }}</label>\n <input id=\"datepicker-range-end\" name=\"end\" type=\"text\" class=\"h-8 min-w-[13ch]\" />\n </div>\n </div>\n </li>\n }\n </ul>\n </form>\n</details>\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
12774
- }], ctorParameters: () => [{ type: i0.DestroyRef }], propDecorators: { datepicker: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DateRangePickerDirective), { isSignal: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }] } });
13506
+ args: [{
13507
+ selector: "Aggregation, aggregation",
13508
+ imports: [AggregationListComponent, AggregationTreeComponent, AggregationDateComponent],
13509
+ template: `
13510
+ @if (isDate(aggregation()?.column)) {
13511
+ <AggregationDate
13512
+ class="max-w-80"
13513
+ [id]="id()"
13514
+ [name]="name()"
13515
+ [column]="column()"
13516
+ [collapsed]="collapsed()"
13517
+ [collapsible]="collapsible()"
13518
+ [showFiltersCount]="showFiltersCount()"
13519
+ [searchable]="searchable()"
13520
+ [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
13521
+ />
13522
+ } @else if (aggregation()?.isTree) {
13523
+ <AggregationTree
13524
+ class="max-w-80"
13525
+ [id]="id()"
13526
+ [name]="name()"
13527
+ [column]="column()"
13528
+ [collapsed]="collapsed()"
13529
+ [collapsible]="collapsible()"
13530
+ [showFiltersCount]="showFiltersCount()"
13531
+ [searchable]="searchable()"
13532
+ [expandedLevel]="expandedLevel()"
13533
+ (onSelect)="onSelect.emit($event)"
13534
+ />
13535
+ }
13536
+ @else {
13537
+ <AggregationList
13538
+ class="w-60"
13539
+ [id]="id()"
13540
+ [name]="name()"
13541
+ [column]="column()"
13542
+ [collapsed]="collapsed()"
13543
+ [collapsible]="collapsible()"
13544
+ [showFiltersCount]="showFiltersCount()"
13545
+ [searchable]="searchable()"
13546
+ (onSelect)="onSelect.emit($event)"
13547
+ />
13548
+ }
13549
+ `,
13550
+ host: {
13551
+ "[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
13552
+ }
13553
+ }]
13554
+ }], propDecorators: { aggregationDate: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateComponent), { isSignal: true }] }], aggregationTree: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationTreeComponent), { isSignal: true }] }], aggregationList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationListComponent), { isSignal: true }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
12775
13555
 
12776
13556
  class AsideFiltersComponent {
12777
13557
  cn = cn;
@@ -12782,14 +13562,23 @@ class AsideFiltersComponent {
12782
13562
  const asideFilters = this.appStore.filters().filter((f) => f.position === "left");
12783
13563
  return this.appStore.getAuthorized(asideFilters);
12784
13564
  }, ...(ngDevMode ? [{ debugName: "asideFilters" }] : []));
13565
+ /**
13566
+ * Checks if the specified column is a date filter.
13567
+ *
13568
+ * @param column The column name to check.
13569
+ * @returns True if the column is a date filter, false otherwise.
13570
+ */
13571
+ isDate(column) {
13572
+ return this.appStore.isDateColumn(column);
13573
+ }
12785
13574
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AsideFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12786
13575
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AsideFiltersComponent, isStandalone: true, selector: "aside-filters, AsideFilters, asidefilters", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "cn('flex flex-col gap-2 overflow-auto', class())" } }, ngImport: i0, template: `
12787
13576
  <div class="flex flex-col gap-2">
12788
13577
  @for (agg of asideFilters(); track agg.name) {
12789
- <Aggregation [name]="agg.name" [column]="agg.column" showFiltersCount />
13578
+ <Aggregation id="aside-filters" [name]="agg.name" [column]="agg.column" showFiltersCount />
12790
13579
  }
12791
13580
  </div>
12792
- `, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "searchTextChange"] }] });
13581
+ `, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel"], outputs: ["onSelect"] }] });
12793
13582
  }
12794
13583
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AsideFiltersComponent, decorators: [{
12795
13584
  type: Component,
@@ -12799,7 +13588,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
12799
13588
  template: `
12800
13589
  <div class="flex flex-col gap-2">
12801
13590
  @for (agg of asideFilters(); track agg.name) {
12802
- <Aggregation [name]="agg.name" [column]="agg.column" showFiltersCount />
13591
+ <Aggregation id="aside-filters" [name]="agg.name" [column]="agg.column" showFiltersCount />
12803
13592
  }
12804
13593
  </div>
12805
13594
  `,
@@ -12814,6 +13603,7 @@ class FilterButtonComponent {
12814
13603
  column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
12815
13604
  position = input("bottom-start", ...(ngDevMode ? [{ debugName: "position" }] : []));
12816
13605
  offset = input(8, ...(ngDevMode ? [{ debugName: "offset" }] : []));
13606
+ expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
12817
13607
  variant = signal("ghost", ...(ngDevMode ? [{ debugName: "variant" }] : []));
12818
13608
  filter = signal({
12819
13609
  name: "",
@@ -12869,7 +13659,7 @@ class FilterButtonComponent {
12869
13659
  return this.appStore.isDateColumn(column);
12870
13660
  }
12871
13661
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FilterButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12872
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FilterButtonComponent, isStandalone: true, selector: "filter-button, FilterButton", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "listitem" }, properties: { "class.hidden": "filter().hidden" } }, viewQueries: [{ propertyName: "popoverRef", first: true, predicate: PopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
13662
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FilterButtonComponent, isStandalone: true, selector: "filter-button, FilterButton", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "listitem" }, properties: { "class.hidden": "filter().hidden" } }, viewQueries: [{ propertyName: "popoverRef", first: true, predicate: PopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
12873
13663
  <Popover [disabled]="filter().disabled" class="group">
12874
13664
  <button
12875
13665
  [variant]="variant()"
@@ -12906,25 +13696,17 @@ class FilterButtonComponent {
12906
13696
  [offset]="offset()"
12907
13697
  >
12908
13698
  @if(contentRef.isVisible) {
12909
- @if (isDate(filter().column)) {
12910
- <DateFilter
12911
- class="*:details-content:[--height:19lh] w-max max-w-80"
12912
- [name]="filter().name"
12913
- [column]="filter().column"
12914
- id="filter-{{ filter().column }}"
12915
- [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
12916
- />
12917
- } @else {
12918
13699
  <Aggregation
12919
- class="*:details-content:[--height:19lh] w-max max-w-80"
13700
+ class="w-60 [--height:19lh]"
13701
+ id="filter-{{ filter().column }}"
12920
13702
  [name]="filter().name"
12921
13703
  [column]="filter().column"
12922
- id="filter-{{ filter().column }}" />
12923
- }
13704
+ [expandedLevel]="expandedLevel()"
13705
+ />
12924
13706
  }
12925
13707
  </PopoverContent>
12926
13708
  </Popover>
12927
- `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { 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", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "searchTextChange"] }, { kind: "component", type: DateComponent, selector: "date-filter,DateFilter", inputs: ["title", "displayEmptyDistributionIntervals"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "pipe", type: OperatorPipe, name: "operator" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
13709
+ `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel"], outputs: ["onSelect"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "pipe", type: OperatorPipe, name: "operator" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
12928
13710
  }
12929
13711
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FilterButtonComponent, decorators: [{
12930
13712
  type: Component,
@@ -12939,7 +13721,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
12939
13721
  TranslocoPipe,
12940
13722
  AggregationComponent,
12941
13723
  SyslangPipe,
12942
- DateComponent,
12943
13724
  BadgeComponent
12944
13725
  ],
12945
13726
  template: `
@@ -12979,21 +13760,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
12979
13760
  [offset]="offset()"
12980
13761
  >
12981
13762
  @if(contentRef.isVisible) {
12982
- @if (isDate(filter().column)) {
12983
- <DateFilter
12984
- class="*:details-content:[--height:19lh] w-max max-w-80"
12985
- [name]="filter().name"
12986
- [column]="filter().column"
12987
- id="filter-{{ filter().column }}"
12988
- [title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
12989
- />
12990
- } @else {
12991
13763
  <Aggregation
12992
- class="*:details-content:[--height:19lh] w-max max-w-80"
13764
+ class="w-60 [--height:19lh]"
13765
+ id="filter-{{ filter().column }}"
12993
13766
  [name]="filter().name"
12994
13767
  [column]="filter().column"
12995
- id="filter-{{ filter().column }}" />
12996
- }
13768
+ [expandedLevel]="expandedLevel()"
13769
+ />
12997
13770
  }
12998
13771
  </PopoverContent>
12999
13772
  </Popover>
@@ -13003,7 +13776,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
13003
13776
  role: "listitem"
13004
13777
  }
13005
13778
  }]
13006
- }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], popoverRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => PopoverComponent), { isSignal: true }] }] } });
13779
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], popoverRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => PopoverComponent), { isSignal: true }] }] } });
13007
13780
 
13008
13781
  const FILTERS_BREAKPOINT = new InjectionToken('FILTERS_BREAKPOINT', { factory: () => 5 });
13009
13782
 
@@ -13079,17 +13852,6 @@ class MoreComponent {
13079
13852
  });
13080
13853
  });
13081
13854
  }
13082
- /**
13083
- * Checks if the specified column is a date filter.
13084
- *
13085
- * @param column The column name to check.
13086
- * @returns True if the column is a date filter, false otherwise.
13087
- */
13088
- isDate(column) {
13089
- if (!column)
13090
- return false;
13091
- return this.appStore.isDateColumn(column);
13092
- }
13093
13855
  /**
13094
13856
  * Checks whether there are active filters for the current aggregation column.
13095
13857
  *
@@ -13104,39 +13866,31 @@ class MoreComponent {
13104
13866
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13105
13867
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MoreComponent, isStandalone: true, selector: "more, More", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, includedFilters: { classPropertyName: "includedFilters", publicName: "includedFilters", isSignal: true, isRequired: false, transformFunction: null }, excludedFilters: { classPropertyName: "excludedFilters", publicName: "excludedFilters", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "divide-y divide-muted-foreground/18" }, ngImport: i0, template: `
13106
13868
  @for (filter of visibleFilters(); track $index) {
13107
- @if (isDate(filter.column)) {
13108
- <DateFilter class="p-1" id="more-filters" [name]="filter.name" [column]="filter.column" collapsible collapsed />
13109
- } @else {
13110
- <Aggregation
13111
- class="w-full max-w-80"
13112
- id="more-filters"
13113
- [attr.title]="'filters.openFilter' | transloco: { filter: filter.display || filter.name }"
13114
- showFiltersCount
13115
- collapsible
13116
- collapsed
13117
- [name]="filter.name"
13118
- [column]="filter.column" />
13119
- }
13120
- }
13121
- `, isInline: true, styles: [":host{scrollbar-width:none}\n"], dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "searchTextChange"] }, { kind: "component", type: DateComponent, selector: "date-filter,DateFilter", inputs: ["title", "displayEmptyDistributionIntervals"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
13869
+ <Aggregation
13870
+ class="w-60 max-w-80 [--height:15lh]"
13871
+ id="more-filters"
13872
+ [attr.title]="'filters.openFilter' | transloco: { filter: filter.display || filter.name }"
13873
+ showFiltersCount
13874
+ [collapsible]="true"
13875
+ [collapsed]="true"
13876
+ [name]="filter.name"
13877
+ [column]="filter.column" />
13878
+ }
13879
+ `, isInline: true, styles: [":host{scrollbar-width:none}\n"], dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel"], outputs: ["onSelect"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
13122
13880
  }
13123
13881
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MoreComponent, decorators: [{
13124
13882
  type: Component,
13125
- args: [{ selector: "more, More", standalone: true, imports: [AggregationComponent, DateComponent, TranslocoPipe], template: `
13883
+ args: [{ selector: "more, More", standalone: true, imports: [AggregationComponent, TranslocoPipe], template: `
13126
13884
  @for (filter of visibleFilters(); track $index) {
13127
- @if (isDate(filter.column)) {
13128
- <DateFilter class="p-1" id="more-filters" [name]="filter.name" [column]="filter.column" collapsible collapsed />
13129
- } @else {
13130
- <Aggregation
13131
- class="w-full max-w-80"
13132
- id="more-filters"
13133
- [attr.title]="'filters.openFilter' | transloco: { filter: filter.display || filter.name }"
13134
- showFiltersCount
13135
- collapsible
13136
- collapsed
13137
- [name]="filter.name"
13138
- [column]="filter.column" />
13139
- }
13885
+ <Aggregation
13886
+ class="w-60 max-w-80 [--height:15lh]"
13887
+ id="more-filters"
13888
+ [attr.title]="'filters.openFilter' | transloco: { filter: filter.display || filter.name }"
13889
+ showFiltersCount
13890
+ [collapsible]="true"
13891
+ [collapsed]="true"
13892
+ [name]="filter.name"
13893
+ [column]="filter.column" />
13140
13894
  }
13141
13895
  `, host: {
13142
13896
  class: "divide-y divide-muted-foreground/18"
@@ -13265,6 +14019,7 @@ class FiltersBarComponent {
13265
14019
  * @defaultValue 8px
13266
14020
  */
13267
14021
  offset = input(8, ...(ngDevMode ? [{ debugName: "offset" }] : []));
14022
+ expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
13268
14023
  /**
13269
14024
  * Event emitted when the clear filters button is clicked.
13270
14025
  * This event can be used to perform additional actions when filters are cleared.
@@ -13357,6 +14112,9 @@ class FiltersBarComponent {
13357
14112
  */
13358
14113
  clearFilters() {
13359
14114
  this.queryParamsStore.clearFilters();
14115
+ this.aggregationsStore.aggregations().forEach(agg => {
14116
+ sessionStorage.removeItem(`agg-${agg.column}`);
14117
+ });
13360
14118
  this.onClearFilters.emit();
13361
14119
  }
13362
14120
  /**
@@ -13395,7 +14153,7 @@ class FiltersBarComponent {
13395
14153
  });
13396
14154
  }
13397
14155
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FiltersBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13398
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FiltersBarComponent, isStandalone: true, selector: "filters-bar, FiltersBar, filtersbar", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, morePosition: { classPropertyName: "morePosition", publicName: "morePosition", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, includeFilters: { classPropertyName: "includeFilters", publicName: "includeFilters", isSignal: true, isRequired: false, transformFunction: null }, excludeFilters: { classPropertyName: "excludeFilters", publicName: "excludeFilters", isSignal: true, isRequired: false, transformFunction: null }, filtersCount: { classPropertyName: "filtersCount", publicName: "filtersCount", isSignal: true, isRequired: false, transformFunction: null }, showMoreFiltersButton: { classPropertyName: "showMoreFiltersButton", publicName: "showMoreFiltersButton", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClearFilters: "onClearFilters", onClearBasket: "onClearBasket" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "cn('block relative', class())" } }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "moreButtonRef", first: true, predicate: MoreButtonComponent, descendants: true, isSignal: true }, { propertyName: "filterButtonRefs", predicate: FilterButtonComponent, descendants: true, isSignal: true }, { propertyName: "overflowManagerRef", first: true, predicate: OverflowManagerDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
14156
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FiltersBarComponent, isStandalone: true, selector: "filters-bar, FiltersBar, filtersbar", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, morePosition: { classPropertyName: "morePosition", publicName: "morePosition", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, includeFilters: { classPropertyName: "includeFilters", publicName: "includeFilters", isSignal: true, isRequired: false, transformFunction: null }, excludeFilters: { classPropertyName: "excludeFilters", publicName: "excludeFilters", isSignal: true, isRequired: false, transformFunction: null }, filtersCount: { classPropertyName: "filtersCount", publicName: "filtersCount", isSignal: true, isRequired: false, transformFunction: null }, showMoreFiltersButton: { classPropertyName: "showMoreFiltersButton", publicName: "showMoreFiltersButton", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClearFilters: "onClearFilters", onClearBasket: "onClearBasket" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "cn('block relative', class())" } }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "moreButtonRef", first: true, predicate: MoreButtonComponent, descendants: true, isSignal: true }, { propertyName: "filterButtonRefs", predicate: FilterButtonComponent, descendants: true, isSignal: true }, { propertyName: "overflowManagerRef", first: true, predicate: OverflowManagerDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
13399
14157
  <ng-container overflowManager [direction]="direction()" [target]="el.nativeElement" (count)="adjustFiltersCount($event)">
13400
14158
  @if (hasFilters()) {
13401
14159
  <button
@@ -13429,7 +14187,7 @@ class FiltersBarComponent {
13429
14187
  role="list"
13430
14188
  aria-label="Filters list">
13431
14189
  @for (filter of authorizedFilters(); track filter.name) {
13432
- <FilterButton class="max-w-60" overflowItem [name]="filter.name" [column]="filter.column" [position]="position()" [offset]="offset()" />
14190
+ <FilterButton class="max-w-60" overflowItem [name]="filter.name" [column]="filter.column" [position]="position()" [offset]="offset()" [expandedLevel]="expandedLevel()" />
13433
14191
  }
13434
14192
 
13435
14193
  <!-- More button to show more filters if there are any, it's hidden by default -->
@@ -13447,7 +14205,7 @@ class FiltersBarComponent {
13447
14205
  </div>
13448
14206
  }
13449
14207
  </ng-container>
13450
- `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: MoreButtonComponent, selector: "more-button, MoreButton", inputs: ["count", "position", "includedFilters", "excludedFilters", "aggregations"] }, { kind: "component", type: FilterButtonComponent, selector: "filter-button, FilterButton", inputs: ["name", "column", "position", "offset"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14208
+ `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: MoreButtonComponent, selector: "more-button, MoreButton", inputs: ["count", "position", "includedFilters", "excludedFilters", "aggregations"] }, { kind: "component", type: FilterButtonComponent, selector: "filter-button, FilterButton", inputs: ["name", "column", "position", "offset", "expandedLevel"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
13451
14209
  }
13452
14210
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FiltersBarComponent, decorators: [{
13453
14211
  type: Component,
@@ -13498,7 +14256,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
13498
14256
  role="list"
13499
14257
  aria-label="Filters list">
13500
14258
  @for (filter of authorizedFilters(); track filter.name) {
13501
- <FilterButton class="max-w-60" overflowItem [name]="filter.name" [column]="filter.column" [position]="position()" [offset]="offset()" />
14259
+ <FilterButton class="max-w-60" overflowItem [name]="filter.name" [column]="filter.column" [position]="position()" [offset]="offset()" [expandedLevel]="expandedLevel()" />
13502
14260
  }
13503
14261
 
13504
14262
  <!-- More button to show more filters if there are any, it's hidden by default -->
@@ -13522,7 +14280,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
13522
14280
  "(click)": "handleClick($event)"
13523
14281
  }
13524
14282
  }]
13525
- }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], morePosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "morePosition", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], includeFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includeFilters", required: false }] }], excludeFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeFilters", required: false }] }], filtersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtersCount", required: false }] }], showMoreFiltersButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMoreFiltersButton", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], onClearFilters: [{ type: i0.Output, args: ["onClearFilters"] }], onClearBasket: [{ type: i0.Output, args: ["onClearBasket"] }], moreButtonRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MoreButtonComponent), { isSignal: true }] }], filterButtonRefs: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => FilterButtonComponent), { isSignal: true }] }], overflowManagerRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => OverflowManagerDirective), { isSignal: true }] }] } });
14283
+ }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], morePosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "morePosition", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], includeFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includeFilters", required: false }] }], excludeFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeFilters", required: false }] }], filtersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtersCount", required: false }] }], showMoreFiltersButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMoreFiltersButton", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], onClearFilters: [{ type: i0.Output, args: ["onClearFilters"] }], onClearBasket: [{ type: i0.Output, args: ["onClearBasket"] }], moreButtonRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MoreButtonComponent), { isSignal: true }] }], filterButtonRefs: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => FilterButtonComponent), { isSignal: true }] }], overflowManagerRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => OverflowManagerDirective), { isSignal: true }] }] } });
13526
14284
 
13527
14285
  class LabelService {
13528
14286
  appStore = inject(AppStore);
@@ -13846,7 +14604,7 @@ class MultiSelectLabelsComponent {
13846
14604
  {{ 'labels.error' | transloco }}
13847
14605
  </div>
13848
14606
  }
13849
- `, isInline: true, styles: [".anchor:has(.popover:popover-open){z-index:var(--z-menu, 1000);border-radius:var(--radius) var(--radius) 0 0}.anchor .popover::backdrop{background-color:transparent;-webkit-backdrop-filter:none;backdrop-filter:none}.popover{width:anchor-size(width);top:anchor(bottom);left:anchor(left)}@supports (-moz-appearance: none){.popover{margin:calc(33.3333333333vh + 30px) 25vw;width:50vw}}@supports (background: -webkit-named-image(i)){.popover{margin:calc(33.3333333333vh + 30px) 25vw;width:50vw}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14607
+ `, isInline: true, styles: [".anchor:has(.popover:popover-open){z-index:var(--z-menu, 1000);border-radius:var(--radius) var(--radius) 0 0}.anchor .popover::backdrop{background-color:transparent;-webkit-backdrop-filter:none;backdrop-filter:none}.popover{width:anchor-size(width);top:anchor(bottom);left:anchor(left)}@supports (-moz-appearance: none){.popover{margin:calc(33.3333333333vh + 30px) 25vw;width:50vw}}@supports (background: -webkit-named-image(i)){.popover{margin:calc(33.3333333333vh + 30px) 25vw;width:50vw}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
13850
14608
  }
13851
14609
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MultiSelectLabelsComponent, decorators: [{
13852
14610
  type: Component,
@@ -14320,7 +15078,7 @@ class SavedSearchDialog {
14320
15078
  </DialogFooter>
14321
15079
  </DialogContent>
14322
15080
  </dialog>
14323
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
15081
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "directive", type: DialogFooterComponent, selector: "DialogFooter", inputs: ["class"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14324
15082
  }
14325
15083
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SavedSearchDialog, decorators: [{
14326
15084
  type: Component,
@@ -14443,7 +15201,13 @@ class UserProfileFormComponent {
14443
15201
  appStore = inject(AppStore);
14444
15202
  transloco = inject(TranslocoService);
14445
15203
  createInputElement = viewChild("avatarInput", ...(ngDevMode ? [{ debugName: "createInputElement" }] : []));
14446
- dataKeys = ["fullName", "mail", "jobTitle", "group", "organisation", "location"];
15204
+ dataKeys = ["fullName", "mail", "jobTitle", "group", "organisation", "location", "skills", "profilePhoto"];
15205
+ keys = computed(() => {
15206
+ const data = this.appStore.general()?.features?.userProfile?.data;
15207
+ return data?.length ? this.dataKeys.filter(key => data.some(k => k === key)) : this.dataKeys;
15208
+ }, ...(ngDevMode ? [{ debugName: "keys" }] : []));
15209
+ formKeys = computed(() => this.keys().filter(k => k !== "profilePhoto"), ...(ngDevMode ? [{ debugName: "formKeys" }] : []));
15210
+ allowProfilePhoto = computed(() => this.keys().some(k => k === "profilePhoto"), ...(ngDevMode ? [{ debugName: "allowProfilePhoto" }] : []));
14447
15211
  initials = computed(() => {
14448
15212
  const fullName = this.userProfile()?.data?.fullName;
14449
15213
  if (!fullName)
@@ -14453,6 +15217,7 @@ class UserProfileFormComponent {
14453
15217
  .map((word) => word[0].toUpperCase())
14454
15218
  .join("");
14455
15219
  }, ...(ngDevMode ? [{ debugName: "initials" }] : []));
15220
+ username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...(ngDevMode ? [{ debugName: "username" }] : []));
14456
15221
  propertyToEdit = signal(undefined, ...(ngDevMode ? [{ debugName: "propertyToEdit" }] : []));
14457
15222
  value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
14458
15223
  currentLanguage = model(...(ngDevMode ? [undefined, { debugName: "currentLanguage" }] : []));
@@ -14464,11 +15229,15 @@ class UserProfileFormComponent {
14464
15229
  principal = computed(() => this.principalStore.principal?.(), ...(ngDevMode ? [{ debugName: "principal" }] : []));
14465
15230
  customData = computed(() => {
14466
15231
  const customData = this.appStore.general()?.features?.userProfile?.customData;
14467
- if (customData?.length)
14468
- return customData;
14469
- const data = this.userProfile()?.customData;
14470
- return data ? Object.keys(data) : [];
15232
+ const profileKeys = this.userProfile()?.customData;
15233
+ return (customData?.length ? customData : (profileKeys ? Object.keys(profileKeys) : []))
15234
+ .filter(key => !this.keys().some(k => k.toLowerCase() === key.toLowerCase())); // hide any duplicates in customData keys from data's
14471
15235
  }, ...(ngDevMode ? [{ debugName: "customData" }] : []));
15236
+ allowChangePassword = computed(() => {
15237
+ const { useCredentials } = globalConfig;
15238
+ const { allowChangePassword = false } = this.appStore.general()?.features || {};
15239
+ return allowChangePassword && useCredentials && this.principal().editablePartition;
15240
+ }, ...(ngDevMode ? [{ debugName: "allowChangePassword" }] : []));
14472
15241
  constructor() {
14473
15242
  this.currentLanguage.set(this.transloco.getActiveLang());
14474
15243
  effect(() => {
@@ -14518,14 +15287,17 @@ class UserProfileFormComponent {
14518
15287
  * @param category data or customData
14519
15288
  * @param propertyName property name
14520
15289
  */
14521
- onDeleteData(category, propertyName) {
14522
- this.userProfile.update((profile) => {
14523
- if (profile?.[category]) {
14524
- profile[category][propertyName] = "";
14525
- }
14526
- return profile;
14527
- });
14528
- this.onSaveData(category, propertyName);
15290
+ async onDeleteData(category, propertyName) {
15291
+ const response = await deleteUserProfileProperty(this.userProfile(), category, propertyName);
15292
+ if (response) {
15293
+ info("Deleted user profile property", response);
15294
+ const userProfile = JSON.parse(JSON.stringify(this.userProfile()));
15295
+ userProfile[category][propertyName] = undefined;
15296
+ this.userProfileResource.set(userProfile);
15297
+ }
15298
+ else {
15299
+ info("Could not delete user profile property", response);
15300
+ }
14529
15301
  }
14530
15302
  /**
14531
15303
  * Saving a data property
@@ -14545,8 +15317,8 @@ class UserProfileFormComponent {
14545
15317
  this.userProfileResource.set({
14546
15318
  ...newUserProfile,
14547
15319
  ...response,
14548
- data: { ...(category === "data" ? newUserProfile?.data : {}), ...(response.data || {}) },
14549
- customData: { ...(category === "customData" ? newUserProfile?.customData : {}), ...(response.customData || {}) }
15320
+ data: { ...(newUserProfile?.data || {}), ...(response.data || {}) },
15321
+ customData: { ...(newUserProfile?.customData || {}), ...(response.customData || {}) }
14550
15322
  });
14551
15323
  }
14552
15324
  this.value.set(undefined);
@@ -14594,7 +15366,7 @@ class UserProfileFormComponent {
14594
15366
  }
14595
15367
  }
14596
15368
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14597
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: UserProfileFormComponent, isStandalone: true, selector: "user-profile-form, UserProfileForm, userprofileform", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, currentLanguage: { classPropertyName: "currentLanguage", publicName: "currentLanguage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", currentLanguage: "currentLanguageChange" }, providers: [provideTranslocoScope("user-profile", "login")], viewQueries: [{ propertyName: "createInputElement", first: true, predicate: ["avatarInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex flex-col gap-2 p-4\">\n <div class=\"flex mb-4\">\n <!-- AVATAR -->\n <Avatar\n class=\"bg-accent text-accent-foreground hover:bg-accent/80 hover:text-accent-foreground/80 cursor-pointer size-14\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage [src]=\"profile?.data?.profilePhoto!\" width=\"44\" height=\"44\" alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n }\n @else {\n <UserIcon class=\"size-7 p-1\" />\n }\n </AvatarFallback>\n </Avatar>\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\" (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (principal().editablePartition) {\n <button [disabled]=\"changingPassword()\" (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <ChangePassword\n [redirectAfterSuccess]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n }\n @else {\n @for (key of dataKeys; track key) {\n <div>\n <p>{{ `userProfile.data.${key}` | transloco}}</p>\n <div class=\"flex flex-row group\">\n <div class=\"grow\" [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === 'data.'+key) {\n <input id=\"user-profile-{{key}}\" type=\"text\" [(ngModel)]=\"value\" (keydown.enter)=\"onSaveData('data', key)\" />\n } @else {\n {{ getDataValue('data', key) || ('userProfile.notDefined' | transloco) }}\n }\n </div>\n @if (propertyToEdit() === 'data.'+key) {\n <button variant=\"ghost\" size=\"icon\" class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('data', key)\">\n <i class=\"fa-fw fas fa-save\"></i>\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"cursor-pointer\" title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\" (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button variant=\"ghost\" size=\"icon\" class=\"invisible group-hover:visible mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"text-destructive invisible group-hover:visible cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\" (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p>{{ `userProfile.customData.${key}` | transloco }}</p>\n <div class=\"flex flex-row group\">\n <div class=\"grow\" [class.text-muted-foreground]=\"!getDataValue('customData', key)\">\n @if (propertyToEdit() === 'customData.'+key) {\n <input id=\"user-profile-{{key}}\" type=\"text\" [(ngModel)]=\"value\" (keydown.enter)=\"onSaveData('customData', key)\" />\n } @else {\n {{ getDataValue('customData', key) || ('userProfile.notDefined' | transloco) }}\n }\n </div>\n @if (propertyToEdit() === 'customData.'+key) {\n <button variant=\"ghost\" size=\"icon\" class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('customData', key)\">\n <i class=\"fa-fw fas fa-save\"></i>\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"cursor-pointer\" title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\" (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button variant=\"ghost\" size=\"icon\" class=\"invisible group-hover:visible mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"text-destructive invisible group-hover:visible cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\" (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n }\n </div>\n}\n@else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n}\n@else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input #avatarInput class=\"hidden\" type=\"file\" accept=\"image/*\" (change)=\"uploadAvatar($event)\" />", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: TrashIcon, selector: "trash-icon, TrashIcon", inputs: ["class"] }, { kind: "component", type: EditIcon, selector: "edit-icon, EditIcon, editicon", inputs: ["class"] }, { kind: "component", type: UndoIcon, selector: "UndoIcon, undoicon, undo-icon", inputs: ["class"] }, { kind: "component", type: AvatarComponent, selector: "avatar, Avatar", inputs: ["class"] }, { kind: "component", type: AvatarFallbackComponent, selector: "avatar-fallback, avatarfallback, AvatarFallback", inputs: ["class"] }, { kind: "component", type: UserIcon, selector: "user-icon, UserIcon", inputs: ["class"] }, { kind: "component", type: AvatarImageComponent, selector: "avatar-image, AvatarImage, avatarimage", inputs: ["class", "src", "alt", "width", "height", "referrerPolicy", "crossOrigin"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: ChangePasswordComponent, selector: "change-password, ChangePassword, changepassword", inputs: ["username", "alert", "redirectAfterSuccess", "currentPassword", "newPassword", "confirmPassword"], outputs: ["success", "cancel", "currentPasswordChange", "newPasswordChange", "confirmPasswordChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
15369
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: UserProfileFormComponent, isStandalone: true, selector: "user-profile-form, UserProfileForm, userprofileform", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, currentLanguage: { classPropertyName: "currentLanguage", publicName: "currentLanguage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", currentLanguage: "currentLanguageChange" }, providers: [provideTranslocoScope("user-profile", "login")], viewQueries: [{ propertyName: "createInputElement", first: true, predicate: ["avatarInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n <!-- AVATAR -->\n <Avatar\n class=\"bg-accent text-accent-foreground hover:bg-accent/80 hover:text-accent-foreground/80 size-14 cursor-pointer\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage\n [src]=\"profile?.data?.profilePhoto!\"\n width=\"44\"\n height=\"44\"\n alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7 p-1\" />\n }\n </AvatarFallback>\n </Avatar>\n @if (profile?.data?.profilePhoto) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"text-destructive invisible cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\"\n (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n @for (key of formKeys(); track key) {\n <div>\n <p>{{ `userProfile.data.${key}` | transloco }}</p>\n <div class=\"group flex flex-row\">\n <div\n class=\"grow whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === \"data.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"data\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"data.\" + key) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('data', key)\">\n <i class=\"fa-fw fas fa-save ml-2\"></i>\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"invisible mx-2 cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"text-destructive invisible cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p>{{ key }}</p>\n <div class=\"group flex flex-row\">\n <div\n class=\"grow whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('customData', key)\">\n @if (propertyToEdit() === \"customData.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"customData\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"customData.\" + key) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('customData', key)\">\n <i class=\"fa-fw fas fa-save ml-2\"></i>\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"invisible mx-2 cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"text-destructive invisible cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n} @else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "component", type: TrashIcon, selector: "trash-icon, TrashIcon", inputs: ["class"] }, { kind: "component", type: EditIcon, selector: "edit-icon, EditIcon, editicon", inputs: ["class"] }, { kind: "component", type: UndoIcon, selector: "UndoIcon, undoicon, undo-icon", inputs: ["class"] }, { kind: "component", type: AvatarComponent, selector: "avatar, Avatar", inputs: ["class"] }, { kind: "component", type: AvatarFallbackComponent, selector: "avatar-fallback, avatarfallback, AvatarFallback", inputs: ["class"] }, { kind: "component", type: UserIcon, selector: "user-icon, UserIcon", inputs: ["class"] }, { kind: "component", type: AvatarImageComponent, selector: "avatar-image, AvatarImage, avatarimage", inputs: ["class", "src", "alt", "width", "height", "referrerPolicy", "crossOrigin"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "size"] }, { kind: "component", type: ChangePasswordComponent, selector: "change-password, ChangePassword, changepassword", inputs: ["username", "alert", "redirectAfterSuccess", "redirectAfterCancel", "currentPassword", "newPassword", "confirmPassword"], outputs: ["success", "cancel", "currentPasswordChange", "newPasswordChange", "confirmPasswordChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14598
15370
  }
14599
15371
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileFormComponent, decorators: [{
14600
15372
  type: Component,
@@ -14612,7 +15384,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
14612
15384
  AvatarImageComponent,
14613
15385
  ButtonComponent,
14614
15386
  ChangePasswordComponent
14615
- ], providers: [provideTranslocoScope("user-profile", "login")], template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex flex-col gap-2 p-4\">\n <div class=\"flex mb-4\">\n <!-- AVATAR -->\n <Avatar\n class=\"bg-accent text-accent-foreground hover:bg-accent/80 hover:text-accent-foreground/80 cursor-pointer size-14\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage [src]=\"profile?.data?.profilePhoto!\" width=\"44\" height=\"44\" alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n }\n @else {\n <UserIcon class=\"size-7 p-1\" />\n }\n </AvatarFallback>\n </Avatar>\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\" (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (principal().editablePartition) {\n <button [disabled]=\"changingPassword()\" (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <ChangePassword\n [redirectAfterSuccess]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n }\n @else {\n @for (key of dataKeys; track key) {\n <div>\n <p>{{ `userProfile.data.${key}` | transloco}}</p>\n <div class=\"flex flex-row group\">\n <div class=\"grow\" [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === 'data.'+key) {\n <input id=\"user-profile-{{key}}\" type=\"text\" [(ngModel)]=\"value\" (keydown.enter)=\"onSaveData('data', key)\" />\n } @else {\n {{ getDataValue('data', key) || ('userProfile.notDefined' | transloco) }}\n }\n </div>\n @if (propertyToEdit() === 'data.'+key) {\n <button variant=\"ghost\" size=\"icon\" class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('data', key)\">\n <i class=\"fa-fw fas fa-save\"></i>\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"cursor-pointer\" title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\" (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button variant=\"ghost\" size=\"icon\" class=\"invisible group-hover:visible mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"text-destructive invisible group-hover:visible cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\" (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p>{{ `userProfile.customData.${key}` | transloco }}</p>\n <div class=\"flex flex-row group\">\n <div class=\"grow\" [class.text-muted-foreground]=\"!getDataValue('customData', key)\">\n @if (propertyToEdit() === 'customData.'+key) {\n <input id=\"user-profile-{{key}}\" type=\"text\" [(ngModel)]=\"value\" (keydown.enter)=\"onSaveData('customData', key)\" />\n } @else {\n {{ getDataValue('customData', key) || ('userProfile.notDefined' | transloco) }}\n }\n </div>\n @if (propertyToEdit() === 'customData.'+key) {\n <button variant=\"ghost\" size=\"icon\" class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('customData', key)\">\n <i class=\"fa-fw fas fa-save\"></i>\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"cursor-pointer\" title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\" (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button variant=\"ghost\" size=\"icon\" class=\"invisible group-hover:visible mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\" [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button variant=\"ghost\" size=\"icon\" class=\"text-destructive invisible group-hover:visible cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\" (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n }\n </div>\n}\n@else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n}\n@else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input #avatarInput class=\"hidden\" type=\"file\" accept=\"image/*\" (change)=\"uploadAvatar($event)\" />" }]
15387
+ ], providers: [provideTranslocoScope("user-profile", "login")], template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n <!-- AVATAR -->\n <Avatar\n class=\"bg-accent text-accent-foreground hover:bg-accent/80 hover:text-accent-foreground/80 size-14 cursor-pointer\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage\n [src]=\"profile?.data?.profilePhoto!\"\n width=\"44\"\n height=\"44\"\n alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7 p-1\" />\n }\n </AvatarFallback>\n </Avatar>\n @if (profile?.data?.profilePhoto) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"text-destructive invisible cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\"\n (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n @for (key of formKeys(); track key) {\n <div>\n <p>{{ `userProfile.data.${key}` | transloco }}</p>\n <div class=\"group flex flex-row\">\n <div\n class=\"grow whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === \"data.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"data\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"data.\" + key) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('data', key)\">\n <i class=\"fa-fw fas fa-save ml-2\"></i>\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"invisible mx-2 cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"text-destructive invisible cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p>{{ key }}</p>\n <div class=\"group flex flex-row\">\n <div\n class=\"grow whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('customData', key)\">\n @if (propertyToEdit() === \"customData.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"customData\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"customData.\" + key) {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"mx-2 cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onSaveData('customData', key)\">\n <i class=\"fa-fw fas fa-save ml-2\"></i>\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"cursor-pointer\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"propertyToEdit.set(undefined)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"invisible mx-2 cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n size=\"icon\"\n class=\"text-destructive invisible cursor-pointer group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n} @else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n" }]
14616
15388
  }], ctorParameters: () => [], propDecorators: { createInputElement: [{ type: i0.ViewChild, args: ["avatarInput", { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], currentLanguage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentLanguage", required: false }] }, { type: i0.Output, args: ["currentLanguageChange"] }] } });
14617
15389
 
14618
15390
  class UserProfileDialog {
@@ -14623,11 +15395,11 @@ class UserProfileDialog {
14623
15395
  this.opened.set(true);
14624
15396
  }
14625
15397
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
14626
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: UserProfileDialog, isStandalone: true, selector: "user-profile-dialog, userprofiledialog, UserProfileDialog", viewQueries: [{ propertyName: "dialog", first: true, predicate: DialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
15398
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: UserProfileDialog, isStandalone: true, selector: "user-profile-dialog, userprofiledialog, UserProfileDialog", providers: [provideTranslocoScope("user-profile")], viewQueries: [{ propertyName: "dialog", first: true, predicate: DialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
14627
15399
  <dialog #dialog>
14628
15400
  <DialogContent class="max-h-11/12 overflow-auto">
14629
15401
  <DialogHeader>
14630
- <DialogTitle>User Profile</DialogTitle>
15402
+ <DialogTitle>{{ 'userProfile.title' | transloco }}</DialogTitle>
14631
15403
  </DialogHeader>
14632
15404
 
14633
15405
  @if (opened()) {
@@ -14636,7 +15408,7 @@ class UserProfileDialog {
14636
15408
 
14637
15409
  </DialogContent>
14638
15410
  </dialog>
14639
- `, isInline: true, dependencies: [{ kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "component", type: UserProfileFormComponent, selector: "user-profile-form, UserProfileForm, userprofileform", inputs: ["value", "currentLanguage"], outputs: ["valueChange", "currentLanguageChange"] }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }] });
15411
+ `, isInline: true, dependencies: [{ kind: "component", type: DialogComponent, selector: "dialog, [dialog]", outputs: ["closed"], exportAs: ["dialog"] }, { kind: "component", type: DialogHeaderComponent, selector: "DialogHeader" }, { kind: "directive", type: DialogContentComponent, selector: "DialogContent", inputs: ["class"] }, { kind: "component", type: UserProfileFormComponent, selector: "user-profile-form, UserProfileForm, userprofileform", inputs: ["value", "currentLanguage"], outputs: ["valueChange", "currentLanguageChange"] }, { kind: "directive", type: DialogTitleComponent, selector: "DialogTitle", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14640
15412
  }
14641
15413
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileDialog, decorators: [{
14642
15414
  type: Component,
@@ -14648,13 +15420,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
14648
15420
  DialogHeaderComponent,
14649
15421
  DialogContentComponent,
14650
15422
  UserProfileFormComponent,
14651
- DialogTitleComponent
15423
+ DialogTitleComponent,
15424
+ TranslocoPipe
14652
15425
  ],
15426
+ providers: [provideTranslocoScope("user-profile")],
14653
15427
  template: `
14654
15428
  <dialog #dialog>
14655
15429
  <DialogContent class="max-h-11/12 overflow-auto">
14656
15430
  <DialogHeader>
14657
- <DialogTitle>User Profile</DialogTitle>
15431
+ <DialogTitle>{{ 'userProfile.title' | transloco }}</DialogTitle>
14658
15432
  </DialogHeader>
14659
15433
 
14660
15434
  @if (opened()) {
@@ -14797,6 +15571,7 @@ const errorInterceptorFn = (request, next) => {
14797
15571
  }));
14798
15572
  };
14799
15573
 
15574
+ const SHOW_ERROR_TOKEN = new HttpContextToken(() => true);
14800
15575
  /**
14801
15576
  * Intercepts HTTP requests and handles errors by displaying toast notifications.
14802
15577
  *
@@ -14814,6 +15589,18 @@ const toastInterceptorFn = (request, next) => {
14814
15589
  }
14815
15590
  return next(request).pipe(catchError$1((err) => {
14816
15591
  const { status, statusText, error, url } = err;
15592
+ if (status === 401 && globalConfig.userOverrideActive && error?.errorCode == 6) {
15593
+ if (request.context.get(SHOW_ERROR_TOKEN)) {
15594
+ const errorMsg = error?.errorMessage || 'The account may be deactivated or inaccessible';
15595
+ notify.error(`Cannot override user: Unauthorized access`, {
15596
+ description: errorMsg,
15597
+ closeButton: true,
15598
+ duration: 5000
15599
+ });
15600
+ request.context.set(SHOW_ERROR_TOKEN, false);
15601
+ return throwError(() => err);
15602
+ }
15603
+ }
14817
15604
  if ([400, 403, 500, 503].includes(status)) {
14818
15605
  // Avoid showing error toasts for preview requests
14819
15606
  if (url.includes("api/v1/preview") === false) {
@@ -14849,5 +15636,5 @@ const queryNameResolver = () => {
14849
15636
  * Generated bundle index. Do not edit.
14850
15637
  */
14851
15638
 
14852
- export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, 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, 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, PasswordExpiryGuard, 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, SignInComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
15639
+ export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationListComponent, 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, 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, SignInComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
14853
15640
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map