@sinequa/atomic-angular 1.2.5 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/sinequa-atomic-angular.mjs +1287 -1405
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +1765 -1895
- 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,152 @@ 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 (Array.isArray(currentFilters.values) && currentFilters.values.length) {
|
|
8715
|
+
// multiple values stored as a string array, e.g. { values: ["alice_martin", "caroline_dubois"] }
|
|
8716
|
+
// (no `filters` sub-array and no single `value`) — mark each matching item as selected
|
|
8717
|
+
currentFilters.values.forEach((value) => {
|
|
8718
|
+
const found = currentItems.find((item) => item.value?.toString().toLocaleLowerCase() === value?.toString().toLocaleLowerCase());
|
|
8719
|
+
if (!found) {
|
|
8720
|
+
currentItems.unshift({ value, display: value, $selected: true });
|
|
8721
|
+
}
|
|
8722
|
+
else {
|
|
8723
|
+
found.$selected = true;
|
|
8724
|
+
}
|
|
8725
|
+
});
|
|
8726
|
+
}
|
|
8727
|
+
else if (currentFilters.value) {
|
|
8728
|
+
const found = currentItems.find((item) => item.value?.toString().toLocaleLowerCase() === currentFilters.value?.toLocaleLowerCase());
|
|
8729
|
+
if (!found) {
|
|
8730
|
+
currentItems.push({ value: currentFilters.value, display: currentFilters.display, $selected: true });
|
|
8731
|
+
}
|
|
8732
|
+
else {
|
|
8733
|
+
found.$selected = true;
|
|
8734
|
+
}
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
}
|
|
8738
|
+
return currentItems;
|
|
8739
|
+
}
|
|
8740
|
+
function applyFilters(appliedFilters, agg, clearFn) {
|
|
8741
|
+
const { name: aggName, column: field } = agg;
|
|
8742
|
+
if (appliedFilters.length > 1) {
|
|
8743
|
+
const display = appliedFilters[0].display;
|
|
8744
|
+
if (agg.isDistribution) {
|
|
8745
|
+
queryParamsStore.updateFilter({
|
|
8746
|
+
operator: "or",
|
|
8747
|
+
filters: appliedFilters,
|
|
8748
|
+
name: aggName,
|
|
8749
|
+
field,
|
|
8750
|
+
display,
|
|
8751
|
+
});
|
|
8752
|
+
}
|
|
8753
|
+
else {
|
|
8754
|
+
const values = appliedFilters.map((f) => f.value);
|
|
8755
|
+
queryParamsStore.updateFilter({
|
|
8756
|
+
operator: "in",
|
|
8757
|
+
name: aggName,
|
|
8758
|
+
field,
|
|
8759
|
+
values,
|
|
8760
|
+
display,
|
|
8761
|
+
filters: appliedFilters,
|
|
8762
|
+
});
|
|
8763
|
+
}
|
|
8764
|
+
}
|
|
8765
|
+
else if (appliedFilters.length === 1) {
|
|
8766
|
+
queryParamsStore.updateFilter(appliedFilters[0]);
|
|
8767
|
+
}
|
|
8768
|
+
else {
|
|
8769
|
+
clearFn();
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
8772
|
+
return {
|
|
8773
|
+
aggregationsStore,
|
|
8774
|
+
queryParamsStore,
|
|
8775
|
+
appStore,
|
|
8776
|
+
aggregationsService,
|
|
8777
|
+
injector,
|
|
8778
|
+
destroyRef,
|
|
8779
|
+
debouncedSearchText,
|
|
8780
|
+
normalizedSearchText,
|
|
8781
|
+
suggests,
|
|
8782
|
+
hasFilters,
|
|
8783
|
+
filtersCount,
|
|
8784
|
+
query,
|
|
8785
|
+
filters,
|
|
8786
|
+
clearSearch,
|
|
8787
|
+
selectItems,
|
|
8788
|
+
addCurrentFiltersToItems,
|
|
8789
|
+
applyFilters,
|
|
8790
|
+
};
|
|
8791
|
+
}
|
|
8792
|
+
|
|
8450
8793
|
/**
|
|
8451
8794
|
* Injection token that indicates whether custom date ranges are allowed.
|
|
8452
8795
|
*
|
|
@@ -8466,507 +8809,67 @@ const FILTER_DATE_ALLOW_CUSTOM_RANGE = new InjectionToken("date allow custom ran
|
|
|
8466
8809
|
factory: () => true
|
|
8467
8810
|
});
|
|
8468
8811
|
|
|
8469
|
-
|
|
8812
|
+
const options = {
|
|
8813
|
+
year: 'numeric',
|
|
8814
|
+
month: '2-digit',
|
|
8815
|
+
day: '2-digit'
|
|
8816
|
+
};
|
|
8817
|
+
class AggregationDateComponent {
|
|
8470
8818
|
cn = cn;
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
|
|
8485
|
-
linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
|
|
8486
|
-
isFiltered = computed(() => {
|
|
8487
|
-
const filters = this.queryParamsStore.getFilter({ field: this.field(), name: this.name() });
|
|
8488
|
-
if (!filters)
|
|
8489
|
-
return false;
|
|
8490
|
-
const values = [this.node().value]; // to also consider the treepath value
|
|
8491
|
-
return (values.some((v) => v === filters.value) ||
|
|
8492
|
-
(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
|
|
8493
|
-
}, ...(ngDevMode ? [{ debugName: "isFiltered" }] : []));
|
|
8494
|
-
name = computed(() => {
|
|
8495
|
-
const value = this.node().display || this.node().value;
|
|
8496
|
-
return typeof value === "string" ? value : `${value}`;
|
|
8497
|
-
}, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
8498
|
-
select(item, e) {
|
|
8499
|
-
e?.stopImmediatePropagation();
|
|
8500
|
-
const selected = !item.$selected;
|
|
8501
|
-
item.$selected = selected;
|
|
8502
|
-
this.onSelect.emit(item);
|
|
8503
|
-
}
|
|
8504
|
-
onTextClick(event) {
|
|
8505
|
-
if (this.quickFilter()) {
|
|
8506
|
-
this.select(this.node(), event);
|
|
8507
|
-
this.onFilter.emit();
|
|
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" }] : []));
|
|
8819
|
+
/* view queries */
|
|
8820
|
+
dateRangeDialog = viewChild(AggregationDateRangeDialogComponent, ...(ngDevMode ? [{ debugName: "dateRangeDialog" }] : []));
|
|
8821
|
+
/* inputs */
|
|
8822
|
+
name = input(null, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
8823
|
+
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
8824
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
8825
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
8826
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
8827
|
+
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
8828
|
+
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
8829
|
+
title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
8830
|
+
displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
|
|
8831
|
+
/* outputs */
|
|
8549
8832
|
onSelect = output();
|
|
8550
8833
|
onApply = output();
|
|
8551
8834
|
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
|
-
*/
|
|
8835
|
+
/* collapse state */
|
|
8573
8836
|
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
8837
|
expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8838
|
+
/* search state — unused by date component but required by AggregationBaseRefs */
|
|
8839
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
8840
|
+
searchInput = signal(undefined, ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
8841
|
+
/* composable — injects stores/services, wires search effects, provides shared methods */
|
|
8842
|
+
base = injectAggregationBase({
|
|
8843
|
+
name: this.name,
|
|
8844
|
+
column: this.column,
|
|
8845
|
+
searchText: this.searchText,
|
|
8846
|
+
searchInput: this.searchInput,
|
|
8847
|
+
expanded: this.expanded,
|
|
8848
|
+
});
|
|
8849
|
+
/* spread from base */
|
|
8850
|
+
aggregationsService = this.base.aggregationsService;
|
|
8851
|
+
queryParamsStore = this.base.queryParamsStore;
|
|
8852
|
+
hasFilters = this.base.hasFilters;
|
|
8853
|
+
destroyRef = this.base.destroyRef;
|
|
8854
|
+
/* injected services */
|
|
8855
|
+
allowCustomRange = inject(FILTER_DATE_ALLOW_CUSTOM_RANGE);
|
|
8856
|
+
transloco = inject(TranslocoService);
|
|
8857
|
+
/* state */
|
|
8588
8858
|
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 */
|
|
8859
|
+
validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
|
|
8595
8860
|
aggregation = computed(() => {
|
|
8596
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
8597
|
-
getState(this.aggregationsStore);
|
|
8598
8861
|
const name = this.name();
|
|
8599
|
-
const column = this.column();
|
|
8600
8862
|
if (name !== null) {
|
|
8601
|
-
const agg = this.aggregationsService.processAggregation(name, column);
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
// overrides "searchable" properties with the input if any
|
|
8607
|
-
agg.searchable = this.searchable() ?? agg.searchable;
|
|
8608
|
-
return agg;
|
|
8609
|
-
}
|
|
8863
|
+
const agg = this.aggregationsService.processAggregation(name, this.column());
|
|
8864
|
+
return {
|
|
8865
|
+
...agg,
|
|
8866
|
+
items: agg?.items?.filter((item) => item.display !== "custom-range") ?? []
|
|
8867
|
+
};
|
|
8610
8868
|
}
|
|
8611
8869
|
return null;
|
|
8612
8870
|
}, ...(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" }] : []));
|
|
8871
|
+
isEmpty = computed(() => this.aggregation() === null, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
8872
|
+
items = computed(() => this.aggregation()?.items ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
8970
8873
|
dateOptions = computed(() => translateAggregationToDateOptions(this.aggregation(), this.displayEmptyDistributionIntervals()), ...(ngDevMode ? [{ debugName: "dateOptions" }] : []));
|
|
8971
8874
|
form = new FormGroup({
|
|
8972
8875
|
option: new FormControl(null),
|
|
@@ -8977,7 +8880,6 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
8977
8880
|
});
|
|
8978
8881
|
today = new Date();
|
|
8979
8882
|
lang = signal(this.transloco.getActiveLang(), ...(ngDevMode ? [{ debugName: "lang" }] : []));
|
|
8980
|
-
validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
|
|
8981
8883
|
formValue = toSignal(this.form.valueChanges, { initialValue: this.form.value });
|
|
8982
8884
|
customRangeFrom = computed(() => {
|
|
8983
8885
|
const from = this.formValue().customRange?.from;
|
|
@@ -8988,7 +8890,6 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
8988
8890
|
return to ? new Date(to).toLocaleDateString(this.lang()) : "";
|
|
8989
8891
|
}, ...(ngDevMode ? [{ debugName: "customRangeTo" }] : []));
|
|
8990
8892
|
constructor() {
|
|
8991
|
-
super();
|
|
8992
8893
|
this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
|
|
8993
8894
|
this.lang.set(lang);
|
|
8994
8895
|
});
|
|
@@ -9007,17 +8908,6 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
9007
8908
|
(changes.option !== "custom-range" || changes.customRange?.from !== null || changes.customRange?.to !== null));
|
|
9008
8909
|
});
|
|
9009
8910
|
}
|
|
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
8911
|
select() {
|
|
9022
8912
|
this.selection.set(true);
|
|
9023
8913
|
}
|
|
@@ -9049,6 +8939,15 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
9049
8939
|
this.onClear.emit();
|
|
9050
8940
|
}
|
|
9051
8941
|
}
|
|
8942
|
+
onHeaderClick(event) {
|
|
8943
|
+
event.preventDefault();
|
|
8944
|
+
const isDate = this.aggregationsService.appStore.isDateColumn(this.aggregation()?.column || "");
|
|
8945
|
+
if (!isDate && this.isEmpty())
|
|
8946
|
+
return;
|
|
8947
|
+
if (this.collapsible()) {
|
|
8948
|
+
this.isCollapsed.update((value) => !value);
|
|
8949
|
+
}
|
|
8950
|
+
}
|
|
9052
8951
|
selectAndOpenDialog() {
|
|
9053
8952
|
this.select();
|
|
9054
8953
|
this.dateRangeDialog()?.open();
|
|
@@ -9174,7 +9073,7 @@ class AggregationDateComponent extends AggregationListComponent {
|
|
|
9174
9073
|
throw new Error("filters.filterInvalid");
|
|
9175
9074
|
}
|
|
9176
9075
|
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 },
|
|
9076
|
+
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
9077
|
}
|
|
9179
9078
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationDateComponent, decorators: [{
|
|
9180
9079
|
type: Component,
|
|
@@ -9192,8 +9091,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
9192
9091
|
IconButtonComponent
|
|
9193
9092
|
], host: {
|
|
9194
9093
|
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 }] }],
|
|
9094
|
+
}, 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"] }]
|
|
9095
|
+
}], 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
9096
|
|
|
9198
9097
|
/**
|
|
9199
9098
|
* Component that allows users to select a date or a date range for filtering search results.
|
|
@@ -9202,7 +9101,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
9202
9101
|
*/
|
|
9203
9102
|
class DateComponent extends AggregationDateComponent {
|
|
9204
9103
|
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-[
|
|
9104
|
+
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
9105
|
}
|
|
9207
9106
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: DateComponent, decorators: [{
|
|
9208
9107
|
type: Component,
|
|
@@ -9219,7 +9118,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
9219
9118
|
FilterXIcon
|
|
9220
9119
|
], host: {
|
|
9221
9120
|
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-[
|
|
9121
|
+
}, 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
9122
|
}] });
|
|
9224
9123
|
|
|
9225
9124
|
class ArticleEntities {
|
|
@@ -9862,6 +9761,8 @@ class AlertDialog {
|
|
|
9862
9761
|
if (!this.alert)
|
|
9863
9762
|
return;
|
|
9864
9763
|
const q = this.alert.query;
|
|
9764
|
+
// `q.filters` widened to `Filter[] | LegacyFilter[]` upstream, but the
|
|
9765
|
+
// query-params store deliberately deals in `LegacyFilter[]` only.
|
|
9865
9766
|
const filters = Array.isArray(q.filters) ? q.filters : undefined;
|
|
9866
9767
|
this.queryParamsStore.patch({ text: q.text, tab: q.tab, basket: q.basket, sort: q.sort, filters, name: q.name });
|
|
9867
9768
|
this.dialog()?.close();
|
|
@@ -10654,6 +10555,15 @@ class SignInComponent {
|
|
|
10654
10555
|
destroyRef;
|
|
10655
10556
|
cn = cn;
|
|
10656
10557
|
config = globalConfig;
|
|
10558
|
+
/**
|
|
10559
|
+
* True when authentication is handled outside the credentials form — i.e. by the
|
|
10560
|
+
* browser/proxy (`useSSO`) or by an auto-configured OAuth/SAML provider. In those
|
|
10561
|
+
* modes this screen shows a loader instead of a login form and initiates the
|
|
10562
|
+
* handshake automatically by calling `handleLogin()`.
|
|
10563
|
+
*/
|
|
10564
|
+
externalAuth = !!(globalConfig.useSSO ||
|
|
10565
|
+
globalConfig.autoOAuthProvider ||
|
|
10566
|
+
globalConfig.autoSAMLProvider);
|
|
10657
10567
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
10658
10568
|
forgotPassword = output();
|
|
10659
10569
|
username = model("", ...(ngDevMode ? [{ debugName: "username" }] : []));
|
|
@@ -10675,6 +10585,30 @@ class SignInComponent {
|
|
|
10675
10585
|
expiresSoonNotified = signal(false, ...(ngDevMode ? [{ debugName: "expiresSoonNotified" }] : []));
|
|
10676
10586
|
constructor(destroyRef) {
|
|
10677
10587
|
this.destroyRef = destroyRef;
|
|
10588
|
+
// If the user is already authenticated when landing here (e.g. page refresh on
|
|
10589
|
+
// /login, or an external handshake completed before this screen was created),
|
|
10590
|
+
// don't sit on the loader: go straight to the returnUrl.
|
|
10591
|
+
if (this.authenticated()) {
|
|
10592
|
+
const url = this.route.snapshot.queryParams["returnUrl"] || "/";
|
|
10593
|
+
this.router.navigateByUrl(url);
|
|
10594
|
+
}
|
|
10595
|
+
// When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML
|
|
10596
|
+
// provider, no credentials form is shown: this screen shows a loader and initiates
|
|
10597
|
+
// the handshake automatically by calling `handleLogin()`. If the handshake never
|
|
10598
|
+
// completes, fall back to /error after 5s; the fallback is cancelled as soon as
|
|
10599
|
+
// the login succeeds (the `authenticated` event then drives navigation).
|
|
10600
|
+
if (this.externalAuth && !this.authenticated()) {
|
|
10601
|
+
const timeout = setTimeout(() => {
|
|
10602
|
+
this.router.navigate(["/error"], {
|
|
10603
|
+
queryParams: { returnUrl: this.route.snapshot.queryParams["returnUrl"] }
|
|
10604
|
+
});
|
|
10605
|
+
}, 5000);
|
|
10606
|
+
destroyRef.onDestroy(() => clearTimeout(timeout));
|
|
10607
|
+
this.handleLogin().then(result => {
|
|
10608
|
+
if (result)
|
|
10609
|
+
clearTimeout(timeout);
|
|
10610
|
+
});
|
|
10611
|
+
}
|
|
10678
10612
|
effect(() => {
|
|
10679
10613
|
const principal = getState(this.principalStore);
|
|
10680
10614
|
if (this.authenticated() && principal && !this.expiresSoonNotified()) {
|
|
@@ -10720,14 +10654,16 @@ class SignInComponent {
|
|
|
10720
10654
|
this.router.navigate(["/login"]);
|
|
10721
10655
|
}
|
|
10722
10656
|
async handleLogin() {
|
|
10723
|
-
login().then((result) => {
|
|
10657
|
+
return login().then((result) => {
|
|
10724
10658
|
if (result) {
|
|
10725
10659
|
this.auditService.notifyLogin();
|
|
10726
10660
|
}
|
|
10661
|
+
return result;
|
|
10727
10662
|
}).catch(error => {
|
|
10728
10663
|
warn("An error occurred while logging in", error);
|
|
10729
10664
|
this.auditService.notify({ type: 'Login_Denied' });
|
|
10730
10665
|
this.router.navigate(["error"]);
|
|
10666
|
+
return false;
|
|
10731
10667
|
});
|
|
10732
10668
|
}
|
|
10733
10669
|
async handleLoginWithCredentials() {
|
|
@@ -10770,7 +10706,7 @@ class SignInComponent {
|
|
|
10770
10706
|
}
|
|
10771
10707
|
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
10708
|
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()) {
|
|
10709
|
+
@if (!authenticated() && !externalAuth) {
|
|
10774
10710
|
<Card
|
|
10775
10711
|
hover="no"
|
|
10776
10712
|
cdkTrapFocus
|
|
@@ -10781,60 +10717,54 @@ class SignInComponent {
|
|
|
10781
10717
|
</CardHeader>
|
|
10782
10718
|
|
|
10783
10719
|
<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
|
-
}
|
|
10720
|
+
<!-- authentication using credentials -->
|
|
10721
|
+
<div class="grid gap-2">
|
|
10722
|
+
<label class="text-sm font-medium" for="username">{{
|
|
10723
|
+
"login.username" | transloco
|
|
10724
|
+
}}</label>
|
|
10725
|
+
<input
|
|
10726
|
+
id="username"
|
|
10727
|
+
type="text"
|
|
10728
|
+
required
|
|
10729
|
+
[(ngModel)]="username"
|
|
10730
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10731
|
+
</div>
|
|
10732
|
+
|
|
10733
|
+
<div class="grid gap-2">
|
|
10734
|
+
<label class="text-sm font-medium" for="password">{{
|
|
10735
|
+
"login.password" | transloco
|
|
10736
|
+
}}</label>
|
|
10737
|
+
<input
|
|
10738
|
+
id="password"
|
|
10739
|
+
type="password"
|
|
10740
|
+
required
|
|
10741
|
+
[(ngModel)]="password"
|
|
10742
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10743
|
+
</div>
|
|
10744
|
+
|
|
10745
|
+
<span
|
|
10746
|
+
class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
|
|
10747
|
+
role="button"
|
|
10748
|
+
tabindex="0"
|
|
10749
|
+
(click)="forgotPassword.emit()"
|
|
10750
|
+
(keydown.enter)="forgotPassword.emit()">
|
|
10751
|
+
{{ "login.forgotPassword" | transloco }}
|
|
10752
|
+
</span>
|
|
10753
|
+
<button variant="primary"
|
|
10754
|
+
[disabled]="!isValid()"
|
|
10755
|
+
(click)="handleLoginWithCredentials()">
|
|
10756
|
+
{{ "login.connect" | transloco }}
|
|
10757
|
+
</button>
|
|
10832
10758
|
</CardContent>
|
|
10833
10759
|
</Card>
|
|
10834
10760
|
} @else {
|
|
10835
|
-
<
|
|
10761
|
+
<div class="flex h-dvh w-full items-center justify-center">
|
|
10762
|
+
<div class="flex flex-col items-center space-y-4">
|
|
10763
|
+
<span class="loader"></span>
|
|
10764
|
+
</div>
|
|
10765
|
+
</div>
|
|
10836
10766
|
}
|
|
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: "
|
|
10767
|
+
`, 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
10768
|
}
|
|
10839
10769
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SignInComponent, decorators: [{
|
|
10840
10770
|
type: Component,
|
|
@@ -10847,10 +10777,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
10847
10777
|
ButtonComponent,
|
|
10848
10778
|
CardComponent,
|
|
10849
10779
|
CardHeaderComponent,
|
|
10850
|
-
CardContentComponent
|
|
10851
|
-
LoadingComponent
|
|
10780
|
+
CardContentComponent
|
|
10852
10781
|
], providers: [provideTranslocoScope("login")], template: `
|
|
10853
|
-
@if (!authenticated()) {
|
|
10782
|
+
@if (!authenticated() && !externalAuth) {
|
|
10854
10783
|
<Card
|
|
10855
10784
|
hover="no"
|
|
10856
10785
|
cdkTrapFocus
|
|
@@ -10861,62 +10790,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
10861
10790
|
</CardHeader>
|
|
10862
10791
|
|
|
10863
10792
|
<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
|
-
}
|
|
10793
|
+
<!-- authentication using credentials -->
|
|
10794
|
+
<div class="grid gap-2">
|
|
10795
|
+
<label class="text-sm font-medium" for="username">{{
|
|
10796
|
+
"login.username" | transloco
|
|
10797
|
+
}}</label>
|
|
10798
|
+
<input
|
|
10799
|
+
id="username"
|
|
10800
|
+
type="text"
|
|
10801
|
+
required
|
|
10802
|
+
[(ngModel)]="username"
|
|
10803
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10804
|
+
</div>
|
|
10805
|
+
|
|
10806
|
+
<div class="grid gap-2">
|
|
10807
|
+
<label class="text-sm font-medium" for="password">{{
|
|
10808
|
+
"login.password" | transloco
|
|
10809
|
+
}}</label>
|
|
10810
|
+
<input
|
|
10811
|
+
id="password"
|
|
10812
|
+
type="password"
|
|
10813
|
+
required
|
|
10814
|
+
[(ngModel)]="password"
|
|
10815
|
+
(keydown.enter)="handleLoginWithCredentials()" />
|
|
10816
|
+
</div>
|
|
10817
|
+
|
|
10818
|
+
<span
|
|
10819
|
+
class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
|
|
10820
|
+
role="button"
|
|
10821
|
+
tabindex="0"
|
|
10822
|
+
(click)="forgotPassword.emit()"
|
|
10823
|
+
(keydown.enter)="forgotPassword.emit()">
|
|
10824
|
+
{{ "login.forgotPassword" | transloco }}
|
|
10825
|
+
</span>
|
|
10826
|
+
<button variant="primary"
|
|
10827
|
+
[disabled]="!isValid()"
|
|
10828
|
+
(click)="handleLoginWithCredentials()">
|
|
10829
|
+
{{ "login.connect" | transloco }}
|
|
10830
|
+
</button>
|
|
10912
10831
|
</CardContent>
|
|
10913
10832
|
</Card>
|
|
10914
10833
|
} @else {
|
|
10915
|
-
<
|
|
10834
|
+
<div class="flex h-dvh w-full items-center justify-center">
|
|
10835
|
+
<div class="flex flex-col items-center space-y-4">
|
|
10836
|
+
<span class="loader"></span>
|
|
10837
|
+
</div>
|
|
10838
|
+
</div>
|
|
10916
10839
|
}
|
|
10917
10840
|
`, host: {
|
|
10918
10841
|
"[class]": "cn('grid h-dvh w-full place-content-center', class())"
|
|
10919
|
-
}, styles: ["input{background-color:var(--background)}\n"] }]
|
|
10842
|
+
}, 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
10843
|
}], 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
10844
|
|
|
10922
10845
|
class AuthPageComponent {
|
|
@@ -11308,7 +11231,7 @@ class OverrideUserDialogComponent {
|
|
|
11308
11231
|
}
|
|
11309
11232
|
}
|
|
11310
11233
|
handleOverrideUser(username, domain) {
|
|
11311
|
-
const { useSSO, createRoutes } = globalConfig;
|
|
11234
|
+
const { useSSO, createRoutes, useCredentials } = globalConfig;
|
|
11312
11235
|
if (username === undefined || domain === undefined) {
|
|
11313
11236
|
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11314
11237
|
}
|
|
@@ -11316,7 +11239,7 @@ class OverrideUserDialogComponent {
|
|
|
11316
11239
|
setGlobalConfig({ userOverrideActive: true, userOverride: { username, domain } });
|
|
11317
11240
|
}
|
|
11318
11241
|
// Login with the new user
|
|
11319
|
-
if (useSSO) {
|
|
11242
|
+
if (useSSO && !useCredentials) {
|
|
11320
11243
|
this.appService
|
|
11321
11244
|
.initialize(createRoutes)
|
|
11322
11245
|
.then(() => {
|
|
@@ -11341,6 +11264,7 @@ class OverrideUserDialogComponent {
|
|
|
11341
11264
|
})
|
|
11342
11265
|
.catch((err) => {
|
|
11343
11266
|
error("An error occured while overriding (initialize)", err);
|
|
11267
|
+
notify.error(err.message, { duration: 2000 });
|
|
11344
11268
|
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11345
11269
|
});
|
|
11346
11270
|
}
|
|
@@ -11898,11 +11822,16 @@ class DrawerAdvancedFiltersComponent extends DrawerComponent {
|
|
|
11898
11822
|
text = "";
|
|
11899
11823
|
constructor() {
|
|
11900
11824
|
super();
|
|
11901
|
-
|
|
11825
|
+
effect(() => {
|
|
11826
|
+
getState(this.appStore);
|
|
11827
|
+
const query = this.appStore.getDefaultQuery();
|
|
11828
|
+
if (query?.name) {
|
|
11829
|
+
this.getFirstPageQuery(query?.name);
|
|
11830
|
+
}
|
|
11831
|
+
});
|
|
11902
11832
|
}
|
|
11903
|
-
async getFirstPageQuery() {
|
|
11904
|
-
const
|
|
11905
|
-
const response = await fetchQuery({ isFirstPage: true, name: query.name });
|
|
11833
|
+
async getFirstPageQuery(queryName) {
|
|
11834
|
+
const response = await fetchQuery({ isFirstPage: true, name: queryName });
|
|
11906
11835
|
this.aggregations.set(response.aggregations);
|
|
11907
11836
|
}
|
|
11908
11837
|
onTabChange(tab) {
|
|
@@ -13037,201 +12966,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
13037
12966
|
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
12967
|
}], 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
12968
|
|
|
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);
|
|
12969
|
+
class AggregationPanelComponent {
|
|
12970
|
+
/* collapse */
|
|
12971
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
12972
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
12973
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
12974
|
+
isDate = input(false, ...(ngDevMode ? [{ debugName: "isDate" }] : []));
|
|
12975
|
+
isEmpty = input(false, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
12976
|
+
/* aggregation data — used for label, search input id and load-more condition */
|
|
12977
|
+
aggregation = input(null, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
12978
|
+
/* header button state (driven by parent) */
|
|
12979
|
+
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12980
|
+
filtersCount = input(0, ...(ngDevMode ? [{ debugName: "filtersCount" }] : []));
|
|
12981
|
+
hasFilters = input(false, ...(ngDevMode ? [{ debugName: "hasFilters" }] : []));
|
|
12982
|
+
selection = input(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
12983
|
+
isAllSelected = input(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
12984
|
+
/* search */
|
|
12985
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
12986
|
+
itemsLength = input(0, ...(ngDevMode ? [{ debugName: "itemsLength" }] : []));
|
|
12987
|
+
/* load more */
|
|
12988
|
+
hasMore = input(false, ...(ngDevMode ? [{ debugName: "hasMore" }] : []));
|
|
12989
|
+
searchedItemsLength = input(0, ...(ngDevMode ? [{ debugName: "searchedItemsLength" }] : []));
|
|
12990
|
+
/* outputs — parent reacts to user actions */
|
|
12991
|
+
cleared = output();
|
|
12992
|
+
applied = output();
|
|
12993
|
+
allSelected = output();
|
|
12994
|
+
allUnselected = output();
|
|
12995
|
+
loadedMore = output();
|
|
12996
|
+
/* internal collapse state */
|
|
12997
|
+
isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
|
|
12998
|
+
expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
12999
|
+
searchInput = viewChild("searchInput", ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
13000
|
+
isInPopover = !!(inject(PopoverContentComponent, { optional: true }) ||
|
|
13001
|
+
inject(DropdownContentComponent, { optional: true }) ||
|
|
13002
|
+
inject(DropdownDirective, { optional: true }));
|
|
13003
|
+
constructor() {
|
|
13004
|
+
effect(() => {
|
|
13005
|
+
if (this.isInPopover && this.searchInput()?.nativeElement && this.expanded() !== null) {
|
|
13006
|
+
setTimeout(() => this.searchInput()?.nativeElement.focus(), 0);
|
|
13099
13007
|
}
|
|
13100
13008
|
});
|
|
13101
13009
|
}
|
|
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;
|
|
13010
|
+
onHeaderClick(event) {
|
|
13011
|
+
if (!this.isDate() && this.isEmpty()) {
|
|
13012
|
+
event.preventDefault();
|
|
13112
13013
|
return;
|
|
13113
13014
|
}
|
|
13114
|
-
this.
|
|
13115
|
-
|
|
13116
|
-
onTextClick(event) {
|
|
13117
|
-
if (this.quickFilter()) {
|
|
13118
|
-
this.select(this.node(), event, true);
|
|
13119
|
-
this.onFilter.emit();
|
|
13015
|
+
if (this.collapsible()) {
|
|
13016
|
+
this.isCollapsed.update((v) => !v);
|
|
13120
13017
|
}
|
|
13018
|
+
event.preventDefault();
|
|
13121
13019
|
}
|
|
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());
|
|
13020
|
+
onToggle(event) {
|
|
13021
|
+
const e = event;
|
|
13022
|
+
this.isCollapsed.set(e.newState === "closed");
|
|
13023
|
+
}
|
|
13024
|
+
clearSearch(e) {
|
|
13025
|
+
e.stopImmediatePropagation();
|
|
13026
|
+
this.searchText.set("");
|
|
13138
13027
|
}
|
|
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:
|
|
13028
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13029
|
+
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
13030
|
}
|
|
13142
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type:
|
|
13031
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationPanelComponent, decorators: [{
|
|
13143
13032
|
type: Component,
|
|
13144
|
-
args: [{ selector: "aggregation-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13033
|
+
args: [{ selector: "AggregationPanel, aggregation-panel", standalone: true, imports: [
|
|
13034
|
+
FormsModule,
|
|
13035
|
+
ButtonComponent,
|
|
13036
|
+
SyslangPipe,
|
|
13037
|
+
TranslocoPipe,
|
|
13038
|
+
BadgeComponent,
|
|
13039
|
+
ChevronRightIcon,
|
|
13040
|
+
InputGroupInput,
|
|
13041
|
+
InputGroupComponent,
|
|
13042
|
+
InputGroupAddonComponent,
|
|
13043
|
+
SearchIcon,
|
|
13044
|
+
FilterIcon,
|
|
13045
|
+
FaIconComponent,
|
|
13046
|
+
FilterXIcon,
|
|
13047
|
+
SquareCheckIcon,
|
|
13048
|
+
SquareIcon,
|
|
13049
|
+
XMarkIcon,
|
|
13050
|
+
IconButtonComponent,
|
|
13051
|
+
], 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" }]
|
|
13052
|
+
}], 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
13053
|
|
|
13150
13054
|
class AggregationTreeComponent {
|
|
13151
13055
|
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);
|
|
13056
|
+
/* inputs */
|
|
13172
13057
|
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
13058
|
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
13178
13059
|
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
13179
13060
|
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
13061
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
13062
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
13063
|
+
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
13064
|
+
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13180
13065
|
expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: (v) => {
|
|
13181
13066
|
const n = numberAttribute(v);
|
|
13182
13067
|
return Number.isNaN(n) ? undefined : n;
|
|
13183
|
-
} }] : [{
|
|
13068
|
+
} }] : [{
|
|
13069
|
+
transform: (v) => {
|
|
13184
13070
|
const n = numberAttribute(v);
|
|
13185
13071
|
return Number.isNaN(n) ? undefined : n;
|
|
13186
|
-
}
|
|
13072
|
+
},
|
|
13073
|
+
}]));
|
|
13074
|
+
/* outputs */
|
|
13187
13075
|
onSelect = output();
|
|
13188
13076
|
onApply = output();
|
|
13189
13077
|
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" }] : []));
|
|
13078
|
+
/* view queries */
|
|
13079
|
+
virtualItems = viewChildren("virtualItem", ...(ngDevMode ? [{ debugName: "virtualItems" }] : []));
|
|
13080
|
+
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
13081
|
+
/* selection state */
|
|
13226
13082
|
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13083
|
+
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
13084
|
+
/* search state */
|
|
13085
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
13086
|
+
/* composable — injects stores/services, wires search effects, provides shared methods */
|
|
13087
|
+
base = injectAggregationBase({
|
|
13088
|
+
name: this.name,
|
|
13089
|
+
column: this.column,
|
|
13090
|
+
searchText: this.searchText,
|
|
13091
|
+
});
|
|
13092
|
+
/* spread from base */
|
|
13093
|
+
aggregationsStore = this.base.aggregationsStore;
|
|
13094
|
+
queryParamsStore = this.base.queryParamsStore;
|
|
13095
|
+
appStore = this.base.appStore;
|
|
13096
|
+
aggregationsService = this.base.aggregationsService;
|
|
13097
|
+
injector = this.base.injector;
|
|
13098
|
+
destroyRef = this.base.destroyRef;
|
|
13099
|
+
debouncedSearchText = this.base.debouncedSearchText;
|
|
13100
|
+
normalizedSearchText = this.base.normalizedSearchText;
|
|
13101
|
+
suggests = this.base.suggests;
|
|
13102
|
+
hasFilters = this.base.hasFilters;
|
|
13103
|
+
filtersCount = this.base.filtersCount;
|
|
13104
|
+
query = this.base.query;
|
|
13105
|
+
filters = this.base.filters;
|
|
13106
|
+
/* features from appStore */
|
|
13107
|
+
showCount = computed(() => this.appStore.general()?.features?.showAggregationItemCount ?? false, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
|
|
13108
|
+
quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
|
|
13109
|
+
isDate = computed(() => this.appStore.isDateColumn(this.aggregation()?.column || ""), ...(ngDevMode ? [{ debugName: "isDate" }] : []));
|
|
13110
|
+
/* virtualizer */
|
|
13111
|
+
virtualizer = injectVirtualizer(() => ({
|
|
13112
|
+
count: this.items().length,
|
|
13113
|
+
estimateSize: () => 32,
|
|
13114
|
+
scrollElement: this.scrollElement(),
|
|
13115
|
+
}));
|
|
13116
|
+
#measureItems = effect(() => this.virtualItems().forEach((el) => this.virtualizer.measureElement(el.nativeElement)), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
|
|
13117
|
+
linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
|
|
13233
13118
|
aggregation = computed(() => {
|
|
13234
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
13235
13119
|
getState(this.aggregationsStore);
|
|
13236
13120
|
const name = this.name();
|
|
13237
13121
|
const column = this.column();
|
|
@@ -13241,21 +13125,17 @@ class AggregationTreeComponent {
|
|
|
13241
13125
|
if (!agg.isTree) {
|
|
13242
13126
|
error("The aggregation tree component does not support list aggregations. Please use the <Aggregation /> component instead.");
|
|
13243
13127
|
}
|
|
13244
|
-
// overrides "expandedLevel" from custom JSON file
|
|
13245
13128
|
const expandedLevel = this.expandedLevel() ?? agg.expandedLevel;
|
|
13246
13129
|
if (expandedLevel) {
|
|
13247
13130
|
this.expandItems(agg.items, expandedLevel);
|
|
13248
13131
|
}
|
|
13249
|
-
// overrides "searchable" properties with the input if any
|
|
13250
13132
|
agg.searchable = this.searchable() ?? agg.searchable;
|
|
13251
13133
|
return agg;
|
|
13252
13134
|
}
|
|
13253
13135
|
}
|
|
13254
13136
|
return null;
|
|
13255
13137
|
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
13256
|
-
/* items of the aggregation */
|
|
13257
13138
|
items = computed(() => {
|
|
13258
|
-
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
13259
13139
|
getState(this.aggregationsStore);
|
|
13260
13140
|
const agg = this.aggregation();
|
|
13261
13141
|
const searchedItems = this.searchedItems();
|
|
@@ -13266,57 +13146,17 @@ class AggregationTreeComponent {
|
|
|
13266
13146
|
else if (agg?.items) {
|
|
13267
13147
|
res = this.addCurrentFiltersToItems();
|
|
13268
13148
|
}
|
|
13269
|
-
// use session storage to keep the selected statuses
|
|
13270
13149
|
const sessionAggItem = sessionStorage.getItem(`agg-${agg?.column}`);
|
|
13271
13150
|
const sessionAgg = JSON.parse(sessionAggItem || "[]");
|
|
13272
13151
|
return this.processAggregations(sessionAgg.length ? this.setSelected(res, sessionAgg) : res);
|
|
13273
13152
|
}, ...(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
13153
|
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
13154
|
searchedItems = computed(() => {
|
|
13313
13155
|
if (!this.suggests())
|
|
13314
13156
|
return [];
|
|
13315
|
-
// if the aggregation is a tree, we transform the suggestions into tree nodes
|
|
13316
13157
|
if (this.aggregation()?.isTree) {
|
|
13317
13158
|
return suggestionsToTreeAggregationNodes(this.suggests(), this.searchText());
|
|
13318
13159
|
}
|
|
13319
|
-
// if the aggregation is not a tree, we return the suggestions as is
|
|
13320
13160
|
return this.suggests()?.map((suggest) => ({
|
|
13321
13161
|
name: this.name(),
|
|
13322
13162
|
value: suggest.normalized || suggest.display || "",
|
|
@@ -13324,41 +13164,18 @@ class AggregationTreeComponent {
|
|
|
13324
13164
|
column: suggest.category,
|
|
13325
13165
|
count: Number(suggest.frequency),
|
|
13326
13166
|
$selected: false,
|
|
13327
|
-
items: []
|
|
13167
|
+
items: [],
|
|
13328
13168
|
}));
|
|
13329
13169
|
}, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
|
|
13330
|
-
linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
|
|
13331
|
-
query;
|
|
13332
|
-
filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
13333
13170
|
constructor() {
|
|
13334
|
-
this.query = buildQuery();
|
|
13335
13171
|
effect(() => {
|
|
13336
|
-
// if the aggregation store changes, remove previous session storage
|
|
13337
13172
|
getState(this.aggregationsStore);
|
|
13338
13173
|
sessionStorage.removeItem(`agg-${this.column()}`);
|
|
13339
13174
|
});
|
|
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
13175
|
effect(() => {
|
|
13358
13176
|
this.filters.set(this.getFilters());
|
|
13359
13177
|
});
|
|
13360
13178
|
this.destroyRef.onDestroy(() => {
|
|
13361
|
-
// If the popover is closed with unapplied selections, reset state so it doesn't persist when reopening
|
|
13362
13179
|
sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
|
|
13363
13180
|
if (this.selection()) {
|
|
13364
13181
|
const unselect = (items) => {
|
|
@@ -13373,40 +13190,6 @@ class AggregationTreeComponent {
|
|
|
13373
13190
|
}
|
|
13374
13191
|
});
|
|
13375
13192
|
}
|
|
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
13193
|
clear() {
|
|
13411
13194
|
const agg = this.aggregation();
|
|
13412
13195
|
if (agg) {
|
|
@@ -13426,72 +13209,25 @@ class AggregationTreeComponent {
|
|
|
13426
13209
|
this.onClear.emit();
|
|
13427
13210
|
}
|
|
13428
13211
|
}
|
|
13429
|
-
/**
|
|
13430
|
-
* Select all filters for the aggregation column.
|
|
13431
|
-
*/
|
|
13432
13212
|
selectAll() {
|
|
13433
13213
|
if (this.items().length) {
|
|
13434
|
-
this.selectItems(this.items(), true);
|
|
13214
|
+
this.base.selectItems(this.items(), true, true);
|
|
13435
13215
|
this.selection.set(true);
|
|
13436
13216
|
this.isAllSelected.set(true);
|
|
13437
13217
|
}
|
|
13438
13218
|
}
|
|
13439
|
-
/**
|
|
13440
|
-
* Unselect all filters for the aggregation column.
|
|
13441
|
-
*/
|
|
13442
13219
|
unselectAll() {
|
|
13443
13220
|
if (this.items().length) {
|
|
13444
|
-
this.selectItems(this.items(), false);
|
|
13221
|
+
this.base.selectItems(this.items(), false, true);
|
|
13445
13222
|
this.select();
|
|
13446
13223
|
this.isAllSelected.set(false);
|
|
13447
13224
|
}
|
|
13448
13225
|
}
|
|
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
13226
|
apply(overrideFilters) {
|
|
13460
13227
|
sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
|
|
13461
13228
|
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
|
-
}
|
|
13229
|
+
const agg = this.aggregation();
|
|
13230
|
+
this.base.applyFilters(filters, agg, () => this.clear());
|
|
13495
13231
|
this.searchText.set("");
|
|
13496
13232
|
this.selection.set(false);
|
|
13497
13233
|
this.onApply.emit();
|
|
@@ -13504,214 +13240,118 @@ class AggregationTreeComponent {
|
|
|
13504
13240
|
}
|
|
13505
13241
|
async open(node) {
|
|
13506
13242
|
const q = this.queryParamsStore.getQuery();
|
|
13507
|
-
delete q.filters;
|
|
13243
|
+
delete q.filters;
|
|
13508
13244
|
const agg = await firstValueFrom(this.aggregationsService.open(q, this.aggregation(), node));
|
|
13509
13245
|
node.$opened = true;
|
|
13510
13246
|
this.aggregationsStore.updateAggregation(agg);
|
|
13511
13247
|
}
|
|
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
13248
|
select() {
|
|
13524
|
-
const selectedItems = this.getFlattenTreeItems().filter(item => item.$selected);
|
|
13249
|
+
const selectedItems = this.getFlattenTreeItems().filter((item) => item.$selected);
|
|
13525
13250
|
this.onSelect.emit(selectedItems);
|
|
13526
|
-
// Keep apply visible if items are selected, or if active filters exist (user may be deselecting to clear)
|
|
13527
13251
|
this.selection.set(selectedItems.length > 0 || this.hasFilters());
|
|
13528
13252
|
this.verifySelected();
|
|
13529
13253
|
sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
|
|
13530
13254
|
}
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13255
|
+
/* item-level methods — called from the ng-template */
|
|
13256
|
+
treeItemName(item) {
|
|
13257
|
+
const value = item.display || item.value;
|
|
13258
|
+
return typeof value === "string" ? value : `${value}`;
|
|
13259
|
+
}
|
|
13260
|
+
isTreeItemFiltered(item, field) {
|
|
13261
|
+
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.treeItemName(item) });
|
|
13262
|
+
if (!filters)
|
|
13263
|
+
return false;
|
|
13264
|
+
const values = [item.value, `/${item.$path}/*`];
|
|
13265
|
+
return (values.some((v) => v === filters.value) ||
|
|
13266
|
+
!!(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
|
|
13267
|
+
}
|
|
13268
|
+
treeItemLevel(item) {
|
|
13269
|
+
const level = (item.$level ?? 0) - 1 + (!item.hasChildren ? 1 : 0);
|
|
13270
|
+
return item.hasChildren === false ? level + 1 : level;
|
|
13271
|
+
}
|
|
13272
|
+
treeChildrenPath(item, parentPath) {
|
|
13273
|
+
return parentPath.concat(`/${item.$path}/*`);
|
|
13274
|
+
}
|
|
13275
|
+
selectTreeItem(node, parent, e, updateChildren = false) {
|
|
13276
|
+
e?.stopImmediatePropagation();
|
|
13277
|
+
const selected = !node.$selected && !node.$selectedVisually;
|
|
13278
|
+
node.$selected = selected;
|
|
13279
|
+
node.$selectedVisually = false;
|
|
13280
|
+
if (updateChildren)
|
|
13281
|
+
this.selectTreeItemChildren(node.items, node.$selected);
|
|
13282
|
+
if (parent)
|
|
13283
|
+
this.handleTreeChildSelect(parent, node);
|
|
13284
|
+
this.select();
|
|
13285
|
+
}
|
|
13286
|
+
toggleTreeNode(e, node) {
|
|
13287
|
+
e.preventDefault();
|
|
13288
|
+
e.stopImmediatePropagation();
|
|
13289
|
+
if (node.items && node.$opened) {
|
|
13290
|
+
node.$opened = false;
|
|
13539
13291
|
return;
|
|
13540
13292
|
}
|
|
13541
|
-
if (
|
|
13542
|
-
|
|
13293
|
+
if (node.items && !node.$opened) {
|
|
13294
|
+
node.$opened = true;
|
|
13295
|
+
return;
|
|
13296
|
+
}
|
|
13297
|
+
this.open(node);
|
|
13298
|
+
}
|
|
13299
|
+
onTreeItemTextClick(node, parent, event) {
|
|
13300
|
+
if (this.quickFilter()) {
|
|
13301
|
+
this.selectTreeItem(node, parent, event, true);
|
|
13302
|
+
this.apply();
|
|
13543
13303
|
}
|
|
13544
|
-
event.preventDefault();
|
|
13545
13304
|
}
|
|
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
13305
|
getFilters() {
|
|
13555
13306
|
if (this.aggregation()?.isTree) {
|
|
13556
13307
|
return this.getFiltersForTree();
|
|
13557
13308
|
}
|
|
13558
13309
|
return this.getFiltersForList();
|
|
13559
13310
|
}
|
|
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
13311
|
getFiltersForTree() {
|
|
13570
13312
|
const { name, column: field } = this.aggregation() || {};
|
|
13571
|
-
if (!name || !field)
|
|
13313
|
+
if (!name || !field)
|
|
13572
13314
|
return [];
|
|
13573
|
-
}
|
|
13574
13315
|
const items = this.getFlattenTreeItems()
|
|
13575
13316
|
.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
|
-
}
|
|
13317
|
+
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13318
|
+
const notSelectedItems = this.getFlattenTreeItems()
|
|
13319
|
+
.filter((item) => !item.$selected)
|
|
13320
|
+
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13321
|
+
const currentFilters = this.queryParamsStore.getFilter({ field, name })?.values || [];
|
|
13322
|
+
if (currentFilters) {
|
|
13323
|
+
const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
|
|
13324
|
+
if (filteredCurrentFilters.length > 0) {
|
|
13325
|
+
items.push(...filteredCurrentFilters);
|
|
13682
13326
|
}
|
|
13683
13327
|
}
|
|
13684
|
-
|
|
13328
|
+
const uniqueItems = Array.from(new Set(items));
|
|
13329
|
+
if (uniqueItems.length === 0)
|
|
13330
|
+
return [];
|
|
13331
|
+
return [{ operator: "in", name, field, values: uniqueItems, display: uniqueItems[0] }];
|
|
13685
13332
|
}
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13333
|
+
getFiltersForList() {
|
|
13334
|
+
const items = this.addCurrentFiltersToItems().filter((item) => item.$selected);
|
|
13335
|
+
const searchedItems = this.searchedItems().filter((item) => item.$selected);
|
|
13336
|
+
const currentItems = [...items, ...searchedItems];
|
|
13337
|
+
const { column, name, isDistribution = false } = this.aggregation() || {};
|
|
13338
|
+
return currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
|
|
13339
|
+
}
|
|
13340
|
+
getFlattenTreeItems() {
|
|
13341
|
+
const flattenItems = (items) => items.reduce((flat, item) => flat.concat(item, item.items ? flattenItems(item.items) : []), []);
|
|
13342
|
+
const searchedItemsFiltered = (this.searchedItems() || []).filter((item) => "items" in item);
|
|
13343
|
+
return [
|
|
13344
|
+
...flattenItems(searchedItemsFiltered),
|
|
13345
|
+
...flattenItems(this.aggregation()?.items || []),
|
|
13346
|
+
];
|
|
13347
|
+
}
|
|
13348
|
+
addCurrentFiltersToItems() {
|
|
13349
|
+
return this.base.addCurrentFiltersToItems(this.aggregation());
|
|
13702
13350
|
}
|
|
13703
|
-
/**
|
|
13704
|
-
* Check whether all items are selected and update isAllSelected accordingly
|
|
13705
|
-
*/
|
|
13706
13351
|
verifySelected() {
|
|
13707
|
-
const someItemsUnselected = (items) =>
|
|
13708
|
-
return items.some((item) => !item.$selected || (item.items?.length && someItemsUnselected(item.items)));
|
|
13709
|
-
};
|
|
13352
|
+
const someItemsUnselected = (items) => items.some((item) => !item.$selected || (item.items?.length && someItemsUnselected(item.items)));
|
|
13710
13353
|
this.isAllSelected.set(!someItemsUnselected(this.items()));
|
|
13711
13354
|
}
|
|
13712
|
-
/**
|
|
13713
|
-
* set @items $selected and $selectedVisually to the values from @savedItems
|
|
13714
|
-
*/
|
|
13715
13355
|
setSelected(items, savedItems) {
|
|
13716
13356
|
return items.map((item) => {
|
|
13717
13357
|
const savedItem = savedItems.find((i) => i.value === item.value);
|
|
@@ -13729,9 +13369,8 @@ class AggregationTreeComponent {
|
|
|
13729
13369
|
if (!this.linkChildren())
|
|
13730
13370
|
return items;
|
|
13731
13371
|
items.forEach((item) => {
|
|
13732
|
-
if (item.items?.length)
|
|
13372
|
+
if (item.items?.length)
|
|
13733
13373
|
this.selectVisually(item.items, item.$selected || false);
|
|
13734
|
-
}
|
|
13735
13374
|
});
|
|
13736
13375
|
return items;
|
|
13737
13376
|
}
|
|
@@ -13742,39 +13381,49 @@ class AggregationTreeComponent {
|
|
|
13742
13381
|
this.selectVisually(item.items, item.$selected || item.$selectedVisually);
|
|
13743
13382
|
});
|
|
13744
13383
|
}
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13384
|
+
selectTreeItemChildren(items, select) {
|
|
13385
|
+
if (!this.linkChildren() || !items?.length)
|
|
13386
|
+
return;
|
|
13387
|
+
items.forEach((item) => {
|
|
13388
|
+
item.$selectedVisually = select;
|
|
13389
|
+
if (select)
|
|
13390
|
+
item.$selected = false;
|
|
13391
|
+
if (item.items?.length)
|
|
13392
|
+
this.selectTreeItemChildren(item.items, select);
|
|
13393
|
+
});
|
|
13394
|
+
}
|
|
13395
|
+
handleTreeChildSelect(parent, child) {
|
|
13396
|
+
if (this.linkChildren() && !child.$selected && parent.items.some((i) => i.$selectedVisually)) {
|
|
13397
|
+
parent.items.forEach((i) => {
|
|
13398
|
+
if (i !== child) {
|
|
13399
|
+
i.$selectedVisually = false;
|
|
13400
|
+
i.$selected = true;
|
|
13401
|
+
}
|
|
13402
|
+
});
|
|
13403
|
+
}
|
|
13404
|
+
if (this.linkChildren() && parent.items.some((i) => !i.$selectedVisually && !i.$selected)) {
|
|
13405
|
+
parent.$selected = false;
|
|
13406
|
+
parent.$selectedVisually = false;
|
|
13407
|
+
}
|
|
13748
13408
|
}
|
|
13749
13409
|
expandItems(items, expandedLevel) {
|
|
13750
|
-
this.traverse(items, (
|
|
13751
|
-
if (!node.$opened && node.items?.length
|
|
13410
|
+
this.traverse(items, (_lineage, node, level) => {
|
|
13411
|
+
if (!node.$opened && node.items?.length > 0 && level < expandedLevel) {
|
|
13752
13412
|
node.$opened = true;
|
|
13753
13413
|
}
|
|
13754
|
-
// if(node.$opened && level >= expandedLevel) {
|
|
13755
|
-
// node.$opened = false;
|
|
13756
|
-
// }
|
|
13757
13414
|
return false;
|
|
13758
13415
|
});
|
|
13759
13416
|
}
|
|
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
13417
|
traverse(nodes, callback) {
|
|
13766
|
-
if (!nodes || nodes.length === 0)
|
|
13418
|
+
if (!nodes || nodes.length === 0)
|
|
13767
13419
|
return false;
|
|
13768
|
-
|
|
13769
|
-
if (!callback) {
|
|
13420
|
+
if (!callback)
|
|
13770
13421
|
return false;
|
|
13771
|
-
}
|
|
13772
13422
|
const lineage = [];
|
|
13773
13423
|
const stack = [];
|
|
13774
13424
|
let _i = nodes.length;
|
|
13775
|
-
while (_i--)
|
|
13425
|
+
while (_i--)
|
|
13776
13426
|
stack.push(nodes[_i]);
|
|
13777
|
-
}
|
|
13778
13427
|
while (stack.length) {
|
|
13779
13428
|
const node = stack.pop();
|
|
13780
13429
|
if (!node) {
|
|
@@ -13782,54 +13431,248 @@ class AggregationTreeComponent {
|
|
|
13782
13431
|
}
|
|
13783
13432
|
else {
|
|
13784
13433
|
lineage.push(node);
|
|
13785
|
-
if (callback(lineage, node, lineage.length - 1))
|
|
13434
|
+
if (callback(lineage, node, lineage.length - 1))
|
|
13786
13435
|
return true;
|
|
13787
|
-
}
|
|
13788
13436
|
stack.push(undefined);
|
|
13789
13437
|
if (node.items && node.items.length > 0) {
|
|
13790
13438
|
_i = node.items.length;
|
|
13791
|
-
while (_i--)
|
|
13439
|
+
while (_i--)
|
|
13792
13440
|
stack.push(node.items[_i]);
|
|
13793
|
-
}
|
|
13794
13441
|
}
|
|
13795
13442
|
}
|
|
13796
13443
|
}
|
|
13797
13444
|
return false;
|
|
13798
13445
|
}
|
|
13799
|
-
clearSearch(e) {
|
|
13800
|
-
e.stopImmediatePropagation();
|
|
13801
|
-
this.searchText.set("");
|
|
13802
|
-
}
|
|
13803
13446
|
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" }] });
|
|
13447
|
+
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
13448
|
}
|
|
13806
13449
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationTreeComponent, decorators: [{
|
|
13807
13450
|
type: Component,
|
|
13808
13451
|
args: [{ selector: "AggregationTree, aggregation-tree, aggregationtree", imports: [
|
|
13452
|
+
NgTemplateOutlet,
|
|
13809
13453
|
FormsModule,
|
|
13810
13454
|
ReactiveFormsModule,
|
|
13811
13455
|
ButtonComponent,
|
|
13812
|
-
|
|
13456
|
+
ChevronRightIcon,
|
|
13813
13457
|
SyslangPipe,
|
|
13814
13458
|
TranslocoPipe,
|
|
13815
|
-
BadgeComponent,
|
|
13816
|
-
ChevronRightIcon,
|
|
13817
|
-
InputGroupInput,
|
|
13818
|
-
InputGroupComponent,
|
|
13819
|
-
InputGroupAddonComponent,
|
|
13820
|
-
SearchIcon,
|
|
13821
|
-
FilterIcon,
|
|
13822
13459
|
FaIconComponent,
|
|
13823
13460
|
TriangleAlertIcon,
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13461
|
+
HighlightWordPipe,
|
|
13462
|
+
AggregationPanelComponent,
|
|
13463
|
+
], standalone: true, host: {
|
|
13464
|
+
"[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())',
|
|
13465
|
+
}, 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"] }]
|
|
13466
|
+
}], 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"] }] } });
|
|
13467
|
+
|
|
13468
|
+
class AggregationListComponent {
|
|
13469
|
+
cn = cn;
|
|
13470
|
+
/* inputs */
|
|
13471
|
+
class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
13472
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
13473
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
13474
|
+
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
13475
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
|
|
13476
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
13477
|
+
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
13478
|
+
showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13479
|
+
/* outputs */
|
|
13480
|
+
onSelect = output();
|
|
13481
|
+
onApply = output();
|
|
13482
|
+
onClear = output();
|
|
13483
|
+
/* view queries */
|
|
13484
|
+
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
13485
|
+
/* selection state */
|
|
13486
|
+
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
13487
|
+
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
13488
|
+
/* search state */
|
|
13489
|
+
searchText = model("", ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
13490
|
+
/* composable — injects stores/services, wires search effects, provides shared methods */
|
|
13491
|
+
base = injectAggregationBase({
|
|
13492
|
+
name: this.name,
|
|
13493
|
+
column: this.column,
|
|
13494
|
+
searchText: this.searchText,
|
|
13495
|
+
});
|
|
13496
|
+
/* spread from base */
|
|
13497
|
+
aggregationsStore = this.base.aggregationsStore;
|
|
13498
|
+
queryParamsStore = this.base.queryParamsStore;
|
|
13499
|
+
appStore = this.base.appStore;
|
|
13500
|
+
aggregationsService = this.base.aggregationsService;
|
|
13501
|
+
injector = this.base.injector;
|
|
13502
|
+
destroyRef = this.base.destroyRef;
|
|
13503
|
+
suggests = this.base.suggests;
|
|
13504
|
+
hasFilters = this.base.hasFilters;
|
|
13505
|
+
filtersCount = this.base.filtersCount;
|
|
13506
|
+
query = this.base.query;
|
|
13507
|
+
filters = this.base.filters;
|
|
13508
|
+
/* features from appStore */
|
|
13509
|
+
showCount = computed(() => this.appStore.general()?.features?.showAggregationItemCount ?? false, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
|
|
13510
|
+
quickFilter = computed(() => this.appStore.general()?.features?.quickFilter, ...(ngDevMode ? [{ debugName: "quickFilter" }] : []));
|
|
13511
|
+
isDate = computed(() => this.appStore.isDateColumn(this.aggregation()?.column || ""), ...(ngDevMode ? [{ debugName: "isDate" }] : []));
|
|
13512
|
+
/* virtualizer */
|
|
13513
|
+
cdr = inject(ChangeDetectorRef);
|
|
13514
|
+
virtualizer = injectVirtualizer(() => ({
|
|
13515
|
+
count: this.items().length,
|
|
13516
|
+
estimateSize: () => 32,
|
|
13517
|
+
scrollElement: this.scrollElement(),
|
|
13518
|
+
}));
|
|
13519
|
+
aggregation = computed(() => {
|
|
13520
|
+
getState(this.aggregationsStore);
|
|
13521
|
+
const name = this.name();
|
|
13522
|
+
const column = this.column();
|
|
13523
|
+
if (name !== null) {
|
|
13524
|
+
const agg = this.aggregationsService.processAggregation(name, column);
|
|
13525
|
+
if (agg) {
|
|
13526
|
+
if (agg.isTree) {
|
|
13527
|
+
error("The aggregation component does not support tree aggregations. Please use the <AggregationTree /> component instead.");
|
|
13528
|
+
}
|
|
13529
|
+
agg.searchable = this.searchable() ?? agg.searchable;
|
|
13530
|
+
return agg;
|
|
13531
|
+
}
|
|
13532
|
+
}
|
|
13533
|
+
return null;
|
|
13534
|
+
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
13535
|
+
items = computed(() => {
|
|
13536
|
+
getState(this.aggregationsStore);
|
|
13537
|
+
const agg = this.aggregation();
|
|
13538
|
+
const searchedItems = this.searchedItems();
|
|
13539
|
+
if (searchedItems.length > 0)
|
|
13540
|
+
return searchedItems;
|
|
13541
|
+
if (!agg?.items)
|
|
13542
|
+
return [];
|
|
13543
|
+
// Reset $selected so items removed from the filter are deselected.
|
|
13544
|
+
// Then return a new array reference so Angular's signal equality check
|
|
13545
|
+
// detects the change and re-renders all component instances.
|
|
13546
|
+
agg.items.forEach(item => { item.$selected = false; });
|
|
13547
|
+
return [...this.addCurrentFiltersToItems()];
|
|
13548
|
+
}, ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
13549
|
+
isEmpty = computed(() => this.items().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
13550
|
+
searchedItems = computed(() => {
|
|
13551
|
+
if (!this.suggests())
|
|
13552
|
+
return [];
|
|
13553
|
+
return this.suggests()?.map((suggest) => {
|
|
13554
|
+
const column = this.appStore.getColumn(suggest.category);
|
|
13555
|
+
const item = {
|
|
13556
|
+
name: this.name(),
|
|
13557
|
+
value: suggest.normalized || suggest.display || "",
|
|
13558
|
+
display: suggest.display,
|
|
13559
|
+
column: column?.name ?? suggest.category,
|
|
13560
|
+
count: Number(suggest.frequency),
|
|
13561
|
+
$selected: false,
|
|
13562
|
+
};
|
|
13563
|
+
if (column?.eType === EngineType.bool) {
|
|
13564
|
+
item.value = Boolean(item.value);
|
|
13565
|
+
}
|
|
13566
|
+
return item;
|
|
13567
|
+
});
|
|
13568
|
+
}, ...(ngDevMode ? [{ debugName: "searchedItems" }] : []));
|
|
13569
|
+
constructor() {
|
|
13570
|
+
this.destroyRef.onDestroy(() => {
|
|
13571
|
+
if (this.selection()) {
|
|
13572
|
+
this.aggregation()?.items?.forEach((item) => {
|
|
13573
|
+
item.$selected = undefined;
|
|
13574
|
+
});
|
|
13575
|
+
}
|
|
13576
|
+
});
|
|
13577
|
+
}
|
|
13578
|
+
clear() {
|
|
13579
|
+
const agg = this.aggregation();
|
|
13580
|
+
if (agg) {
|
|
13581
|
+
this.queryParamsStore.removeFilterByName(agg.name, agg.column);
|
|
13582
|
+
this.selection.set(false);
|
|
13583
|
+
this.isAllSelected.set(false);
|
|
13584
|
+
}
|
|
13585
|
+
this.onSelect.emit([]);
|
|
13586
|
+
this.onClear.emit();
|
|
13587
|
+
}
|
|
13588
|
+
selectAll() {
|
|
13589
|
+
if (this.items().length) {
|
|
13590
|
+
this.base.selectItems(this.items(), true);
|
|
13591
|
+
this.selection.set(true);
|
|
13592
|
+
this.isAllSelected.set(true);
|
|
13593
|
+
}
|
|
13594
|
+
}
|
|
13595
|
+
unselectAll() {
|
|
13596
|
+
if (this.items().length) {
|
|
13597
|
+
this.base.selectItems(this.items(), false);
|
|
13598
|
+
this.select();
|
|
13599
|
+
this.isAllSelected.set(false);
|
|
13600
|
+
}
|
|
13601
|
+
}
|
|
13602
|
+
apply() {
|
|
13603
|
+
const agg = this.aggregation();
|
|
13604
|
+
if (!agg)
|
|
13605
|
+
return;
|
|
13606
|
+
this.base.applyFilters(this.getFilters(), agg, () => this.clear());
|
|
13607
|
+
this.searchText.set("");
|
|
13608
|
+
this.selection.set(false);
|
|
13609
|
+
this.onApply.emit();
|
|
13610
|
+
}
|
|
13611
|
+
loadMore() {
|
|
13612
|
+
const q = this.queryParamsStore.getQuery();
|
|
13613
|
+
this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
|
|
13614
|
+
this.aggregationsStore.updateAggregation(aggregation);
|
|
13615
|
+
this.cdr.detectChanges();
|
|
13616
|
+
});
|
|
13617
|
+
}
|
|
13618
|
+
select() {
|
|
13619
|
+
const selectedItems = this.items().filter((item) => item.$selected);
|
|
13620
|
+
this.onSelect.emit(selectedItems);
|
|
13621
|
+
this.selection.set(selectedItems.length > 0 || this.hasFilters());
|
|
13622
|
+
}
|
|
13623
|
+
/* item-level methods — called from the ng-template */
|
|
13624
|
+
listItemName(item) {
|
|
13625
|
+
const value = item.display || item.value;
|
|
13626
|
+
return typeof value === "string" ? value : `${value}`;
|
|
13627
|
+
}
|
|
13628
|
+
isListItemFiltered(item, field) {
|
|
13629
|
+
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
|
|
13630
|
+
if (!filters)
|
|
13631
|
+
return false;
|
|
13632
|
+
const values = [item.value];
|
|
13633
|
+
return (values.some((v) => v === filters.value) ||
|
|
13634
|
+
!!(filters.values?.length && filters.values.some((value) => values.some((v) => v === value))));
|
|
13635
|
+
}
|
|
13636
|
+
selectListItem(item, e) {
|
|
13637
|
+
e?.stopImmediatePropagation();
|
|
13638
|
+
item.$selected = !item.$selected;
|
|
13639
|
+
this.select();
|
|
13640
|
+
}
|
|
13641
|
+
onListItemTextClick(item, event) {
|
|
13642
|
+
if (this.quickFilter()) {
|
|
13643
|
+
this.selectListItem(item, event);
|
|
13644
|
+
this.apply();
|
|
13645
|
+
}
|
|
13646
|
+
}
|
|
13647
|
+
getFilters() {
|
|
13648
|
+
const items = this.items().filter((item) => item.$selected);
|
|
13649
|
+
const searchedItems = this.searchedItems().filter((item) => item.$selected);
|
|
13650
|
+
const currentItems = [...items, ...searchedItems];
|
|
13651
|
+
const { column, name, isDistribution = false } = this.aggregation() || {};
|
|
13652
|
+
return currentItems.map((item) => this.aggregationsService.toFilter(item, column, name, isDistribution));
|
|
13653
|
+
}
|
|
13654
|
+
addCurrentFiltersToItems() {
|
|
13655
|
+
return this.base.addCurrentFiltersToItems(this.aggregation());
|
|
13656
|
+
}
|
|
13657
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13658
|
+
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" }] });
|
|
13659
|
+
}
|
|
13660
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationListComponent, decorators: [{
|
|
13661
|
+
type: Component,
|
|
13662
|
+
args: [{ selector: "AggregationList, aggregation-list, aggregationlist", imports: [
|
|
13663
|
+
NgTemplateOutlet,
|
|
13664
|
+
FormsModule,
|
|
13665
|
+
ReactiveFormsModule,
|
|
13666
|
+
SyslangPipe,
|
|
13667
|
+
TranslocoPipe,
|
|
13668
|
+
FaIconComponent,
|
|
13669
|
+
TriangleAlertIcon,
|
|
13670
|
+
HighlightWordPipe,
|
|
13671
|
+
AggregationPanelComponent,
|
|
13829
13672
|
], standalone: true, host: {
|
|
13830
|
-
"[class]": 'cn("block h-[inherit] max-h-[inherit]
|
|
13831
|
-
}, template: "@if (
|
|
13832
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
13673
|
+
"[class]": 'cn("block h-[inherit] max-h-[inherit]", class())',
|
|
13674
|
+
}, 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"] }]
|
|
13675
|
+
}], 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
13676
|
|
|
13834
13677
|
/**
|
|
13835
13678
|
* 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 +13829,7 @@ class AggregationComponent {
|
|
|
13986
13829
|
(onClear)="onClear.emit($event)"
|
|
13987
13830
|
/>
|
|
13988
13831
|
}
|
|
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", "
|
|
13832
|
+
`, 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
13833
|
}
|
|
13991
13834
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AggregationComponent, decorators: [{
|
|
13992
13835
|
type: Component,
|
|
@@ -14302,6 +14145,7 @@ class MoreComponent {
|
|
|
14302
14145
|
includedFilters = input([], ...(ngDevMode ? [{ debugName: "includedFilters" }] : []));
|
|
14303
14146
|
excludedFilters = input([], ...(ngDevMode ? [{ debugName: "excludedFilters" }] : []));
|
|
14304
14147
|
aggregations = input(...(ngDevMode ? [undefined, { debugName: "aggregations" }] : []));
|
|
14148
|
+
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14305
14149
|
appStore = inject(AppStore);
|
|
14306
14150
|
aggregationsStore = inject(AggregationsStore);
|
|
14307
14151
|
queryParamsStore = inject(QueryParamsStore);
|
|
@@ -14329,7 +14173,7 @@ class MoreComponent {
|
|
|
14329
14173
|
effect(() => {
|
|
14330
14174
|
const count = this.count();
|
|
14331
14175
|
const authorizedFilters = this.aggregationsService
|
|
14332
|
-
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters())
|
|
14176
|
+
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
|
|
14333
14177
|
.toSpliced(0, count);
|
|
14334
14178
|
const f = authorizedFilters.map((agg) => {
|
|
14335
14179
|
const { icon = "far fa-list", hidden = false } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
|
|
@@ -14380,7 +14224,7 @@ class MoreComponent {
|
|
|
14380
14224
|
return count > 0;
|
|
14381
14225
|
}
|
|
14382
14226
|
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: `
|
|
14227
|
+
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
14228
|
@for (filter of visibleFilters(); track $index) {
|
|
14385
14229
|
<Aggregation
|
|
14386
14230
|
class="w-60 max-w-80 px-1 [--height:15lh]"
|
|
@@ -14411,7 +14255,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14411
14255
|
`, host: {
|
|
14412
14256
|
class: "divide-y divide-muted-foreground/18"
|
|
14413
14257
|
}, 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 }] }] } });
|
|
14258
|
+
}], 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
14259
|
|
|
14416
14260
|
class MoreButtonComponent {
|
|
14417
14261
|
appStore = inject(AppStore);
|
|
@@ -14423,10 +14267,11 @@ class MoreButtonComponent {
|
|
|
14423
14267
|
includedFilters = input([], ...(ngDevMode ? [{ debugName: "includedFilters" }] : []));
|
|
14424
14268
|
excludedFilters = input([], ...(ngDevMode ? [{ debugName: "excludedFilters" }] : []));
|
|
14425
14269
|
aggregations = input(...(ngDevMode ? [undefined, { debugName: "aggregations" }] : []));
|
|
14270
|
+
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14426
14271
|
totalFiltersCount = computed(() => {
|
|
14427
14272
|
const count = this.count();
|
|
14428
14273
|
const authorizedFilters = this.aggregationsService
|
|
14429
|
-
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters())
|
|
14274
|
+
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
|
|
14430
14275
|
.toSpliced(0, count);
|
|
14431
14276
|
const total = authorizedFilters.reduce((acc, filter) => {
|
|
14432
14277
|
const f = this.queryParamsStore.getFilter(filter);
|
|
@@ -14436,7 +14281,7 @@ class MoreButtonComponent {
|
|
|
14436
14281
|
return total;
|
|
14437
14282
|
}, ...(ngDevMode ? [{ debugName: "totalFiltersCount" }] : []));
|
|
14438
14283
|
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: `
|
|
14284
|
+
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
14285
|
<Popover class="group/more">
|
|
14441
14286
|
<button
|
|
14442
14287
|
variant="ghost"
|
|
@@ -14454,11 +14299,11 @@ class MoreButtonComponent {
|
|
|
14454
14299
|
|
|
14455
14300
|
<PopoverContent #contentRef="popoverContent" [position]="position()" class="min-w-max">
|
|
14456
14301
|
@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" />
|
|
14302
|
+
<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
14303
|
}
|
|
14459
14304
|
</PopoverContent>
|
|
14460
14305
|
</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" }] });
|
|
14306
|
+
`, 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
14307
|
}
|
|
14463
14308
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MoreButtonComponent, decorators: [{
|
|
14464
14309
|
type: Component,
|
|
@@ -14484,13 +14329,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14484
14329
|
|
|
14485
14330
|
<PopoverContent #contentRef="popoverContent" [position]="position()" class="min-w-max">
|
|
14486
14331
|
@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" />
|
|
14332
|
+
<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
14333
|
}
|
|
14489
14334
|
</PopoverContent>
|
|
14490
14335
|
</Popover>
|
|
14491
14336
|
`
|
|
14492
14337
|
}]
|
|
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 }] }] } });
|
|
14338
|
+
}], 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
14339
|
|
|
14495
14340
|
class FiltersBarComponent {
|
|
14496
14341
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
@@ -14528,6 +14373,15 @@ class FiltersBarComponent {
|
|
|
14528
14373
|
* @default true
|
|
14529
14374
|
*/
|
|
14530
14375
|
showMoreFiltersButton = input(true, ...(ngDevMode ? [{ debugName: "showMoreFiltersButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14376
|
+
/**
|
|
14377
|
+
* When enabled, only the filters flagged with `homepage: true` in the "filters" custom JSON
|
|
14378
|
+
* are displayed. If no filter is flagged, the bar shows no filters.
|
|
14379
|
+
*
|
|
14380
|
+
* Accepts a boolean value or a string that can be transformed to a boolean.
|
|
14381
|
+
*
|
|
14382
|
+
* @default false
|
|
14383
|
+
*/
|
|
14384
|
+
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14531
14385
|
direction = input("horizontal", ...(ngDevMode ? [{ debugName: "direction" }] : []));
|
|
14532
14386
|
/**
|
|
14533
14387
|
* The distance in pixels between the popover and its trigger element.
|
|
@@ -14577,50 +14431,76 @@ class FiltersBarComponent {
|
|
|
14577
14431
|
return this.aggregationsStore.aggregations().length > 0;
|
|
14578
14432
|
return false;
|
|
14579
14433
|
}, ...(ngDevMode ? [{ debugName: "hasAggregations" }] : []));
|
|
14434
|
+
/**
|
|
14435
|
+
* The full list of authorized filters, NOT capped by `filtersCount`.
|
|
14436
|
+
*
|
|
14437
|
+
* This computed signal performs the following operations:
|
|
14438
|
+
* 1. Retrieves aggregations from either the component's aggregations input or the app store
|
|
14439
|
+
* 2. Filters aggregations based on the route's filter criteria configuration
|
|
14440
|
+
* 3. Excludes filters specified in the `excludeFilters` list
|
|
14441
|
+
* 4. If `includeFilters` is not empty, only includes filters present in that list
|
|
14442
|
+
* 5. Maps the filtered aggregations to objects containing only `name` and `column` properties
|
|
14443
|
+
*/
|
|
14444
|
+
allAuthorizedFilters = computed(() => {
|
|
14445
|
+
return this.aggregationsService
|
|
14446
|
+
.getAuthorizedFilters(this.aggregations(), this.includeFilters(), this.excludeFilters(), this.homepage())
|
|
14447
|
+
.map((f) => ({ name: f.name, column: f.column }));
|
|
14448
|
+
}, ...(ngDevMode ? [{ debugName: "allAuthorizedFilters" }] : []));
|
|
14580
14449
|
/**
|
|
14581
14450
|
* Computes the list of additional filters that can be displayed in the "more filters" popover.
|
|
14582
14451
|
*
|
|
14583
|
-
*
|
|
14584
|
-
*
|
|
14585
|
-
*
|
|
14452
|
+
* Derived from the FULL authorized list (not the one capped by `filtersCount`), so the
|
|
14453
|
+
* filters beyond `filtersCount` — which are never rendered in the bar — are still counted.
|
|
14454
|
+
* Otherwise, when every rendered filter fits in the container, this list would be empty and
|
|
14455
|
+
* the "more" button would be hidden even though more filters exist beyond the cap.
|
|
14586
14456
|
*
|
|
14587
|
-
* This property manages the visibility
|
|
14457
|
+
* This property manages the visibility of the "more filters" button in the UI.
|
|
14588
14458
|
*
|
|
14589
14459
|
* @returns An array of Aggregation objects representing the additional filters available.
|
|
14590
14460
|
*/
|
|
14591
14461
|
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 }))
|
|
14462
|
+
const moreFiltersAggregations = this.allAuthorizedFilters()
|
|
14596
14463
|
.toSpliced(0, this.visibleFiltersCount())
|
|
14597
14464
|
.map((f) => this.aggregationsStore.getAggregation(f.column, "column"));
|
|
14598
14465
|
return moreFiltersAggregations;
|
|
14599
14466
|
}, ...(ngDevMode ? [{ debugName: "hasMoreFilters" }] : []));
|
|
14600
14467
|
/**
|
|
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`
|
|
14468
|
+
* The authorized filters rendered as buttons in the bar, limited to the number
|
|
14469
|
+
* specified by `filtersCount`.
|
|
14610
14470
|
*
|
|
14611
14471
|
* @returns An array of authorized filter objects, each containing `name` and `column` properties
|
|
14612
14472
|
*/
|
|
14613
14473
|
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;
|
|
14474
|
+
return this.allAuthorizedFilters().toSpliced(this.filtersCount());
|
|
14619
14475
|
}, ...(ngDevMode ? [{ debugName: "authorizedFilters" }] : []));
|
|
14476
|
+
/**
|
|
14477
|
+
* Whether some authorized filters exist beyond the `filtersCount` cap.
|
|
14478
|
+
*
|
|
14479
|
+
* Those filters are never rendered in the bar and are only reachable through
|
|
14480
|
+
* the "more" button, which is therefore permanently visible: the overflow
|
|
14481
|
+
* manager must always reserve its space so the last filter button never
|
|
14482
|
+
* overlaps it (`reserveStop`).
|
|
14483
|
+
*/
|
|
14484
|
+
hasCappedFilters = computed(() => this.allAuthorizedFilters().length > this.filtersCount(), ...(ngDevMode ? [{ debugName: "hasCappedFilters" }] : []));
|
|
14620
14485
|
constructor() {
|
|
14621
14486
|
this.transloco.events$
|
|
14622
14487
|
.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(100))
|
|
14623
14488
|
.subscribe(() => this.overflowManagerRef()?.countItems());
|
|
14489
|
+
// Recount the overflow whenever the applied filters or basket change (e.g.
|
|
14490
|
+
// a filter modified or removed from the "more filters" popover). A
|
|
14491
|
+
// FilterButton hidden by the overflow manager (display: none) emits no
|
|
14492
|
+
// resize notification when its natural width changes, so it could fit in
|
|
14493
|
+
// the bar again without the manager knowing. afterRenderEffect guarantees
|
|
14494
|
+
// the DOM already reflects the new state when we measure.
|
|
14495
|
+
afterRenderEffect({
|
|
14496
|
+
read: () => {
|
|
14497
|
+
// track filters and basket changes (getState is reactive here)
|
|
14498
|
+
const { filters, basket } = getState(this.queryParamsStore);
|
|
14499
|
+
void filters;
|
|
14500
|
+
void basket;
|
|
14501
|
+
this.overflowManagerRef()?.countItems();
|
|
14502
|
+
}
|
|
14503
|
+
});
|
|
14624
14504
|
}
|
|
14625
14505
|
/**
|
|
14626
14506
|
* Clears all filters (included baskets) by invoking the clearFilters method on the queryParamsStore.
|
|
@@ -14669,8 +14549,8 @@ class FiltersBarComponent {
|
|
|
14669
14549
|
});
|
|
14670
14550
|
}
|
|
14671
14551
|
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">
|
|
14552
|
+
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: `
|
|
14553
|
+
<div overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14674
14554
|
@if (hasFilters()) {
|
|
14675
14555
|
<button
|
|
14676
14556
|
variant="destructive"
|
|
@@ -14717,11 +14597,12 @@ class FiltersBarComponent {
|
|
|
14717
14597
|
[position]="morePosition()"
|
|
14718
14598
|
[includedFilters]="includeFilters()"
|
|
14719
14599
|
[excludedFilters]="excludeFilters()"
|
|
14720
|
-
[aggregations]="aggregations()"
|
|
14600
|
+
[aggregations]="aggregations()"
|
|
14601
|
+
[homepage]="homepage()" />
|
|
14721
14602
|
}
|
|
14722
14603
|
}
|
|
14723
14604
|
</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" }] });
|
|
14605
|
+
`, 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
14606
|
}
|
|
14726
14607
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: FiltersBarComponent, decorators: [{
|
|
14727
14608
|
type: Component,
|
|
@@ -14741,7 +14622,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14741
14622
|
],
|
|
14742
14623
|
providers: [provideTranslocoScope("filters")],
|
|
14743
14624
|
template: `
|
|
14744
|
-
<div overflowManager [direction]="direction()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14625
|
+
<div overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
|
|
14745
14626
|
@if (hasFilters()) {
|
|
14746
14627
|
<button
|
|
14747
14628
|
variant="destructive"
|
|
@@ -14788,17 +14669,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
14788
14669
|
[position]="morePosition()"
|
|
14789
14670
|
[includedFilters]="includeFilters()"
|
|
14790
14671
|
[excludedFilters]="excludeFilters()"
|
|
14791
|
-
[aggregations]="aggregations()"
|
|
14672
|
+
[aggregations]="aggregations()"
|
|
14673
|
+
[homepage]="homepage()" />
|
|
14792
14674
|
}
|
|
14793
14675
|
}
|
|
14794
14676
|
</div>
|
|
14795
14677
|
`,
|
|
14796
14678
|
host: {
|
|
14797
|
-
"[class]": "cn('block relative', class())",
|
|
14679
|
+
"[class]": "cn('block relative min-w-0', class())",
|
|
14798
14680
|
"(click)": "handleClick($event)"
|
|
14799
14681
|
}
|
|
14800
14682
|
}]
|
|
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 }] }] } });
|
|
14683
|
+
}], 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
14684
|
|
|
14803
14685
|
class LabelService {
|
|
14804
14686
|
appStore = inject(AppStore);
|
|
@@ -16187,5 +16069,5 @@ const queryNameResolver = () => {
|
|
|
16187
16069
|
* Generated bundle index. Do not edit.
|
|
16188
16070
|
*/
|
|
16189
16071
|
|
|
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 };
|
|
16072
|
+
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
16073
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|