@sinequa/atomic-angular 1.2.5 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/sinequa-atomic-angular.mjs +1282 -1413
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +1766 -1896
- package/package.json +1 -1
|
@@ -1,26 +1,26 @@
|
|
|
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,
|
|
3
|
-
import { BehaviorSubject, Subscription, firstValueFrom, catchError, map, Subject, of, tap, EMPTY, throwError, filter, shareReplay,
|
|
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, ChangeDetectionStrategy, ViewContainerRef, numberAttribute, viewChildren, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
3
|
+
import { BehaviorSubject, Subscription, firstValueFrom, catchError, map, Subject, of, tap, EMPTY, throwError, filter, shareReplay, switchMap, from, fromEvent, debounceTime } from 'rxjs';
|
|
4
4
|
import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
|
|
5
|
-
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SquareCheckIcon, SquareMinusIcon, SquareIcon, SwitchComponent, SelectOptionDirective, DialogService, XMarkIcon, InboxIcon, SparklesIcon, FileOutputIcon, TabsComponent, TabsListComponent, TabComponent, FrownIcon, ChevronLeftIcon, ChevronsLeftIcon, ChevronsRightIcon, ArrowUpAzIcon, ArrowDownZaIcon, ArrowUpRightFromSquareIcon, ToggleRightIcon, ToggleLeftIcon, Separator, SheetCloseDirective, ArrowLeftIcon, SheetService, DateRangePickerDirective, DatepickerDirective,
|
|
5
|
+
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SquareCheckIcon, SquareMinusIcon, SquareIcon, SwitchComponent, SelectOptionDirective, DialogService, XMarkIcon, InboxIcon, SparklesIcon, FileOutputIcon, TabsComponent, TabsListComponent, TabComponent, SidebarMenuComponent, SidebarMenuItemComponent, SidebarMenuButtonComponent, TooltipDirective, FrownIcon, ChevronLeftIcon, ChevronsLeftIcon, ChevronsRightIcon, ArrowUpAzIcon, ArrowDownZaIcon, ArrowUpRightFromSquareIcon, ToggleRightIcon, ToggleLeftIcon, Separator, SheetCloseDirective, ArrowLeftIcon, SheetService, DateRangePickerDirective, DatepickerDirective, FilterIcon, FilterXIcon, IconButtonComponent, HighlighterIcon, TagsIcon, SpinnerIcon, MagnifyingGlassIcon, LoadingCircleIcon, CircleCheckIcon, PopoverComponent, BellIcon, TrashIcon, BarsIcon, CardComponent, CardHeaderComponent, CardContentComponent, CardFooterComponent, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, EyeSlashIcon, EyeIcon, BookmarkIcon, PopoverContentComponent, UserIcon, FolderIcon, VerticalDividerComponent, CommentIcon, ThumbsUpIcon, ThumbsDownIcon, DropdownDirective, SearchIcon, TriangleAlertIcon, ListFilterIcon, BreakpointObserverService, TrashCanIcon, CircleXIcon, InfoCircleIcon, HorizontalDividerComponent, HistoryIcon, StarIcon, FlagEnglishIcon, FlagFrenchIcon, EditIcon, UndoIcon, SaveIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
|
|
6
6
|
import highlightWords from 'highlight-words';
|
|
7
|
-
import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterModule } from '@angular/router';
|
|
7
|
+
import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
|
|
8
8
|
import { withDevtools } from '@angular-architects/ngrx-toolkit';
|
|
9
9
|
import { signalStore, signalStoreFeature, withState, withMethods, patchState, getState, withComputed } from '@ngrx/signals';
|
|
10
10
|
import { EngineType, fetchApp, extraColumns, sysLang, globalConfig, getQueryParamsFromUrl, clearSessionTokens, login, info, warn, error, setGlobalConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, buildPathsAndLevels, escapeExpr, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, fetchQuery, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
|
|
11
|
-
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
11
|
+
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
12
12
|
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
|
|
13
13
|
import { HttpClient, HttpParams, httpResource, HttpResponse, HttpHeaders, HttpContextToken } from '@angular/common/http';
|
|
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';
|
|
19
18
|
import * as i1$1 from '@angular/cdk/drag-drop';
|
|
20
19
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
21
20
|
import * as i2 from '@angular/cdk/a11y';
|
|
22
21
|
import { A11yModule } from '@angular/cdk/a11y';
|
|
23
22
|
import { Overlay } from '@angular/cdk/overlay';
|
|
23
|
+
import { injectVirtualizer } from '@tanstack/angular-virtual';
|
|
24
24
|
import { catchError as catchError$1, switchMap as switchMap$1 } from 'rxjs/operators';
|
|
25
25
|
|
|
26
26
|
class BackdropService {
|
|
@@ -450,9 +450,13 @@ function withAppFeatures() {
|
|
|
450
450
|
* @returns The alias of the column if it exists, otherwise the original column name.
|
|
451
451
|
*/
|
|
452
452
|
getColumnAlias(column) {
|
|
453
|
+
if (typeof column !== "string" || column.length === 0) {
|
|
454
|
+
return column;
|
|
455
|
+
}
|
|
453
456
|
const state = getState(store);
|
|
454
457
|
const schema = state.indexes._.columns;
|
|
455
|
-
const
|
|
458
|
+
const key = column.toLocaleLowerCase();
|
|
459
|
+
const col = Object.hasOwn(schema, key) ? schema[key] : undefined;
|
|
456
460
|
if (col) {
|
|
457
461
|
return col.aliases?.[0] ? `${col.aliases[0].charAt(0).toLowerCase()}${col.aliases[0].slice(1)}` : column;
|
|
458
462
|
}
|
|
@@ -465,8 +469,15 @@ function withAppFeatures() {
|
|
|
465
469
|
* @returns The column definition as a `CCColumn` object if found, or `undefined` if the column does not exist.
|
|
466
470
|
*/
|
|
467
471
|
getColumn(columnOrAlias) {
|
|
472
|
+
if (typeof columnOrAlias !== "string" || columnOrAlias.length === 0) {
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
468
475
|
const state = getState(store);
|
|
469
|
-
|
|
476
|
+
const columnMap = state.columnMap;
|
|
477
|
+
if (!columnMap)
|
|
478
|
+
return undefined;
|
|
479
|
+
const key = columnOrAlias.toLocaleLowerCase();
|
|
480
|
+
return Object.hasOwn(columnMap, key) ? columnMap[key] : undefined;
|
|
470
481
|
},
|
|
471
482
|
/**
|
|
472
483
|
* Determines if the specified column is of a date-related type.
|
|
@@ -1513,6 +1524,16 @@ function withThemes(app, themes) {
|
|
|
1513
1524
|
return app;
|
|
1514
1525
|
}
|
|
1515
1526
|
|
|
1527
|
+
/**
|
|
1528
|
+
* Signs in the user by checking the global configuration for authentication method and acting accordingly.
|
|
1529
|
+
*
|
|
1530
|
+
* This function first clears any existing session tokens to ensure a clean authentication state. It then checks the
|
|
1531
|
+
* global configuration to determine whether to use credentials-based authentication or Single Sign-On (SSO). If
|
|
1532
|
+
* credentials are used, it redirects the user to the login page. If SSO is enabled, it reloads the page to trigger
|
|
1533
|
+
* the SSO login process. If neither method is specified, it attempts a standard login and handles any errors that
|
|
1534
|
+
* may occur during the process.
|
|
1535
|
+
* @returns A promise resolving to a boolean indicating the success of the sign-in operation.
|
|
1536
|
+
*/
|
|
1516
1537
|
async function signIn() {
|
|
1517
1538
|
assertInInjectionContext(signIn);
|
|
1518
1539
|
const router = inject(Router);
|
|
@@ -1523,23 +1544,24 @@ async function signIn() {
|
|
|
1523
1544
|
// If credentials are used, redirect to the login page
|
|
1524
1545
|
if (useCredentials) {
|
|
1525
1546
|
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1526
|
-
return; // prevent further execution
|
|
1547
|
+
return false; // prevent further execution
|
|
1527
1548
|
}
|
|
1528
1549
|
// SSO is set to true when the browser handles authentication automatically
|
|
1529
1550
|
// If SSO is used, reload the page to trigger SSO login
|
|
1530
1551
|
if (useSSO) {
|
|
1531
1552
|
// reload the page to trigger SSO login
|
|
1532
1553
|
window.location.reload();
|
|
1533
|
-
return; // prevent further execution
|
|
1554
|
+
return false; // prevent further execution
|
|
1534
1555
|
}
|
|
1535
1556
|
// Otherwise, perform a standard login
|
|
1536
1557
|
try {
|
|
1537
1558
|
const response = await login();
|
|
1538
1559
|
if (response) {
|
|
1539
1560
|
info("Response from login", response);
|
|
1561
|
+
return true;
|
|
1540
1562
|
}
|
|
1541
1563
|
else {
|
|
1542
|
-
warn("
|
|
1564
|
+
warn("Response from login", response);
|
|
1543
1565
|
}
|
|
1544
1566
|
}
|
|
1545
1567
|
catch (err) {
|
|
@@ -1550,7 +1572,7 @@ async function signIn() {
|
|
|
1550
1572
|
}
|
|
1551
1573
|
throw err;
|
|
1552
1574
|
}
|
|
1553
|
-
return; // prevent further execution
|
|
1575
|
+
return false; // prevent further execution
|
|
1554
1576
|
}
|
|
1555
1577
|
|
|
1556
1578
|
/**
|
|
@@ -1571,24 +1593,31 @@ async function withBootstrapApp(applicationService, { createRoutes = true }) {
|
|
|
1571
1593
|
return new Promise(resolve => {
|
|
1572
1594
|
// Check if the user is authenticated
|
|
1573
1595
|
signIn()
|
|
1574
|
-
.then(() => {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1596
|
+
.then(async (response) => {
|
|
1597
|
+
if (response) {
|
|
1598
|
+
info('User authenticated, initializing application...');
|
|
1599
|
+
// Initialize the application.
|
|
1600
|
+
// Awaited so the APP_INITIALIZER does not resolve (and bootstrap does not
|
|
1601
|
+
// complete) until initialization is done. Otherwise routed components render
|
|
1602
|
+
// and set their page title before `initialize()` runs `setGeneralApp()`, which
|
|
1603
|
+
// would then overwrite the page title with the bare application name.
|
|
1604
|
+
try {
|
|
1605
|
+
await applicationService.initialize(createRoutes);
|
|
1606
|
+
info(`Application initialized with routes: ${createRoutes} successfully.`);
|
|
1607
|
+
}
|
|
1608
|
+
catch (err) {
|
|
1609
|
+
error(`Error initializing application with routes: ${createRoutes}:`, err);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
else {
|
|
1613
|
+
info('User not authenticated, skipping application initialization.');
|
|
1614
|
+
}
|
|
1615
|
+
;
|
|
1587
1616
|
})
|
|
1588
1617
|
.catch(err => {
|
|
1589
1618
|
error('Error while signing in:', err);
|
|
1590
|
-
|
|
1591
|
-
|
|
1619
|
+
})
|
|
1620
|
+
.finally(() => resolve());
|
|
1592
1621
|
});
|
|
1593
1622
|
}
|
|
1594
1623
|
|
|
@@ -2745,19 +2774,45 @@ function withSavedSearchesFeatures() {
|
|
|
2745
2774
|
})));
|
|
2746
2775
|
}
|
|
2747
2776
|
|
|
2777
|
+
/**
|
|
2778
|
+
* Canonical default shape of the user settings state.
|
|
2779
|
+
*
|
|
2780
|
+
* Shared between `initialize()` (overlaid under the fetched settings) and `reset()`
|
|
2781
|
+
* (used as the replacement state) so the two cannot drift apart. Keep in sync with
|
|
2782
|
+
* the `UserSettingsState` type.
|
|
2783
|
+
*/
|
|
2784
|
+
const USER_SETTINGS_DEFAULTS = {
|
|
2785
|
+
bookmarks: [],
|
|
2786
|
+
recentSearches: [],
|
|
2787
|
+
savedSearches: [],
|
|
2788
|
+
baskets: [],
|
|
2789
|
+
alerts: [],
|
|
2790
|
+
assistants: {},
|
|
2791
|
+
language: undefined,
|
|
2792
|
+
collapseAssistant: undefined,
|
|
2793
|
+
userTheme: undefined,
|
|
2794
|
+
agents: { isDebugMode: false }
|
|
2795
|
+
};
|
|
2748
2796
|
function withUserSettingsFeatures() {
|
|
2749
2797
|
return signalStoreFeature(withState({ language: undefined, collapseAssistant: undefined }), withMethods(store => ({
|
|
2750
2798
|
/**
|
|
2751
2799
|
* Initializes the user settings store by fetching the user settings from the backend API
|
|
2752
2800
|
* and patching the store with the retrieved settings.
|
|
2753
2801
|
*
|
|
2802
|
+
* The fetched settings are overlaid on top of {@link USER_SETTINGS_DEFAULTS} so that any
|
|
2803
|
+
* key absent from the new user's settings resets to its default instead of retaining the
|
|
2804
|
+
* previous user's in-memory value. This is required because `initialize()` runs both on
|
|
2805
|
+
* first login and on every user override (impersonation): a plain merge would leak the
|
|
2806
|
+
* previous user's data (e.g. `recentSearches`) into the new user's session and backend.
|
|
2807
|
+
*
|
|
2754
2808
|
* @returns {Promise<void>} A promise that resolves when the initialization is complete.
|
|
2755
2809
|
*/
|
|
2756
2810
|
async initialize() {
|
|
2757
|
-
// Fetch the user settings from the backend API and
|
|
2811
|
+
// Fetch the user settings from the backend API and overlay them on the defaults,
|
|
2812
|
+
// so missing keys reset rather than keep the previous user's values.
|
|
2758
2813
|
try {
|
|
2759
2814
|
const settings = await fetchUserSettings();
|
|
2760
|
-
patchState(store, settings);
|
|
2815
|
+
patchState(store, { ...USER_SETTINGS_DEFAULTS, ...settings });
|
|
2761
2816
|
}
|
|
2762
2817
|
catch (err) {
|
|
2763
2818
|
error('Error fetching user settings:', err);
|
|
@@ -2775,17 +2830,7 @@ function withUserSettingsFeatures() {
|
|
|
2775
2830
|
async reset() {
|
|
2776
2831
|
// Reset the user settings to the initial state
|
|
2777
2832
|
await deleteUserSettings();
|
|
2778
|
-
patchState(store,
|
|
2779
|
-
bookmarks: [],
|
|
2780
|
-
recentSearches: [],
|
|
2781
|
-
savedSearches: [],
|
|
2782
|
-
baskets: [],
|
|
2783
|
-
alerts: [],
|
|
2784
|
-
assistants: {},
|
|
2785
|
-
language: undefined,
|
|
2786
|
-
collapseAssistant: undefined,
|
|
2787
|
-
agents: { isDebugMode: false }
|
|
2788
|
-
});
|
|
2833
|
+
patchState(store, USER_SETTINGS_DEFAULTS);
|
|
2789
2834
|
}
|
|
2790
2835
|
})));
|
|
2791
2836
|
}
|
|
@@ -3265,39 +3310,53 @@ class AggregationsService {
|
|
|
3265
3310
|
return node;
|
|
3266
3311
|
}
|
|
3267
3312
|
/* aggregations helpers fot the filters components */
|
|
3268
|
-
getAuthorizedFilters(aggregations, includedFilters = [], excludedFilters = []) {
|
|
3313
|
+
getAuthorizedFilters(aggregations, includedFilters = [], excludedFilters = [], homepageOnly = false) {
|
|
3269
3314
|
const agg = aggregations || this.appStore.getAuthorizedFilters();
|
|
3270
3315
|
const authorizedFilters = agg
|
|
3271
3316
|
.filter((f) => this.getFilterCriteria()(f)) // filter only the filters present
|
|
3317
|
+
.filter((f) => !homepageOnly || this.getHomepageFilterCriteria()(f)) // when requested, keep only filters flagged `homepage: true`
|
|
3272
3318
|
.filter((f) => !excludedFilters.includes(f.name))
|
|
3273
3319
|
.filter((f) => !includedFilters.length || includedFilters.includes(f.name))
|
|
3274
3320
|
.map((f) => ({ field: f.column, column: f.column, name: f.name })); // field is needed for filters constructions
|
|
3275
3321
|
return authorizedFilters;
|
|
3276
3322
|
}
|
|
3323
|
+
/**
|
|
3324
|
+
* Determines whether a custom JSON filter refers to the given aggregation.
|
|
3325
|
+
*
|
|
3326
|
+
* Matches by `name` when it is defined, otherwise falls back to `column`,
|
|
3327
|
+
* resolving column/alias ambiguity through the app store's column map.
|
|
3328
|
+
*
|
|
3329
|
+
* @param filter - The filter object coming from the custom JSON.
|
|
3330
|
+
* @param agg - The aggregation returned by the backend.
|
|
3331
|
+
* @returns `true` if the filter refers to the aggregation.
|
|
3332
|
+
*/
|
|
3333
|
+
matchesAggregation(filter, agg) {
|
|
3334
|
+
// if filter.name is defined, use it to compare
|
|
3335
|
+
if (filter.name) {
|
|
3336
|
+
return filter.name.toLocaleLowerCase() === agg.name.toLocaleLowerCase();
|
|
3337
|
+
}
|
|
3338
|
+
// fallback to column comparison
|
|
3339
|
+
// column can be a column's name or an alias
|
|
3340
|
+
// resolve ambiguity between column and alias
|
|
3341
|
+
const { columnMap } = getState(this.appStore);
|
|
3342
|
+
// get the actual column for both filter and f
|
|
3343
|
+
const aggColumn = columnMap?.[agg.column.toLocaleLowerCase()];
|
|
3344
|
+
const filterColumn = columnMap?.[filter?.column?.toLocaleLowerCase() || filter.name.toLocaleLowerCase()];
|
|
3345
|
+
// if either column is not found, fallback to comparing the raw values
|
|
3346
|
+
if (!aggColumn || !filterColumn) {
|
|
3347
|
+
return filter?.column?.toLocaleLowerCase() === agg?.column?.toLocaleLowerCase();
|
|
3348
|
+
}
|
|
3349
|
+
// compare the actual column names coming from the column map
|
|
3350
|
+
return aggColumn?.name === filterColumn?.name;
|
|
3351
|
+
}
|
|
3277
3352
|
getFilterCriteria = () => {
|
|
3278
3353
|
// filter: object filter from the custom JSON
|
|
3279
3354
|
// agg: object aggregation returned by the backend
|
|
3280
|
-
return (agg) =>
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
}
|
|
3286
|
-
// fallback to column comparison
|
|
3287
|
-
// column can be a column's name or an alias
|
|
3288
|
-
// resolve ambiguity between column and alias
|
|
3289
|
-
const { columnMap } = getState(this.appStore);
|
|
3290
|
-
// get the actual column for both filter and f
|
|
3291
|
-
const aggColumn = columnMap?.[agg.column.toLocaleLowerCase()];
|
|
3292
|
-
const filterColumn = columnMap?.[filter?.column?.toLocaleLowerCase() || filter.name.toLocaleLowerCase()];
|
|
3293
|
-
// if either column is not found, fallback to comparing the raw values
|
|
3294
|
-
if (!aggColumn || !filterColumn) {
|
|
3295
|
-
return filter?.column?.toLocaleLowerCase() === agg?.column?.toLocaleLowerCase();
|
|
3296
|
-
}
|
|
3297
|
-
// compare the actual column names coming from the column map
|
|
3298
|
-
return aggColumn?.name === filterColumn?.name;
|
|
3299
|
-
});
|
|
3300
|
-
};
|
|
3355
|
+
return (agg) => this.appStore.filters().some((filter) => this.matchesAggregation(filter, agg));
|
|
3356
|
+
};
|
|
3357
|
+
getHomepageFilterCriteria = () => {
|
|
3358
|
+
// only consider filters explicitly flagged with `homepage: true` in the custom JSON
|
|
3359
|
+
return (agg) => this.appStore.filters().some((filter) => filter.homepage && this.matchesAggregation(filter, agg));
|
|
3301
3360
|
};
|
|
3302
3361
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3303
3362
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationsService, providedIn: "root" });
|
|
@@ -3365,14 +3424,10 @@ function AuthGuard() {
|
|
|
3365
3424
|
const { loginPath, useCredentials, useSSO } = globalConfig;
|
|
3366
3425
|
if (state.url.startsWith("/login"))
|
|
3367
3426
|
return true;
|
|
3368
|
-
// If the user is not authenticated, navigate to the login page
|
|
3427
|
+
// If the user is not authenticated, navigate to the login page.
|
|
3428
|
+
// The login page handles every authentication method (credentials, OAuth, SAML).
|
|
3369
3429
|
if (!isAuthenticated() && !useSSO) {
|
|
3370
|
-
|
|
3371
|
-
router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
|
|
3372
|
-
}
|
|
3373
|
-
else {
|
|
3374
|
-
router.navigate(["loading"], { queryParams: { returnUrl: state.url } });
|
|
3375
|
-
}
|
|
3430
|
+
router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
|
|
3376
3431
|
return false;
|
|
3377
3432
|
}
|
|
3378
3433
|
// If the user is authenticated, initialize the principal store if it's in the initial state
|
|
@@ -3424,6 +3479,7 @@ class ApplicationService {
|
|
|
3424
3479
|
components = inject(ROUTE_COMPONENTS);
|
|
3425
3480
|
defaultComponent = computed(() => this.components.find((c) => c.path === "all")?.component, ...(ngDevMode ? [{ debugName: "defaultComponent" }] : []));
|
|
3426
3481
|
defaultLayoutComponent = computed(() => this.components.find((c) => c.isRoot)?.component, ...(ngDevMode ? [{ debugName: "defaultLayoutComponent" }] : []));
|
|
3482
|
+
routerConfig = signal(undefined, ...(ngDevMode ? [{ debugName: "routerConfig" }] : []));
|
|
3427
3483
|
/**
|
|
3428
3484
|
* Initializes the application and creates routes if needed.
|
|
3429
3485
|
*
|
|
@@ -3610,6 +3666,7 @@ class ApplicationService {
|
|
|
3610
3666
|
searchPath.children = [...children, ...existingChildren, wildcardPath];
|
|
3611
3667
|
const newConfig = [searchPath, ...currentConfig];
|
|
3612
3668
|
info("ApplicationService: createRoutes -> newConfig", newConfig);
|
|
3669
|
+
this.routerConfig.set(newConfig);
|
|
3613
3670
|
// finally we reset the router config with the new routes
|
|
3614
3671
|
this.router.resetConfig(newConfig);
|
|
3615
3672
|
}
|
|
@@ -3684,10 +3741,10 @@ class ApplicationService {
|
|
|
3684
3741
|
// If general is not defined or is an empty object, do nothing
|
|
3685
3742
|
if (!general || (typeof general === "object" && Object.keys(general).length === 0))
|
|
3686
3743
|
return;
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3744
|
+
// NB: the document title is intentionally NOT set here. The page-specific title is
|
|
3745
|
+
// owned by the routed components/layouts via `setTitle()`. Setting it here would
|
|
3746
|
+
// overwrite the page title with the bare application name during bootstrap.
|
|
3747
|
+
// The initial title falls back to the static <title> defined in index.html.
|
|
3691
3748
|
const { light, dark, alt } = general.logo || {};
|
|
3692
3749
|
document.documentElement.style.setProperty("--logo-alt-text", `'${alt || general.name}'`);
|
|
3693
3750
|
// light mode logo configuration
|
|
@@ -3981,7 +4038,7 @@ class NavigationService {
|
|
|
3981
4038
|
* - Maps all router events to `RouterEvent`.
|
|
3982
4039
|
* - Filters the events to only include instances of `NavigationEnd`.
|
|
3983
4040
|
* - Taps into the event stream to extract the route name from the URL and notify the audit service of route changes,
|
|
3984
|
-
* excluding
|
|
4041
|
+
* excluding duplicate navigations.
|
|
3985
4042
|
* - Updates the `urlAfterNavigation` property with the current URL after navigation.
|
|
3986
4043
|
* - Shares the replayed value with a buffer size of 1 to ensure subscribers receive the latest emitted value.
|
|
3987
4044
|
*
|
|
@@ -3989,8 +4046,7 @@ class NavigationService {
|
|
|
3989
4046
|
*/
|
|
3990
4047
|
navigationEnd$ = this.router.events.pipe(map((event) => event), filter((event) => event instanceof NavigationEnd), tap((event) => {
|
|
3991
4048
|
const url = event.url.slice(1).split("?")[0]; // Extract route name
|
|
3992
|
-
|
|
3993
|
-
if (url && url !== "loading" && url !== this.urlAfterNavigation) {
|
|
4049
|
+
if (url && url !== this.urlAfterNavigation) {
|
|
3994
4050
|
this.auditService.notifyRouteChange(url);
|
|
3995
4051
|
}
|
|
3996
4052
|
}), tap((event) => {
|
|
@@ -5321,6 +5377,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
5321
5377
|
}]
|
|
5322
5378
|
}] });
|
|
5323
5379
|
|
|
5380
|
+
/**
|
|
5381
|
+
* @deprecated This component and its `/loading` route are no longer used.
|
|
5382
|
+
* Authentication is handled at bootstrap by `withBootstrapApp`/`signIn`, and the
|
|
5383
|
+
* `AuthGuard` now redirects unauthenticated users to the login page directly.
|
|
5384
|
+
* Will be removed in a future major release. Do not use in new code.
|
|
5385
|
+
*/
|
|
5324
5386
|
class LoadingComponent {
|
|
5325
5387
|
state = computed(() => getState(this.application), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
5326
5388
|
application = inject(ApplicationStore);
|
|
@@ -6174,6 +6236,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
6174
6236
|
}]
|
|
6175
6237
|
}], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], updatedCollections: [{ type: i0.Output, args: ["updatedCollections"] }] } });
|
|
6176
6238
|
|
|
6239
|
+
function injectRouteNavigation(path, showCount) {
|
|
6240
|
+
const router = inject(Router);
|
|
6241
|
+
const route = inject(ActivatedRoute);
|
|
6242
|
+
const queryParamsStore = inject(QueryParamsStore);
|
|
6243
|
+
const queryService = inject(QueryService);
|
|
6244
|
+
const routerConfig = inject(ApplicationService).routerConfig;
|
|
6245
|
+
const searchText = computed(() => getState(queryParamsStore).text || "", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
6246
|
+
const currentPath = computed(() => {
|
|
6247
|
+
const { tab: currentTab } = getState(queryParamsStore);
|
|
6248
|
+
let current = route.snapshot;
|
|
6249
|
+
while (current.firstChild)
|
|
6250
|
+
current = current.firstChild;
|
|
6251
|
+
const childPath = current.url.map((s) => s.path).join("/");
|
|
6252
|
+
return childPath || currentTab || "all";
|
|
6253
|
+
}, ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
|
|
6254
|
+
const tabs = computed(() => {
|
|
6255
|
+
const result = queryService.result();
|
|
6256
|
+
const config = routerConfig() ?? router.config;
|
|
6257
|
+
return (config
|
|
6258
|
+
.find((item) => item.path === path())
|
|
6259
|
+
?.children?.filter((c) => c.path !== "**" && !c.redirectTo)
|
|
6260
|
+
.map((child) => {
|
|
6261
|
+
const data = child.data;
|
|
6262
|
+
return {
|
|
6263
|
+
display: data?.display || child.path || "",
|
|
6264
|
+
wsQueryTab: data?.wsQueryTab || child.path || "",
|
|
6265
|
+
path: child.path || "",
|
|
6266
|
+
routerLink: `/${path()}/${child.path}`,
|
|
6267
|
+
icon: data?.icon || "",
|
|
6268
|
+
queryName: data?.queryName,
|
|
6269
|
+
count: showCount()
|
|
6270
|
+
? result.queryName === data?.queryName
|
|
6271
|
+
? result.tabs?.find((t) => t.name === (data?.wsQueryTab || child.path))?.count
|
|
6272
|
+
: undefined
|
|
6273
|
+
: undefined
|
|
6274
|
+
};
|
|
6275
|
+
}) ?? []);
|
|
6276
|
+
}, ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
6277
|
+
return { tabs, currentPath, searchText };
|
|
6278
|
+
}
|
|
6279
|
+
|
|
6177
6280
|
/**
|
|
6178
6281
|
* Directive to mark a child element for special processing.
|
|
6179
6282
|
* This can be used to apply specific styles or behaviors to child elements.
|
|
@@ -6877,8 +6980,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
6877
6980
|
}]
|
|
6878
6981
|
}] });
|
|
6879
6982
|
/**
|
|
6880
|
-
* Directive to be used on the
|
|
6881
|
-
*
|
|
6983
|
+
* Directive to be used on the "more" trigger element. The overflow manager
|
|
6984
|
+
* reserves this element's size when not all items fit, so the last visible
|
|
6985
|
+
* item never overlaps it. Its position is not used for measurement.
|
|
6882
6986
|
*/
|
|
6883
6987
|
class OverflowStopDirective {
|
|
6884
6988
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverflowStopDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -6892,10 +6996,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
6892
6996
|
}]
|
|
6893
6997
|
}] });
|
|
6894
6998
|
/**
|
|
6895
|
-
* Directive that
|
|
6896
|
-
*
|
|
6897
|
-
*
|
|
6898
|
-
*
|
|
6999
|
+
* Directive that counts how many items fit inside the container and hides the
|
|
7000
|
+
* overflowing ones. The boundary is the container's own content edge, which
|
|
7001
|
+
* keeps the measurement correct even when the host lives inside a flex layout
|
|
7002
|
+
* (we never depend on a sibling's position). The stop element is used only to
|
|
7003
|
+
* reserve space for the "more" trigger when not all items fit.
|
|
7004
|
+
*
|
|
7005
|
+
* The directive applies `display: none` on the items that do not fit.
|
|
6899
7006
|
*
|
|
6900
7007
|
* You can specify a target element to observe for resize events, otherwise the
|
|
6901
7008
|
* directive will observe the element itself.
|
|
@@ -6934,10 +7041,26 @@ class OverflowManagerDirective {
|
|
|
6934
7041
|
target = input(...(ngDevMode ? [undefined, { debugName: "target" }] : []));
|
|
6935
7042
|
margin = input(4, ...(ngDevMode ? [{ debugName: "margin" }] : []));
|
|
6936
7043
|
direction = input("horizontal", ...(ngDevMode ? [{ debugName: "direction" }] : []));
|
|
7044
|
+
/**
|
|
7045
|
+
* Always reserve the stop element's size, even when every item fits inside
|
|
7046
|
+
* the container. Use it when the stop trigger is permanently visible (e.g.
|
|
7047
|
+
* the "more" button also gives access to items that are never rendered in
|
|
7048
|
+
* the container), so the last item never overlaps it.
|
|
7049
|
+
*/
|
|
7050
|
+
reserveStop = input(false, ...(ngDevMode ? [{ debugName: "reserveStop", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
6937
7051
|
count = output();
|
|
6938
7052
|
el = inject(ElementRef).nativeElement;
|
|
6939
7053
|
destroyRef = inject(DestroyRef);
|
|
6940
7054
|
resizeObserver = new ResizeObserver(() => this.countItems());
|
|
7055
|
+
// Recompute when an individual item's size changes (label update, async
|
|
7056
|
+
// translation, font load…), not only when the container resizes. Items we
|
|
7057
|
+
// hid with `display: none` report a 0×0 size; ignore those notifications so
|
|
7058
|
+
// our own show/hide toggling doesn't trigger redundant recounts.
|
|
7059
|
+
itemsResizeObserver = new ResizeObserver((entries) => {
|
|
7060
|
+
const visibleItemResized = entries.some((entry) => entry.target.style.display !== "none");
|
|
7061
|
+
if (visibleItemResized)
|
|
7062
|
+
this.countItems();
|
|
7063
|
+
});
|
|
6941
7064
|
countSub;
|
|
6942
7065
|
_lastCount;
|
|
6943
7066
|
constructor() {
|
|
@@ -6949,21 +7072,37 @@ class OverflowManagerDirective {
|
|
|
6949
7072
|
this.countItems();
|
|
6950
7073
|
}
|
|
6951
7074
|
});
|
|
7075
|
+
// (re)observe every item whenever the projected list changes, so that
|
|
7076
|
+
// added/removed items and per-item size changes both trigger a recount
|
|
7077
|
+
effect(() => {
|
|
7078
|
+
const items = this.items();
|
|
7079
|
+
this.itemsResizeObserver.disconnect();
|
|
7080
|
+
items.forEach((item) => this.itemsResizeObserver.observe(item.nativeElement));
|
|
7081
|
+
});
|
|
6952
7082
|
// listens to the count output and toggles the visibility of the items
|
|
6953
7083
|
this.countSub = this.count.subscribe((count) => this.toggleToCount(count));
|
|
6954
7084
|
this.destroyRef.onDestroy(() => {
|
|
6955
7085
|
this.resizeObserver.disconnect();
|
|
7086
|
+
this.itemsResizeObserver.disconnect();
|
|
6956
7087
|
this.countSub?.unsubscribe();
|
|
6957
7088
|
this.countSub = undefined;
|
|
6958
7089
|
});
|
|
6959
7090
|
}
|
|
6960
7091
|
/**
|
|
6961
|
-
* Counts the number of items that can fit
|
|
7092
|
+
* Counts the number of items that can fit inside the container.
|
|
7093
|
+
*
|
|
7094
|
+
* The boundary is the container's own content edge (not the position of the
|
|
7095
|
+
* stop element). This is what makes the measurement robust when the host is
|
|
7096
|
+
* placed inside a flex layout: we never rely on a sibling staying where we
|
|
7097
|
+
* expect it. The stop element is only used for its *size*, to reserve space
|
|
7098
|
+
* for the "more" trigger when not all items fit.
|
|
7099
|
+
*
|
|
6962
7100
|
* Emits the count if it has changed.
|
|
6963
7101
|
*/
|
|
6964
7102
|
countItems() {
|
|
6965
7103
|
if (!this.items() || this.items().length === 0 || !this.stop())
|
|
6966
7104
|
return;
|
|
7105
|
+
const horizontal = this.direction() === "horizontal";
|
|
6967
7106
|
// Reset all items to their natural size before measuring so that previously
|
|
6968
7107
|
// hidden items (display: none) don't corrupt the layout and position of
|
|
6969
7108
|
// their siblings.
|
|
@@ -6971,19 +7110,33 @@ class OverflowManagerDirective {
|
|
|
6971
7110
|
item.nativeElement.style.display = "";
|
|
6972
7111
|
});
|
|
6973
7112
|
// getBoundingClientRect() forces a synchronous reflow, so positions are
|
|
6974
|
-
// accurate after the reset above.
|
|
7113
|
+
// accurate after the reset above. Using rects (not offsetWidth) means the
|
|
7114
|
+
// inter-item gap is accounted for automatically.
|
|
7115
|
+
const containerRect = this.el.getBoundingClientRect();
|
|
6975
7116
|
const stopRect = this.stop().nativeElement.getBoundingClientRect();
|
|
6976
7117
|
const itemsRects = this.items().map((item) => item.nativeElement.getBoundingClientRect());
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
7118
|
+
// The container's content edge, with a small slack margin.
|
|
7119
|
+
const containerEnd = (horizontal ? containerRect.right : containerRect.bottom) - this.margin();
|
|
7120
|
+
const itemEnd = (rect) => (horizontal ? rect.right : rect.bottom);
|
|
7121
|
+
let count;
|
|
7122
|
+
// If every item fits within the container, no "more" trigger is needed and
|
|
7123
|
+
// we don't reserve any space for it — unless the trigger is permanently
|
|
7124
|
+
// visible (reserveStop), in which case its space is always reserved.
|
|
7125
|
+
if (!this.reserveStop() && itemEnd(itemsRects[itemsRects.length - 1]) <= containerEnd) {
|
|
7126
|
+
count = itemsRects.length;
|
|
7127
|
+
}
|
|
7128
|
+
else {
|
|
7129
|
+
// Otherwise reserve the stop element's own size so the last visible item
|
|
7130
|
+
// never overlaps the "more" trigger.
|
|
7131
|
+
const reserve = horizontal ? stopRect.width : stopRect.height;
|
|
7132
|
+
const limit = containerEnd - reserve;
|
|
7133
|
+
count = 0;
|
|
7134
|
+
for (const rect of itemsRects) {
|
|
7135
|
+
if (itemEnd(rect) <= limit)
|
|
7136
|
+
count++;
|
|
7137
|
+
else
|
|
7138
|
+
break;
|
|
7139
|
+
}
|
|
6987
7140
|
}
|
|
6988
7141
|
if (this._lastCount !== count) {
|
|
6989
7142
|
this._lastCount = count;
|
|
@@ -7006,7 +7159,7 @@ class OverflowManagerDirective {
|
|
|
7006
7159
|
});
|
|
7007
7160
|
}
|
|
7008
7161
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverflowManagerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
7009
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.18", type: OverflowManagerDirective, isStandalone: true, selector: "[overflowManager]", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { count: "count" }, queries: [{ propertyName: "items", predicate: OverflowItemDirective, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "stop", first: true, predicate: OverflowStopDirective, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0 });
|
|
7162
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.18", type: OverflowManagerDirective, isStandalone: true, selector: "[overflowManager]", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, reserveStop: { classPropertyName: "reserveStop", publicName: "reserveStop", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { count: "count" }, queries: [{ propertyName: "items", predicate: OverflowItemDirective, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "stop", first: true, predicate: OverflowStopDirective, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0 });
|
|
7010
7163
|
}
|
|
7011
7164
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverflowManagerDirective, decorators: [{
|
|
7012
7165
|
type: Directive,
|
|
@@ -7014,7 +7167,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7014
7167
|
selector: "[overflowManager]",
|
|
7015
7168
|
standalone: true
|
|
7016
7169
|
}]
|
|
7017
|
-
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => OverflowItemDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], stop: [{ type: i0.ContentChild, args: [i0.forwardRef(() => OverflowStopDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], count: [{ type: i0.Output, args: ["count"] }] } });
|
|
7170
|
+
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => OverflowItemDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], stop: [{ type: i0.ContentChild, args: [i0.forwardRef(() => OverflowStopDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], reserveStop: [{ type: i0.Input, args: [{ isSignal: true, alias: "reserveStop", required: false }] }], count: [{ type: i0.Output, args: ["count"] }] } });
|
|
7018
7171
|
|
|
7019
7172
|
/**
|
|
7020
7173
|
* Directive that selects an article on click.
|
|
@@ -7113,102 +7266,69 @@ class NavbarTabsComponent {
|
|
|
7113
7266
|
cn = cn;
|
|
7114
7267
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
7115
7268
|
router = inject(Router);
|
|
7116
|
-
|
|
7117
|
-
queryParamsStore = inject(QueryParamsStore);
|
|
7118
|
-
// Injecting the QueryService to access last search results
|
|
7119
|
-
queryService = inject(QueryService);
|
|
7269
|
+
showCount = input(true, ...(ngDevMode ? [{ debugName: "showCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
7120
7270
|
/**
|
|
7121
|
-
*
|
|
7122
|
-
*
|
|
7271
|
+
* When enabled (default), tab labels are never clipped: tabs that do not fit
|
|
7272
|
+
* collapse into the ellipsis dropdown instead. Set to `false` to truncate
|
|
7273
|
+
* overflowing labels with an ellipsis inside their slot.
|
|
7123
7274
|
*
|
|
7124
|
-
* @
|
|
7125
|
-
* This works only if tabSearch is enabled in the administration panel.
|
|
7275
|
+
* @default true
|
|
7126
7276
|
*/
|
|
7127
|
-
|
|
7128
|
-
noTruncate = input(false, ...(ngDevMode ? [{ debugName: "noTruncate", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
7277
|
+
noTruncate = input(true, ...(ngDevMode ? [{ debugName: "noTruncate", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
7129
7278
|
minTabWidth = input(...(ngDevMode ? [undefined, { debugName: "minTabWidth" }] : []));
|
|
7279
|
+
path = input("search", ...(ngDevMode ? [{ debugName: "path" }] : []));
|
|
7280
|
+
nav = injectRouteNavigation(this.path, this.showCount);
|
|
7281
|
+
visibleTabCount = signal(undefined, ...(ngDevMode ? [{ debugName: "visibleTabCount" }] : []));
|
|
7282
|
+
moreTabs = computed(() => this.nav.tabs().slice(this.visibleTabCount()), ...(ngDevMode ? [{ debugName: "moreTabs" }] : []));
|
|
7130
7283
|
/**
|
|
7131
|
-
*
|
|
7132
|
-
*
|
|
7284
|
+
* Minimum width applied to a tab (through the `--tab-min-width` CSS variable)
|
|
7285
|
+
* when labels are truncated (`noTruncate` is `false`), so a tab never shrinks
|
|
7286
|
+
* into an unusable sliver. An explicit `minTabWidth` input always wins.
|
|
7133
7287
|
*
|
|
7134
|
-
*
|
|
7135
|
-
*
|
|
7136
|
-
* to populate the tabs in the navbar.
|
|
7288
|
+
* - tab with an icon: the icon (16px) plus the tab's horizontal padding (1.5rem)
|
|
7289
|
+
* - text-only tab: enough room for a few characters and the ellipsis
|
|
7137
7290
|
*/
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
// get current tab from the query params store
|
|
7146
|
-
const { tab: currentTab } = getState(this.queryParamsStore);
|
|
7147
|
-
// navigate to the deepest child route
|
|
7148
|
-
let current = this.route.snapshot;
|
|
7149
|
-
while (current.firstChild) {
|
|
7150
|
-
current = current.firstChild;
|
|
7151
|
-
}
|
|
7152
|
-
// childPath exists when we click on a tab, otherwise it's an empty string
|
|
7153
|
-
const childPath = current.url.map((segment) => segment.path).join("/");
|
|
7154
|
-
// return the child path if present, otherwise the current tab from the query params, or 'all' as a fallback
|
|
7155
|
-
return childPath || currentTab || "all";
|
|
7156
|
-
}, ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
|
|
7157
|
-
// create tabs from the search routes
|
|
7158
|
-
tabs = computed(() => {
|
|
7159
|
-
const result = this.queryService.result();
|
|
7160
|
-
const r = this.router.config
|
|
7161
|
-
.find((item) => item.path === this.path())
|
|
7162
|
-
?.children?.filter((c) => c.path !== "**")
|
|
7163
|
-
.map((child) => {
|
|
7164
|
-
const data = child.data;
|
|
7165
|
-
return {
|
|
7166
|
-
display: data?.display || child.path || "",
|
|
7167
|
-
wsQueryTab: data?.wsQueryTab || child.path || "",
|
|
7168
|
-
path: child.path || "",
|
|
7169
|
-
routerLink: `/${this.path()}/${child.path}`,
|
|
7170
|
-
icon: data?.icon || "",
|
|
7171
|
-
queryName: data?.queryName,
|
|
7172
|
-
// get the count from the last search result if showCount is true and queryName matches
|
|
7173
|
-
count: this.showCount()
|
|
7174
|
-
? result.queryName === data?.queryName
|
|
7175
|
-
? result.tabs?.find((tab) => tab.name === (data?.wsQueryTab || child.path))?.count
|
|
7176
|
-
: undefined
|
|
7177
|
-
: undefined
|
|
7178
|
-
};
|
|
7179
|
-
}) ?? [];
|
|
7180
|
-
return r;
|
|
7181
|
-
}, ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
7182
|
-
moreTabs = computed(() => this.tabs().slice(this.visibleTabCount()), ...(ngDevMode ? [{ debugName: "moreTabs" }] : []));
|
|
7291
|
+
tabMinWidth(hasIcon) {
|
|
7292
|
+
if (this.minTabWidth())
|
|
7293
|
+
return this.minTabWidth();
|
|
7294
|
+
if (this.noTruncate())
|
|
7295
|
+
return undefined;
|
|
7296
|
+
return hasIcon ? "calc(1.5rem + 16px)" : "calc(1.5rem + 4ch)";
|
|
7297
|
+
}
|
|
7183
7298
|
changeTab() { }
|
|
7184
7299
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NavbarTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7185
7300
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: NavbarTabsComponent, isStandalone: true, selector: "navbar-tabs", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null }, noTruncate: { classPropertyName: "noTruncate", publicName: "noTruncate", isSignal: true, isRequired: false, transformFunction: null }, minTabWidth: { classPropertyName: "minTabWidth", publicName: "minTabWidth", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "cn('block', class())" } }, ngImport: i0, template: `
|
|
7186
7301
|
<!-- do not display the tabs if there are no tabs -->
|
|
7187
|
-
@if (tabs().length > 0) {
|
|
7302
|
+
@if (nav.tabs().length > 0) {
|
|
7188
7303
|
<div overflowManager (count)="visibleTabCount.set($event)" class="flex items-end rounded-[inherit] bg-inherit">
|
|
7189
7304
|
<Tabs class="flex-1 min-w-0 overflow-hidden">
|
|
7190
7305
|
<TabsList class="w-full">
|
|
7191
|
-
@for (tab of tabs(); track $index) {
|
|
7306
|
+
@for (tab of nav.tabs(); track $index) {
|
|
7192
7307
|
<Tab
|
|
7193
|
-
class="group"
|
|
7308
|
+
[class]="cn('group', !noTruncate() && 'basis-[12.5rem]')"
|
|
7194
7309
|
title="{{ tab.display | syslang | transloco }}"
|
|
7195
7310
|
value="{{ tab.display }}"
|
|
7196
7311
|
overflowItem
|
|
7197
7312
|
[noTruncate]="noTruncate()"
|
|
7198
|
-
[style.--tab-min-width]="
|
|
7199
|
-
[attr.aria-selected]="
|
|
7313
|
+
[style.--tab-min-width]="tabMinWidth(!!tab.icon)"
|
|
7314
|
+
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
7200
7315
|
[attr.disabled]="showCount() && tab.count === 0 ? '' : null"
|
|
7201
|
-
[active]="
|
|
7316
|
+
[active]="nav.currentPath() === tab.path"
|
|
7202
7317
|
[routerLink]="[tab.routerLink]"
|
|
7203
|
-
[queryParams]="{ n: tab.queryName, q: searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7318
|
+
[queryParams]="{ n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7204
7319
|
(click)="changeTab()"
|
|
7205
|
-
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: { n: tab.queryName, q: searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined } })"
|
|
7320
|
+
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: { n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined } })"
|
|
7206
7321
|
>
|
|
7207
|
-
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && 'overflow-hidden min-w-0')">
|
|
7322
|
+
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
|
|
7208
7323
|
@if (tab.icon) {
|
|
7209
|
-
|
|
7324
|
+
<!-- the icon never shrinks: the label truncates first -->
|
|
7325
|
+
<FaIcon [faClass]="tab.icon" class="shrink-0" aria-hidden="true" />
|
|
7210
7326
|
}
|
|
7211
|
-
|
|
7327
|
+
<!-- when the tab gets too narrow to show anything meaningful next to
|
|
7328
|
+
the icon, hide the label entirely instead of a clipped sliver.
|
|
7329
|
+
w-0 + invisible (not display: none) keeps the label's line box,
|
|
7330
|
+
so the tab height doesn't collapse to the icon's height -->
|
|
7331
|
+
<span [class]="cn(!noTruncate() && 'truncate', !noTruncate() && tab.icon && '@max-[3rem]:w-0 @max-[3rem]:invisible')">{{ tab.display | syslang | transloco }}</span>
|
|
7212
7332
|
<!-- Show count badge only if count is > 0 -->
|
|
7213
7333
|
@if((tab.count ?? 0) > 0) {
|
|
7214
7334
|
<Badge size="sm">{{ tab.count }}</Badge>
|
|
@@ -7235,8 +7355,8 @@ class NavbarTabsComponent {
|
|
|
7235
7355
|
<MenuItem>
|
|
7236
7356
|
<a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
|
|
7237
7357
|
[routerLink]="[tab.routerLink]"
|
|
7238
|
-
[queryParams]="{ n: tab.queryName, q: searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7239
|
-
[attr.aria-selected]="
|
|
7358
|
+
[queryParams]="{ n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7359
|
+
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
7240
7360
|
[attr.aria-label]="tab.display | syslang | transloco"
|
|
7241
7361
|
(click)="changeTab()">
|
|
7242
7362
|
@if (tab.icon) {
|
|
@@ -7250,7 +7370,7 @@ class NavbarTabsComponent {
|
|
|
7250
7370
|
</Menu>
|
|
7251
7371
|
</div>
|
|
7252
7372
|
}
|
|
7253
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: MenuComponent, selector: "menu, Menu", inputs: ["disabled"] }, { kind: "directive", type: MenuItemComponent, selector: "menu-item, menuitem, MenuItem", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: MenuContentComponent, selector: "MenuContent, menucontent, menu-content", inputs: ["class", "position"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "noTruncate", "value", "active"], outputs: ["clicked"] }, { 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: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size"] }, { kind: "component", type: EllipsisIcon, selector: "ellipsis-icon, EllipsisIcon, ellipsisicon", inputs: ["class", "orientation"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
7373
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: MenuComponent, selector: "menu, Menu", inputs: ["disabled"] }, { kind: "directive", type: MenuItemComponent, selector: "menu-item, menuitem, MenuItem", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: MenuContentComponent, selector: "MenuContent, menucontent, menu-content", inputs: ["class", "position"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "noTruncate", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction", "reserveStop"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size"] }, { kind: "component", type: EllipsisIcon, selector: "ellipsis-icon, EllipsisIcon, ellipsisicon", inputs: ["class", "orientation"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
7254
7374
|
}
|
|
7255
7375
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NavbarTabsComponent, decorators: [{
|
|
7256
7376
|
type: Component,
|
|
@@ -7277,31 +7397,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7277
7397
|
],
|
|
7278
7398
|
template: `
|
|
7279
7399
|
<!-- do not display the tabs if there are no tabs -->
|
|
7280
|
-
@if (tabs().length > 0) {
|
|
7400
|
+
@if (nav.tabs().length > 0) {
|
|
7281
7401
|
<div overflowManager (count)="visibleTabCount.set($event)" class="flex items-end rounded-[inherit] bg-inherit">
|
|
7282
7402
|
<Tabs class="flex-1 min-w-0 overflow-hidden">
|
|
7283
7403
|
<TabsList class="w-full">
|
|
7284
|
-
@for (tab of tabs(); track $index) {
|
|
7404
|
+
@for (tab of nav.tabs(); track $index) {
|
|
7285
7405
|
<Tab
|
|
7286
|
-
class="group"
|
|
7406
|
+
[class]="cn('group', !noTruncate() && 'basis-[12.5rem]')"
|
|
7287
7407
|
title="{{ tab.display | syslang | transloco }}"
|
|
7288
7408
|
value="{{ tab.display }}"
|
|
7289
7409
|
overflowItem
|
|
7290
7410
|
[noTruncate]="noTruncate()"
|
|
7291
|
-
[style.--tab-min-width]="
|
|
7292
|
-
[attr.aria-selected]="
|
|
7411
|
+
[style.--tab-min-width]="tabMinWidth(!!tab.icon)"
|
|
7412
|
+
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
7293
7413
|
[attr.disabled]="showCount() && tab.count === 0 ? '' : null"
|
|
7294
|
-
[active]="
|
|
7414
|
+
[active]="nav.currentPath() === tab.path"
|
|
7295
7415
|
[routerLink]="[tab.routerLink]"
|
|
7296
|
-
[queryParams]="{ n: tab.queryName, q: searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7416
|
+
[queryParams]="{ n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7297
7417
|
(click)="changeTab()"
|
|
7298
|
-
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: { n: tab.queryName, q: searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined } })"
|
|
7418
|
+
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: { n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined } })"
|
|
7299
7419
|
>
|
|
7300
|
-
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && 'overflow-hidden min-w-0')">
|
|
7420
|
+
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
|
|
7301
7421
|
@if (tab.icon) {
|
|
7302
|
-
|
|
7422
|
+
<!-- the icon never shrinks: the label truncates first -->
|
|
7423
|
+
<FaIcon [faClass]="tab.icon" class="shrink-0" aria-hidden="true" />
|
|
7303
7424
|
}
|
|
7304
|
-
|
|
7425
|
+
<!-- when the tab gets too narrow to show anything meaningful next to
|
|
7426
|
+
the icon, hide the label entirely instead of a clipped sliver.
|
|
7427
|
+
w-0 + invisible (not display: none) keeps the label's line box,
|
|
7428
|
+
so the tab height doesn't collapse to the icon's height -->
|
|
7429
|
+
<span [class]="cn(!noTruncate() && 'truncate', !noTruncate() && tab.icon && '@max-[3rem]:w-0 @max-[3rem]:invisible')">{{ tab.display | syslang | transloco }}</span>
|
|
7305
7430
|
<!-- Show count badge only if count is > 0 -->
|
|
7306
7431
|
@if((tab.count ?? 0) > 0) {
|
|
7307
7432
|
<Badge size="sm">{{ tab.count }}</Badge>
|
|
@@ -7328,8 +7453,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7328
7453
|
<MenuItem>
|
|
7329
7454
|
<a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
|
|
7330
7455
|
[routerLink]="[tab.routerLink]"
|
|
7331
|
-
[queryParams]="{ n: tab.queryName, q: searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7332
|
-
[attr.aria-selected]="
|
|
7456
|
+
[queryParams]="{ n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }"
|
|
7457
|
+
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
7333
7458
|
[attr.aria-label]="tab.display | syslang | transloco"
|
|
7334
7459
|
(click)="changeTab()">
|
|
7335
7460
|
@if (tab.icon) {
|
|
@@ -7350,6 +7475,73 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7350
7475
|
}]
|
|
7351
7476
|
}], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", required: false }] }], noTruncate: [{ type: i0.Input, args: [{ isSignal: true, alias: "noTruncate", required: false }] }], minTabWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTabWidth", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }] } });
|
|
7352
7477
|
|
|
7478
|
+
class SidebarNavComponent {
|
|
7479
|
+
path = input("search", ...(ngDevMode ? [{ debugName: "path" }] : []));
|
|
7480
|
+
showCount = input(false, ...(ngDevMode ? [{ debugName: "showCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
7481
|
+
nav = injectRouteNavigation(this.path, this.showCount);
|
|
7482
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SidebarNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7483
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: SidebarNavComponent, isStandalone: true, selector: "sidebar-nav", inputs: { path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
7484
|
+
<sidebar-menu>
|
|
7485
|
+
@for (tab of nav.tabs(); track tab.path) {
|
|
7486
|
+
@let label = tab.display | syslang | transloco;
|
|
7487
|
+
<sidebar-menu-item>
|
|
7488
|
+
<sidebar-menu-button
|
|
7489
|
+
[tooltip]="label" tooltip-position="right"
|
|
7490
|
+
[routerLink]="tab.routerLink"
|
|
7491
|
+
routerLinkActive="active"
|
|
7492
|
+
#rla="routerLinkActive"
|
|
7493
|
+
[attr.data-active]="rla.isActive || null"
|
|
7494
|
+
[queryParams]="{ n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }">
|
|
7495
|
+
@if (tab.icon) {
|
|
7496
|
+
<FaIcon [faClass]="tab.icon" aria-hidden="true" />
|
|
7497
|
+
}
|
|
7498
|
+
<span>{{ label }}</span>
|
|
7499
|
+
</sidebar-menu-button>
|
|
7500
|
+
</sidebar-menu-item>
|
|
7501
|
+
}
|
|
7502
|
+
</sidebar-menu>
|
|
7503
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "directive", type: SidebarMenuComponent, selector: "sidebar-menu", inputs: ["class"] }, { kind: "directive", type: SidebarMenuItemComponent, selector: "sidebar-menu-item", inputs: ["class"] }, { kind: "directive", type: SidebarMenuButtonComponent, selector: "sidebar-menu-button", inputs: ["isActive", "variant", "size", "class"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip]", inputs: ["tooltip", "strategy", "tooltip-position", "tooltip-delay", "tooltip-duration", "tooltip-offset"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
7504
|
+
}
|
|
7505
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SidebarNavComponent, decorators: [{
|
|
7506
|
+
type: Component,
|
|
7507
|
+
args: [{
|
|
7508
|
+
selector: "sidebar-nav",
|
|
7509
|
+
standalone: true,
|
|
7510
|
+
imports: [
|
|
7511
|
+
RouterLink,
|
|
7512
|
+
TranslocoPipe,
|
|
7513
|
+
SyslangPipe,
|
|
7514
|
+
FaIconComponent,
|
|
7515
|
+
SidebarMenuComponent,
|
|
7516
|
+
SidebarMenuItemComponent,
|
|
7517
|
+
SidebarMenuButtonComponent,
|
|
7518
|
+
RouterLinkActive,
|
|
7519
|
+
TooltipDirective
|
|
7520
|
+
],
|
|
7521
|
+
template: `
|
|
7522
|
+
<sidebar-menu>
|
|
7523
|
+
@for (tab of nav.tabs(); track tab.path) {
|
|
7524
|
+
@let label = tab.display | syslang | transloco;
|
|
7525
|
+
<sidebar-menu-item>
|
|
7526
|
+
<sidebar-menu-button
|
|
7527
|
+
[tooltip]="label" tooltip-position="right"
|
|
7528
|
+
[routerLink]="tab.routerLink"
|
|
7529
|
+
routerLinkActive="active"
|
|
7530
|
+
#rla="routerLinkActive"
|
|
7531
|
+
[attr.data-active]="rla.isActive || null"
|
|
7532
|
+
[queryParams]="{ n: tab.queryName, q: nav.searchText(), t: tab.wsQueryTab, f: undefined, sort: undefined, id: undefined, page: undefined }">
|
|
7533
|
+
@if (tab.icon) {
|
|
7534
|
+
<FaIcon [faClass]="tab.icon" aria-hidden="true" />
|
|
7535
|
+
}
|
|
7536
|
+
<span>{{ label }}</span>
|
|
7537
|
+
</sidebar-menu-button>
|
|
7538
|
+
</sidebar-menu-item>
|
|
7539
|
+
}
|
|
7540
|
+
</sidebar-menu>
|
|
7541
|
+
`
|
|
7542
|
+
}]
|
|
7543
|
+
}], propDecorators: { path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", required: false }] }] } });
|
|
7544
|
+
|
|
7353
7545
|
class NoResultComponent {
|
|
7354
7546
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NoResultComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7355
7547
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: NoResultComponent, isStandalone: true, selector: "NoResult", host: { classAttribute: "p-4 flex flex-col gap-2 rounded-md" }, providers: [provideTranslocoScope("no-result")], ngImport: i0, template: `
|
|
@@ -7608,21 +7800,21 @@ class SponsoredResultsComponent {
|
|
|
7608
7800
|
const { sponsoredLinks } = getState(this.appStore);
|
|
7609
7801
|
return sponsoredLinks;
|
|
7610
7802
|
}, ...(ngDevMode ? [{ debugName: "sponsoredLinks" }] : []));
|
|
7611
|
-
sponsoredResults =
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
}
|
|
7803
|
+
sponsoredResults = resource({
|
|
7804
|
+
params: () => ({
|
|
7805
|
+
query: this.queryParamStore.getQuery(),
|
|
7806
|
+
links: this.sponsoredLinks(),
|
|
7807
|
+
slice: this.slice(),
|
|
7808
|
+
}),
|
|
7809
|
+
loader: async ({ params: { query, links, slice } }) => {
|
|
7810
|
+
const results = await withFetch(() => fetchSponsoredLinks(links, query), this.injector);
|
|
7811
|
+
return results?.slice(0, slice) ?? [];
|
|
7812
|
+
},
|
|
7813
|
+
defaultValue: [],
|
|
7814
|
+
});
|
|
7623
7815
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SponsoredResultsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7624
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: SponsoredResultsComponent, isStandalone: true, selector: "sponsored-results, SponsoredResults, sponsoredresults", inputs: { slice: { classPropertyName: "slice", publicName: "slice", isSignal: true, isRequired: false, transformFunction: null }, displayPromoted: { classPropertyName: "displayPromoted", publicName: "displayPromoted", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "list" } }, queries: [{ propertyName: "childElement", first: true, predicate: ChildMarkerDirective, descendants: true, isSignal: true }], ngImport: i0, template: ` @if (sponsoredResults()
|
|
7625
|
-
@for (link of sponsoredResults(); track $index) {
|
|
7816
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: SponsoredResultsComponent, isStandalone: true, selector: "sponsored-results, SponsoredResults, sponsoredresults", inputs: { slice: { classPropertyName: "slice", publicName: "slice", isSignal: true, isRequired: false, transformFunction: null }, displayPromoted: { classPropertyName: "displayPromoted", publicName: "displayPromoted", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "list" } }, queries: [{ propertyName: "childElement", first: true, predicate: ChildMarkerDirective, descendants: true, isSignal: true }], ngImport: i0, template: ` @if (sponsoredResults.value().length) {
|
|
7817
|
+
@for (link of sponsoredResults.value(); track $index) {
|
|
7626
7818
|
<li role="listitem" class="text-primary flex items-center gap-2 rounded px-3 py-2 font-bold">
|
|
7627
7819
|
<a href="{{ link.url }}" target="_blank" rel="noopener" title="{{ link.tooltip }}" class="result-link peer flex items-center gap-2 hover:underline">
|
|
7628
7820
|
<arrow-up-right-from-square-icon />
|
|
@@ -7645,8 +7837,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7645
7837
|
selector: 'sponsored-results, SponsoredResults, sponsoredresults',
|
|
7646
7838
|
imports: [NgTemplateOutlet, ArrowUpRightFromSquareIcon],
|
|
7647
7839
|
standalone: true,
|
|
7648
|
-
template: ` @if (sponsoredResults()
|
|
7649
|
-
@for (link of sponsoredResults(); track $index) {
|
|
7840
|
+
template: ` @if (sponsoredResults.value().length) {
|
|
7841
|
+
@for (link of sponsoredResults.value(); track $index) {
|
|
7650
7842
|
<li role="listitem" class="text-primary flex items-center gap-2 rounded px-3 py-2 font-bold">
|
|
7651
7843
|
<a href="{{ link.url }}" target="_blank" rel="noopener" title="{{ link.tooltip }}" class="result-link peer flex items-center gap-2 hover:underline">
|
|
7652
7844
|
<arrow-up-right-from-square-icon />
|
|
@@ -7666,7 +7858,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7666
7858
|
role: 'list'
|
|
7667
7859
|
}
|
|
7668
7860
|
}]
|
|
7669
|
-
}],
|
|
7861
|
+
}], propDecorators: { childElement: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ChildMarkerDirective), { isSignal: true }] }], slice: [{ type: i0.Input, args: [{ isSignal: true, alias: "slice", required: false }] }], displayPromoted: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayPromoted", required: false }] }] } });
|
|
7670
7862
|
|
|
7671
7863
|
class ThemeSelectorComponent {
|
|
7672
7864
|
scope = input.required(...(ngDevMode ? [{ debugName: "scope" }] : []));
|
|
@@ -7905,11 +8097,16 @@ class AdvancedFiltersComponent {
|
|
|
7905
8097
|
}, ...(ngDevMode ? [{ debugName: "allowEmptySearch" }] : []));
|
|
7906
8098
|
text = "";
|
|
7907
8099
|
constructor() {
|
|
7908
|
-
|
|
8100
|
+
effect(() => {
|
|
8101
|
+
getState(this.appStore);
|
|
8102
|
+
const query = this.appStore.getDefaultQuery();
|
|
8103
|
+
if (query?.name) {
|
|
8104
|
+
this.getFirstPageQuery(query?.name);
|
|
8105
|
+
}
|
|
8106
|
+
});
|
|
7909
8107
|
}
|
|
7910
|
-
async getFirstPageQuery() {
|
|
7911
|
-
const
|
|
7912
|
-
const response = await fetchQuery({ isFirstPage: true, name: query.name });
|
|
8108
|
+
async getFirstPageQuery(queryName) {
|
|
8109
|
+
const response = await fetchQuery({ isFirstPage: true, name: queryName });
|
|
7913
8110
|
this.aggregations.set(response.aggregations);
|
|
7914
8111
|
}
|
|
7915
8112
|
onTabChange(tab) {
|
|
@@ -8447,6 +8644,139 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
8447
8644
|
}]
|
|
8448
8645
|
}], propDecorators: { dialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DialogComponent), { isSignal: true }] }], customRange: [{ type: i0.ViewChild, args: ['customRange', { isSignal: true }] }], dualPickers: [{ type: i0.ViewChild, args: ['dualPickers', { isSignal: true }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], lang: [{ type: i0.Input, args: [{ isSignal: true, alias: "lang", required: false }] }], useDateRange: [{ type: i0.Input, args: [{ isSignal: true, alias: "useDateRange", required: false }] }], rangeSelected: [{ type: i0.Output, args: ["rangeSelected"] }] } });
|
|
8449
8646
|
|
|
8647
|
+
function injectAggregationBase(refs) {
|
|
8648
|
+
const aggregationsStore = inject(AggregationsStore);
|
|
8649
|
+
const queryParamsStore = inject(QueryParamsStore);
|
|
8650
|
+
const appStore = inject(AppStore);
|
|
8651
|
+
const aggregationsService = inject(AggregationsService);
|
|
8652
|
+
const injector = inject(Injector);
|
|
8653
|
+
const destroyRef = inject(DestroyRef);
|
|
8654
|
+
const debouncedSearchText = debouncedSignal(refs.searchText, 300);
|
|
8655
|
+
const normalizedSearchText = computed(() => debouncedSearchText()
|
|
8656
|
+
.normalize("NFD")
|
|
8657
|
+
.replace(/[̀-ͯ]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
|
|
8658
|
+
const suggests = signal([], ...(ngDevMode ? [{ debugName: "suggests" }] : []));
|
|
8659
|
+
const _filterCount = computed(() => {
|
|
8660
|
+
getState(aggregationsStore);
|
|
8661
|
+
const { count = 0 } = queryParamsStore.getFilter({ field: refs.column() ?? undefined, name: refs.name() ?? undefined }) || {};
|
|
8662
|
+
return count;
|
|
8663
|
+
}, ...(ngDevMode ? [{ debugName: "_filterCount" }] : []));
|
|
8664
|
+
const hasFilters = computed(() => _filterCount() > 0, ...(ngDevMode ? [{ debugName: "hasFilters" }] : []));
|
|
8665
|
+
const filtersCount = _filterCount;
|
|
8666
|
+
const query = buildQuery();
|
|
8667
|
+
const filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
8668
|
+
if (refs.searchInput && refs.expanded) {
|
|
8669
|
+
effect(() => {
|
|
8670
|
+
if (refs.searchInput()?.nativeElement && refs.expanded() !== null) {
|
|
8671
|
+
setTimeout(() => refs.searchInput()?.nativeElement.focus(), 0);
|
|
8672
|
+
}
|
|
8673
|
+
});
|
|
8674
|
+
}
|
|
8675
|
+
// Track both debounced text and column so a column change also resets suggestions.
|
|
8676
|
+
// switchMap cancels the in-flight fetch when a new value arrives, avoiding race conditions.
|
|
8677
|
+
const searchTrigger = computed(() => ({ text: debouncedSearchText(), column: refs.column() }), ...(ngDevMode ? [{ debugName: "searchTrigger" }] : []));
|
|
8678
|
+
toObservable(searchTrigger)
|
|
8679
|
+
.pipe(takeUntilDestroyed(destroyRef), switchMap(({ text, column }) => {
|
|
8680
|
+
if (text === "" || column === null)
|
|
8681
|
+
return of([]);
|
|
8682
|
+
const q = queryParamsStore.getQuery();
|
|
8683
|
+
return from(withFetch(() => fetchSuggestField(normalizedSearchText(), [column || ""], q), injector));
|
|
8684
|
+
}))
|
|
8685
|
+
.subscribe((result) => suggests.set(result || []));
|
|
8686
|
+
function clearSearch(e) {
|
|
8687
|
+
e.stopImmediatePropagation();
|
|
8688
|
+
refs.searchText.set("");
|
|
8689
|
+
}
|
|
8690
|
+
function selectItems(items, selected, recursive = false) {
|
|
8691
|
+
items.forEach((item) => {
|
|
8692
|
+
if (item.count > 0)
|
|
8693
|
+
item.$selected = selected;
|
|
8694
|
+
if (recursive && item.items?.length)
|
|
8695
|
+
selectItems(item.items, selected, true);
|
|
8696
|
+
});
|
|
8697
|
+
}
|
|
8698
|
+
function addCurrentFiltersToItems(aggregation) {
|
|
8699
|
+
const currentItems = aggregation?.items || [];
|
|
8700
|
+
if (!aggregation?.isTree && !aggregation?.isDistribution) {
|
|
8701
|
+
const currentFilters = queryParamsStore.getFilter({ field: aggregation?.column, name: aggregation?.name });
|
|
8702
|
+
if (currentFilters) {
|
|
8703
|
+
if (Array.isArray(currentFilters.filters) && currentFilters.filters.length) {
|
|
8704
|
+
currentFilters.filters.forEach((filter) => {
|
|
8705
|
+
const found = currentItems.find((item) => item.value?.toString().toLocaleLowerCase() === filter.value?.toLocaleLowerCase());
|
|
8706
|
+
if (!found) {
|
|
8707
|
+
currentItems.unshift({ value: filter.value, display: filter.display, $selected: true });
|
|
8708
|
+
}
|
|
8709
|
+
else {
|
|
8710
|
+
found.$selected = true;
|
|
8711
|
+
}
|
|
8712
|
+
});
|
|
8713
|
+
}
|
|
8714
|
+
else if (currentFilters.value) {
|
|
8715
|
+
const found = currentItems.find((item) => item.value?.toString().toLocaleLowerCase() === currentFilters.value?.toLocaleLowerCase());
|
|
8716
|
+
if (!found) {
|
|
8717
|
+
currentItems.push({ value: currentFilters.value, display: currentFilters.display, $selected: true });
|
|
8718
|
+
}
|
|
8719
|
+
else {
|
|
8720
|
+
found.$selected = true;
|
|
8721
|
+
}
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8725
|
+
return currentItems;
|
|
8726
|
+
}
|
|
8727
|
+
function applyFilters(appliedFilters, agg, clearFn) {
|
|
8728
|
+
const { name: aggName, column: field } = agg;
|
|
8729
|
+
if (appliedFilters.length > 1) {
|
|
8730
|
+
const display = appliedFilters[0].display;
|
|
8731
|
+
if (agg.isDistribution) {
|
|
8732
|
+
queryParamsStore.updateFilter({
|
|
8733
|
+
operator: "or",
|
|
8734
|
+
filters: appliedFilters,
|
|
8735
|
+
name: aggName,
|
|
8736
|
+
field,
|
|
8737
|
+
display,
|
|
8738
|
+
});
|
|
8739
|
+
}
|
|
8740
|
+
else {
|
|
8741
|
+
const values = appliedFilters.map((f) => f.value);
|
|
8742
|
+
queryParamsStore.updateFilter({
|
|
8743
|
+
operator: "in",
|
|
8744
|
+
name: aggName,
|
|
8745
|
+
field,
|
|
8746
|
+
values,
|
|
8747
|
+
display,
|
|
8748
|
+
filters: appliedFilters,
|
|
8749
|
+
});
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
else if (appliedFilters.length === 1) {
|
|
8753
|
+
queryParamsStore.updateFilter(appliedFilters[0]);
|
|
8754
|
+
}
|
|
8755
|
+
else {
|
|
8756
|
+
clearFn();
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8759
|
+
return {
|
|
8760
|
+
aggregationsStore,
|
|
8761
|
+
queryParamsStore,
|
|
8762
|
+
appStore,
|
|
8763
|
+
aggregationsService,
|
|
8764
|
+
injector,
|
|
8765
|
+
destroyRef,
|
|
8766
|
+
debouncedSearchText,
|
|
8767
|
+
normalizedSearchText,
|
|
8768
|
+
suggests,
|
|
8769
|
+
hasFilters,
|
|
8770
|
+
filtersCount,
|
|
8771
|
+
query,
|
|
8772
|
+
filters,
|
|
8773
|
+
clearSearch,
|
|
8774
|
+
selectItems,
|
|
8775
|
+
addCurrentFiltersToItems,
|
|
8776
|
+
applyFilters,
|
|
8777
|
+
};
|
|
8778
|
+
}
|
|
8779
|
+
|
|
8450
8780
|
/**
|
|
8451
8781
|
* Injection token that indicates whether custom date ranges are allowed.
|
|
8452
8782
|
*
|
|
@@ -8466,507 +8796,67 @@ const FILTER_DATE_ALLOW_CUSTOM_RANGE = new InjectionToken("date allow custom ran
|
|
|
8466
8796
|
factory: () => true
|
|
8467
8797
|
});
|
|
8468
8798
|
|
|
8469
|
-
|
|
8799
|
+
const options = {
|
|
8800
|
+
year: 'numeric',
|
|
8801
|
+
month: '2-digit',
|
|
8802
|
+
day: '2-digit'
|
|
8803
|
+
};
|
|
8804
|
+
class AggregationDateComponent {
|
|
8470
8805
|
cn = cn;
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8806
|
+
/* view queries */
|
|
8807
|
+
dateRangeDialog = viewChild(AggregationDateRangeDialogComponent, ...(ngDevMode ? [{ debugName: "dateRangeDialog" }] : []));
|
|
8808
|
+
/* inputs */
|
|
8809
|
+
name = input(null, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
8810
|
+
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
8811
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
8812
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
8813
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
8814
|
+
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
8815
|
+
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
8816
|
+
title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
8817
|
+
displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
|
|
8818
|
+
/* outputs */
|
|
8474
8819
|
onSelect = output();
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
searchText =
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8511
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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',\r\n node().$selected && ''\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 @let icon = node().icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((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: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
8512
|
-
}
|
|
8513
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListItemComponent, decorators: [{
|
|
8514
|
-
type: Component,
|
|
8515
|
-
args: [{ selector: "aggregation-list-item, AggregationListItem, aggregationlistitem", standalone: true, imports: [HighlightWordPipe, ListItemComponent, SyslangPipe, TranslocoPipe, FaIconComponent], 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',\r\n node().$selected && ''\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 @let icon = node().icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((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"] }]
|
|
8516
|
-
}], propDecorators: { disabled: [{
|
|
8517
|
-
type: HostBinding,
|
|
8518
|
-
args: ["attr.disabled"]
|
|
8519
|
-
}], 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 }] }] } });
|
|
8520
|
-
|
|
8521
|
-
class AggregationListComponent {
|
|
8522
|
-
cn = cn;
|
|
8523
|
-
cdr = inject(ChangeDetectorRef);
|
|
8524
|
-
/* virtualizer */
|
|
8525
|
-
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
8526
|
-
virtualizer = injectVirtualizer(() => ({
|
|
8527
|
-
count: this.items().length,
|
|
8528
|
-
estimateSize: () => 32,
|
|
8529
|
-
scrollElement: this.scrollElement()
|
|
8530
|
-
}));
|
|
8531
|
-
searchInput = viewChild("searchInput", ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
8532
|
-
/* stores */
|
|
8533
|
-
aggregationsStore = inject(AggregationsStore);
|
|
8534
|
-
queryParamsStore = inject(QueryParamsStore);
|
|
8535
|
-
appStore = inject(AppStore);
|
|
8536
|
-
/* services */
|
|
8537
|
-
aggregationsService = inject(AggregationsService);
|
|
8538
|
-
el = inject(ElementRef);
|
|
8539
|
-
injector = inject(Injector);
|
|
8540
|
-
destroyRef = inject(DestroyRef);
|
|
8541
|
-
class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
8542
|
-
/**
|
|
8543
|
-
* The name of the <details> element. When you provide the same id, the component work as an accordion
|
|
8544
|
-
* @defaultValue null
|
|
8545
|
-
*/
|
|
8546
|
-
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
8547
|
-
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
8548
|
-
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
8549
|
-
onSelect = output();
|
|
8550
|
-
onApply = output();
|
|
8551
|
-
onClear = output();
|
|
8552
|
-
/**
|
|
8553
|
-
* Determines whether the aggregation component can be collapsed or expanded.
|
|
8554
|
-
* When true, the component will display collapse/expand controls allowing users
|
|
8555
|
-
* to show or hide the aggregation content.
|
|
8556
|
-
*
|
|
8557
|
-
* @default false
|
|
8558
|
-
*/
|
|
8559
|
-
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
8560
|
-
/**
|
|
8561
|
-
* Controls whether the aggregation component is in a collapsed state.
|
|
8562
|
-
* When true, the component will be visually collapsed/hidden.
|
|
8563
|
-
* When false, the component will be expanded/visible.
|
|
8564
|
-
*
|
|
8565
|
-
* @default false
|
|
8566
|
-
*/
|
|
8567
|
-
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
8568
|
-
/**
|
|
8569
|
-
* A computed signal that tracks the collapsed state of the component.
|
|
8570
|
-
* This signal is linked to the `collapsed()` signal and automatically updates
|
|
8571
|
-
* when the collapsed state changes.
|
|
8572
|
-
*/
|
|
8573
|
-
isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
|
|
8574
|
-
/**
|
|
8575
|
-
* Computed property that returns an empty string when the component is not collapsed,
|
|
8576
|
-
* or null when the component is collapsed. This is typically used to control
|
|
8577
|
-
* expansion state in UI components with conditional rendering or styling.
|
|
8578
|
-
*
|
|
8579
|
-
* @returns empty string if not collapsed, null if collapsed
|
|
8580
|
-
*/
|
|
8581
|
-
expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
8582
|
-
/**
|
|
8583
|
-
* A boolean flag indicating whether the component is searchable.
|
|
8584
|
-
* This property is initialized to `undefined` by default.
|
|
8585
|
-
* "Undefined" and not "false" because this input overrides the custom json settings
|
|
8586
|
-
*/
|
|
8587
|
-
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
8588
|
-
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
8589
|
-
/**
|
|
8590
|
-
* A boolean flag indicating whether we want to see the filters count when some is applied
|
|
8591
|
-
* This property is initialized to `false` by default.
|
|
8592
|
-
*/
|
|
8593
|
-
showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
8594
|
-
/* aggregation */
|
|
8595
|
-
aggregation = computed(() => {
|
|
8596
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
8597
|
-
getState(this.aggregationsStore);
|
|
8598
|
-
const name = this.name();
|
|
8599
|
-
const column = this.column();
|
|
8600
|
-
if (name !== null) {
|
|
8601
|
-
const agg = this.aggregationsService.processAggregation(name, column);
|
|
8602
|
-
if (agg) {
|
|
8603
|
-
if (agg.isTree) {
|
|
8604
|
-
error("The aggregation component does not support tree aggregations. Please use the <AggregationTree /> component instead.");
|
|
8605
|
-
}
|
|
8606
|
-
// overrides "searchable" properties with the input if any
|
|
8607
|
-
agg.searchable = this.searchable() ?? agg.searchable;
|
|
8608
|
-
return agg;
|
|
8609
|
-
}
|
|
8820
|
+
onApply = output();
|
|
8821
|
+
onClear = output();
|
|
8822
|
+
/* collapse state */
|
|
8823
|
+
isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
|
|
8824
|
+
expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
8825
|
+
/* search state — unused by date component but required by AggregationBaseRefs */
|
|
8826
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
8827
|
+
searchInput = signal(undefined, ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
8828
|
+
/* composable — injects stores/services, wires search effects, provides shared methods */
|
|
8829
|
+
base = injectAggregationBase({
|
|
8830
|
+
name: this.name,
|
|
8831
|
+
column: this.column,
|
|
8832
|
+
searchText: this.searchText,
|
|
8833
|
+
searchInput: this.searchInput,
|
|
8834
|
+
expanded: this.expanded,
|
|
8835
|
+
});
|
|
8836
|
+
/* spread from base */
|
|
8837
|
+
aggregationsService = this.base.aggregationsService;
|
|
8838
|
+
queryParamsStore = this.base.queryParamsStore;
|
|
8839
|
+
hasFilters = this.base.hasFilters;
|
|
8840
|
+
destroyRef = this.base.destroyRef;
|
|
8841
|
+
/* injected services */
|
|
8842
|
+
allowCustomRange = inject(FILTER_DATE_ALLOW_CUSTOM_RANGE);
|
|
8843
|
+
transloco = inject(TranslocoService);
|
|
8844
|
+
/* state */
|
|
8845
|
+
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
8846
|
+
validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
|
|
8847
|
+
aggregation = computed(() => {
|
|
8848
|
+
const name = this.name();
|
|
8849
|
+
if (name !== null) {
|
|
8850
|
+
const agg = this.aggregationsService.processAggregation(name, this.column());
|
|
8851
|
+
return {
|
|
8852
|
+
...agg,
|
|
8853
|
+
items: agg?.items?.filter((item) => item.display !== "custom-range") ?? []
|
|
8854
|
+
};
|
|
8610
8855
|
}
|
|
8611
8856
|
return null;
|
|
8612
8857
|
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
8613
|
-
|
|
8614
|
-
items = computed(() => {
|
|
8615
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
8616
|
-
getState(this.aggregationsStore);
|
|
8617
|
-
const agg = this.aggregation();
|
|
8618
|
-
const searchedItems = this.searchedItems();
|
|
8619
|
-
if (searchedItems.length > 0) {
|
|
8620
|
-
return searchedItems;
|
|
8621
|
-
}
|
|
8622
|
-
else if (agg?.items) {
|
|
8623
|
-
const res = this.addCurrentFiltersToItems();
|
|
8624
|
-
return res;
|
|
8625
|
-
}
|
|
8626
|
-
return [];
|
|
8627
|
-
}, ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
8628
|
-
/**
|
|
8629
|
-
* Computed signal that determines whether the items collection is empty.
|
|
8630
|
-
* @returns True if the items array has no elements, false otherwise.
|
|
8631
|
-
*/
|
|
8632
|
-
isEmpty = computed(() => this.items().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
8633
|
-
/**
|
|
8634
|
-
* A computed property that determines whether there are active filters
|
|
8635
|
-
* for the current aggregation column.
|
|
8636
|
-
*
|
|
8637
|
-
* if True, the clear button is shown.
|
|
8638
|
-
*
|
|
8639
|
-
* @returns {boolean} `true` if the filter count for the aggregation column is greater than 0, otherwise `false`.
|
|
8640
|
-
*/
|
|
8641
|
-
hasFilters = computed(() => {
|
|
8642
|
-
const { count = 0 } = this.queryParamsStore.getFilter({ field: this.aggregation()?.column, name: this.aggregation()?.name }) || {};
|
|
8643
|
-
return count > 0;
|
|
8644
|
-
}, ...(ngDevMode ? [{ debugName: "hasFilters" }] : []));
|
|
8645
|
-
/**
|
|
8646
|
-
* A computed property that returns the number of items of this aggregation applied in the active filters
|
|
8647
|
-
*
|
|
8648
|
-
* if more than 0 and the showCount input is set as True, the count number is shown.
|
|
8649
|
-
*
|
|
8650
|
-
* @returns {number} the filters count.
|
|
8651
|
-
*/
|
|
8652
|
-
filtersCount = computed(() => {
|
|
8653
|
-
const { count = 0 } = this.queryParamsStore.getFilter({ field: this.aggregation()?.column, name: this.aggregation()?.name }) || {};
|
|
8654
|
-
return count;
|
|
8655
|
-
}, ...(ngDevMode ? [{ debugName: "filtersCount" }] : []));
|
|
8656
|
-
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
8657
|
-
/* search feature */
|
|
8658
|
-
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
8659
|
-
debouncedSearchText = debouncedSignal(this.searchText, 300);
|
|
8660
|
-
normalizedSearchText = computed(() => this.debouncedSearchText()
|
|
8661
|
-
.normalize("NFD")
|
|
8662
|
-
.replace(/[\u0300-\u036f]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
|
|
8663
|
-
/* suggestions */
|
|
8664
|
-
suggests = signal([], ...(ngDevMode ? [{ debugName: "suggests" }] : []));
|
|
8665
|
-
/* searched items */
|
|
8666
|
-
searchedItems = computed(() => {
|
|
8667
|
-
if (!this.suggests())
|
|
8668
|
-
return [];
|
|
8669
|
-
// if the aggregation is not a tree, we return the suggestions as is
|
|
8670
|
-
return this.suggests()?.map(suggest => {
|
|
8671
|
-
const column = this.appStore.getColumn(suggest.category);
|
|
8672
|
-
const item = {
|
|
8673
|
-
name: this.name(),
|
|
8674
|
-
value: suggest.normalized || suggest.display || "",
|
|
8675
|
-
display: suggest.display,
|
|
8676
|
-
column: column?.name ?? suggest.category,
|
|
8677
|
-
count: Number(suggest.frequency),
|
|
8678
|
-
$selected: false
|
|
8679
|
-
};
|
|
8680
|
-
// if the column is of type boolean, we need to convert the value to a boolean
|
|
8681
|
-
if (column?.eType === EngineType.bool) {
|
|
8682
|
-
item.value = Boolean(item.value);
|
|
8683
|
-
}
|
|
8684
|
-
return item;
|
|
8685
|
-
});
|
|
8686
|
-
}, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
|
|
8687
|
-
query;
|
|
8688
|
-
filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
8689
|
-
constructor() {
|
|
8690
|
-
this.query = buildQuery();
|
|
8691
|
-
effect(() => {
|
|
8692
|
-
// focus the search input when expanded
|
|
8693
|
-
if (this.searchInput()?.nativeElement && this.expanded() !== null) {
|
|
8694
|
-
setTimeout(() => {
|
|
8695
|
-
this.searchInput()?.nativeElement.focus();
|
|
8696
|
-
}, 0);
|
|
8697
|
-
}
|
|
8698
|
-
});
|
|
8699
|
-
effect(async () => {
|
|
8700
|
-
if (this.debouncedSearchText() === "" || this.aggregation() === null) {
|
|
8701
|
-
this.suggests.set([]);
|
|
8702
|
-
return;
|
|
8703
|
-
}
|
|
8704
|
-
const query = this.queryParamsStore.getQuery();
|
|
8705
|
-
const suggests = (await withFetch(() => fetchSuggestField(this.normalizedSearchText(), [this.aggregation()?.column || ""], query), this.injector)) || [];
|
|
8706
|
-
this.suggests.set(suggests);
|
|
8707
|
-
});
|
|
8708
|
-
this.destroyRef.onDestroy(() => {
|
|
8709
|
-
// If the popover is closed with unapplied selections, reset $selected to undefined
|
|
8710
|
-
// so that processAggregation can recompute it from active filters on next open
|
|
8711
|
-
if (this.selection()) {
|
|
8712
|
-
this.aggregation()?.items?.forEach(item => {
|
|
8713
|
-
item.$selected = undefined;
|
|
8714
|
-
});
|
|
8715
|
-
}
|
|
8716
|
-
});
|
|
8717
|
-
}
|
|
8718
|
-
/**
|
|
8719
|
-
* Clears the current filter for the aggregation column.
|
|
8720
|
-
*
|
|
8721
|
-
* This method updates the filter in the `queryParamsStore` by setting the display value
|
|
8722
|
-
* of the current aggregation column to an empty string.
|
|
8723
|
-
*/
|
|
8724
|
-
clear() {
|
|
8725
|
-
const agg = this.aggregation();
|
|
8726
|
-
if (agg) {
|
|
8727
|
-
this.queryParamsStore.removeFilterByName(agg.name, agg.column);
|
|
8728
|
-
this.selection.set(false);
|
|
8729
|
-
this.isAllSelected.set(false);
|
|
8730
|
-
}
|
|
8731
|
-
this.onSelect.emit([]);
|
|
8732
|
-
this.onClear.emit();
|
|
8733
|
-
}
|
|
8734
|
-
/**
|
|
8735
|
-
* Select all filters for the aggregation column.
|
|
8736
|
-
*/
|
|
8737
|
-
selectAll() {
|
|
8738
|
-
if (this.items().length) {
|
|
8739
|
-
this.selectItems(this.items(), true);
|
|
8740
|
-
this.selection.set(true);
|
|
8741
|
-
this.isAllSelected.set(true);
|
|
8742
|
-
}
|
|
8743
|
-
}
|
|
8744
|
-
/**
|
|
8745
|
-
* Unselect all filters for the aggregation column.
|
|
8746
|
-
*/
|
|
8747
|
-
unselectAll() {
|
|
8748
|
-
if (this.items().length) {
|
|
8749
|
-
this.selectItems(this.items(), false);
|
|
8750
|
-
this.select();
|
|
8751
|
-
this.isAllSelected.set(false);
|
|
8752
|
-
}
|
|
8753
|
-
}
|
|
8754
|
-
/**
|
|
8755
|
-
* Applies the current filters to the query parameters store.
|
|
8756
|
-
*
|
|
8757
|
-
* - If there are multiple filters, they are wrapped in an "or" filter.
|
|
8758
|
-
* - If the aggregation is not a distribution, the filters are merged into a single filter with an "in" operator.
|
|
8759
|
-
* - If there is only one filter, it is directly applied.
|
|
8760
|
-
* - If there are no filters, the current filters are cleared.
|
|
8761
|
-
*
|
|
8762
|
-
* After applying the filters, the search text is reset.
|
|
8763
|
-
*/
|
|
8764
|
-
apply() {
|
|
8765
|
-
const filters = this.getFilters();
|
|
8766
|
-
const agg = this.aggregation();
|
|
8767
|
-
if (!agg)
|
|
8768
|
-
return;
|
|
8769
|
-
const { name, column: field } = agg;
|
|
8770
|
-
// if filters length > 1, we need to wrap them in an "or" filter
|
|
8771
|
-
if (filters.length > 1) {
|
|
8772
|
-
const display = filters[0].display;
|
|
8773
|
-
// if aggregation not a distribution, we need to merge the filters into a single filter with an in operator
|
|
8774
|
-
// with the values of the filters
|
|
8775
|
-
if (this.aggregation()?.isDistribution) {
|
|
8776
|
-
this.queryParamsStore.updateFilter({
|
|
8777
|
-
operator: "or",
|
|
8778
|
-
filters,
|
|
8779
|
-
name,
|
|
8780
|
-
field,
|
|
8781
|
-
display
|
|
8782
|
-
});
|
|
8783
|
-
}
|
|
8784
|
-
else {
|
|
8785
|
-
const values = filters.map(filter => filter.value);
|
|
8786
|
-
this.queryParamsStore.updateFilter({
|
|
8787
|
-
operator: "in",
|
|
8788
|
-
name,
|
|
8789
|
-
field,
|
|
8790
|
-
values,
|
|
8791
|
-
display,
|
|
8792
|
-
filters
|
|
8793
|
-
});
|
|
8794
|
-
}
|
|
8795
|
-
}
|
|
8796
|
-
else if (filters.length === 1) {
|
|
8797
|
-
this.queryParamsStore.updateFilter(filters[0]);
|
|
8798
|
-
}
|
|
8799
|
-
else {
|
|
8800
|
-
this.clear();
|
|
8801
|
-
}
|
|
8802
|
-
this.searchText.set("");
|
|
8803
|
-
this.selection.set(false);
|
|
8804
|
-
this.onApply.emit();
|
|
8805
|
-
}
|
|
8806
|
-
loadMore() {
|
|
8807
|
-
const q = this.queryParamsStore.getQuery();
|
|
8808
|
-
this.aggregationsService.loadMore(q, this.aggregation()).subscribe(aggregation => {
|
|
8809
|
-
this.aggregationsStore.updateAggregation(aggregation);
|
|
8810
|
-
this.cdr.detectChanges();
|
|
8811
|
-
});
|
|
8812
|
-
}
|
|
8813
|
-
/**
|
|
8814
|
-
* Updates the selected state of the given item in the aggregation list.
|
|
8815
|
-
*
|
|
8816
|
-
* @param item - The item to be selected or deselected.
|
|
8817
|
-
*
|
|
8818
|
-
* This method iterates through the items in the aggregation list and updates
|
|
8819
|
-
* the `$selected` property of the item that matches the value of the given item.
|
|
8820
|
-
*
|
|
8821
|
-
* If the item is selected, the selection count is incremented by 1.
|
|
8822
|
-
* If the item is deselected, the selection count is decremented by 1.
|
|
8823
|
-
*/
|
|
8824
|
-
select() {
|
|
8825
|
-
const selectedItems = this.items().filter(item => item.$selected);
|
|
8826
|
-
this.onSelect.emit(selectedItems);
|
|
8827
|
-
// Keep apply visible if items are selected, or if active filters exist (user may be deselecting to clear)
|
|
8828
|
-
this.selection.set(selectedItems.length > 0 || this.hasFilters());
|
|
8829
|
-
}
|
|
8830
|
-
/**
|
|
8831
|
-
* Updates the collapsed status on header click if the component is collapsible.
|
|
8832
|
-
*/
|
|
8833
|
-
onHeaderClick(event) {
|
|
8834
|
-
event.preventDefault();
|
|
8835
|
-
const isDate = this.aggregationsService.appStore.isDateColumn(this.aggregation()?.column || "");
|
|
8836
|
-
// prevent header click if no items are present
|
|
8837
|
-
if (!isDate && this.isEmpty()) {
|
|
8838
|
-
return;
|
|
8839
|
-
}
|
|
8840
|
-
if (this.collapsible()) {
|
|
8841
|
-
this.isCollapsed.update(value => !value);
|
|
8842
|
-
}
|
|
8843
|
-
}
|
|
8844
|
-
/**
|
|
8845
|
-
* Retrieves a list of filters based on the selected items.
|
|
8846
|
-
*
|
|
8847
|
-
* This method filters the items to include only those that are selected,
|
|
8848
|
-
* and then maps each selected item to a filter using the `toFilter` method.
|
|
8849
|
-
*
|
|
8850
|
-
* @returns {LegacyFilter[]} An array of filters corresponding to the selected items.
|
|
8851
|
-
*/
|
|
8852
|
-
getFilters() {
|
|
8853
|
-
const items = this.addCurrentFiltersToItems().filter(item => item.$selected);
|
|
8854
|
-
const searchedItems = this.searchedItems().filter(item => item.$selected);
|
|
8855
|
-
const currentItems = [...items, ...searchedItems];
|
|
8856
|
-
const { column, name, isDistribution = false } = this.aggregation() || {};
|
|
8857
|
-
const selectedItems = currentItems.map(item => this.aggregationsService.toFilter(item, column, name, isDistribution));
|
|
8858
|
-
return selectedItems;
|
|
8859
|
-
}
|
|
8860
|
-
addCurrentFiltersToItems() {
|
|
8861
|
-
const aggItems = (this.aggregation()?.items) || [];
|
|
8862
|
-
// add the current filters to the current items only if they are not already present
|
|
8863
|
-
if (!this.aggregation()?.isTree && (!this.aggregation()?.isDistribution || this.aggregation()?.isDistribution === false)) {
|
|
8864
|
-
// get all active filters for the current aggregation/column
|
|
8865
|
-
const activeFilters = this.queryParamsStore.getFilter({
|
|
8866
|
-
field: this.aggregation()?.column,
|
|
8867
|
-
name: this.aggregation()?.name
|
|
8868
|
-
});
|
|
8869
|
-
// if there are active filters, we need to add them to the current items
|
|
8870
|
-
if (activeFilters) {
|
|
8871
|
-
// multiples filters
|
|
8872
|
-
if (activeFilters.filters) {
|
|
8873
|
-
activeFilters.filters.forEach((filter) => {
|
|
8874
|
-
// check if the filter is already present in the current items
|
|
8875
|
-
// if not, add it to the current items
|
|
8876
|
-
const found = aggItems.find(item => item.value && item.value.toLocaleString().toLocaleLowerCase() === filter.value?.toLocaleLowerCase());
|
|
8877
|
-
if (!found) {
|
|
8878
|
-
// add it to the current items
|
|
8879
|
-
aggItems.unshift({
|
|
8880
|
-
value: filter.value,
|
|
8881
|
-
display: filter.display,
|
|
8882
|
-
$selected: true
|
|
8883
|
-
});
|
|
8884
|
-
}
|
|
8885
|
-
});
|
|
8886
|
-
}
|
|
8887
|
-
else {
|
|
8888
|
-
// single filter
|
|
8889
|
-
const found = aggItems.find(item => item.value?.toString().toLocaleLowerCase() === activeFilters.value?.toLocaleLowerCase());
|
|
8890
|
-
if (!found) {
|
|
8891
|
-
// add it to the current items
|
|
8892
|
-
aggItems.push({
|
|
8893
|
-
value: activeFilters.value,
|
|
8894
|
-
display: activeFilters.display,
|
|
8895
|
-
$selected: true
|
|
8896
|
-
});
|
|
8897
|
-
}
|
|
8898
|
-
}
|
|
8899
|
-
}
|
|
8900
|
-
}
|
|
8901
|
-
return aggItems;
|
|
8902
|
-
}
|
|
8903
|
-
/**
|
|
8904
|
-
* Update the $selected property to the selected parameter to all items
|
|
8905
|
-
*
|
|
8906
|
-
* @param items the items to apply to
|
|
8907
|
-
* @param selected the selected status
|
|
8908
|
-
*/
|
|
8909
|
-
selectItems(items, selected) {
|
|
8910
|
-
items.forEach(item => {
|
|
8911
|
-
// don't select disabled items
|
|
8912
|
-
if (item.count > 0) {
|
|
8913
|
-
item.$selected = selected;
|
|
8914
|
-
}
|
|
8915
|
-
});
|
|
8916
|
-
}
|
|
8917
|
-
onToggle(event) {
|
|
8918
|
-
const e = event;
|
|
8919
|
-
this.isCollapsed.set(e.newState === "closed");
|
|
8920
|
-
}
|
|
8921
|
-
clearSearch(e) {
|
|
8922
|
-
e.stopImmediatePropagation();
|
|
8923
|
-
this.searchText.set("");
|
|
8924
|
-
}
|
|
8925
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8926
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }, { 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 <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <AggregationTree /> 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 @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow 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\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n @if (hasFilters()) {\r\n @let label = \"filters.clearFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <filter-x-icon />\r\n </button>\r\n }\r\n @if (selection()) {\r\n @let label = \"filters.apply\" | transloco;\r\n <button\r\n variant=\"accent\"\r\n size=\"sm\"\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); apply()\">\r\n <FilterIcon />\r\n {{ label }}\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n @let label = \"filters.unselectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <square-check-icon />\r\n </button>\r\n } @else {\r\n @let label = \"filters.selectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <square-icon />\r\n </button>\r\n }\r\n }\r\n\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </icon-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 <InputGroupAddon align=\"inline-end\" class=\"gap-0.5!\">\r\n <icon-button\r\n size=\"sm\"\r\n [class]=\"\r\n searchText().length > 0\r\n ? 'rotate-90 cursor-pointer opacity-100 transition-[rotate,opacity] duration-500'\r\n : 'pointer-events-none rotate-0 opacity-0 transition-[rotate,opacity] duration-500'\r\n \"\r\n aria-label=\"Clear search\"\r\n [tabindex]=\"searchText().length > 0 ? 0 : -1\"\r\n (keydown.enter)=\"clearSearch($event)\"\r\n (click)=\"clearSearch($event)\">\r\n <XMarkIcon />\r\n </icon-button>\r\n <ng-content />\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 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", "scheme", "iconOnly", "size", "solid"] }, { 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", "scheme", "size"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", 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: "component", type: SearchIcon, selector: "SearchIcon", inputs: ["class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "component", type: SquareCheckIcon, selector: "square-check-icon, SquareCheckIcon", inputs: ["class"] }, { kind: "component", type: SquareIcon, selector: "square-icon, SquareIcon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "component", type: XMarkIcon, selector: "XMarkIcon, xmark-icon, x-mark-icon", inputs: ["class"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
8927
|
-
}
|
|
8928
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListComponent, decorators: [{
|
|
8929
|
-
type: Component,
|
|
8930
|
-
args: [{ selector: "AggregationList, aggregation-list, aggregationlist", imports: [
|
|
8931
|
-
FormsModule,
|
|
8932
|
-
ReactiveFormsModule,
|
|
8933
|
-
ButtonComponent,
|
|
8934
|
-
AggregationListItemComponent,
|
|
8935
|
-
SyslangPipe,
|
|
8936
|
-
TranslocoPipe,
|
|
8937
|
-
BadgeComponent,
|
|
8938
|
-
ChevronRightIcon,
|
|
8939
|
-
InputGroupInput,
|
|
8940
|
-
InputGroupComponent,
|
|
8941
|
-
InputGroupAddonComponent,
|
|
8942
|
-
SearchIcon,
|
|
8943
|
-
FilterIcon,
|
|
8944
|
-
AggregationListItemComponent,
|
|
8945
|
-
FaIconComponent,
|
|
8946
|
-
TriangleAlertIcon,
|
|
8947
|
-
FilterIcon,
|
|
8948
|
-
FilterXIcon,
|
|
8949
|
-
SquareCheckIcon,
|
|
8950
|
-
SquareIcon,
|
|
8951
|
-
IconButtonComponent,
|
|
8952
|
-
XMarkIcon
|
|
8953
|
-
], standalone: true, host: {
|
|
8954
|
-
"[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
|
|
8955
|
-
}, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <AggregationTree /> 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 @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow 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\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n @if (hasFilters()) {\r\n @let label = \"filters.clearFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <filter-x-icon />\r\n </button>\r\n }\r\n @if (selection()) {\r\n @let label = \"filters.apply\" | transloco;\r\n <button\r\n variant=\"accent\"\r\n size=\"sm\"\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); apply()\">\r\n <FilterIcon />\r\n {{ label }}\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n @let label = \"filters.unselectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <square-check-icon />\r\n </button>\r\n } @else {\r\n @let label = \"filters.selectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <square-icon />\r\n </button>\r\n }\r\n }\r\n\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </icon-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 <InputGroupAddon align=\"inline-end\" class=\"gap-0.5!\">\r\n <icon-button\r\n size=\"sm\"\r\n [class]=\"\r\n searchText().length > 0\r\n ? 'rotate-90 cursor-pointer opacity-100 transition-[rotate,opacity] duration-500'\r\n : 'pointer-events-none rotate-0 opacity-0 transition-[rotate,opacity] duration-500'\r\n \"\r\n aria-label=\"Clear search\"\r\n [tabindex]=\"searchText().length > 0 ? 0 : -1\"\r\n (keydown.enter)=\"clearSearch($event)\"\r\n (click)=\"clearSearch($event)\">\r\n <XMarkIcon />\r\n </icon-button>\r\n <ng-content />\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 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"] }]
|
|
8956
|
-
}], 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"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], 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"] }] } });
|
|
8957
|
-
|
|
8958
|
-
const options = {
|
|
8959
|
-
year: 'numeric',
|
|
8960
|
-
month: '2-digit',
|
|
8961
|
-
day: '2-digit'
|
|
8962
|
-
};
|
|
8963
|
-
class AggregationDateComponent extends AggregationListComponent {
|
|
8964
|
-
dateRangeDialog = viewChild(AggregationDateRangeDialogComponent, ...(ngDevMode ? [{ debugName: "dateRangeDialog" }] : []));
|
|
8965
|
-
title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
8966
|
-
displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
|
|
8967
|
-
allowCustomRange = inject(FILTER_DATE_ALLOW_CUSTOM_RANGE);
|
|
8968
|
-
transloco = inject(TranslocoService);
|
|
8969
|
-
name = input(null, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
8858
|
+
isEmpty = computed(() => this.aggregation() === null, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
8859
|
+
items = computed(() => this.aggregation()?.items ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
8970
8860
|
dateOptions = computed(() => translateAggregationToDateOptions(this.aggregation(), this.displayEmptyDistributionIntervals()), ...(ngDevMode ? [{ debugName: "dateOptions" }] : []));
|
|
8971
8861
|
form = new FormGroup({
|
|
8972
8862
|
option: new FormControl(null),
|
|
@@ -8977,7 +8867,6 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
8977
8867
|
});
|
|
8978
8868
|
today = new Date();
|
|
8979
8869
|
lang = signal(this.transloco.getActiveLang(), ...(ngDevMode ? [{ debugName: "lang" }] : []));
|
|
8980
|
-
validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
|
|
8981
8870
|
formValue = toSignal(this.form.valueChanges, { initialValue: this.form.value });
|
|
8982
8871
|
customRangeFrom = computed(() => {
|
|
8983
8872
|
const from = this.formValue().customRange?.from;
|
|
@@ -8988,7 +8877,6 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
8988
8877
|
return to ? new Date(to).toLocaleDateString(this.lang()) : "";
|
|
8989
8878
|
}, ...(ngDevMode ? [{ debugName: "customRangeTo" }] : []));
|
|
8990
8879
|
constructor() {
|
|
8991
|
-
super();
|
|
8992
8880
|
this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
|
|
8993
8881
|
this.lang.set(lang);
|
|
8994
8882
|
});
|
|
@@ -9007,17 +8895,6 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
9007
8895
|
(changes.option !== "custom-range" || changes.customRange?.from !== null || changes.customRange?.to !== null));
|
|
9008
8896
|
});
|
|
9009
8897
|
}
|
|
9010
|
-
aggregation = computed(() => {
|
|
9011
|
-
const name = this.name();
|
|
9012
|
-
if (name !== null) {
|
|
9013
|
-
const agg = this.aggregationsService.processAggregation(name, this.column());
|
|
9014
|
-
return {
|
|
9015
|
-
...agg,
|
|
9016
|
-
items: agg?.items?.filter((item) => item.display !== "custom-range") ?? []
|
|
9017
|
-
};
|
|
9018
|
-
}
|
|
9019
|
-
return null;
|
|
9020
|
-
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
9021
8898
|
select() {
|
|
9022
8899
|
this.selection.set(true);
|
|
9023
8900
|
}
|
|
@@ -9049,6 +8926,15 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
9049
8926
|
this.onClear.emit();
|
|
9050
8927
|
}
|
|
9051
8928
|
}
|
|
8929
|
+
onHeaderClick(event) {
|
|
8930
|
+
event.preventDefault();
|
|
8931
|
+
const isDate = this.aggregationsService.appStore.isDateColumn(this.aggregation()?.column || "");
|
|
8932
|
+
if (!isDate && this.isEmpty())
|
|
8933
|
+
return;
|
|
8934
|
+
if (this.collapsible()) {
|
|
8935
|
+
this.isCollapsed.update((value) => !value);
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
9052
8938
|
selectAndOpenDialog() {
|
|
9053
8939
|
this.select();
|
|
9054
8940
|
this.dateRangeDialog()?.open();
|
|
@@ -9174,7 +9060,7 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
9174
9060
|
throw new Error("filters.filterInvalid");
|
|
9175
9061
|
}
|
|
9176
9062
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9177
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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 },
|
|
9063
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AggregationDateComponent, isStandalone: true, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "dateRangeDialog", first: true, predicate: AggregationDateRangeDialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
9178
9064
|
}
|
|
9179
9065
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationDateComponent, decorators: [{
|
|
9180
9066
|
type: Component,
|
|
@@ -9192,8 +9078,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
9192
9078
|
IconButtonComponent
|
|
9193
9079
|
], host: {
|
|
9194
9080
|
class: "@container"
|
|
9195
|
-
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[
|
|
9196
|
-
}], ctorParameters: () => [], propDecorators: { dateRangeDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateRangeDialogComponent), { isSignal: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }],
|
|
9081
|
+
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
|
|
9082
|
+
}], ctorParameters: () => [], propDecorators: { dateRangeDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateRangeDialogComponent), { isSignal: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
9197
9083
|
|
|
9198
9084
|
/**
|
|
9199
9085
|
* Component that allows users to select a date or a date range for filtering search results.
|
|
@@ -9202,7 +9088,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
9202
9088
|
*/
|
|
9203
9089
|
class DateComponent extends AggregationDateComponent {
|
|
9204
9090
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DateComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
9205
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[
|
|
9091
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
9206
9092
|
}
|
|
9207
9093
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DateComponent, decorators: [{
|
|
9208
9094
|
type: Component,
|
|
@@ -9219,7 +9105,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
9219
9105
|
FilterXIcon
|
|
9220
9106
|
], host: {
|
|
9221
9107
|
class: "@container"
|
|
9222
|
-
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[
|
|
9108
|
+
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
|
|
9223
9109
|
}] });
|
|
9224
9110
|
|
|
9225
9111
|
class ArticleEntities {
|
|
@@ -9862,6 +9748,8 @@ class AlertDialog {
|
|
|
9862
9748
|
if (!this.alert)
|
|
9863
9749
|
return;
|
|
9864
9750
|
const q = this.alert.query;
|
|
9751
|
+
// `q.filters` widened to `Filter[] | LegacyFilter[]` upstream, but the
|
|
9752
|
+
// query-params store deliberately deals in `LegacyFilter[]` only.
|
|
9865
9753
|
const filters = Array.isArray(q.filters) ? q.filters : undefined;
|
|
9866
9754
|
this.queryParamsStore.patch({ text: q.text, tab: q.tab, basket: q.basket, sort: q.sort, filters, name: q.name });
|
|
9867
9755
|
this.dialog()?.close();
|
|
@@ -10654,6 +10542,15 @@ class SignInComponent {
|
|
|
10654
10542
|
destroyRef;
|
|
10655
10543
|
cn = cn;
|
|
10656
10544
|
config = globalConfig;
|
|
10545
|
+
/**
|
|
10546
|
+
* True when authentication is handled outside the credentials form — i.e. by the
|
|
10547
|
+
* browser/proxy (`useSSO`) or by an auto-configured OAuth/SAML provider. In those
|
|
10548
|
+
* modes this screen shows a loader instead of a login form and initiates the
|
|
10549
|
+
* handshake automatically by calling `handleLogin()`.
|
|
10550
|
+
*/
|
|
10551
|
+
externalAuth = !!(globalConfig.useSSO ||
|
|
10552
|
+
globalConfig.autoOAuthProvider ||
|
|
10553
|
+
globalConfig.autoSAMLProvider);
|
|
10657
10554
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
10658
10555
|
forgotPassword = output();
|
|
10659
10556
|
username = model("", ...(ngDevMode ? [{ debugName: "username" }] : []));
|
|
@@ -10675,6 +10572,30 @@ class SignInComponent {
|
|
|
10675
10572
|
expiresSoonNotified = signal(false, ...(ngDevMode ? [{ debugName: "expiresSoonNotified" }] : []));
|
|
10676
10573
|
constructor(destroyRef) {
|
|
10677
10574
|
this.destroyRef = destroyRef;
|
|
10575
|
+
// If the user is already authenticated when landing here (e.g. page refresh on
|
|
10576
|
+
// /login, or an external handshake completed before this screen was created),
|
|
10577
|
+
// don't sit on the loader: go straight to the returnUrl.
|
|
10578
|
+
if (this.authenticated()) {
|
|
10579
|
+
const url = this.route.snapshot.queryParams["returnUrl"] || "/";
|
|
10580
|
+
this.router.navigateByUrl(url);
|
|
10581
|
+
}
|
|
10582
|
+
// When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML
|
|
10583
|
+
// provider, no credentials form is shown: this screen shows a loader and initiates
|
|
10584
|
+
// the handshake automatically by calling `handleLogin()`. If the handshake never
|
|
10585
|
+
// completes, fall back to /error after 5s; the fallback is cancelled as soon as
|
|
10586
|
+
// the login succeeds (the `authenticated` event then drives navigation).
|
|
10587
|
+
if (this.externalAuth && !this.authenticated()) {
|
|
10588
|
+
const timeout = setTimeout(() => {
|
|
10589
|
+
this.router.navigate(["/error"], {
|
|
10590
|
+
queryParams: { returnUrl: this.route.snapshot.queryParams["returnUrl"] }
|
|
10591
|
+
});
|
|
10592
|
+
}, 5000);
|
|
10593
|
+
destroyRef.onDestroy(() => clearTimeout(timeout));
|
|
10594
|
+
this.handleLogin().then(result => {
|
|
10595
|
+
if (result)
|
|
10596
|
+
clearTimeout(timeout);
|
|
10597
|
+
});
|
|
10598
|
+
}
|
|
10678
10599
|
effect(() => {
|
|
10679
10600
|
const principal = getState(this.principalStore);
|
|
10680
10601
|
if (this.authenticated() && principal && !this.expiresSoonNotified()) {
|
|
@@ -10720,14 +10641,16 @@ class SignInComponent {
|
|
|
10720
10641
|
this.router.navigate(["/login"]);
|
|
10721
10642
|
}
|
|
10722
10643
|
async handleLogin() {
|
|
10723
|
-
login().then((result) => {
|
|
10644
|
+
return login().then((result) => {
|
|
10724
10645
|
if (result) {
|
|
10725
10646
|
this.auditService.notifyLogin();
|
|
10726
10647
|
}
|
|
10648
|
+
return result;
|
|
10727
10649
|
}).catch(error => {
|
|
10728
10650
|
warn("An error occurred while logging in", error);
|
|
10729
10651
|
this.auditService.notify({ type: 'Login_Denied' });
|
|
10730
10652
|
this.router.navigate(["error"]);
|
|
10653
|
+
return false;
|
|
10731
10654
|
});
|
|
10732
10655
|
}
|
|
10733
10656
|
async handleLoginWithCredentials() {
|
|
@@ -10770,7 +10693,7 @@ class SignInComponent {
|
|
|
10770
10693
|
}
|
|
10771
10694
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SignInComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
10772
10695
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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: `
|
|
10773
|
-
@if (!authenticated()) {
|
|
10696
|
+
@if (!authenticated() && !externalAuth) {
|
|
10774
10697
|
<Card
|
|
10775
10698
|
hover="no"
|
|
10776
10699
|
cdkTrapFocus
|
|
@@ -10781,60 +10704,54 @@ class SignInComponent {
|
|
|
10781
10704
|
</CardHeader>
|
|
10782
10705
|
|
|
10783
10706
|
<CardContent class="grid gap-4">
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
(click)="handleLoginWithCredentials()">
|
|
10823
|
-
{{ "login.connect" | transloco }}
|
|
10824
|
-
</button>
|
|
10825
|
-
}
|
|
10826
|
-
@else {
|
|
10827
|
-
<!-- authentication using OAuth or SAML provider -->
|
|
10828
|
-
<button (click)="handleLogin()">
|
|
10829
|
-
{{ "login.SignInWith" | transloco : { provider: config.autoOAuthProvider ? "OAuth" : "SAML" } }}
|
|
10830
|
-
</button>
|
|
10831
|
-
}
|
|
10707
|
+
<!-- authentication using credentials -->
|
|
10708
|
+
<div class="grid gap-2">
|
|
10709
|
+
<label class="text-sm font-medium" for="username">{{
|
|
10710
|
+
"login.username" | transloco
|
|
10711
|
+
}}</label>
|
|
10712
|
+
<input
|
|
10713
|
+
id="username"
|
|
10714
|
+
type="text"
|
|
10715
|
+
required
|
|
10716
|
+
[(ngModel)]="username"
|
|
10717
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10718
|
+
</div>
|
|
10719
|
+
|
|
10720
|
+
<div class="grid gap-2">
|
|
10721
|
+
<label class="text-sm font-medium" for="password">{{
|
|
10722
|
+
"login.password" | transloco
|
|
10723
|
+
}}</label>
|
|
10724
|
+
<input
|
|
10725
|
+
id="password"
|
|
10726
|
+
type="password"
|
|
10727
|
+
required
|
|
10728
|
+
[(ngModel)]="password"
|
|
10729
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10730
|
+
</div>
|
|
10731
|
+
|
|
10732
|
+
<span
|
|
10733
|
+
class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
|
|
10734
|
+
role="button"
|
|
10735
|
+
tabindex="0"
|
|
10736
|
+
(click)="forgotPassword.emit()"
|
|
10737
|
+
(keydown.enter)="forgotPassword.emit()">
|
|
10738
|
+
{{ "login.forgotPassword" | transloco }}
|
|
10739
|
+
</span>
|
|
10740
|
+
<button variant="primary"
|
|
10741
|
+
[disabled]="!isValid()"
|
|
10742
|
+
(click)="handleLoginWithCredentials()">
|
|
10743
|
+
{{ "login.connect" | transloco }}
|
|
10744
|
+
</button>
|
|
10832
10745
|
</CardContent>
|
|
10833
10746
|
</Card>
|
|
10834
10747
|
} @else {
|
|
10835
|
-
<
|
|
10748
|
+
<div class="flex h-dvh w-full items-center justify-center">
|
|
10749
|
+
<div class="flex flex-col items-center space-y-4">
|
|
10750
|
+
<span class="loader"></span>
|
|
10751
|
+
</div>
|
|
10752
|
+
</div>
|
|
10836
10753
|
}
|
|
10837
|
-
`, 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", "scheme", "iconOnly", "size", "solid"] }, { 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: "
|
|
10754
|
+
`, isInline: true, styles: ["input{background-color:var(--background)}.loader{--w: 96px;--h: 96px;transform:rotate(45deg);perspective:1000px;border-radius:50%;width:var(--w);height:var(--h);color:#0040bf}.loader:before,.loader:after{content:\"\";display:block;position:absolute;top:0;left:0;width:inherit;height:inherit;border-radius:50%;transform:rotateX(70deg);animation:1s spin linear infinite}.loader:after{color:#ff854a;transform:rotateY(70deg);animation-delay:.4s}@keyframes spin{0%,to{box-shadow:.4em 0 0 0 currentcolor}12%{box-shadow:.4em .4em 0 0 currentcolor}25%{box-shadow:0 .4em 0 0 currentcolor}37%{box-shadow:-.4em .4em 0 0 currentcolor}50%{box-shadow:-.4em 0 0 0 currentcolor}62%{box-shadow:-.4em -.4em 0 0 currentcolor}75%{box-shadow:0 -.4em 0 0 currentcolor}87%{box-shadow:.4em -.4em 0 0 currentcolor}}\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", "scheme", "iconOnly", "size", "solid"] }, { 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: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
10838
10755
|
}
|
|
10839
10756
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SignInComponent, decorators: [{
|
|
10840
10757
|
type: Component,
|
|
@@ -10847,10 +10764,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
10847
10764
|
ButtonComponent,
|
|
10848
10765
|
CardComponent,
|
|
10849
10766
|
CardHeaderComponent,
|
|
10850
|
-
CardContentComponent
|
|
10851
|
-
LoadingComponent
|
|
10767
|
+
CardContentComponent
|
|
10852
10768
|
], providers: [provideTranslocoScope("login")], template: `
|
|
10853
|
-
@if (!authenticated()) {
|
|
10769
|
+
@if (!authenticated() && !externalAuth) {
|
|
10854
10770
|
<Card
|
|
10855
10771
|
hover="no"
|
|
10856
10772
|
cdkTrapFocus
|
|
@@ -10861,62 +10777,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
10861
10777
|
</CardHeader>
|
|
10862
10778
|
|
|
10863
10779
|
<CardContent class="grid gap-4">
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
(click)="handleLoginWithCredentials()">
|
|
10903
|
-
{{ "login.connect" | transloco }}
|
|
10904
|
-
</button>
|
|
10905
|
-
}
|
|
10906
|
-
@else {
|
|
10907
|
-
<!-- authentication using OAuth or SAML provider -->
|
|
10908
|
-
<button (click)="handleLogin()">
|
|
10909
|
-
{{ "login.SignInWith" | transloco : { provider: config.autoOAuthProvider ? "OAuth" : "SAML" } }}
|
|
10910
|
-
</button>
|
|
10911
|
-
}
|
|
10780
|
+
<!-- authentication using credentials -->
|
|
10781
|
+
<div class="grid gap-2">
|
|
10782
|
+
<label class="text-sm font-medium" for="username">{{
|
|
10783
|
+
"login.username" | transloco
|
|
10784
|
+
}}</label>
|
|
10785
|
+
<input
|
|
10786
|
+
id="username"
|
|
10787
|
+
type="text"
|
|
10788
|
+
required
|
|
10789
|
+
[(ngModel)]="username"
|
|
10790
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10791
|
+
</div>
|
|
10792
|
+
|
|
10793
|
+
<div class="grid gap-2">
|
|
10794
|
+
<label class="text-sm font-medium" for="password">{{
|
|
10795
|
+
"login.password" | transloco
|
|
10796
|
+
}}</label>
|
|
10797
|
+
<input
|
|
10798
|
+
id="password"
|
|
10799
|
+
type="password"
|
|
10800
|
+
required
|
|
10801
|
+
[(ngModel)]="password"
|
|
10802
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10803
|
+
</div>
|
|
10804
|
+
|
|
10805
|
+
<span
|
|
10806
|
+
class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
|
|
10807
|
+
role="button"
|
|
10808
|
+
tabindex="0"
|
|
10809
|
+
(click)="forgotPassword.emit()"
|
|
10810
|
+
(keydown.enter)="forgotPassword.emit()">
|
|
10811
|
+
{{ "login.forgotPassword" | transloco }}
|
|
10812
|
+
</span>
|
|
10813
|
+
<button variant="primary"
|
|
10814
|
+
[disabled]="!isValid()"
|
|
10815
|
+
(click)="handleLoginWithCredentials()">
|
|
10816
|
+
{{ "login.connect" | transloco }}
|
|
10817
|
+
</button>
|
|
10912
10818
|
</CardContent>
|
|
10913
10819
|
</Card>
|
|
10914
10820
|
} @else {
|
|
10915
|
-
<
|
|
10821
|
+
<div class="flex h-dvh w-full items-center justify-center">
|
|
10822
|
+
<div class="flex flex-col items-center space-y-4">
|
|
10823
|
+
<span class="loader"></span>
|
|
10824
|
+
</div>
|
|
10825
|
+
</div>
|
|
10916
10826
|
}
|
|
10917
10827
|
`, host: {
|
|
10918
10828
|
"[class]": "cn('grid h-dvh w-full place-content-center', class())"
|
|
10919
|
-
}, styles: ["input{background-color:var(--background)}\n"] }]
|
|
10829
|
+
}, styles: ["input{background-color:var(--background)}.loader{--w: 96px;--h: 96px;transform:rotate(45deg);perspective:1000px;border-radius:50%;width:var(--w);height:var(--h);color:#0040bf}.loader:before,.loader:after{content:\"\";display:block;position:absolute;top:0;left:0;width:inherit;height:inherit;border-radius:50%;transform:rotateX(70deg);animation:1s spin linear infinite}.loader:after{color:#ff854a;transform:rotateY(70deg);animation-delay:.4s}@keyframes spin{0%,to{box-shadow:.4em 0 0 0 currentcolor}12%{box-shadow:.4em .4em 0 0 currentcolor}25%{box-shadow:0 .4em 0 0 currentcolor}37%{box-shadow:-.4em .4em 0 0 currentcolor}50%{box-shadow:-.4em 0 0 0 currentcolor}62%{box-shadow:-.4em -.4em 0 0 currentcolor}75%{box-shadow:0 -.4em 0 0 currentcolor}87%{box-shadow:.4em -.4em 0 0 currentcolor}}\n"] }]
|
|
10920
10830
|
}], 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"] }] } });
|
|
10921
10831
|
|
|
10922
10832
|
class AuthPageComponent {
|
|
@@ -11308,7 +11218,7 @@ class OverrideUserDialogComponent {
|
|
|
11308
11218
|
}
|
|
11309
11219
|
}
|
|
11310
11220
|
handleOverrideUser(username, domain) {
|
|
11311
|
-
const { useSSO, createRoutes } = globalConfig;
|
|
11221
|
+
const { useSSO, createRoutes, useCredentials } = globalConfig;
|
|
11312
11222
|
if (username === undefined || domain === undefined) {
|
|
11313
11223
|
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11314
11224
|
}
|
|
@@ -11316,7 +11226,7 @@ class OverrideUserDialogComponent {
|
|
|
11316
11226
|
setGlobalConfig({ userOverrideActive: true, userOverride: { username, domain } });
|
|
11317
11227
|
}
|
|
11318
11228
|
// Login with the new user
|
|
11319
|
-
if (useSSO) {
|
|
11229
|
+
if (useSSO && !useCredentials) {
|
|
11320
11230
|
this.appService
|
|
11321
11231
|
.initialize(createRoutes)
|
|
11322
11232
|
.then(() => {
|
|
@@ -11341,6 +11251,7 @@ class OverrideUserDialogComponent {
|
|
|
11341
11251
|
})
|
|
11342
11252
|
.catch((err) => {
|
|
11343
11253
|
error("An error occured while overriding (initialize)", err);
|
|
11254
|
+
notify.error(err.message, { duration: 2000 });
|
|
11344
11255
|
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11345
11256
|
});
|
|
11346
11257
|
}
|
|
@@ -11898,11 +11809,16 @@ class DrawerAdvancedFiltersComponent extends DrawerComponent {
|
|
|
11898
11809
|
text = "";
|
|
11899
11810
|
constructor() {
|
|
11900
11811
|
super();
|
|
11901
|
-
|
|
11812
|
+
effect(() => {
|
|
11813
|
+
getState(this.appStore);
|
|
11814
|
+
const query = this.appStore.getDefaultQuery();
|
|
11815
|
+
if (query?.name) {
|
|
11816
|
+
this.getFirstPageQuery(query?.name);
|
|
11817
|
+
}
|
|
11818
|
+
});
|
|
11902
11819
|
}
|
|
11903
|
-
async getFirstPageQuery() {
|
|
11904
|
-
const
|
|
11905
|
-
const response = await fetchQuery({ isFirstPage: true, name: query.name });
|
|
11820
|
+
async getFirstPageQuery(queryName) {
|
|
11821
|
+
const response = await fetchQuery({ isFirstPage: true, name: queryName });
|
|
11906
11822
|
this.aggregations.set(response.aggregations);
|
|
11907
11823
|
}
|
|
11908
11824
|
onTabChange(tab) {
|
|
@@ -13037,201 +12953,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
13037
12953
|
args: [{ selector: 'feedback, Feedback', standalone: true, imports: [ButtonComponent, MenuComponent, MenuContentComponent, MenuItemComponent, TranslocoPipe, FeedbackDialogComponent, CommentIcon, XMarkIcon, ThumbsUpIcon, ThumbsDownIcon, FaIconComponent], providers: [provideTranslocoScope('feedback')], template: "<menu>\r\n @let feedback = \"feedback.label\" | transloco;\r\n <button [variant]=\"variant()\" [solid]=\"solid()\" [aria-label]=\"feedback\">\r\n <comment-icon />\r\n <span>{{ feedback }}</span>\r\n <x-mark-icon class=\"ms-2\" (click)=\"close($event)\" />\r\n </button>\r\n\r\n <MenuContent>\r\n @if (!disliked()) {\r\n @let feedbackLike = \"feedback.like\" | transloco;\r\n @let feedbackLiked = \"feedback.liked\" | transloco;\r\n <menuitem (click)=\"like()\" aria-label=\"feedback\">\r\n @if (liked()) {\r\n <thumbs-up-icon [fill]=\"true\" />\r\n {{ feedbackLiked }}\r\n } @else {\r\n <thumbs-up-icon />\r\n {{ feedbackLike }}\r\n }\r\n </menuitem>\r\n }\r\n @if (!liked()) {\r\n <menuitem\r\n (click)=\"dislike()\"\r\n [aria-label]=\"'feedback.dislike' | transloco\">\r\n @if (disliked()) {\r\n <thumbs-down-icon [fill]=\"true\" />\r\n {{ \"feedback.disliked\" | transloco }}\r\n } @else {\r\n <thumbs-down-icon />\r\n {{ \"feedback.dislike\" | transloco }}\r\n }\r\n </menuitem>\r\n }\r\n @for (menu of menus; track $index) {\r\n @let feedbackTitle = \"feedback.\" + menu.type + \".title\" | transloco;\r\n <menuitem\r\n (click)=\"openFeedbackDialog(menu.type)\"\r\n [aria-label]=\"feedbackTitle\">\r\n <fa-icon [faClass]=\"menu.icon\" />\r\n {{ feedbackTitle }}\r\n </menuitem>\r\n }\r\n </MenuContent>\r\n</menu>\r\n\r\n<feedback-dialog />\r\n" }]
|
|
13038
12954
|
}], 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 }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], solid: [{ type: i0.Input, args: [{ isSignal: true, alias: "solid", required: false }] }] } });
|
|
13039
12955
|
|
|
13040
|
-
class
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
}
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
select(item, e, updateChildren) {
|
|
13079
|
-
e?.stopImmediatePropagation();
|
|
13080
|
-
const selected = !item.$selected && !item.$selectedVisually;
|
|
13081
|
-
item.$selected = selected;
|
|
13082
|
-
item.$selectedVisually = false;
|
|
13083
|
-
if (updateChildren) {
|
|
13084
|
-
// apply selection to chilren when selected
|
|
13085
|
-
this.selectChildren(item.items, item.$selected);
|
|
13086
|
-
}
|
|
13087
|
-
this.onSelect.emit(item);
|
|
13088
|
-
}
|
|
13089
|
-
selectChildren(items, select = true) {
|
|
13090
|
-
if (!this.linkChildren() || !items?.length || select === undefined)
|
|
13091
|
-
return;
|
|
13092
|
-
items.forEach((item) => {
|
|
13093
|
-
item.$selectedVisually = select;
|
|
13094
|
-
if (select) {
|
|
13095
|
-
item.$selected = false;
|
|
13096
|
-
}
|
|
13097
|
-
if (item.items?.length) {
|
|
13098
|
-
this.selectChildren(item.items, select);
|
|
12956
|
+
class AggregationPanelComponent {
|
|
12957
|
+
/* collapse */
|
|
12958
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
12959
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
12960
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
12961
|
+
isDate = input(false, ...(ngDevMode ? [{ debugName: "isDate" }] : []));
|
|
12962
|
+
isEmpty = input(false, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
12963
|
+
/* aggregation data — used for label, search input id and load-more condition */
|
|
12964
|
+
aggregation = input(null, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
12965
|
+
/* header button state (driven by parent) */
|
|
12966
|
+
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12967
|
+
filtersCount = input(0, ...(ngDevMode ? [{ debugName: "filtersCount" }] : []));
|
|
12968
|
+
hasFilters = input(false, ...(ngDevMode ? [{ debugName: "hasFilters" }] : []));
|
|
12969
|
+
selection = input(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
12970
|
+
isAllSelected = input(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
12971
|
+
/* search */
|
|
12972
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
12973
|
+
itemsLength = input(0, ...(ngDevMode ? [{ debugName: "itemsLength" }] : []));
|
|
12974
|
+
/* load more */
|
|
12975
|
+
hasMore = input(false, ...(ngDevMode ? [{ debugName: "hasMore" }] : []));
|
|
12976
|
+
searchedItemsLength = input(0, ...(ngDevMode ? [{ debugName: "searchedItemsLength" }] : []));
|
|
12977
|
+
/* outputs — parent reacts to user actions */
|
|
12978
|
+
cleared = output();
|
|
12979
|
+
applied = output();
|
|
12980
|
+
allSelected = output();
|
|
12981
|
+
allUnselected = output();
|
|
12982
|
+
loadedMore = output();
|
|
12983
|
+
/* internal collapse state */
|
|
12984
|
+
isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
|
|
12985
|
+
expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
12986
|
+
searchInput = viewChild("searchInput", ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
12987
|
+
isInPopover = !!(inject(PopoverContentComponent, { optional: true }) ||
|
|
12988
|
+
inject(DropdownContentComponent, { optional: true }) ||
|
|
12989
|
+
inject(DropdownDirective, { optional: true }));
|
|
12990
|
+
constructor() {
|
|
12991
|
+
effect(() => {
|
|
12992
|
+
if (this.isInPopover && this.searchInput()?.nativeElement && this.expanded() !== null) {
|
|
12993
|
+
setTimeout(() => this.searchInput()?.nativeElement.focus(), 0);
|
|
13099
12994
|
}
|
|
13100
12995
|
});
|
|
13101
12996
|
}
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
e.stopImmediatePropagation();
|
|
13106
|
-
if (node.items && node.$opened === true) {
|
|
13107
|
-
node.$opened = false;
|
|
13108
|
-
return;
|
|
13109
|
-
}
|
|
13110
|
-
if (node.items && !node.$opened) {
|
|
13111
|
-
node.$opened = true;
|
|
12997
|
+
onHeaderClick(event) {
|
|
12998
|
+
if (!this.isDate() && this.isEmpty()) {
|
|
12999
|
+
event.preventDefault();
|
|
13112
13000
|
return;
|
|
13113
13001
|
}
|
|
13114
|
-
this.
|
|
13115
|
-
|
|
13116
|
-
onTextClick(event) {
|
|
13117
|
-
if (this.quickFilter()) {
|
|
13118
|
-
this.select(this.node(), event, true);
|
|
13119
|
-
this.onFilter.emit();
|
|
13002
|
+
if (this.collapsible()) {
|
|
13003
|
+
this.isCollapsed.update((v) => !v);
|
|
13120
13004
|
}
|
|
13005
|
+
event.preventDefault();
|
|
13121
13006
|
}
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
i.$selected = true;
|
|
13130
|
-
}
|
|
13131
|
-
});
|
|
13132
|
-
}
|
|
13133
|
-
if (this.linkChildren() && this.node().items.some((i) => !i.$selectedVisually && !i.$selected)) {
|
|
13134
|
-
this.node().$selected = false;
|
|
13135
|
-
this.node().$selectedVisually = false;
|
|
13136
|
-
}
|
|
13137
|
-
this.onSelect.emit(this.node());
|
|
13007
|
+
onToggle(event) {
|
|
13008
|
+
const e = event;
|
|
13009
|
+
this.isCollapsed.set(e.newState === "closed");
|
|
13010
|
+
}
|
|
13011
|
+
clearSearch(e) {
|
|
13012
|
+
e.stopImmediatePropagation();
|
|
13013
|
+
this.searchText.set("");
|
|
13138
13014
|
}
|
|
13139
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type:
|
|
13140
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type:
|
|
13015
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13016
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AggregationPanelComponent, isStandalone: true, selector: "AggregationPanel, aggregation-panel", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, isDate: { classPropertyName: "isDate", publicName: "isDate", isSignal: true, isRequired: false, transformFunction: null }, isEmpty: { classPropertyName: "isEmpty", publicName: "isEmpty", isSignal: true, isRequired: false, transformFunction: null }, aggregation: { classPropertyName: "aggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, filtersCount: { classPropertyName: "filtersCount", publicName: "filtersCount", isSignal: true, isRequired: false, transformFunction: null }, hasFilters: { classPropertyName: "hasFilters", publicName: "hasFilters", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, isAllSelected: { classPropertyName: "isAllSelected", publicName: "isAllSelected", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null }, itemsLength: { classPropertyName: "itemsLength", publicName: "itemsLength", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, searchedItemsLength: { classPropertyName: "searchedItemsLength", publicName: "searchedItemsLength", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchText: "searchTextChange", cleared: "cleared", applied: "applied", allSelected: "allSelected", allUnselected: "allUnselected", loadedMore: "loadedMore" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<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 @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <Badge size=\"xs\" class=\"ml-1\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n @if (!isCollapsed()) {\r\n @if (hasFilters()) {\r\n @let label = \"filters.clearFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); cleared.emit()\">\r\n <filter-x-icon />\r\n </button>\r\n }\r\n @if (selection()) {\r\n @let label = \"filters.apply\" | transloco;\r\n <button\r\n variant=\"accent\"\r\n size=\"sm\"\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); applied.emit()\">\r\n <FilterIcon />\r\n {{ label }}\r\n </button>\r\n }\r\n\r\n @if (isAllSelected()) {\r\n @let label = \"filters.unselectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); allUnselected.emit()\">\r\n <square-check-icon />\r\n </button>\r\n } @else {\r\n @let label = \"filters.selectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); allSelected.emit()\">\r\n <square-icon />\r\n </button>\r\n }\r\n }\r\n\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n @if (aggregation()?.searchable && itemsLength() > 0) {\r\n <InputGroup class=\"group/item mt-1\">\r\n <input\r\n #searchInput\r\n input-group\r\n id=\"aggregation-input-{{ aggregation()?.column }}\"\r\n type=\"text\"\r\n [attr.aria-label]=\"'search' | transloco\"\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 <InputGroupAddon align=\"inline-end\" class=\"gap-0.5!\">\r\n <icon-button\r\n size=\"sm\"\r\n [class]=\"\r\n searchText().length > 0\r\n ? 'rotate-90 cursor-pointer opacity-100 transition-[rotate,opacity] duration-500'\r\n : 'pointer-events-none rotate-0 opacity-0 transition-[rotate,opacity] duration-500'\r\n \"\r\n aria-label=\"Clear search\"\r\n [tabindex]=\"searchText().length > 0 ? 0 : -1\"\r\n (keydown.enter)=\"clearSearch($event)\"\r\n (click)=\"clearSearch($event)\">\r\n <XMarkIcon />\r\n </icon-button>\r\n <ng-content select=\"[search-addon]\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n }\r\n\r\n <ng-content />\r\n\r\n @if (hasMore() && searchedItemsLength() === 0) {\r\n <button\r\n class=\"mt-1 flex w-full justify-center\"\r\n [attr.aria-label]=\"'loadMore' | transloco\"\r\n (click)=\"loadedMore.emit()\">\r\n {{ \"loadMore\" | transloco }}\r\n </button>\r\n }\r\n</details>\r\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: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", 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: "component", type: SearchIcon, selector: "SearchIcon", inputs: ["class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "component", type: SquareCheckIcon, selector: "square-check-icon, SquareCheckIcon", inputs: ["class"] }, { kind: "component", type: SquareIcon, selector: "square-icon, SquareIcon", inputs: ["class"] }, { kind: "component", type: XMarkIcon, selector: "XMarkIcon, xmark-icon, x-mark-icon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
13141
13017
|
}
|
|
13142
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type:
|
|
13018
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationPanelComponent, decorators: [{
|
|
13143
13019
|
type: Component,
|
|
13144
|
-
args: [{ selector: "aggregation-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13020
|
+
args: [{ selector: "AggregationPanel, aggregation-panel", standalone: true, imports: [
|
|
13021
|
+
FormsModule,
|
|
13022
|
+
ButtonComponent,
|
|
13023
|
+
SyslangPipe,
|
|
13024
|
+
TranslocoPipe,
|
|
13025
|
+
BadgeComponent,
|
|
13026
|
+
ChevronRightIcon,
|
|
13027
|
+
InputGroupInput,
|
|
13028
|
+
InputGroupComponent,
|
|
13029
|
+
InputGroupAddonComponent,
|
|
13030
|
+
SearchIcon,
|
|
13031
|
+
FilterIcon,
|
|
13032
|
+
FaIconComponent,
|
|
13033
|
+
FilterXIcon,
|
|
13034
|
+
SquareCheckIcon,
|
|
13035
|
+
SquareIcon,
|
|
13036
|
+
XMarkIcon,
|
|
13037
|
+
IconButtonComponent,
|
|
13038
|
+
], template: "<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 @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <Badge size=\"xs\" class=\"ml-1\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n @if (!isCollapsed()) {\r\n @if (hasFilters()) {\r\n @let label = \"filters.clearFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); cleared.emit()\">\r\n <filter-x-icon />\r\n </button>\r\n }\r\n @if (selection()) {\r\n @let label = \"filters.apply\" | transloco;\r\n <button\r\n variant=\"accent\"\r\n size=\"sm\"\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); applied.emit()\">\r\n <FilterIcon />\r\n {{ label }}\r\n </button>\r\n }\r\n\r\n @if (isAllSelected()) {\r\n @let label = \"filters.unselectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); allUnselected.emit()\">\r\n <square-check-icon />\r\n </button>\r\n } @else {\r\n @let label = \"filters.selectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); allSelected.emit()\">\r\n <square-icon />\r\n </button>\r\n }\r\n }\r\n\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n @if (aggregation()?.searchable && itemsLength() > 0) {\r\n <InputGroup class=\"group/item mt-1\">\r\n <input\r\n #searchInput\r\n input-group\r\n id=\"aggregation-input-{{ aggregation()?.column }}\"\r\n type=\"text\"\r\n [attr.aria-label]=\"'search' | transloco\"\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 <InputGroupAddon align=\"inline-end\" class=\"gap-0.5!\">\r\n <icon-button\r\n size=\"sm\"\r\n [class]=\"\r\n searchText().length > 0\r\n ? 'rotate-90 cursor-pointer opacity-100 transition-[rotate,opacity] duration-500'\r\n : 'pointer-events-none rotate-0 opacity-0 transition-[rotate,opacity] duration-500'\r\n \"\r\n aria-label=\"Clear search\"\r\n [tabindex]=\"searchText().length > 0 ? 0 : -1\"\r\n (keydown.enter)=\"clearSearch($event)\"\r\n (click)=\"clearSearch($event)\">\r\n <XMarkIcon />\r\n </icon-button>\r\n <ng-content select=\"[search-addon]\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n }\r\n\r\n <ng-content />\r\n\r\n @if (hasMore() && searchedItemsLength() === 0) {\r\n <button\r\n class=\"mt-1 flex w-full justify-center\"\r\n [attr.aria-label]=\"'loadMore' | transloco\"\r\n (click)=\"loadedMore.emit()\">\r\n {{ \"loadMore\" | transloco }}\r\n </button>\r\n }\r\n</details>\r\n" }]
|
|
13039
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], isDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDate", required: false }] }], isEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEmpty", required: false }] }], aggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], filtersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtersCount", required: false }] }], hasFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasFilters", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], isAllSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAllSelected", required: false }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }], itemsLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsLength", required: false }] }], hasMore: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMore", required: false }] }], searchedItemsLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchedItemsLength", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }], applied: [{ type: i0.Output, args: ["applied"] }], allSelected: [{ type: i0.Output, args: ["allSelected"] }], allUnselected: [{ type: i0.Output, args: ["allUnselected"] }], loadedMore: [{ type: i0.Output, args: ["loadedMore"] }], searchInput: [{ type: i0.ViewChild, args: ["searchInput", { isSignal: true }] }] } });
|
|
13149
13040
|
|
|
13150
13041
|
class AggregationTreeComponent {
|
|
13151
13042
|
cn = cn;
|
|
13152
|
-
|
|
13153
|
-
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
13154
|
-
virtualizer = injectVirtualizer(() => ({
|
|
13155
|
-
count: this.items().length,
|
|
13156
|
-
estimateSize: () => 32,
|
|
13157
|
-
scrollElement: this.scrollElement()
|
|
13158
|
-
}));
|
|
13159
|
-
#measureItems = effect(() => this.virtualItems().forEach((el) => {
|
|
13160
|
-
this.virtualizer.measureElement(el.nativeElement);
|
|
13161
|
-
}), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
|
|
13162
|
-
searchInput = viewChild("searchInput", ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
13163
|
-
/* stores */
|
|
13164
|
-
aggregationsStore = inject(AggregationsStore);
|
|
13165
|
-
queryParamsStore = inject(QueryParamsStore);
|
|
13166
|
-
appStore = inject(AppStore);
|
|
13167
|
-
/* services */
|
|
13168
|
-
aggregationsService = inject(AggregationsService);
|
|
13169
|
-
el = inject(ElementRef);
|
|
13170
|
-
injector = inject(Injector);
|
|
13171
|
-
destroyRef = inject(DestroyRef);
|
|
13043
|
+
/* inputs */
|
|
13172
13044
|
class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
13173
|
-
/**
|
|
13174
|
-
* The name of the <details> element. When you provide the same id, the component work as an accordion
|
|
13175
|
-
* @defaultValue null
|
|
13176
|
-
*/
|
|
13177
13045
|
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
13178
13046
|
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
13179
13047
|
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
13048
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
13049
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
13050
|
+
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
13051
|
+
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13180
13052
|
expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: (v) => {
|
|
13181
13053
|
const n = numberAttribute(v);
|
|
13182
13054
|
return Number.isNaN(n) ? undefined : n;
|
|
13183
|
-
} }] : [{
|
|
13055
|
+
} }] : [{
|
|
13056
|
+
transform: (v) => {
|
|
13184
13057
|
const n = numberAttribute(v);
|
|
13185
13058
|
return Number.isNaN(n) ? undefined : n;
|
|
13186
|
-
}
|
|
13059
|
+
},
|
|
13060
|
+
}]));
|
|
13061
|
+
/* outputs */
|
|
13187
13062
|
onSelect = output();
|
|
13188
13063
|
onApply = output();
|
|
13189
13064
|
onClear = output();
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
*
|
|
13195
|
-
* @default false
|
|
13196
|
-
*/
|
|
13197
|
-
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
13198
|
-
/**
|
|
13199
|
-
* Controls whether the aggregation component is in a collapsed state.
|
|
13200
|
-
* When true, the component will be visually collapsed/hidden.
|
|
13201
|
-
* When false, the component will be expanded/visible.
|
|
13202
|
-
*
|
|
13203
|
-
* @default false
|
|
13204
|
-
*/
|
|
13205
|
-
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
13206
|
-
/**
|
|
13207
|
-
* A computed signal that tracks the collapsed state of the component.
|
|
13208
|
-
* This signal is linked to the `collapsed()` signal and automatically updates
|
|
13209
|
-
* when the collapsed state changes.
|
|
13210
|
-
*/
|
|
13211
|
-
isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
|
|
13212
|
-
/**
|
|
13213
|
-
* Computed property that returns an empty string when the component is not collapsed,
|
|
13214
|
-
* or null when the component is collapsed. This is typically used to control
|
|
13215
|
-
* expansion state in UI components with conditional rendering or styling.
|
|
13216
|
-
*
|
|
13217
|
-
* @returns Empty string if not collapsed, null if collapsed
|
|
13218
|
-
*/
|
|
13219
|
-
expanded = computed(() => (this.isCollapsed() ? null : ''), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
13220
|
-
/**
|
|
13221
|
-
* A boolean flag indicating whether the component is searchable.
|
|
13222
|
-
* This property is initialized to `undefined` by default.
|
|
13223
|
-
* "Undefined" and not "false" because this input overrides the custom json settings
|
|
13224
|
-
*/
|
|
13225
|
-
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
13065
|
+
/* view queries */
|
|
13066
|
+
virtualItems = viewChildren("virtualItem", ...(ngDevMode ? [{ debugName: "virtualItems" }] : []));
|
|
13067
|
+
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
13068
|
+
/* selection state */
|
|
13226
13069
|
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13070
|
+
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
13071
|
+
/* search state */
|
|
13072
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
13073
|
+
/* composable — injects stores/services, wires search effects, provides shared methods */
|
|
13074
|
+
base = injectAggregationBase({
|
|
13075
|
+
name: this.name,
|
|
13076
|
+
column: this.column,
|
|
13077
|
+
searchText: this.searchText,
|
|
13078
|
+
});
|
|
13079
|
+
/* spread from base */
|
|
13080
|
+
aggregationsStore = this.base.aggregationsStore;
|
|
13081
|
+
queryParamsStore = this.base.queryParamsStore;
|
|
13082
|
+
appStore = this.base.appStore;
|
|
13083
|
+
aggregationsService = this.base.aggregationsService;
|
|
13084
|
+
injector = this.base.injector;
|
|
13085
|
+
destroyRef = this.base.destroyRef;
|
|
13086
|
+
debouncedSearchText = this.base.debouncedSearchText;
|
|
13087
|
+
normalizedSearchText = this.base.normalizedSearchText;
|
|
13088
|
+
suggests = this.base.suggests;
|
|
13089
|
+
hasFilters = this.base.hasFilters;
|
|
13090
|
+
filtersCount = this.base.filtersCount;
|
|
13091
|
+
query = this.base.query;
|
|
13092
|
+
filters = this.base.filters;
|
|
13093
|
+
/* features from appStore */
|
|
13094
|
+
showCount = computed(() => this.appStore.general()?.features?.showAggregationItemCount ?? false, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
|
|
13095
|
+
quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
|
|
13096
|
+
isDate = computed(() => this.appStore.isDateColumn(this.aggregation()?.column || ""), ...(ngDevMode ? [{ debugName: "isDate" }] : []));
|
|
13097
|
+
/* virtualizer */
|
|
13098
|
+
virtualizer = injectVirtualizer(() => ({
|
|
13099
|
+
count: this.items().length,
|
|
13100
|
+
estimateSize: () => 32,
|
|
13101
|
+
scrollElement: this.scrollElement(),
|
|
13102
|
+
}));
|
|
13103
|
+
#measureItems = effect(() => this.virtualItems().forEach((el) => this.virtualizer.measureElement(el.nativeElement)), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
|
|
13104
|
+
linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
|
|
13233
13105
|
aggregation = computed(() => {
|
|
13234
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
13235
13106
|
getState(this.aggregationsStore);
|
|
13236
13107
|
const name = this.name();
|
|
13237
13108
|
const column = this.column();
|
|
@@ -13241,21 +13112,17 @@ class AggregationTreeComponent {
|
|
|
13241
13112
|
if (!agg.isTree) {
|
|
13242
13113
|
error("The aggregation tree component does not support list aggregations. Please use the <Aggregation /> component instead.");
|
|
13243
13114
|
}
|
|
13244
|
-
// overrides "expandedLevel" from custom JSON file
|
|
13245
13115
|
const expandedLevel = this.expandedLevel() ?? agg.expandedLevel;
|
|
13246
13116
|
if (expandedLevel) {
|
|
13247
13117
|
this.expandItems(agg.items, expandedLevel);
|
|
13248
13118
|
}
|
|
13249
|
-
// overrides "searchable" properties with the input if any
|
|
13250
13119
|
agg.searchable = this.searchable() ?? agg.searchable;
|
|
13251
13120
|
return agg;
|
|
13252
13121
|
}
|
|
13253
13122
|
}
|
|
13254
13123
|
return null;
|
|
13255
13124
|
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
13256
|
-
/* items of the aggregation */
|
|
13257
13125
|
items = computed(() => {
|
|
13258
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
13259
13126
|
getState(this.aggregationsStore);
|
|
13260
13127
|
const agg = this.aggregation();
|
|
13261
13128
|
const searchedItems = this.searchedItems();
|
|
@@ -13266,57 +13133,17 @@ class AggregationTreeComponent {
|
|
|
13266
13133
|
else if (agg?.items) {
|
|
13267
13134
|
res = this.addCurrentFiltersToItems();
|
|
13268
13135
|
}
|
|
13269
|
-
// use session storage to keep the selected statuses
|
|
13270
13136
|
const sessionAggItem = sessionStorage.getItem(`agg-${agg?.column}`);
|
|
13271
13137
|
const sessionAgg = JSON.parse(sessionAggItem || "[]");
|
|
13272
13138
|
return this.processAggregations(sessionAgg.length ? this.setSelected(res, sessionAgg) : res);
|
|
13273
13139
|
}, ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
13274
|
-
/**
|
|
13275
|
-
* Computed signal that determines whether the items collection is empty.
|
|
13276
|
-
* @returns True if the items array has no elements, false otherwise.
|
|
13277
|
-
*/
|
|
13278
13140
|
isEmpty = computed(() => this.items().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
13279
|
-
/**
|
|
13280
|
-
* A computed property that determines whether there are active filters
|
|
13281
|
-
* for the current aggregation column.
|
|
13282
|
-
*
|
|
13283
|
-
* if True, the clear button is shown.
|
|
13284
|
-
*
|
|
13285
|
-
* @returns {boolean} `true` if the filter count for the aggregation column is greater than 0, otherwise `false`.
|
|
13286
|
-
*/
|
|
13287
|
-
hasFilters = computed(() => {
|
|
13288
|
-
const { count = 0 } = this.queryParamsStore.getFilter({ field: this.aggregation()?.column, name: this.aggregation()?.name }) || {};
|
|
13289
|
-
return count > 0;
|
|
13290
|
-
}, ...(ngDevMode ? [{ debugName: "hasFilters" }] : []));
|
|
13291
|
-
/**
|
|
13292
|
-
* A computed property that returns the number of items of this aggregation applied in the active filters
|
|
13293
|
-
*
|
|
13294
|
-
* if more than 0 and the showCount input is set as True, the count number is shown.
|
|
13295
|
-
*
|
|
13296
|
-
* @returns {number} the filters count.
|
|
13297
|
-
*/
|
|
13298
|
-
filtersCount = computed(() => {
|
|
13299
|
-
const { count = 0 } = this.queryParamsStore.getFilter({ field: this.aggregation()?.column, name: this.aggregation()?.name }) || {};
|
|
13300
|
-
return count;
|
|
13301
|
-
}, ...(ngDevMode ? [{ debugName: "filtersCount" }] : []));
|
|
13302
|
-
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
13303
|
-
/* search feature */
|
|
13304
|
-
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
13305
|
-
debouncedSearchText = debouncedSignal(this.searchText, 300);
|
|
13306
|
-
normalizedSearchText = computed(() => this.debouncedSearchText()
|
|
13307
|
-
.normalize("NFD")
|
|
13308
|
-
.replace(/[\u0300-\u036f]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
|
|
13309
|
-
/* suggestions */
|
|
13310
|
-
suggests = signal([], ...(ngDevMode ? [{ debugName: "suggests" }] : []));
|
|
13311
|
-
/* searched items */
|
|
13312
13141
|
searchedItems = computed(() => {
|
|
13313
13142
|
if (!this.suggests())
|
|
13314
13143
|
return [];
|
|
13315
|
-
// if the aggregation is a tree, we transform the suggestions into tree nodes
|
|
13316
13144
|
if (this.aggregation()?.isTree) {
|
|
13317
13145
|
return suggestionsToTreeAggregationNodes(this.suggests(), this.searchText());
|
|
13318
13146
|
}
|
|
13319
|
-
// if the aggregation is not a tree, we return the suggestions as is
|
|
13320
13147
|
return this.suggests()?.map((suggest) => ({
|
|
13321
13148
|
name: this.name(),
|
|
13322
13149
|
value: suggest.normalized || suggest.display || "",
|
|
@@ -13324,41 +13151,18 @@ class AggregationTreeComponent {
|
|
|
13324
13151
|
column: suggest.category,
|
|
13325
13152
|
count: Number(suggest.frequency),
|
|
13326
13153
|
$selected: false,
|
|
13327
|
-
items: []
|
|
13154
|
+
items: [],
|
|
13328
13155
|
}));
|
|
13329
13156
|
}, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
|
|
13330
|
-
linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
|
|
13331
|
-
query;
|
|
13332
|
-
filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
13333
13157
|
constructor() {
|
|
13334
|
-
this.query = buildQuery();
|
|
13335
13158
|
effect(() => {
|
|
13336
|
-
// if the aggregation store changes, remove previous session storage
|
|
13337
13159
|
getState(this.aggregationsStore);
|
|
13338
13160
|
sessionStorage.removeItem(`agg-${this.column()}`);
|
|
13339
13161
|
});
|
|
13340
|
-
effect(() => {
|
|
13341
|
-
// focus the search input when expanded
|
|
13342
|
-
if (this.searchInput()?.nativeElement && this.expanded() !== null) {
|
|
13343
|
-
setTimeout(() => {
|
|
13344
|
-
this.searchInput()?.nativeElement.focus();
|
|
13345
|
-
}, 0);
|
|
13346
|
-
}
|
|
13347
|
-
});
|
|
13348
|
-
effect(async () => {
|
|
13349
|
-
if (this.debouncedSearchText() === "" || this.aggregation() === null) {
|
|
13350
|
-
this.suggests.set([]);
|
|
13351
|
-
return;
|
|
13352
|
-
}
|
|
13353
|
-
const query = this.queryParamsStore.getQuery();
|
|
13354
|
-
const suggests = (await withFetch(() => fetchSuggestField(this.normalizedSearchText(), [this.aggregation()?.column || ""], query), this.injector)) || [];
|
|
13355
|
-
this.suggests.set(suggests);
|
|
13356
|
-
});
|
|
13357
13162
|
effect(() => {
|
|
13358
13163
|
this.filters.set(this.getFilters());
|
|
13359
13164
|
});
|
|
13360
13165
|
this.destroyRef.onDestroy(() => {
|
|
13361
|
-
// If the popover is closed with unapplied selections, reset state so it doesn't persist when reopening
|
|
13362
13166
|
sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
|
|
13363
13167
|
if (this.selection()) {
|
|
13364
13168
|
const unselect = (items) => {
|
|
@@ -13373,40 +13177,6 @@ class AggregationTreeComponent {
|
|
|
13373
13177
|
}
|
|
13374
13178
|
});
|
|
13375
13179
|
}
|
|
13376
|
-
// compare currentItems and updatedItems to add the new sub items
|
|
13377
|
-
// from updatedItems into currentItems to not alter the already loaded items
|
|
13378
|
-
addNewItems(currentItems, updatedItems, selectedParent) {
|
|
13379
|
-
updatedItems.forEach((item) => {
|
|
13380
|
-
const currentItem = currentItems.find((i) => i.value === item.value || i.value === `/${item.$path}/*`);
|
|
13381
|
-
if (currentItem) {
|
|
13382
|
-
if (currentItem.items?.length) {
|
|
13383
|
-
this.addNewItems(currentItem.items, item.items, item.$selected);
|
|
13384
|
-
}
|
|
13385
|
-
else {
|
|
13386
|
-
if (selectedParent || currentItem.$selected) {
|
|
13387
|
-
// select children
|
|
13388
|
-
const selectedItems = (items) => items.map((item) => {
|
|
13389
|
-
item.$selected = true;
|
|
13390
|
-
if (item.items)
|
|
13391
|
-
item.items = selectedItems(item.items);
|
|
13392
|
-
return item;
|
|
13393
|
-
});
|
|
13394
|
-
if (item.items)
|
|
13395
|
-
currentItem.items = selectedItems(item.items);
|
|
13396
|
-
}
|
|
13397
|
-
else {
|
|
13398
|
-
currentItem.items = item.items;
|
|
13399
|
-
}
|
|
13400
|
-
}
|
|
13401
|
-
}
|
|
13402
|
-
});
|
|
13403
|
-
}
|
|
13404
|
-
/**
|
|
13405
|
-
* Clears the current filter for the aggregation column.
|
|
13406
|
-
*
|
|
13407
|
-
* This method updates the filter in the `queryParamsStore` by setting the display value
|
|
13408
|
-
* of the current aggregation column to an empty string.
|
|
13409
|
-
*/
|
|
13410
13180
|
clear() {
|
|
13411
13181
|
const agg = this.aggregation();
|
|
13412
13182
|
if (agg) {
|
|
@@ -13426,72 +13196,25 @@ class AggregationTreeComponent {
|
|
|
13426
13196
|
this.onClear.emit();
|
|
13427
13197
|
}
|
|
13428
13198
|
}
|
|
13429
|
-
/**
|
|
13430
|
-
* Select all filters for the aggregation column.
|
|
13431
|
-
*/
|
|
13432
13199
|
selectAll() {
|
|
13433
13200
|
if (this.items().length) {
|
|
13434
|
-
this.selectItems(this.items(), true);
|
|
13201
|
+
this.base.selectItems(this.items(), true, true);
|
|
13435
13202
|
this.selection.set(true);
|
|
13436
13203
|
this.isAllSelected.set(true);
|
|
13437
13204
|
}
|
|
13438
13205
|
}
|
|
13439
|
-
/**
|
|
13440
|
-
* Unselect all filters for the aggregation column.
|
|
13441
|
-
*/
|
|
13442
13206
|
unselectAll() {
|
|
13443
13207
|
if (this.items().length) {
|
|
13444
|
-
this.selectItems(this.items(), false);
|
|
13208
|
+
this.base.selectItems(this.items(), false, true);
|
|
13445
13209
|
this.select();
|
|
13446
13210
|
this.isAllSelected.set(false);
|
|
13447
13211
|
}
|
|
13448
13212
|
}
|
|
13449
|
-
/**
|
|
13450
|
-
* Applies the current filters to the query parameters store.
|
|
13451
|
-
*
|
|
13452
|
-
* - If there are multiple filters, they are wrapped in an "or" filter.
|
|
13453
|
-
* - If the aggregation is not a distribution, the filters are merged into a single filter with an "in" operator.
|
|
13454
|
-
* - If there is only one filter, it is directly applied.
|
|
13455
|
-
* - If there are no filters, the current filters are cleared.
|
|
13456
|
-
*
|
|
13457
|
-
* After applying the filters, the search text is reset.
|
|
13458
|
-
*/
|
|
13459
13213
|
apply(overrideFilters) {
|
|
13460
13214
|
sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
|
|
13461
13215
|
const filters = overrideFilters || this.getFilters();
|
|
13462
|
-
const
|
|
13463
|
-
|
|
13464
|
-
if (filters.length > 1) {
|
|
13465
|
-
const display = filters[0].display;
|
|
13466
|
-
// if aggregation not a distribution, we need to merge the filters into a single filter with an in operator
|
|
13467
|
-
// with the values of the filters
|
|
13468
|
-
if (this.aggregation()?.isDistribution) {
|
|
13469
|
-
this.queryParamsStore.updateFilter({
|
|
13470
|
-
operator: "or",
|
|
13471
|
-
filters,
|
|
13472
|
-
name,
|
|
13473
|
-
field,
|
|
13474
|
-
display
|
|
13475
|
-
});
|
|
13476
|
-
}
|
|
13477
|
-
else {
|
|
13478
|
-
const values = filters.map((filter) => filter.value);
|
|
13479
|
-
this.queryParamsStore.updateFilter({
|
|
13480
|
-
operator: "in",
|
|
13481
|
-
name,
|
|
13482
|
-
field,
|
|
13483
|
-
values,
|
|
13484
|
-
display,
|
|
13485
|
-
filters
|
|
13486
|
-
});
|
|
13487
|
-
}
|
|
13488
|
-
}
|
|
13489
|
-
else if (filters.length === 1) {
|
|
13490
|
-
this.queryParamsStore.updateFilter(filters[0]);
|
|
13491
|
-
}
|
|
13492
|
-
else {
|
|
13493
|
-
this.clear();
|
|
13494
|
-
}
|
|
13216
|
+
const agg = this.aggregation();
|
|
13217
|
+
this.base.applyFilters(filters, agg, () => this.clear());
|
|
13495
13218
|
this.searchText.set("");
|
|
13496
13219
|
this.selection.set(false);
|
|
13497
13220
|
this.onApply.emit();
|
|
@@ -13504,214 +13227,118 @@ class AggregationTreeComponent {
|
|
|
13504
13227
|
}
|
|
13505
13228
|
async open(node) {
|
|
13506
13229
|
const q = this.queryParamsStore.getQuery();
|
|
13507
|
-
delete q.filters;
|
|
13230
|
+
delete q.filters;
|
|
13508
13231
|
const agg = await firstValueFrom(this.aggregationsService.open(q, this.aggregation(), node));
|
|
13509
13232
|
node.$opened = true;
|
|
13510
13233
|
this.aggregationsStore.updateAggregation(agg);
|
|
13511
13234
|
}
|
|
13512
|
-
/**
|
|
13513
|
-
* Updates the selected state of the given item in the aggregation list.
|
|
13514
|
-
*
|
|
13515
|
-
* @param item - The item to be selected or deselected.
|
|
13516
|
-
*
|
|
13517
|
-
* This method iterates through the items in the aggregation list and updates
|
|
13518
|
-
* the `$selected` property of the item that matches the value of the given item.
|
|
13519
|
-
*
|
|
13520
|
-
* If the item is selected, the selection count is incremented by 1.
|
|
13521
|
-
* If the item is deselected, the selection count is decremented by 1.
|
|
13522
|
-
*/
|
|
13523
13235
|
select() {
|
|
13524
|
-
const selectedItems = this.getFlattenTreeItems().filter(item => item.$selected);
|
|
13236
|
+
const selectedItems = this.getFlattenTreeItems().filter((item) => item.$selected);
|
|
13525
13237
|
this.onSelect.emit(selectedItems);
|
|
13526
|
-
// Keep apply visible if items are selected, or if active filters exist (user may be deselecting to clear)
|
|
13527
13238
|
this.selection.set(selectedItems.length > 0 || this.hasFilters());
|
|
13528
13239
|
this.verifySelected();
|
|
13529
13240
|
sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
|
|
13530
13241
|
}
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13242
|
+
/* item-level methods — called from the ng-template */
|
|
13243
|
+
treeItemName(item) {
|
|
13244
|
+
const value = item.display || item.value;
|
|
13245
|
+
return typeof value === "string" ? value : `${value}`;
|
|
13246
|
+
}
|
|
13247
|
+
isTreeItemFiltered(item, field) {
|
|
13248
|
+
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.treeItemName(item) });
|
|
13249
|
+
if (!filters)
|
|
13250
|
+
return false;
|
|
13251
|
+
const values = [item.value, `/${item.$path}/*`];
|
|
13252
|
+
return (values.some((v) => v === filters.value) ||
|
|
13253
|
+
!!(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
|
|
13254
|
+
}
|
|
13255
|
+
treeItemLevel(item) {
|
|
13256
|
+
const level = (item.$level ?? 0) - 1 + (!item.hasChildren ? 1 : 0);
|
|
13257
|
+
return item.hasChildren === false ? level + 1 : level;
|
|
13258
|
+
}
|
|
13259
|
+
treeChildrenPath(item, parentPath) {
|
|
13260
|
+
return parentPath.concat(`/${item.$path}/*`);
|
|
13261
|
+
}
|
|
13262
|
+
selectTreeItem(node, parent, e, updateChildren = false) {
|
|
13263
|
+
e?.stopImmediatePropagation();
|
|
13264
|
+
const selected = !node.$selected && !node.$selectedVisually;
|
|
13265
|
+
node.$selected = selected;
|
|
13266
|
+
node.$selectedVisually = false;
|
|
13267
|
+
if (updateChildren)
|
|
13268
|
+
this.selectTreeItemChildren(node.items, node.$selected);
|
|
13269
|
+
if (parent)
|
|
13270
|
+
this.handleTreeChildSelect(parent, node);
|
|
13271
|
+
this.select();
|
|
13272
|
+
}
|
|
13273
|
+
toggleTreeNode(e, node) {
|
|
13274
|
+
e.preventDefault();
|
|
13275
|
+
e.stopImmediatePropagation();
|
|
13276
|
+
if (node.items && node.$opened) {
|
|
13277
|
+
node.$opened = false;
|
|
13539
13278
|
return;
|
|
13540
13279
|
}
|
|
13541
|
-
if (
|
|
13542
|
-
|
|
13280
|
+
if (node.items && !node.$opened) {
|
|
13281
|
+
node.$opened = true;
|
|
13282
|
+
return;
|
|
13283
|
+
}
|
|
13284
|
+
this.open(node);
|
|
13285
|
+
}
|
|
13286
|
+
onTreeItemTextClick(node, parent, event) {
|
|
13287
|
+
if (this.quickFilter()) {
|
|
13288
|
+
this.selectTreeItem(node, parent, event, true);
|
|
13289
|
+
this.apply();
|
|
13543
13290
|
}
|
|
13544
|
-
event.preventDefault();
|
|
13545
13291
|
}
|
|
13546
|
-
/**
|
|
13547
|
-
* Retrieves the appropriate filters based on the aggregation type.
|
|
13548
|
-
*
|
|
13549
|
-
* If the aggregation is a tree structure, it returns filters specific to trees.
|
|
13550
|
-
* Otherwise, it returns filters for a list structure.
|
|
13551
|
-
*
|
|
13552
|
-
* @returns Filters for either a tree or list aggregation.
|
|
13553
|
-
*/
|
|
13554
13292
|
getFilters() {
|
|
13555
13293
|
if (this.aggregation()?.isTree) {
|
|
13556
13294
|
return this.getFiltersForTree();
|
|
13557
13295
|
}
|
|
13558
13296
|
return this.getFiltersForList();
|
|
13559
13297
|
}
|
|
13560
|
-
/**
|
|
13561
|
-
* Retrieves the filters for the tree structure.
|
|
13562
|
-
*
|
|
13563
|
-
* This method collects the selected items from the tree, constructs their paths,
|
|
13564
|
-
* and creates a filter object based on these paths. If no items are selected,
|
|
13565
|
-
* it returns an empty array.
|
|
13566
|
-
*
|
|
13567
|
-
* @returns {LegacyFilter[]} An array of filters for the tree structure.
|
|
13568
|
-
*/
|
|
13569
13298
|
getFiltersForTree() {
|
|
13570
13299
|
const { name, column: field } = this.aggregation() || {};
|
|
13571
|
-
if (!name || !field)
|
|
13300
|
+
if (!name || !field)
|
|
13572
13301
|
return [];
|
|
13573
|
-
}
|
|
13574
13302
|
const items = this.getFlattenTreeItems()
|
|
13575
13303
|
.filter((item) => item.$selected)
|
|
13576
|
-
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13577
|
-
const notSelectedItems = this.getFlattenTreeItems()
|
|
13578
|
-
.filter((item) => !item.$selected)
|
|
13579
|
-
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
|
|
13586
|
-
// if the current filters are not selected, we need to add them to the items
|
|
13587
|
-
if (filteredCurrentFilters.length > 0) {
|
|
13588
|
-
// if the current filters are not selected, we need to add them to the items
|
|
13589
|
-
items.push(...filteredCurrentFilters);
|
|
13590
|
-
}
|
|
13591
|
-
}
|
|
13592
|
-
// remove duplicates
|
|
13593
|
-
const uniqueItems = Array.from(new Set(items));
|
|
13594
|
-
// if no items are selected, return an empty array
|
|
13595
|
-
if (uniqueItems.length === 0)
|
|
13596
|
-
return [];
|
|
13597
|
-
const filter = { operator: "in", name, field, values: uniqueItems, display: uniqueItems[0] };
|
|
13598
|
-
return [filter];
|
|
13599
|
-
}
|
|
13600
|
-
/**
|
|
13601
|
-
* Retrieves a list of filters based on the selected items.
|
|
13602
|
-
*
|
|
13603
|
-
* This method filters the items to include only those that are selected,
|
|
13604
|
-
* and then maps each selected item to a filter using the `toFilter` method.
|
|
13605
|
-
*
|
|
13606
|
-
* @returns {LegacyFilter[]} An array of filters corresponding to the selected items.
|
|
13607
|
-
*/
|
|
13608
|
-
getFiltersForList() {
|
|
13609
|
-
const items = this.addCurrentFiltersToItems().filter((item) => item.$selected); // this.items().filter(item => item.$selected) || [];
|
|
13610
|
-
const searchedItems = this.searchedItems().filter((item) => item.$selected);
|
|
13611
|
-
const currentItems = [...items, ...searchedItems];
|
|
13612
|
-
const { column, name, isDistribution = false } = this.aggregation() || {};
|
|
13613
|
-
const selectedItems = currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
|
|
13614
|
-
return selectedItems;
|
|
13615
|
-
}
|
|
13616
|
-
/**
|
|
13617
|
-
* Recursively flattens a tree structure of `TreeAggregationNode` items into a single array.
|
|
13618
|
-
*
|
|
13619
|
-
* @returns {TreeAggregationNode[]} An array containing all nodes from the tree structure, flattened.
|
|
13620
|
-
*/
|
|
13621
|
-
getFlattenTreeItems() {
|
|
13622
|
-
const flattenItems = (items) => {
|
|
13623
|
-
return items.reduce((flat, item) => {
|
|
13624
|
-
return flat.concat(item, item.items ? flattenItems(item.items) : []);
|
|
13625
|
-
}, []);
|
|
13626
|
-
};
|
|
13627
|
-
// we need to flatten both the searched items and the current items to get all the items in the tree
|
|
13628
|
-
const searchedItemsFiltered = (this.searchedItems() || []).filter((item) => "items" in item);
|
|
13629
|
-
const searchItems = flattenItems(searchedItemsFiltered);
|
|
13630
|
-
const items = flattenItems(this.aggregation()?.items || []);
|
|
13631
|
-
const flattenedTreeItems = [...searchItems, ...items];
|
|
13632
|
-
return flattenedTreeItems;
|
|
13633
|
-
}
|
|
13634
|
-
addCurrentFiltersToItems() {
|
|
13635
|
-
const currentItems = this.aggregation()?.items || [];
|
|
13636
|
-
// add the current filters to the current items only if they are not already present
|
|
13637
|
-
if (!this.aggregation()?.isTree &&
|
|
13638
|
-
(!this.aggregation()?.isDistribution || this.aggregation()?.isDistribution === false)) {
|
|
13639
|
-
// get the current filters for the current aggregation
|
|
13640
|
-
const currentFilters = this.queryParamsStore.getFilter({
|
|
13641
|
-
field: this.aggregation()?.column,
|
|
13642
|
-
name: this.aggregation()?.name
|
|
13643
|
-
});
|
|
13644
|
-
// if there are current filters, we need to add them to the current items
|
|
13645
|
-
if (currentFilters) {
|
|
13646
|
-
// multiples filters
|
|
13647
|
-
if (currentFilters.filters) {
|
|
13648
|
-
currentFilters.filters.forEach((filter) => {
|
|
13649
|
-
// check if the filter is already present in the current items
|
|
13650
|
-
// if not, add it to the current items
|
|
13651
|
-
const found = currentItems.find((item) => item.value.toLocaleLowerCase() === filter.value?.toLocaleLowerCase());
|
|
13652
|
-
if (!found) {
|
|
13653
|
-
// add it to the current items
|
|
13654
|
-
currentItems.unshift({
|
|
13655
|
-
value: filter.value,
|
|
13656
|
-
display: filter.display,
|
|
13657
|
-
$selected: true
|
|
13658
|
-
});
|
|
13659
|
-
}
|
|
13660
|
-
else {
|
|
13661
|
-
// mark as selected the existing item
|
|
13662
|
-
found.$selected = true;
|
|
13663
|
-
}
|
|
13664
|
-
});
|
|
13665
|
-
}
|
|
13666
|
-
else {
|
|
13667
|
-
// single filter
|
|
13668
|
-
const found = currentItems.find((item) => item.value.toLocaleLowerCase() === currentFilters.value?.toLocaleLowerCase());
|
|
13669
|
-
if (!found) {
|
|
13670
|
-
// add it to the current items
|
|
13671
|
-
currentItems.push({
|
|
13672
|
-
value: currentFilters.value,
|
|
13673
|
-
display: currentFilters.display,
|
|
13674
|
-
$selected: true
|
|
13675
|
-
});
|
|
13676
|
-
}
|
|
13677
|
-
else {
|
|
13678
|
-
// mark as selected the existing item
|
|
13679
|
-
found.$selected = true;
|
|
13680
|
-
}
|
|
13681
|
-
}
|
|
13304
|
+
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13305
|
+
const notSelectedItems = this.getFlattenTreeItems()
|
|
13306
|
+
.filter((item) => !item.$selected)
|
|
13307
|
+
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13308
|
+
const currentFilters = this.queryParamsStore.getFilter({ field, name })?.values || [];
|
|
13309
|
+
if (currentFilters) {
|
|
13310
|
+
const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
|
|
13311
|
+
if (filteredCurrentFilters.length > 0) {
|
|
13312
|
+
items.push(...filteredCurrentFilters);
|
|
13682
13313
|
}
|
|
13683
13314
|
}
|
|
13684
|
-
|
|
13315
|
+
const uniqueItems = Array.from(new Set(items));
|
|
13316
|
+
if (uniqueItems.length === 0)
|
|
13317
|
+
return [];
|
|
13318
|
+
return [{ operator: "in", name, field, values: uniqueItems, display: uniqueItems[0] }];
|
|
13685
13319
|
}
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13320
|
+
getFiltersForList() {
|
|
13321
|
+
const items = this.addCurrentFiltersToItems().filter((item) => item.$selected);
|
|
13322
|
+
const searchedItems = this.searchedItems().filter((item) => item.$selected);
|
|
13323
|
+
const currentItems = [...items, ...searchedItems];
|
|
13324
|
+
const { column, name, isDistribution = false } = this.aggregation() || {};
|
|
13325
|
+
return currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
|
|
13326
|
+
}
|
|
13327
|
+
getFlattenTreeItems() {
|
|
13328
|
+
const flattenItems = (items) => items.reduce((flat, item) => flat.concat(item, item.items ? flattenItems(item.items) : []), []);
|
|
13329
|
+
const searchedItemsFiltered = (this.searchedItems() || []).filter((item) => "items" in item);
|
|
13330
|
+
return [
|
|
13331
|
+
...flattenItems(searchedItemsFiltered),
|
|
13332
|
+
...flattenItems(this.aggregation()?.items || []),
|
|
13333
|
+
];
|
|
13334
|
+
}
|
|
13335
|
+
addCurrentFiltersToItems() {
|
|
13336
|
+
return this.base.addCurrentFiltersToItems(this.aggregation());
|
|
13702
13337
|
}
|
|
13703
|
-
/**
|
|
13704
|
-
* Check whether all items are selected and update isAllSelected accordingly
|
|
13705
|
-
*/
|
|
13706
13338
|
verifySelected() {
|
|
13707
|
-
const someItemsUnselected = (items) =>
|
|
13708
|
-
return items.some((item) => !item.$selected || (item.items?.length && someItemsUnselected(item.items)));
|
|
13709
|
-
};
|
|
13339
|
+
const someItemsUnselected = (items) => items.some((item) => !item.$selected || (item.items?.length && someItemsUnselected(item.items)));
|
|
13710
13340
|
this.isAllSelected.set(!someItemsUnselected(this.items()));
|
|
13711
13341
|
}
|
|
13712
|
-
/**
|
|
13713
|
-
* set @items $selected and $selectedVisually to the values from @savedItems
|
|
13714
|
-
*/
|
|
13715
13342
|
setSelected(items, savedItems) {
|
|
13716
13343
|
return items.map((item) => {
|
|
13717
13344
|
const savedItem = savedItems.find((i) => i.value === item.value);
|
|
@@ -13729,9 +13356,8 @@ class AggregationTreeComponent {
|
|
|
13729
13356
|
if (!this.linkChildren())
|
|
13730
13357
|
return items;
|
|
13731
13358
|
items.forEach((item) => {
|
|
13732
|
-
if (item.items?.length)
|
|
13359
|
+
if (item.items?.length)
|
|
13733
13360
|
this.selectVisually(item.items, item.$selected || false);
|
|
13734
|
-
}
|
|
13735
13361
|
});
|
|
13736
13362
|
return items;
|
|
13737
13363
|
}
|
|
@@ -13742,39 +13368,49 @@ class AggregationTreeComponent {
|
|
|
13742
13368
|
this.selectVisually(item.items, item.$selected || item.$selectedVisually);
|
|
13743
13369
|
});
|
|
13744
13370
|
}
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13371
|
+
selectTreeItemChildren(items, select) {
|
|
13372
|
+
if (!this.linkChildren() || !items?.length)
|
|
13373
|
+
return;
|
|
13374
|
+
items.forEach((item) => {
|
|
13375
|
+
item.$selectedVisually = select;
|
|
13376
|
+
if (select)
|
|
13377
|
+
item.$selected = false;
|
|
13378
|
+
if (item.items?.length)
|
|
13379
|
+
this.selectTreeItemChildren(item.items, select);
|
|
13380
|
+
});
|
|
13381
|
+
}
|
|
13382
|
+
handleTreeChildSelect(parent, child) {
|
|
13383
|
+
if (this.linkChildren() && !child.$selected && parent.items.some((i) => i.$selectedVisually)) {
|
|
13384
|
+
parent.items.forEach((i) => {
|
|
13385
|
+
if (i !== child) {
|
|
13386
|
+
i.$selectedVisually = false;
|
|
13387
|
+
i.$selected = true;
|
|
13388
|
+
}
|
|
13389
|
+
});
|
|
13390
|
+
}
|
|
13391
|
+
if (this.linkChildren() && parent.items.some((i) => !i.$selectedVisually && !i.$selected)) {
|
|
13392
|
+
parent.$selected = false;
|
|
13393
|
+
parent.$selectedVisually = false;
|
|
13394
|
+
}
|
|
13748
13395
|
}
|
|
13749
13396
|
expandItems(items, expandedLevel) {
|
|
13750
|
-
this.traverse(items, (
|
|
13751
|
-
if (!node.$opened && node.items?.length
|
|
13397
|
+
this.traverse(items, (_lineage, node, level) => {
|
|
13398
|
+
if (!node.$opened && node.items?.length > 0 && level < expandedLevel) {
|
|
13752
13399
|
node.$opened = true;
|
|
13753
13400
|
}
|
|
13754
|
-
// if(node.$opened && level >= expandedLevel) {
|
|
13755
|
-
// node.$opened = false;
|
|
13756
|
-
// }
|
|
13757
13401
|
return false;
|
|
13758
13402
|
});
|
|
13759
13403
|
}
|
|
13760
|
-
/**
|
|
13761
|
-
* Traverses a tree structure, executing a callback function at every node
|
|
13762
|
-
* @param nodes the nodes to traverse
|
|
13763
|
-
* @param callback the callback function
|
|
13764
|
-
*/
|
|
13765
13404
|
traverse(nodes, callback) {
|
|
13766
|
-
if (!nodes || nodes.length === 0)
|
|
13405
|
+
if (!nodes || nodes.length === 0)
|
|
13767
13406
|
return false;
|
|
13768
|
-
|
|
13769
|
-
if (!callback) {
|
|
13407
|
+
if (!callback)
|
|
13770
13408
|
return false;
|
|
13771
|
-
}
|
|
13772
13409
|
const lineage = [];
|
|
13773
13410
|
const stack = [];
|
|
13774
13411
|
let _i = nodes.length;
|
|
13775
|
-
while (_i--)
|
|
13412
|
+
while (_i--)
|
|
13776
13413
|
stack.push(nodes[_i]);
|
|
13777
|
-
}
|
|
13778
13414
|
while (stack.length) {
|
|
13779
13415
|
const node = stack.pop();
|
|
13780
13416
|
if (!node) {
|
|
@@ -13782,54 +13418,248 @@ class AggregationTreeComponent {
|
|
|
13782
13418
|
}
|
|
13783
13419
|
else {
|
|
13784
13420
|
lineage.push(node);
|
|
13785
|
-
if (callback(lineage, node, lineage.length - 1))
|
|
13421
|
+
if (callback(lineage, node, lineage.length - 1))
|
|
13786
13422
|
return true;
|
|
13787
|
-
}
|
|
13788
13423
|
stack.push(undefined);
|
|
13789
13424
|
if (node.items && node.items.length > 0) {
|
|
13790
13425
|
_i = node.items.length;
|
|
13791
|
-
while (_i--)
|
|
13426
|
+
while (_i--)
|
|
13792
13427
|
stack.push(node.items[_i]);
|
|
13793
|
-
}
|
|
13794
13428
|
}
|
|
13795
13429
|
}
|
|
13796
13430
|
}
|
|
13797
13431
|
return false;
|
|
13798
13432
|
}
|
|
13799
|
-
clearSearch(e) {
|
|
13800
|
-
e.stopImmediatePropagation();
|
|
13801
|
-
this.searchText.set("");
|
|
13802
|
-
}
|
|
13803
13433
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13804
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\",class())" } }, viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }, { 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 <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the <Aggregation /> 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 @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <fa-icon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow truncate\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (showFiltersCount() && filtersCount() > 0) {\r\n <!-- count -->\r\n <Badge size=\"xs\" class=\"ml-1\">\r\n {{ filtersCount() }}\r\n </Badge>\r\n }\r\n <!-- apply filter block -->\r\n @if (!isCollapsed()) {\r\n @if (hasFilters()) {\r\n @let label = \"filters.clearFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); clear()\">\r\n <filter-x-icon />\r\n </button>\r\n }\r\n @if (selection()) {\r\n @let label = \"filters.apply\" | transloco;\r\n <button\r\n variant=\"accent\"\r\n size=\"sm\"\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); apply()\">\r\n <FilterIcon />\r\n {{ label }}\r\n </button>\r\n }\r\n\r\n <!-- select / unselect all -->\r\n @if (isAllSelected()) {\r\n @let label = \"filters.unselectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); unselectAll()\">\r\n <square-check-icon />\r\n </button>\r\n } @else {\r\n @let label = \"filters.selectAllFilters\" | transloco;\r\n <button\r\n variant=\"none\"\r\n icon-button\r\n [aria-label]=\"label\"\r\n (click)=\"$event.stopPropagation(); selectAll()\">\r\n <square-icon />\r\n </button>\r\n }\r\n }\r\n\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\r\n </icon-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 <InputGroupAddon align=\"inline-end\" class=\"gap-0.5!\">\r\n <icon-button\r\n size=\"sm\"\r\n [class]=\"\r\n searchText().length > 0\r\n ? 'rotate-90 cursor-pointer opacity-100 transition-[rotate,opacity] duration-500'\r\n : 'pointer-events-none rotate-0 opacity-0 transition-[rotate,opacity] duration-500'\r\n \"\r\n aria-label=\"Clear search\"\r\n [tabindex]=\"searchText().length > 0 ? 0 : -1\"\r\n (keydown.enter)=\"clearSearch($event)\"\r\n (click)=\"clearSearch($event)\">\r\n <XMarkIcon />\r\n </icon-button>\r\n <ng-content />\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 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", "scheme", "iconOnly", "size", "solid"] }, { 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", "scheme", "size"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", 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: "component", type: SearchIcon, selector: "SearchIcon", inputs: ["class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "component", type: SquareCheckIcon, selector: "square-check-icon, SquareCheckIcon", inputs: ["class"] }, { kind: "component", type: SquareIcon, selector: "square-icon, SquareIcon", inputs: ["class"] }, { kind: "component", type: XMarkIcon, selector: "XMarkIcon, xmark-icon, x-mark-icon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
13434
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AggregationTreeComponent, isStandalone: true, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\", class())" } }, viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the <Aggregation /> component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
|
|
13805
13435
|
}
|
|
13806
13436
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationTreeComponent, decorators: [{
|
|
13807
13437
|
type: Component,
|
|
13808
13438
|
args: [{ selector: "AggregationTree, aggregation-tree, aggregationtree", imports: [
|
|
13439
|
+
NgTemplateOutlet,
|
|
13809
13440
|
FormsModule,
|
|
13810
13441
|
ReactiveFormsModule,
|
|
13811
13442
|
ButtonComponent,
|
|
13812
|
-
|
|
13443
|
+
ChevronRightIcon,
|
|
13813
13444
|
SyslangPipe,
|
|
13814
13445
|
TranslocoPipe,
|
|
13815
|
-
BadgeComponent,
|
|
13816
|
-
ChevronRightIcon,
|
|
13817
|
-
InputGroupInput,
|
|
13818
|
-
InputGroupComponent,
|
|
13819
|
-
InputGroupAddonComponent,
|
|
13820
|
-
SearchIcon,
|
|
13821
|
-
FilterIcon,
|
|
13822
13446
|
FaIconComponent,
|
|
13823
13447
|
TriangleAlertIcon,
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13448
|
+
HighlightWordPipe,
|
|
13449
|
+
AggregationPanelComponent,
|
|
13450
|
+
], standalone: true, host: {
|
|
13451
|
+
"[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())',
|
|
13452
|
+
}, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the <Aggregation /> component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"] }]
|
|
13453
|
+
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], virtualItems: [{ type: i0.ViewChildren, args: ["virtualItem", { isSignal: true }] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
13454
|
+
|
|
13455
|
+
class AggregationListComponent {
|
|
13456
|
+
cn = cn;
|
|
13457
|
+
/* inputs */
|
|
13458
|
+
class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
13459
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
13460
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
13461
|
+
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
13462
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
13463
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
13464
|
+
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
13465
|
+
showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13466
|
+
/* outputs */
|
|
13467
|
+
onSelect = output();
|
|
13468
|
+
onApply = output();
|
|
13469
|
+
onClear = output();
|
|
13470
|
+
/* view queries */
|
|
13471
|
+
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
13472
|
+
/* selection state */
|
|
13473
|
+
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
13474
|
+
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
13475
|
+
/* search state */
|
|
13476
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
13477
|
+
/* composable — injects stores/services, wires search effects, provides shared methods */
|
|
13478
|
+
base = injectAggregationBase({
|
|
13479
|
+
name: this.name,
|
|
13480
|
+
column: this.column,
|
|
13481
|
+
searchText: this.searchText,
|
|
13482
|
+
});
|
|
13483
|
+
/* spread from base */
|
|
13484
|
+
aggregationsStore = this.base.aggregationsStore;
|
|
13485
|
+
queryParamsStore = this.base.queryParamsStore;
|
|
13486
|
+
appStore = this.base.appStore;
|
|
13487
|
+
aggregationsService = this.base.aggregationsService;
|
|
13488
|
+
injector = this.base.injector;
|
|
13489
|
+
destroyRef = this.base.destroyRef;
|
|
13490
|
+
suggests = this.base.suggests;
|
|
13491
|
+
hasFilters = this.base.hasFilters;
|
|
13492
|
+
filtersCount = this.base.filtersCount;
|
|
13493
|
+
query = this.base.query;
|
|
13494
|
+
filters = this.base.filters;
|
|
13495
|
+
/* features from appStore */
|
|
13496
|
+
showCount = computed(() => this.appStore.general()?.features?.showAggregationItemCount ?? false, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
|
|
13497
|
+
quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
|
|
13498
|
+
isDate = computed(() => this.appStore.isDateColumn(this.aggregation()?.column || ""), ...(ngDevMode ? [{ debugName: "isDate" }] : []));
|
|
13499
|
+
/* virtualizer */
|
|
13500
|
+
cdr = inject(ChangeDetectorRef);
|
|
13501
|
+
virtualizer = injectVirtualizer(() => ({
|
|
13502
|
+
count: this.items().length,
|
|
13503
|
+
estimateSize: () => 32,
|
|
13504
|
+
scrollElement: this.scrollElement(),
|
|
13505
|
+
}));
|
|
13506
|
+
aggregation = computed(() => {
|
|
13507
|
+
getState(this.aggregationsStore);
|
|
13508
|
+
const name = this.name();
|
|
13509
|
+
const column = this.column();
|
|
13510
|
+
if (name !== null) {
|
|
13511
|
+
const agg = this.aggregationsService.processAggregation(name, column);
|
|
13512
|
+
if (agg) {
|
|
13513
|
+
if (agg.isTree) {
|
|
13514
|
+
error("The aggregation component does not support tree aggregations. Please use the <AggregationTree /> component instead.");
|
|
13515
|
+
}
|
|
13516
|
+
agg.searchable = this.searchable() ?? agg.searchable;
|
|
13517
|
+
return agg;
|
|
13518
|
+
}
|
|
13519
|
+
}
|
|
13520
|
+
return null;
|
|
13521
|
+
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
13522
|
+
items = computed(() => {
|
|
13523
|
+
getState(this.aggregationsStore);
|
|
13524
|
+
const agg = this.aggregation();
|
|
13525
|
+
const searchedItems = this.searchedItems();
|
|
13526
|
+
if (searchedItems.length > 0)
|
|
13527
|
+
return searchedItems;
|
|
13528
|
+
if (!agg?.items)
|
|
13529
|
+
return [];
|
|
13530
|
+
// Reset $selected so items removed from the filter are deselected.
|
|
13531
|
+
// Then return a new array reference so Angular's signal equality check
|
|
13532
|
+
// detects the change and re-renders all component instances.
|
|
13533
|
+
agg.items.forEach(item => { item.$selected = false; });
|
|
13534
|
+
return [...this.addCurrentFiltersToItems()];
|
|
13535
|
+
}, ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
13536
|
+
isEmpty = computed(() => this.items().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
13537
|
+
searchedItems = computed(() => {
|
|
13538
|
+
if (!this.suggests())
|
|
13539
|
+
return [];
|
|
13540
|
+
return this.suggests()?.map((suggest) => {
|
|
13541
|
+
const column = this.appStore.getColumn(suggest.category);
|
|
13542
|
+
const item = {
|
|
13543
|
+
name: this.name(),
|
|
13544
|
+
value: suggest.normalized || suggest.display || "",
|
|
13545
|
+
display: suggest.display,
|
|
13546
|
+
column: column?.name ?? suggest.category,
|
|
13547
|
+
count: Number(suggest.frequency),
|
|
13548
|
+
$selected: false,
|
|
13549
|
+
};
|
|
13550
|
+
if (column?.eType === EngineType.bool) {
|
|
13551
|
+
item.value = Boolean(item.value);
|
|
13552
|
+
}
|
|
13553
|
+
return item;
|
|
13554
|
+
});
|
|
13555
|
+
}, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
|
|
13556
|
+
constructor() {
|
|
13557
|
+
this.destroyRef.onDestroy(() => {
|
|
13558
|
+
if (this.selection()) {
|
|
13559
|
+
this.aggregation()?.items?.forEach((item) => {
|
|
13560
|
+
item.$selected = undefined;
|
|
13561
|
+
});
|
|
13562
|
+
}
|
|
13563
|
+
});
|
|
13564
|
+
}
|
|
13565
|
+
clear() {
|
|
13566
|
+
const agg = this.aggregation();
|
|
13567
|
+
if (agg) {
|
|
13568
|
+
this.queryParamsStore.removeFilterByName(agg.name, agg.column);
|
|
13569
|
+
this.selection.set(false);
|
|
13570
|
+
this.isAllSelected.set(false);
|
|
13571
|
+
}
|
|
13572
|
+
this.onSelect.emit([]);
|
|
13573
|
+
this.onClear.emit();
|
|
13574
|
+
}
|
|
13575
|
+
selectAll() {
|
|
13576
|
+
if (this.items().length) {
|
|
13577
|
+
this.base.selectItems(this.items(), true);
|
|
13578
|
+
this.selection.set(true);
|
|
13579
|
+
this.isAllSelected.set(true);
|
|
13580
|
+
}
|
|
13581
|
+
}
|
|
13582
|
+
unselectAll() {
|
|
13583
|
+
if (this.items().length) {
|
|
13584
|
+
this.base.selectItems(this.items(), false);
|
|
13585
|
+
this.select();
|
|
13586
|
+
this.isAllSelected.set(false);
|
|
13587
|
+
}
|
|
13588
|
+
}
|
|
13589
|
+
apply() {
|
|
13590
|
+
const agg = this.aggregation();
|
|
13591
|
+
if (!agg)
|
|
13592
|
+
return;
|
|
13593
|
+
this.base.applyFilters(this.getFilters(), agg, () => this.clear());
|
|
13594
|
+
this.searchText.set("");
|
|
13595
|
+
this.selection.set(false);
|
|
13596
|
+
this.onApply.emit();
|
|
13597
|
+
}
|
|
13598
|
+
loadMore() {
|
|
13599
|
+
const q = this.queryParamsStore.getQuery();
|
|
13600
|
+
this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
|
|
13601
|
+
this.aggregationsStore.updateAggregation(aggregation);
|
|
13602
|
+
this.cdr.detectChanges();
|
|
13603
|
+
});
|
|
13604
|
+
}
|
|
13605
|
+
select() {
|
|
13606
|
+
const selectedItems = this.items().filter((item) => item.$selected);
|
|
13607
|
+
this.onSelect.emit(selectedItems);
|
|
13608
|
+
this.selection.set(selectedItems.length > 0 || this.hasFilters());
|
|
13609
|
+
}
|
|
13610
|
+
/* item-level methods — called from the ng-template */
|
|
13611
|
+
listItemName(item) {
|
|
13612
|
+
const value = item.display || item.value;
|
|
13613
|
+
return typeof value === "string" ? value : `${value}`;
|
|
13614
|
+
}
|
|
13615
|
+
isListItemFiltered(item, field) {
|
|
13616
|
+
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
|
|
13617
|
+
if (!filters)
|
|
13618
|
+
return false;
|
|
13619
|
+
const values = [item.value];
|
|
13620
|
+
return (values.some((v) => v === filters.value) ||
|
|
13621
|
+
!!(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
|
|
13622
|
+
}
|
|
13623
|
+
selectListItem(item, e) {
|
|
13624
|
+
e?.stopImmediatePropagation();
|
|
13625
|
+
item.$selected = !item.$selected;
|
|
13626
|
+
this.select();
|
|
13627
|
+
}
|
|
13628
|
+
onListItemTextClick(item, event) {
|
|
13629
|
+
if (this.quickFilter()) {
|
|
13630
|
+
this.selectListItem(item, event);
|
|
13631
|
+
this.apply();
|
|
13632
|
+
}
|
|
13633
|
+
}
|
|
13634
|
+
getFilters() {
|
|
13635
|
+
const items = this.items().filter((item) => item.$selected);
|
|
13636
|
+
const searchedItems = this.searchedItems().filter((item) => item.$selected);
|
|
13637
|
+
const currentItems = [...items, ...searchedItems];
|
|
13638
|
+
const { column, name, isDistribution = false } = this.aggregation() || {};
|
|
13639
|
+
return currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
|
|
13640
|
+
}
|
|
13641
|
+
addCurrentFiltersToItems() {
|
|
13642
|
+
return this.base.addCurrentFiltersToItems(this.aggregation());
|
|
13643
|
+
}
|
|
13644
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13645
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class())" } }, viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <AggregationTree /> component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
|
|
13646
|
+
}
|
|
13647
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListComponent, decorators: [{
|
|
13648
|
+
type: Component,
|
|
13649
|
+
args: [{ selector: "AggregationList, aggregation-list, aggregationlist", imports: [
|
|
13650
|
+
NgTemplateOutlet,
|
|
13651
|
+
FormsModule,
|
|
13652
|
+
ReactiveFormsModule,
|
|
13653
|
+
SyslangPipe,
|
|
13654
|
+
TranslocoPipe,
|
|
13655
|
+
FaIconComponent,
|
|
13656
|
+
TriangleAlertIcon,
|
|
13657
|
+
HighlightWordPipe,
|
|
13658
|
+
AggregationPanelComponent,
|
|
13829
13659
|
], standalone: true, host: {
|
|
13830
|
-
"[class]": 'cn("block h-[inherit] max-h-[inherit]
|
|
13831
|
-
}, template: "@if (
|
|
13832
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
13660
|
+
"[class]": 'cn("block h-[inherit] max-h-[inherit]", class())',
|
|
13661
|
+
}, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <AggregationTree /> component instead.\r\n </div>\r\n}\r\n<aggregation-panel\r\n [id]=\"id()\"\r\n [collapsible]=\"collapsible()\"\r\n [collapsed]=\"collapsed()\"\r\n [isEmpty]=\"isEmpty()\"\r\n [isDate]=\"isDate()\"\r\n [aggregation]=\"aggregation()\"\r\n [showFiltersCount]=\"showFiltersCount()\"\r\n [filtersCount]=\"filtersCount()\"\r\n [hasFilters]=\"hasFilters()\"\r\n [selection]=\"selection()\"\r\n [isAllSelected]=\"isAllSelected()\"\r\n [(searchText)]=\"searchText\"\r\n [itemsLength]=\"items().length\"\r\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\r\n [searchedItemsLength]=\"searchedItems().length\"\r\n (cleared)=\"clear()\"\r\n (applied)=\"apply()\"\r\n (allSelected)=\"selectAll()\"\r\n (allUnselected)=\"unselectAll()\"\r\n (loadedMore)=\"loadMore()\">\r\n <div\r\n #scrollElement\r\n class=\"scrollbar-thin max-h-(--scroll-height,20rem) w-full overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"] }]
|
|
13662
|
+
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
13833
13663
|
|
|
13834
13664
|
/**
|
|
13835
13665
|
* 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.
|
|
@@ -13986,7 +13816,7 @@ class AggregationComponent {
|
|
|
13986
13816
|
(onClear)="onClear.emit($event)"
|
|
13987
13817
|
/>
|
|
13988
13818
|
}
|
|
13989
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }, { kind: "component", type: AggregationTreeComponent, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: ["class", "id", "name", "column", "
|
|
13819
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }, { kind: "component", type: AggregationTreeComponent, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "expandedLevel", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }, { kind: "component", type: AggregationDateComponent, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: ["name", "column", "id", "collapsible", "collapsed", "searchable", "showFiltersCount", "title", "displayEmptyDistributionIntervals", "searchText"], outputs: ["onSelect", "onApply", "onClear", "searchTextChange"] }] });
|
|
13990
13820
|
}
|
|
13991
13821
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationComponent, decorators: [{
|
|
13992
13822
|
type: Component,
|
|
@@ -14302,6 +14132,7 @@ class MoreComponent {
|
|
|
14302
14132
|
includedFilters = input([], ...(ngDevMode ? [{ debugName: "includedFilters" }] : []));
|
|
14303
14133
|
excludedFilters = input([], ...(ngDevMode ? [{ debugName: "excludedFilters" }] : []));
|
|
14304
14134
|
aggregations = input(...(ngDevMode ? [undefined, { debugName: "aggregations" }] : []));
|
|
14135
|
+
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14305
14136
|
appStore = inject(AppStore);
|
|
14306
14137
|
aggregationsStore = inject(AggregationsStore);
|
|
14307
14138
|
queryParamsStore = inject(QueryParamsStore);
|
|
@@ -14329,7 +14160,7 @@ class MoreComponent {
|
|
|
14329
14160
|
effect(() => {
|
|
14330
14161
|
const count = this.count();
|
|
14331
14162
|
const authorizedFilters = this.aggregationsService
|
|
14332
|
-
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters())
|
|
14163
|
+
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
|
|
14333
14164
|
.toSpliced(0, count);
|
|
14334
14165
|
const f = authorizedFilters.map((agg) => {
|
|
14335
14166
|
const { icon = "far fa-list", hidden = false } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
|
|
@@ -14380,7 +14211,7 @@ class MoreComponent {
|
|
|
14380
14211
|
return count > 0;
|
|
14381
14212
|
}
|
|
14382
14213
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14383
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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: `
|
|
14214
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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 }, homepage: { classPropertyName: "homepage", publicName: "homepage", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "divide-y divide-muted-foreground/18" }, ngImport: i0, template: `
|
|
14384
14215
|
@for (filter of visibleFilters(); track $index) {
|
|
14385
14216
|
<Aggregation
|
|
14386
14217
|
class="w-60 max-w-80 px-1 [--height:15lh]"
|
|
@@ -14411,7 +14242,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14411
14242
|
`, host: {
|
|
14412
14243
|
class: "divide-y divide-muted-foreground/18"
|
|
14413
14244
|
}, styles: [":host{scrollbar-width:none}\n"] }]
|
|
14414
|
-
}], ctorParameters: () => [], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], includedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includedFilters", required: false }] }], excludedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludedFilters", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }] } });
|
|
14245
|
+
}], ctorParameters: () => [], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], includedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includedFilters", required: false }] }], excludedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludedFilters", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], homepage: [{ type: i0.Input, args: [{ isSignal: true, alias: "homepage", required: false }] }] } });
|
|
14415
14246
|
|
|
14416
14247
|
class MoreButtonComponent {
|
|
14417
14248
|
appStore = inject(AppStore);
|
|
@@ -14423,10 +14254,11 @@ class MoreButtonComponent {
|
|
|
14423
14254
|
includedFilters = input([], ...(ngDevMode ? [{ debugName: "includedFilters" }] : []));
|
|
14424
14255
|
excludedFilters = input([], ...(ngDevMode ? [{ debugName: "excludedFilters" }] : []));
|
|
14425
14256
|
aggregations = input(...(ngDevMode ? [undefined, { debugName: "aggregations" }] : []));
|
|
14257
|
+
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14426
14258
|
totalFiltersCount = computed(() => {
|
|
14427
14259
|
const count = this.count();
|
|
14428
14260
|
const authorizedFilters = this.aggregationsService
|
|
14429
|
-
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters())
|
|
14261
|
+
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
|
|
14430
14262
|
.toSpliced(0, count);
|
|
14431
14263
|
const total = authorizedFilters.reduce((acc, filter) => {
|
|
14432
14264
|
const f = this.queryParamsStore.getFilter(filter);
|
|
@@ -14436,7 +14268,7 @@ class MoreButtonComponent {
|
|
|
14436
14268
|
return total;
|
|
14437
14269
|
}, ...(ngDevMode ? [{ debugName: "totalFiltersCount" }] : []));
|
|
14438
14270
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MoreButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14439
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: MoreButtonComponent, isStandalone: true, selector: "more-button, MoreButton", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", 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 } }, ngImport: i0, template: `
|
|
14271
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: MoreButtonComponent, isStandalone: true, selector: "more-button, MoreButton", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", 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 }, homepage: { classPropertyName: "homepage", publicName: "homepage", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
14440
14272
|
<Popover class="group/more">
|
|
14441
14273
|
<button
|
|
14442
14274
|
variant="ghost"
|
|
@@ -14454,11 +14286,11 @@ class MoreButtonComponent {
|
|
|
14454
14286
|
|
|
14455
14287
|
<PopoverContent #contentRef="popoverContent" [position]="position()" class="min-w-max">
|
|
14456
14288
|
@if(contentRef.isVisible) {
|
|
14457
|
-
<More [count]="count()" [includedFilters]="includedFilters()" [excludedFilters]="excludedFilters()" [aggregations]="aggregations()" class="block h-full w-full max-w-80 [--height:55vh] min-w-40 overflow-hidden" />
|
|
14289
|
+
<More [count]="count()" [includedFilters]="includedFilters()" [excludedFilters]="excludedFilters()" [aggregations]="aggregations()" [homepage]="homepage()" class="block h-full w-full max-w-80 [--height:55vh] min-w-40 overflow-hidden" />
|
|
14458
14290
|
}
|
|
14459
14291
|
</PopoverContent>
|
|
14460
14292
|
</Popover>
|
|
14461
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: MoreComponent, selector: "more, More", inputs: ["count", "includedFilters", "excludedFilters", "aggregations"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size"] }, { kind: "component", type: ListFilterIcon, selector: "list-filter-icon, ListFilterIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
14293
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: MoreComponent, selector: "more, More", inputs: ["count", "includedFilters", "excludedFilters", "aggregations", "homepage"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size"] }, { kind: "component", type: ListFilterIcon, selector: "list-filter-icon, ListFilterIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
14462
14294
|
}
|
|
14463
14295
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MoreButtonComponent, decorators: [{
|
|
14464
14296
|
type: Component,
|
|
@@ -14484,13 +14316,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14484
14316
|
|
|
14485
14317
|
<PopoverContent #contentRef="popoverContent" [position]="position()" class="min-w-max">
|
|
14486
14318
|
@if(contentRef.isVisible) {
|
|
14487
|
-
<More [count]="count()" [includedFilters]="includedFilters()" [excludedFilters]="excludedFilters()" [aggregations]="aggregations()" class="block h-full w-full max-w-80 [--height:55vh] min-w-40 overflow-hidden" />
|
|
14319
|
+
<More [count]="count()" [includedFilters]="includedFilters()" [excludedFilters]="excludedFilters()" [aggregations]="aggregations()" [homepage]="homepage()" class="block h-full w-full max-w-80 [--height:55vh] min-w-40 overflow-hidden" />
|
|
14488
14320
|
}
|
|
14489
14321
|
</PopoverContent>
|
|
14490
14322
|
</Popover>
|
|
14491
14323
|
`
|
|
14492
14324
|
}]
|
|
14493
|
-
}], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], includedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includedFilters", required: false }] }], excludedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludedFilters", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }] } });
|
|
14325
|
+
}], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], includedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includedFilters", required: false }] }], excludedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludedFilters", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], homepage: [{ type: i0.Input, args: [{ isSignal: true, alias: "homepage", required: false }] }] } });
|
|
14494
14326
|
|
|
14495
14327
|
class FiltersBarComponent {
|
|
14496
14328
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
@@ -14528,6 +14360,15 @@ class FiltersBarComponent {
|
|
|
14528
14360
|
* @default true
|
|
14529
14361
|
*/
|
|
14530
14362
|
showMoreFiltersButton = input(true, ...(ngDevMode ? [{ debugName: "showMoreFiltersButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14363
|
+
/**
|
|
14364
|
+
* When enabled, only the filters flagged with `homepage: true` in the "filters" custom JSON
|
|
14365
|
+
* are displayed. If no filter is flagged, the bar shows no filters.
|
|
14366
|
+
*
|
|
14367
|
+
* Accepts a boolean value or a string that can be transformed to a boolean.
|
|
14368
|
+
*
|
|
14369
|
+
* @default false
|
|
14370
|
+
*/
|
|
14371
|
+
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14531
14372
|
direction = input("horizontal", ...(ngDevMode ? [{ debugName: "direction" }] : []));
|
|
14532
14373
|
/**
|
|
14533
14374
|
* The distance in pixels between the popover and its trigger element.
|
|
@@ -14577,50 +14418,76 @@ class FiltersBarComponent {
|
|
|
14577
14418
|
return this.aggregationsStore.aggregations().length > 0;
|
|
14578
14419
|
return false;
|
|
14579
14420
|
}, ...(ngDevMode ? [{ debugName: "hasAggregations" }] : []));
|
|
14421
|
+
/**
|
|
14422
|
+
* The full list of authorized filters, NOT capped by `filtersCount`.
|
|
14423
|
+
*
|
|
14424
|
+
* This computed signal performs the following operations:
|
|
14425
|
+
* 1. Retrieves aggregations from either the component's aggregations input or the app store
|
|
14426
|
+
* 2. Filters aggregations based on the route's filter criteria configuration
|
|
14427
|
+
* 3. Excludes filters specified in the `excludeFilters` list
|
|
14428
|
+
* 4. If `includeFilters` is not empty, only includes filters present in that list
|
|
14429
|
+
* 5. Maps the filtered aggregations to objects containing only `name` and `column` properties
|
|
14430
|
+
*/
|
|
14431
|
+
allAuthorizedFilters = computed(() => {
|
|
14432
|
+
return this.aggregationsService
|
|
14433
|
+
.getAuthorizedFilters(this.aggregations(), this.includeFilters(), this.excludeFilters(), this.homepage())
|
|
14434
|
+
.map((f) => ({ name: f.name, column: f.column }));
|
|
14435
|
+
}, ...(ngDevMode ? [{ debugName: "allAuthorizedFilters" }] : []));
|
|
14580
14436
|
/**
|
|
14581
14437
|
* Computes the list of additional filters that can be displayed in the "more filters" popover.
|
|
14582
14438
|
*
|
|
14583
|
-
*
|
|
14584
|
-
*
|
|
14585
|
-
*
|
|
14439
|
+
* Derived from the FULL authorized list (not the one capped by `filtersCount`), so the
|
|
14440
|
+
* filters beyond `filtersCount` — which are never rendered in the bar — are still counted.
|
|
14441
|
+
* Otherwise, when every rendered filter fits in the container, this list would be empty and
|
|
14442
|
+
* the "more" button would be hidden even though more filters exist beyond the cap.
|
|
14586
14443
|
*
|
|
14587
|
-
* This property manages the visibility
|
|
14444
|
+
* This property manages the visibility of the "more filters" button in the UI.
|
|
14588
14445
|
*
|
|
14589
14446
|
* @returns An array of Aggregation objects representing the additional filters available.
|
|
14590
14447
|
*/
|
|
14591
14448
|
hasMoreFilters = computed(() => {
|
|
14592
|
-
const moreFiltersAggregations = this.
|
|
14593
|
-
.filter((f) => !this.excludeFilters().includes(f.name)) // filter out the excluded filters
|
|
14594
|
-
.filter((f) => !this.includeFilters().length || this.includeFilters().includes(f.name)) // exclude filters not included in includeFilters if not empty
|
|
14595
|
-
.map((f) => ({ column: f.column, name: f.name }))
|
|
14449
|
+
const moreFiltersAggregations = this.allAuthorizedFilters()
|
|
14596
14450
|
.toSpliced(0, this.visibleFiltersCount())
|
|
14597
14451
|
.map((f) => this.aggregationsStore.getAggregation(f.column, "column"));
|
|
14598
14452
|
return moreFiltersAggregations;
|
|
14599
14453
|
}, ...(ngDevMode ? [{ debugName: "hasMoreFilters" }] : []));
|
|
14600
14454
|
/**
|
|
14601
|
-
*
|
|
14602
|
-
*
|
|
14603
|
-
* This computed signal performs the following operations:
|
|
14604
|
-
* 1. Retrieves aggregations from either the component's aggregations input or the app store
|
|
14605
|
-
* 2. Filters aggregations based on the route's filter criteria configuration
|
|
14606
|
-
* 3. Excludes filters specified in the `excludeFilters` list
|
|
14607
|
-
* 4. If `includeFilters` is not empty, only includes filters present in that list
|
|
14608
|
-
* 5. Maps the filtered aggregations to objects containing only `name` and `column` properties
|
|
14609
|
-
* 6. Limits the result to the number specified by `filtersCount`
|
|
14455
|
+
* The authorized filters rendered as buttons in the bar, limited to the number
|
|
14456
|
+
* specified by `filtersCount`.
|
|
14610
14457
|
*
|
|
14611
14458
|
* @returns An array of authorized filter objects, each containing `name` and `column` properties
|
|
14612
14459
|
*/
|
|
14613
14460
|
authorizedFilters = computed(() => {
|
|
14614
|
-
|
|
14615
|
-
.getAuthorizedFilters(this.aggregations(), this.includeFilters(), this.excludeFilters())
|
|
14616
|
-
.map((f) => ({ name: f.name, column: f.column }))
|
|
14617
|
-
.toSpliced(this.filtersCount());
|
|
14618
|
-
return authorizedFilters;
|
|
14461
|
+
return this.allAuthorizedFilters().toSpliced(this.filtersCount());
|
|
14619
14462
|
}, ...(ngDevMode ? [{ debugName: "authorizedFilters" }] : []));
|
|
14463
|
+
/**
|
|
14464
|
+
* Whether some authorized filters exist beyond the `filtersCount` cap.
|
|
14465
|
+
*
|
|
14466
|
+
* Those filters are never rendered in the bar and are only reachable through
|
|
14467
|
+
* the "more" button, which is therefore permanently visible: the overflow
|
|
14468
|
+
* manager must always reserve its space so the last filter button never
|
|
14469
|
+
* overlaps it (`reserveStop`).
|
|
14470
|
+
*/
|
|
14471
|
+
hasCappedFilters = computed(() => this.allAuthorizedFilters().length > this.filtersCount(), ...(ngDevMode ? [{ debugName: "hasCappedFilters" }] : []));
|
|
14620
14472
|
constructor() {
|
|
14621
14473
|
this.transloco.events$
|
|
14622
14474
|
.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(100))
|
|
14623
14475
|
.subscribe(() => this.overflowManagerRef()?.countItems());
|
|
14476
|
+
// Recount the overflow whenever the applied filters or basket change (e.g.
|
|
14477
|
+
// a filter modified or removed from the "more filters" popover). A
|
|
14478
|
+
// FilterButton hidden by the overflow manager (display: none) emits no
|
|
14479
|
+
// resize notification when its natural width changes, so it could fit in
|
|
14480
|
+
// the bar again without the manager knowing. afterRenderEffect guarantees
|
|
14481
|
+
// the DOM already reflects the new state when we measure.
|
|
14482
|
+
afterRenderEffect({
|
|
14483
|
+
read: () => {
|
|
14484
|
+
// track filters and basket changes (getState is reactive here)
|
|
14485
|
+
const { filters, basket } = getState(this.queryParamsStore);
|
|
14486
|
+
void filters;
|
|
14487
|
+
void basket;
|
|
14488
|
+
this.overflowManagerRef()?.countItems();
|
|
14489
|
+
}
|
|
14490
|
+
});
|
|
14624
14491
|
}
|
|
14625
14492
|
/**
|
|
14626
14493
|
* Clears all filters (included baskets) by invoking the clearFilters method on the queryParamsStore.
|
|
@@ -14669,8 +14536,8 @@ class FiltersBarComponent {
|
|
|
14669
14536
|
});
|
|
14670
14537
|
}
|
|
14671
14538
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: FiltersBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14672
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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: `
|
|
14673
|
-
<div overflowManager [direction]="direction()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14539
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", 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 }, homepage: { classPropertyName: "homepage", publicName: "homepage", 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 min-w-0', 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: `
|
|
14540
|
+
<div overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14674
14541
|
@if (hasFilters()) {
|
|
14675
14542
|
<button
|
|
14676
14543
|
variant="destructive"
|
|
@@ -14717,11 +14584,12 @@ class FiltersBarComponent {
|
|
|
14717
14584
|
[position]="morePosition()"
|
|
14718
14585
|
[includedFilters]="includeFilters()"
|
|
14719
14586
|
[excludedFilters]="excludeFilters()"
|
|
14720
|
-
[aggregations]="aggregations()"
|
|
14587
|
+
[aggregations]="aggregations()"
|
|
14588
|
+
[homepage]="homepage()" />
|
|
14721
14589
|
}
|
|
14722
14590
|
}
|
|
14723
14591
|
</div>
|
|
14724
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { 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: "component", type: TrashCanIcon, selector: "trash-can-icon, TrashCanIcon", inputs: ["class"] }, { kind: "component", type: InboxIcon, selector: "inbox-icon, InboxIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
14592
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: MoreButtonComponent, selector: "more-button, MoreButton", inputs: ["count", "position", "includedFilters", "excludedFilters", "aggregations", "homepage"] }, { kind: "component", type: FilterButtonComponent, selector: "filter-button, FilterButton", inputs: ["name", "column", "position", "offset", "expandedLevel"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction", "reserveStop"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "component", type: TrashCanIcon, selector: "trash-can-icon, TrashCanIcon", inputs: ["class"] }, { kind: "component", type: InboxIcon, selector: "inbox-icon, InboxIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
14725
14593
|
}
|
|
14726
14594
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: FiltersBarComponent, decorators: [{
|
|
14727
14595
|
type: Component,
|
|
@@ -14741,7 +14609,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14741
14609
|
],
|
|
14742
14610
|
providers: [provideTranslocoScope("filters")],
|
|
14743
14611
|
template: `
|
|
14744
|
-
<div overflowManager [direction]="direction()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14612
|
+
<div overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14745
14613
|
@if (hasFilters()) {
|
|
14746
14614
|
<button
|
|
14747
14615
|
variant="destructive"
|
|
@@ -14788,17 +14656,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14788
14656
|
[position]="morePosition()"
|
|
14789
14657
|
[includedFilters]="includeFilters()"
|
|
14790
14658
|
[excludedFilters]="excludeFilters()"
|
|
14791
|
-
[aggregations]="aggregations()"
|
|
14659
|
+
[aggregations]="aggregations()"
|
|
14660
|
+
[homepage]="homepage()" />
|
|
14792
14661
|
}
|
|
14793
14662
|
}
|
|
14794
14663
|
</div>
|
|
14795
14664
|
`,
|
|
14796
14665
|
host: {
|
|
14797
|
-
"[class]": "cn('block relative', class())",
|
|
14666
|
+
"[class]": "cn('block relative min-w-0', class())",
|
|
14798
14667
|
"(click)": "handleClick($event)"
|
|
14799
14668
|
}
|
|
14800
14669
|
}]
|
|
14801
|
-
}], 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 }] }] } });
|
|
14670
|
+
}], 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 }] }], homepage: [{ type: i0.Input, args: [{ isSignal: true, alias: "homepage", 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 }] }] } });
|
|
14802
14671
|
|
|
14803
14672
|
class LabelService {
|
|
14804
14673
|
appStore = inject(AppStore);
|
|
@@ -16187,5 +16056,5 @@ const queryNameResolver = () => {
|
|
|
16187
16056
|
* Generated bundle index. Do not edit.
|
|
16188
16057
|
*/
|
|
16189
16058
|
|
|
16190
|
-
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, 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 };
|
|
16059
|
+
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, 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, injectRouteNavigation, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
16191
16060
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|