@sinequa/atomic-angular 1.7.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/sinequa-atomic-angular.mjs +960 -165
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +492 -498
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, EnvironmentInjector, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, resource, ChangeDetectionStrategy, ViewContainerRef, numberAttribute, viewChildren, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, Subscription, Subject, of, catchError, map, tap, EMPTY, throwError, filter, shareReplay,
|
|
3
|
+
import { BehaviorSubject, Subscription, Subject, of, catchError, map, tap, EMPTY, throwError, from, filter as filter$1, shareReplay, switchMap, firstValueFrom, fromEvent, debounceTime } from 'rxjs';
|
|
4
4
|
import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
|
|
5
5
|
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent, CardComponent, CardHeaderComponent, CardContentComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SquareCheckIcon, SquareMinusIcon, SquareIcon, SwitchComponent, SelectOptionDirective, DialogService, XMarkIcon, InboxIcon, SparklesIcon, FileOutputIcon, TabsComponent, TabsListComponent, TabComponent, SidebarMenuComponent, SidebarMenuItemComponent, SidebarMenuButtonComponent, TooltipDirective, FrownIcon, ChevronLeftIcon, ChevronsLeftIcon, ChevronsRightIcon, ArrowUpAzIcon, ArrowDownZaIcon, ArrowUpRightFromSquareIcon, ToggleRightIcon, ToggleLeftIcon, Separator, SheetCloseDirective, ArrowLeftIcon, SheetService, DateRangePickerDirective, DatepickerDirective, FilterIcon, FilterXIcon, IconButtonComponent, HighlighterIcon, TagsIcon, SpinnerIcon, MagnifyingGlassIcon, LoadingCircleIcon, CircleCheckIcon, PopoverComponent, BellIcon, TrashIcon, BarsIcon, CardFooterComponent, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, EyeSlashIcon, EyeIcon, BookmarkIcon, PopoverContentComponent, UserIcon, FolderIcon, DialogDescriptionComponent, VerticalDividerComponent, CommentIcon, ThumbsUpIcon, ThumbsDownIcon, DropdownDirective, SearchIcon, TriangleAlertIcon, ListFilterIcon, BreakpointObserverService, TrashCanIcon, CircleXIcon, InfoCircleIcon, HorizontalDividerComponent, HistoryIcon, StarIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, EditIcon, UndoIcon, SaveIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
|
|
6
6
|
import highlightWords from 'highlight-words';
|
|
7
7
|
import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
|
|
8
8
|
import { withDevtools } from '@angular-architects/ngrx-toolkit';
|
|
9
9
|
import { signalStore, signalStoreFeature, withState, withMethods, patchState, getState, withComputed } from '@ngrx/signals';
|
|
10
|
-
import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, getQueryParamsFromUrl, clearSessionTokens, login, info, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks,
|
|
10
|
+
import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, clearSessionTokens, login, info, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, fetchQuery, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
|
|
11
11
|
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
12
12
|
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
|
|
13
13
|
import { HttpClient, HttpParams, httpResource, HttpResponse, HttpContextToken, HttpHeaders } from '@angular/common/http';
|
|
@@ -1365,6 +1365,64 @@ class InlineWorker {
|
|
|
1365
1365
|
}
|
|
1366
1366
|
}
|
|
1367
1367
|
|
|
1368
|
+
/**
|
|
1369
|
+
* Converts a `LegacyFilter` to the structured `Filter` model, built with the
|
|
1370
|
+
* `filter.*` helpers of `@sinequa/atomic`. The result is assignable as-is to
|
|
1371
|
+
* `query.filters` / `fetchQuery({ filters })`.
|
|
1372
|
+
*
|
|
1373
|
+
* Covers the shapes produced by the aggregation components when applying
|
|
1374
|
+
* filters (`toFilter`, `getFiltersForTree`, `getFormValueFilter`,
|
|
1375
|
+
* `buildAppliedFilter`): `in` values, `and`/`or` expression trees, `between`
|
|
1376
|
+
* ranges, scalar leaves (`eq`/`contains`/`gte`/`lte`/…) and the operator-less
|
|
1377
|
+
* `{ field, value }` form (treated as `eq`).
|
|
1378
|
+
*
|
|
1379
|
+
* Returns `undefined` for an absent filter or a degenerate shape (leaf without
|
|
1380
|
+
* `field`/`value`, empty combinator) — consistent with the `filter.and`/`or`
|
|
1381
|
+
* collapsing rules and with an absent `query.filters`.
|
|
1382
|
+
*/
|
|
1383
|
+
function legacyFilterToFilter(legacy) {
|
|
1384
|
+
if (!legacy)
|
|
1385
|
+
return undefined;
|
|
1386
|
+
const { operator, field } = legacy;
|
|
1387
|
+
switch (operator) {
|
|
1388
|
+
case "and":
|
|
1389
|
+
case "or": {
|
|
1390
|
+
const children = (legacy.filters ?? []).map((f) => legacyFilterToFilter(f));
|
|
1391
|
+
return operator === "and" ? filter.and(...children) : filter.or(...children);
|
|
1392
|
+
}
|
|
1393
|
+
case "not": {
|
|
1394
|
+
const children = (legacy.filters ?? []).map((f) => legacyFilterToFilter(f));
|
|
1395
|
+
return filter.not(...children);
|
|
1396
|
+
}
|
|
1397
|
+
case "in":
|
|
1398
|
+
return field && legacy.values?.length ? filter.in(field, legacy.values) : undefined;
|
|
1399
|
+
case "between":
|
|
1400
|
+
return field && legacy.start !== undefined && legacy.end !== undefined
|
|
1401
|
+
? filter.between(field, legacy.start, legacy.end)
|
|
1402
|
+
: undefined;
|
|
1403
|
+
case "null":
|
|
1404
|
+
return field ? filter.isNull(field) : undefined;
|
|
1405
|
+
case "notnull":
|
|
1406
|
+
return field ? filter.isNotNull(field) : undefined;
|
|
1407
|
+
case "eq":
|
|
1408
|
+
case "neq":
|
|
1409
|
+
case "gt":
|
|
1410
|
+
case "gte":
|
|
1411
|
+
case "lt":
|
|
1412
|
+
case "lte":
|
|
1413
|
+
case "like":
|
|
1414
|
+
case "contains":
|
|
1415
|
+
case "regex":
|
|
1416
|
+
return field && legacy.value !== undefined ? filter[operator](field, legacy.value) : undefined;
|
|
1417
|
+
case undefined:
|
|
1418
|
+
// operator-less leaf ({ field, value }) — the `toFilter` form for
|
|
1419
|
+
// non-string values; semantically an equality
|
|
1420
|
+
return field && legacy.value !== undefined ? filter.eq(field, legacy.value) : undefined;
|
|
1421
|
+
default:
|
|
1422
|
+
return undefined;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1368
1426
|
/**
|
|
1369
1427
|
* Retrieves the query name from the current route.
|
|
1370
1428
|
*
|
|
@@ -3209,11 +3267,13 @@ class AggregationsService {
|
|
|
3209
3267
|
queryParamsStore = inject(QueryParamsStore);
|
|
3210
3268
|
nonSearchableColumns = inject(NON_SEARCHABLE_COLUMNS);
|
|
3211
3269
|
API_URL = `${globalConfig.backendUrl}/api/v1`;
|
|
3212
|
-
loadMore(query, aggregation, audit) {
|
|
3270
|
+
loadMore(query, aggregation, audit, options) {
|
|
3213
3271
|
const { app } = globalConfig;
|
|
3214
3272
|
// skip cached items for computation
|
|
3215
3273
|
const skip = aggregation.items?.filter((i) => !i.$cached)?.length || 0;
|
|
3216
|
-
|
|
3274
|
+
// options.count carries the page size of a controlled (non-attached) query —
|
|
3275
|
+
// getAggregationCount only knows queries attached to the current app
|
|
3276
|
+
const count = options?.count ?? (this.appStore.getAggregationCount(query.name, aggregation.name) || 10);
|
|
3217
3277
|
const aggregations = { [aggregation.name]: { skip, count } };
|
|
3218
3278
|
const q = { ...query, action: "aggregate", aggregations };
|
|
3219
3279
|
// add audit record if available
|
|
@@ -3242,7 +3302,7 @@ class AggregationsService {
|
|
|
3242
3302
|
info("aggregation.loadMore success - data: ", response);
|
|
3243
3303
|
}));
|
|
3244
3304
|
}
|
|
3245
|
-
open(query, aggregation, item) {
|
|
3305
|
+
open(query, aggregation, item, options) {
|
|
3246
3306
|
const value = `/${item.$path}/*`;
|
|
3247
3307
|
const expression = `${aggregation.column}:${escapeExpr(value)}`;
|
|
3248
3308
|
const q = {
|
|
@@ -3252,7 +3312,18 @@ class AggregationsService {
|
|
|
3252
3312
|
open: [{ expression, aggregation: aggregation.name }]
|
|
3253
3313
|
};
|
|
3254
3314
|
// we dont' want to use the query params (filters) when opening nodes
|
|
3255
|
-
|
|
3315
|
+
// Guard-free mode calls fetchQuery directly: QueryService.search reads the shared
|
|
3316
|
+
// QueryParamsStore and publishes into its app-wide `result` signal, which a
|
|
3317
|
+
// controlled (per-instance) aggregation must not touch.
|
|
3318
|
+
const results$ = options?.guardFree
|
|
3319
|
+
? from(fetchQuery(q)).pipe(tap((results) => {
|
|
3320
|
+
results.aggregations?.forEach((agg) => {
|
|
3321
|
+
if (agg.isTree)
|
|
3322
|
+
buildPathsAndLevels(agg);
|
|
3323
|
+
});
|
|
3324
|
+
}))
|
|
3325
|
+
: this.queryService.search(q, false);
|
|
3326
|
+
return results$.pipe(map((results) => {
|
|
3256
3327
|
// find the node in the tree and replace it with the new one
|
|
3257
3328
|
const items = results.aggregations[0].items;
|
|
3258
3329
|
// if the open call returns no items, we need to update the node to indicate it has no children
|
|
@@ -3285,6 +3356,19 @@ class AggregationsService {
|
|
|
3285
3356
|
const agg = this.getAggregation(name, column);
|
|
3286
3357
|
if (!agg)
|
|
3287
3358
|
return null;
|
|
3359
|
+
return this.enrichAggregation(agg);
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Applies the app-level customization (custom JSON display/icon/hidden/searchable,
|
|
3363
|
+
* non-searchable columns) and marks the `$selected` items on the given aggregation.
|
|
3364
|
+
*
|
|
3365
|
+
* @param agg - The aggregation to enrich (mutated in place and returned).
|
|
3366
|
+
* @param selectionValues - Controlled selection: the selected values (list) or
|
|
3367
|
+
* `"/A/B/*"` paths (tree). When `undefined`, the selection is derived from the
|
|
3368
|
+
* shared `QueryParamsStore` filters (default behavior). An empty array means
|
|
3369
|
+
* "nothing selected" and skips the store read entirely.
|
|
3370
|
+
*/
|
|
3371
|
+
enrichAggregation(agg, selectionValues) {
|
|
3288
3372
|
const { items = [], display = agg.name, icon, hidden, expandedLevel, searchable = true } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
|
|
3289
3373
|
agg.display = display;
|
|
3290
3374
|
agg.icon = icon;
|
|
@@ -3298,9 +3382,15 @@ class AggregationsService {
|
|
|
3298
3382
|
agg.searchable = false;
|
|
3299
3383
|
}
|
|
3300
3384
|
if (agg.items) {
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3385
|
+
let flattenedValues;
|
|
3386
|
+
if (selectionValues !== undefined) {
|
|
3387
|
+
flattenedValues = selectionValues;
|
|
3388
|
+
}
|
|
3389
|
+
else {
|
|
3390
|
+
// get current filters for the aggregation
|
|
3391
|
+
const filters = this.queryParamsStore.getFilter({ field: agg.column, name: agg.name });
|
|
3392
|
+
flattenedValues = filters ? this.flattenFilters([filters]) : [];
|
|
3393
|
+
}
|
|
3304
3394
|
if (agg.isTree) {
|
|
3305
3395
|
const selectedAgg = agg.items.map((item) => this.selectNode(item, flattenedValues));
|
|
3306
3396
|
const openedAgg = selectedAgg.map((item) => this.openParentNodes(item, flattenedValues));
|
|
@@ -3450,16 +3540,33 @@ class AggregationsService {
|
|
|
3450
3540
|
}
|
|
3451
3541
|
/* aggregations helpers fot the filters components */
|
|
3452
3542
|
getAuthorizedFilters(aggregations, includedFilters = [], excludedFilters = [], homepageOnly = false) {
|
|
3543
|
+
// An explicit list is a deliberate developer choice: it bypasses the `hidden`
|
|
3544
|
+
// admin rule. The rule only applies to the default, config-driven path.
|
|
3545
|
+
const explicit = aggregations !== undefined;
|
|
3453
3546
|
const agg = aggregations || this.appStore.getAuthorizedFilters();
|
|
3454
3547
|
const authorizedFilters = agg
|
|
3455
3548
|
.filter((f) => this.getFilterCriteria()(f)) // filter only the filters present
|
|
3456
3549
|
.filter((f) => !homepageOnly || this.getHomepageFilterCriteria()(f)) // when requested, keep only filters flagged `homepage: true`
|
|
3457
3550
|
.filter((f) => !this.getLeftOnlyFilterCriteria()(f)) // exclude left-only filters (rendered in the left drawer, not the bar)
|
|
3551
|
+
.filter((f) => explicit || !this.getHiddenFilterCriteria()(f)) // exclude admin-hidden filters (default path only)
|
|
3458
3552
|
.filter((f) => !excludedFilters.includes(f.name))
|
|
3459
3553
|
.filter((f) => !includedFilters.length || includedFilters.includes(f.name))
|
|
3460
3554
|
.map((f) => ({ field: f.column, column: f.column, name: f.name })); // field is needed for filters constructions
|
|
3461
3555
|
return authorizedFilters;
|
|
3462
3556
|
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Removes the aggregations flagged `hidden: true` in the `filters` custom JSON
|
|
3559
|
+
* configuration (the administrator does not want them displayed).
|
|
3560
|
+
*
|
|
3561
|
+
* Exposed for consumers that build their own aggregation list without going through
|
|
3562
|
+
* {@link getAuthorizedFilters} (e.g. the aside filters drawer).
|
|
3563
|
+
*
|
|
3564
|
+
* @param aggregations - The aggregations to filter.
|
|
3565
|
+
* @returns The aggregations that are not flagged hidden.
|
|
3566
|
+
*/
|
|
3567
|
+
rejectHidden(aggregations) {
|
|
3568
|
+
return aggregations.filter((agg) => !this.getHiddenFilterCriteria()(agg));
|
|
3569
|
+
}
|
|
3463
3570
|
/**
|
|
3464
3571
|
* Determines whether a custom JSON filter refers to the given aggregation.
|
|
3465
3572
|
*
|
|
@@ -3503,6 +3610,10 @@ class AggregationsService {
|
|
|
3503
3610
|
// `position: 'both'` (and an undefined position) stay in the bar.
|
|
3504
3611
|
return (agg) => this.appStore.filters().some((filter) => filter.position === "left" && this.matchesAggregation(filter, agg));
|
|
3505
3612
|
};
|
|
3613
|
+
getHiddenFilterCriteria = () => {
|
|
3614
|
+
// an aggregation is hidden when a matching filter in the custom JSON has `hidden: true`
|
|
3615
|
+
return (agg) => this.appStore.filters().some((filter) => filter.hidden && this.matchesAggregation(filter, agg));
|
|
3616
|
+
};
|
|
3506
3617
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3507
3618
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationsService, providedIn: "root" });
|
|
3508
3619
|
}
|
|
@@ -4201,7 +4312,7 @@ class NavigationService {
|
|
|
4201
4312
|
*
|
|
4202
4313
|
* @type Observable<RouterEvent>
|
|
4203
4314
|
*/
|
|
4204
|
-
navigationEnd$ = this.router.events.pipe(map((event) => event), filter((event) => event instanceof NavigationEnd), tap((event) => {
|
|
4315
|
+
navigationEnd$ = this.router.events.pipe(map((event) => event), filter$1((event) => event instanceof NavigationEnd), tap((event) => {
|
|
4205
4316
|
const url = event.url.slice(1).split("?")[0]; // Extract route name
|
|
4206
4317
|
if (url && url !== this.urlAfterNavigation) {
|
|
4207
4318
|
this.auditService.notifyRouteChange(url);
|
|
@@ -6488,6 +6599,43 @@ function injectRouteNavigation(path, showCount) {
|
|
|
6488
6599
|
return { tabs, currentPath, searchText };
|
|
6489
6600
|
}
|
|
6490
6601
|
|
|
6602
|
+
/**
|
|
6603
|
+
* Marker directive: project a custom template for the content/label region of
|
|
6604
|
+
* each aggregation item. Put it on an `<ng-template>` inside `<aggregation-list>`
|
|
6605
|
+
* or `<aggregation-tree>`:
|
|
6606
|
+
*
|
|
6607
|
+
* ```html
|
|
6608
|
+
* <aggregation-list name="Languages" column="language" [aggregation]="agg">
|
|
6609
|
+
* <ng-template aggregationItem let-item let-count="count" let-selected="selected">
|
|
6610
|
+
* <span>{{ item.display }}</span>
|
|
6611
|
+
* <span class="badge">{{ count }}</span>
|
|
6612
|
+
* </ng-template>
|
|
6613
|
+
* </aggregation-list>
|
|
6614
|
+
* ```
|
|
6615
|
+
*
|
|
6616
|
+
* When no template is projected, the component renders its default row content.
|
|
6617
|
+
* The `let-*` variables are typed under `strictTemplates` via
|
|
6618
|
+
* {@link AggregationItemDirective.ngTemplateContextGuard}; the host must import
|
|
6619
|
+
* this directive in its component `imports`.
|
|
6620
|
+
*/
|
|
6621
|
+
class AggregationItemDirective {
|
|
6622
|
+
template = inject(TemplateRef);
|
|
6623
|
+
/**
|
|
6624
|
+
* Narrows the `let-*` of `<ng-template aggregationItem>` under `strictTemplates`.
|
|
6625
|
+
* Tree-only fields (`level`/`expanded`/`hasChildren`) are typed as optional; a tree
|
|
6626
|
+
* template can re-annotate them via {@link AggregationTreeItemContext} if desired.
|
|
6627
|
+
*/
|
|
6628
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
6629
|
+
return true;
|
|
6630
|
+
}
|
|
6631
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6632
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.25", type: AggregationItemDirective, isStandalone: true, selector: "[aggregationItem]", ngImport: i0 });
|
|
6633
|
+
}
|
|
6634
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationItemDirective, decorators: [{
|
|
6635
|
+
type: Directive,
|
|
6636
|
+
args: [{ selector: "[aggregationItem]" }]
|
|
6637
|
+
}] });
|
|
6638
|
+
|
|
6491
6639
|
/**
|
|
6492
6640
|
* Directive to mark a child element for special processing.
|
|
6493
6641
|
* This can be used to apply specific styles or behaviors to child elements.
|
|
@@ -8912,12 +9060,98 @@ function injectAggregationBase(refs) {
|
|
|
8912
9060
|
const aggregationsService = inject(AggregationsService);
|
|
8913
9061
|
const injector = inject(Injector);
|
|
8914
9062
|
const destroyRef = inject(DestroyRef);
|
|
9063
|
+
/* controlled mode — each axis is controlled when its input is provided.
|
|
9064
|
+
Writes (store/URL/sessionStorage) are gated by the global `isControlled` flag. */
|
|
9065
|
+
const isQueryControlled = computed(() => refs.query?.() !== undefined, ...(ngDevMode ? [{ debugName: "isQueryControlled" }] : []));
|
|
9066
|
+
const isDataControlled = computed(() => refs.aggregation?.() !== undefined, ...(ngDevMode ? [{ debugName: "isDataControlled" }] : []));
|
|
9067
|
+
const isSelectionControlled = computed(() => refs.selection?.() !== undefined, ...(ngDevMode ? [{ debugName: "isSelectionControlled" }] : []));
|
|
9068
|
+
const isControlled = computed(() => isQueryControlled() || isDataControlled() || isSelectionControlled(), ...(ngDevMode ? [{ debugName: "isControlled" }] : []));
|
|
9069
|
+
/** The query the aggregation's own operations run against. */
|
|
9070
|
+
function resolvedQuery() {
|
|
9071
|
+
return refs.query?.() ?? queryParamsStore.getQuery();
|
|
9072
|
+
}
|
|
9073
|
+
/**
|
|
9074
|
+
* Load-more page size when the query axis is controlled: the host's query
|
|
9075
|
+
* carries its own aggregation counts (`query.aggregations[name].count`), since
|
|
9076
|
+
* `AppStore.getAggregationCount` only knows queries attached to the current app.
|
|
9077
|
+
* Returns `undefined` in default mode — the service then resolves the count
|
|
9078
|
+
* from the app configuration, as today.
|
|
9079
|
+
*/
|
|
9080
|
+
function resolvedAggregationCount(agg) {
|
|
9081
|
+
const q = refs.query?.();
|
|
9082
|
+
if (!q)
|
|
9083
|
+
return undefined;
|
|
9084
|
+
const aggregations = q.aggregations;
|
|
9085
|
+
if (aggregations && !Array.isArray(aggregations)) {
|
|
9086
|
+
return aggregations[agg.name]?.count ?? 10;
|
|
9087
|
+
}
|
|
9088
|
+
return 10;
|
|
9089
|
+
}
|
|
9090
|
+
/**
|
|
9091
|
+
* Local aggregation state in controlled mode, seeded from the `aggregation`
|
|
9092
|
+
* input (or the shared store when only the query/selection axis is controlled).
|
|
9093
|
+
* `loadMore`/`open` write their results here instead of the shared store.
|
|
9094
|
+
*
|
|
9095
|
+
* The seed is deep-cloned: items are mutated in place (`$selected`, `$opened`),
|
|
9096
|
+
* so working on the host's (or the store's) objects would leak state across
|
|
9097
|
+
* instances and mutate the host's data. Any reference change of the input
|
|
9098
|
+
* reseeds the signal and discards local enrichments (loaded pages, opened nodes).
|
|
9099
|
+
*/
|
|
9100
|
+
const controlledAggregation = linkedSignal(() => {
|
|
9101
|
+
if (!isControlled())
|
|
9102
|
+
return null;
|
|
9103
|
+
const source = refs.aggregation?.() ??
|
|
9104
|
+
(getState(aggregationsStore), aggregationsService.getAggregation(refs.name() ?? "", refs.column()));
|
|
9105
|
+
return source ? structuredClone(source) : null;
|
|
9106
|
+
}, ...(ngDevMode ? [{ debugName: "controlledAggregation" }] : []));
|
|
8915
9107
|
const debouncedSearchText = debouncedSignal(refs.searchText, 300);
|
|
8916
9108
|
const normalizedSearchText = computed(() => debouncedSearchText()
|
|
8917
9109
|
.normalize("NFD")
|
|
8918
9110
|
.replace(/[̀-ͯ]/g, ""), ...(ngDevMode ? [{ debugName: "normalizedSearchText" }] : []));
|
|
8919
9111
|
const suggests = signal([], ...(ngDevMode ? [{ debugName: "suggests" }] : []));
|
|
9112
|
+
/**
|
|
9113
|
+
* The selected values (list) or `"/A/B/*"` paths (tree) in controlled mode.
|
|
9114
|
+
* `undefined` in default mode — the selection is then derived from the shared
|
|
9115
|
+
* `QueryParamsStore` filters. In controlled mode without a `selection` input,
|
|
9116
|
+
* this is an empty array: the store filters belong to the shared query, not to
|
|
9117
|
+
* the host's, and must never leak into a controlled instance.
|
|
9118
|
+
*/
|
|
9119
|
+
const selectionValues = computed(() => {
|
|
9120
|
+
const sel = refs.selection?.();
|
|
9121
|
+
if (sel) {
|
|
9122
|
+
switch (sel.kind) {
|
|
9123
|
+
case "values":
|
|
9124
|
+
return sel.values;
|
|
9125
|
+
case "paths":
|
|
9126
|
+
return sel.paths;
|
|
9127
|
+
case "option":
|
|
9128
|
+
case "range":
|
|
9129
|
+
return [];
|
|
9130
|
+
}
|
|
9131
|
+
}
|
|
9132
|
+
return isControlled() ? [] : undefined;
|
|
9133
|
+
}, ...(ngDevMode ? [{ debugName: "selectionValues" }] : []));
|
|
9134
|
+
/** The controlled selection expressed in the shape `getFilter()` returns. */
|
|
9135
|
+
const selectionAsFilter = computed(() => {
|
|
9136
|
+
const sel = refs.selection?.();
|
|
9137
|
+
return sel?.kind === "values" && sel.values.length ? { values: sel.values } : null;
|
|
9138
|
+
}, ...(ngDevMode ? [{ debugName: "selectionAsFilter" }] : []));
|
|
8920
9139
|
const _filterCount = computed(() => {
|
|
9140
|
+
if (isControlled()) {
|
|
9141
|
+
const sel = refs.selection?.();
|
|
9142
|
+
if (!sel)
|
|
9143
|
+
return 0;
|
|
9144
|
+
switch (sel.kind) {
|
|
9145
|
+
case "values":
|
|
9146
|
+
return sel.values.length;
|
|
9147
|
+
case "paths":
|
|
9148
|
+
return sel.paths.length;
|
|
9149
|
+
case "option":
|
|
9150
|
+
return sel.option ? 1 : 0;
|
|
9151
|
+
case "range":
|
|
9152
|
+
return sel.from || sel.to ? 1 : 0;
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
8921
9155
|
getState(aggregationsStore);
|
|
8922
9156
|
const { count = 0 } = queryParamsStore.getFilter({ field: refs.column() ?? undefined, name: refs.name() ?? undefined }) || {};
|
|
8923
9157
|
return count;
|
|
@@ -8940,7 +9174,7 @@ function injectAggregationBase(refs) {
|
|
|
8940
9174
|
.pipe(takeUntilDestroyed(destroyRef), switchMap(({ text, column }) => {
|
|
8941
9175
|
if (text === "" || column === null)
|
|
8942
9176
|
return of([]);
|
|
8943
|
-
const q =
|
|
9177
|
+
const q = resolvedQuery();
|
|
8944
9178
|
return from(withFetch(() => fetchSuggestField(normalizedSearchText(), [column || ""], q), injector));
|
|
8945
9179
|
}))
|
|
8946
9180
|
.subscribe((result) => suggests.set(result || []));
|
|
@@ -8959,7 +9193,9 @@ function injectAggregationBase(refs) {
|
|
|
8959
9193
|
function addCurrentFiltersToItems(aggregation) {
|
|
8960
9194
|
const currentItems = aggregation?.items || [];
|
|
8961
9195
|
if (!aggregation?.isTree && !aggregation?.isDistribution) {
|
|
8962
|
-
const currentFilters =
|
|
9196
|
+
const currentFilters = isControlled()
|
|
9197
|
+
? selectionAsFilter()
|
|
9198
|
+
: queryParamsStore.getFilter({ field: aggregation?.column, name: aggregation?.name });
|
|
8963
9199
|
if (currentFilters) {
|
|
8964
9200
|
if (Array.isArray(currentFilters.filters) && currentFilters.filters.length) {
|
|
8965
9201
|
currentFilters.filters.forEach((filter) => {
|
|
@@ -8998,34 +9234,45 @@ function injectAggregationBase(refs) {
|
|
|
8998
9234
|
}
|
|
8999
9235
|
return currentItems;
|
|
9000
9236
|
}
|
|
9001
|
-
|
|
9237
|
+
/**
|
|
9238
|
+
* Aggregates the applied item filters into the single `LegacyFilter` that
|
|
9239
|
+
* `applyFilters` writes to the store. Also used by the controlled mode to
|
|
9240
|
+
* build the emitted structured filter, so both modes stay strictly
|
|
9241
|
+
* consistent. Returns `undefined` when nothing is applied (the clear case).
|
|
9242
|
+
*/
|
|
9243
|
+
function buildAppliedFilter(appliedFilters, agg) {
|
|
9002
9244
|
const { name: aggName, column: field } = agg;
|
|
9003
|
-
const syncUrl = refs.syncUrl?.() ?? true;
|
|
9004
9245
|
if (appliedFilters.length > 1) {
|
|
9005
9246
|
const display = appliedFilters[0].display;
|
|
9006
9247
|
if (agg.isDistribution) {
|
|
9007
|
-
|
|
9248
|
+
return {
|
|
9008
9249
|
operator: "or",
|
|
9009
9250
|
filters: appliedFilters,
|
|
9010
9251
|
name: aggName,
|
|
9011
9252
|
field,
|
|
9012
9253
|
display,
|
|
9013
|
-
}
|
|
9014
|
-
}
|
|
9015
|
-
else {
|
|
9016
|
-
const values = appliedFilters.map((f) => f.value);
|
|
9017
|
-
queryParamsStore.updateFilter({
|
|
9018
|
-
operator: "in",
|
|
9019
|
-
name: aggName,
|
|
9020
|
-
field,
|
|
9021
|
-
values,
|
|
9022
|
-
display,
|
|
9023
|
-
filters: appliedFilters,
|
|
9024
|
-
}, undefined, { syncUrl });
|
|
9254
|
+
};
|
|
9025
9255
|
}
|
|
9256
|
+
const values = appliedFilters.map((f) => f.value);
|
|
9257
|
+
return {
|
|
9258
|
+
operator: "in",
|
|
9259
|
+
name: aggName,
|
|
9260
|
+
field,
|
|
9261
|
+
values,
|
|
9262
|
+
display,
|
|
9263
|
+
filters: appliedFilters,
|
|
9264
|
+
};
|
|
9026
9265
|
}
|
|
9027
|
-
|
|
9028
|
-
|
|
9266
|
+
if (appliedFilters.length === 1) {
|
|
9267
|
+
return appliedFilters[0];
|
|
9268
|
+
}
|
|
9269
|
+
return undefined;
|
|
9270
|
+
}
|
|
9271
|
+
function applyFilters(appliedFilters, agg, clearFn) {
|
|
9272
|
+
const syncUrl = refs.syncUrl?.() ?? true;
|
|
9273
|
+
const combined = buildAppliedFilter(appliedFilters, agg);
|
|
9274
|
+
if (combined) {
|
|
9275
|
+
queryParamsStore.updateFilter(combined, undefined, { syncUrl });
|
|
9029
9276
|
}
|
|
9030
9277
|
else {
|
|
9031
9278
|
clearFn();
|
|
@@ -9038,6 +9285,14 @@ function injectAggregationBase(refs) {
|
|
|
9038
9285
|
aggregationsService,
|
|
9039
9286
|
injector,
|
|
9040
9287
|
destroyRef,
|
|
9288
|
+
isControlled,
|
|
9289
|
+
isQueryControlled,
|
|
9290
|
+
isDataControlled,
|
|
9291
|
+
isSelectionControlled,
|
|
9292
|
+
resolvedQuery,
|
|
9293
|
+
resolvedAggregationCount,
|
|
9294
|
+
controlledAggregation,
|
|
9295
|
+
selectionValues,
|
|
9041
9296
|
debouncedSearchText,
|
|
9042
9297
|
normalizedSearchText,
|
|
9043
9298
|
suggests,
|
|
@@ -9048,6 +9303,7 @@ function injectAggregationBase(refs) {
|
|
|
9048
9303
|
clearSearch,
|
|
9049
9304
|
selectItems,
|
|
9050
9305
|
addCurrentFiltersToItems,
|
|
9306
|
+
buildAppliedFilter,
|
|
9051
9307
|
applyFilters,
|
|
9052
9308
|
};
|
|
9053
9309
|
}
|
|
@@ -9095,12 +9351,56 @@ class AggregationDateComponent {
|
|
|
9095
9351
|
* @default true
|
|
9096
9352
|
*/
|
|
9097
9353
|
syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
9354
|
+
/**
|
|
9355
|
+
* Controlled query. When set, the component's own operations run against this
|
|
9356
|
+
* query instead of reading the shared `QueryParamsStore`. Use to target a query
|
|
9357
|
+
* not tied to the current app, without mutating the shared store or the URL.
|
|
9358
|
+
*
|
|
9359
|
+
* @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
|
|
9360
|
+
*/
|
|
9361
|
+
controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
|
|
9362
|
+
/**
|
|
9363
|
+
* Controlled data. When set, the component renders this aggregation object
|
|
9364
|
+
* instead of reading it from the shared `AggregationsStore`.
|
|
9365
|
+
*
|
|
9366
|
+
* @default undefined — read `AggregationsStore` (current behavior)
|
|
9367
|
+
*/
|
|
9368
|
+
controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
|
|
9369
|
+
/**
|
|
9370
|
+
* Controlled selection. When set, the date form is derived from this value
|
|
9371
|
+
* instead of the shared `QueryParamsStore` filters:
|
|
9372
|
+
* `{ kind: "option", option }` re-selects the date option whose aggregation
|
|
9373
|
+
* item `value` matches (the real filter value, never the `display`);
|
|
9374
|
+
* `{ kind: "range", from, to }` fills the custom range from the bounds.
|
|
9375
|
+
* The two kinds are mutually exclusive — a date selection is either a preset
|
|
9376
|
+
* or a custom range.
|
|
9377
|
+
*
|
|
9378
|
+
* @default undefined — derive from `QueryParamsStore` filters (current behavior)
|
|
9379
|
+
*/
|
|
9380
|
+
controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
|
|
9098
9381
|
title = input({ label: "Date", icon: "far fa-calendar-day" }, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
9099
9382
|
displayEmptyDistributionIntervals = input(false, ...(ngDevMode ? [{ debugName: "displayEmptyDistributionIntervals" }] : []));
|
|
9100
9383
|
/* outputs */
|
|
9101
9384
|
onSelect = output();
|
|
9102
9385
|
onApply = output();
|
|
9103
9386
|
onClear = output();
|
|
9387
|
+
/**
|
|
9388
|
+
* Emits the new selection (`{ kind: "option" }` preset or `{ kind: "range" }`
|
|
9389
|
+
* custom range) in controlled mode. The counterpart of the `[selection]` input,
|
|
9390
|
+
* emitted before `filtersChange`. Date is a radio list, so this fires **live at
|
|
9391
|
+
* selection time** — picking a preset radio, or confirming a date in the custom
|
|
9392
|
+
* range picker — not on an apply click (the custom-range radio alone, with no
|
|
9393
|
+
* date picked, emits nothing). Also emitted on clear. In controlled mode this is
|
|
9394
|
+
* the only way changes surface: nothing is written to the shared stores or the URL.
|
|
9395
|
+
*/
|
|
9396
|
+
selectionChange = output();
|
|
9397
|
+
/**
|
|
9398
|
+
* Emits the selection as a structured `Filter` (the exact filter the default
|
|
9399
|
+
* mode would write to the store), ready for `fetchQuery({ filters })`.
|
|
9400
|
+
* `undefined` on clear. Controlled mode only — emitted right after
|
|
9401
|
+
* `selectionChange` (see its live-emission timing).
|
|
9402
|
+
*/
|
|
9403
|
+
filtersChange = output();
|
|
9104
9404
|
/* collapse state */
|
|
9105
9405
|
isCollapsed = linkedSignal(() => this.collapsed(), ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
|
|
9106
9406
|
expanded = computed(() => (this.isCollapsed() ? null : ""), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
@@ -9115,8 +9415,12 @@ class AggregationDateComponent {
|
|
|
9115
9415
|
searchInput: this.searchInput,
|
|
9116
9416
|
expanded: this.expanded,
|
|
9117
9417
|
syncUrl: this.syncUrl,
|
|
9418
|
+
query: this.controlledQuery,
|
|
9419
|
+
aggregation: this.controlledAggregation,
|
|
9420
|
+
selection: this.controlledSelection,
|
|
9118
9421
|
});
|
|
9119
9422
|
/* spread from base */
|
|
9423
|
+
isControlled = this.base.isControlled;
|
|
9120
9424
|
aggregationsService = this.base.aggregationsService;
|
|
9121
9425
|
queryParamsStore = this.base.queryParamsStore;
|
|
9122
9426
|
hasFilters = this.base.hasFilters;
|
|
@@ -9127,7 +9431,24 @@ class AggregationDateComponent {
|
|
|
9127
9431
|
/* state */
|
|
9128
9432
|
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
9129
9433
|
validSelection = signal(false, ...(ngDevMode ? [{ debugName: "validSelection" }] : []));
|
|
9434
|
+
/**
|
|
9435
|
+
* Guards live emission while the form is seeded programmatically from the
|
|
9436
|
+
* `[selection]` input (see the seeding effect below). `form.setValue`/`patchValue`
|
|
9437
|
+
* emit `valueChanges` synchronously; without this flag a controlled host that
|
|
9438
|
+
* re-feeds `[selection]` from `selectionChange` would loop indefinitely.
|
|
9439
|
+
*/
|
|
9440
|
+
seedingForm = false;
|
|
9130
9441
|
aggregation = computed(() => {
|
|
9442
|
+
if (this.isControlled()) {
|
|
9443
|
+
const controlled = this.base.controlledAggregation();
|
|
9444
|
+
if (!controlled)
|
|
9445
|
+
return null;
|
|
9446
|
+
const agg = this.aggregationsService.enrichAggregation(controlled, []);
|
|
9447
|
+
return {
|
|
9448
|
+
...agg,
|
|
9449
|
+
items: agg.items?.filter((item) => item.display !== "custom-range") ?? []
|
|
9450
|
+
};
|
|
9451
|
+
}
|
|
9131
9452
|
const name = this.name();
|
|
9132
9453
|
if (name !== null) {
|
|
9133
9454
|
const agg = this.aggregationsService.processAggregation(name, this.column());
|
|
@@ -9163,28 +9484,75 @@ class AggregationDateComponent {
|
|
|
9163
9484
|
this.transloco.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
|
|
9164
9485
|
this.lang.set(lang);
|
|
9165
9486
|
});
|
|
9166
|
-
// apply current date filter from queryParamsStore
|
|
9487
|
+
// apply current date filter from the selection input (controlled) or the queryParamsStore
|
|
9167
9488
|
effect(() => {
|
|
9168
9489
|
const agg = this.aggregation();
|
|
9169
|
-
if (agg)
|
|
9490
|
+
if (!agg)
|
|
9491
|
+
return;
|
|
9492
|
+
// seeding the form from an input must not re-emit through valueChanges
|
|
9493
|
+
this.seedingForm = true;
|
|
9494
|
+
try {
|
|
9495
|
+
if (this.isControlled()) {
|
|
9496
|
+
this.updateFormFromSelection(this.controlledSelection());
|
|
9497
|
+
return;
|
|
9498
|
+
}
|
|
9170
9499
|
this.updateForm(this.queryParamsStore.getFilter({
|
|
9171
9500
|
field: agg.column,
|
|
9172
9501
|
name: agg.name
|
|
9173
9502
|
}));
|
|
9174
9503
|
}
|
|
9504
|
+
finally {
|
|
9505
|
+
this.seedingForm = false;
|
|
9506
|
+
}
|
|
9175
9507
|
});
|
|
9176
9508
|
this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((changes) => {
|
|
9177
9509
|
this.validSelection.set(!!changes.option &&
|
|
9178
9510
|
(changes.option !== "custom-range" || changes.customRange?.from !== null || changes.customRange?.to !== null));
|
|
9511
|
+
// controlled date is a radio list: emit the selection live, without waiting
|
|
9512
|
+
// for an apply click. Presets are complete as soon as the radio is picked;
|
|
9513
|
+
// the custom range is emitted only once a real date is chosen (see
|
|
9514
|
+
// `onRangeSelected`), never on toggling the "custom-range" radio, so a stale
|
|
9515
|
+
// range left in the form is not surfaced.
|
|
9516
|
+
if (!this.seedingForm &&
|
|
9517
|
+
this.isControlled() &&
|
|
9518
|
+
changes.option &&
|
|
9519
|
+
changes.option !== "custom-range") {
|
|
9520
|
+
try {
|
|
9521
|
+
this.emitCurrentSelection();
|
|
9522
|
+
}
|
|
9523
|
+
catch (err) {
|
|
9524
|
+
warn("Error emitting date selection:", err);
|
|
9525
|
+
}
|
|
9526
|
+
}
|
|
9179
9527
|
});
|
|
9180
9528
|
}
|
|
9529
|
+
/**
|
|
9530
|
+
* Emits the current form selection through `selectionChange` then `filtersChange`
|
|
9531
|
+
* (controlled mode). Shared by `apply()` and the live emission paths so both
|
|
9532
|
+
* produce identical payloads. Throws if the form is not a valid selection
|
|
9533
|
+
* (`getFormValueFilter`) — callers decide how to surface it.
|
|
9534
|
+
*/
|
|
9535
|
+
emitCurrentSelection() {
|
|
9536
|
+
const filter = this.getFormValueFilter();
|
|
9537
|
+
const option = this.form.value.option;
|
|
9538
|
+
// a date selection is either a preset (the item's value — the real filter
|
|
9539
|
+
// value, robust across days) or a custom range (its bounds) — never both
|
|
9540
|
+
this.selectionChange.emit(option && option !== "custom-range"
|
|
9541
|
+
? { kind: "option", option: this.optionToItemValue(option) ?? option }
|
|
9542
|
+
: this.toRangeSelection(filter));
|
|
9543
|
+
this.filtersChange.emit(legacyFilterToFilter(filter));
|
|
9544
|
+
}
|
|
9181
9545
|
select() {
|
|
9182
9546
|
this.selection.set(true);
|
|
9183
9547
|
}
|
|
9184
9548
|
apply() {
|
|
9185
9549
|
try {
|
|
9186
|
-
|
|
9187
|
-
|
|
9550
|
+
if (this.isControlled()) {
|
|
9551
|
+
this.emitCurrentSelection();
|
|
9552
|
+
}
|
|
9553
|
+
else {
|
|
9554
|
+
this.queryParamsStore.updateFilter(this.getFormValueFilter(), undefined, { syncUrl: this.syncUrl() });
|
|
9555
|
+
}
|
|
9188
9556
|
this.selection.set(false);
|
|
9189
9557
|
this.onApply.emit();
|
|
9190
9558
|
}
|
|
@@ -9205,7 +9573,13 @@ class AggregationDateComponent {
|
|
|
9205
9573
|
});
|
|
9206
9574
|
const agg = this.aggregation();
|
|
9207
9575
|
if (agg && notify) {
|
|
9208
|
-
|
|
9576
|
+
if (this.isControlled()) {
|
|
9577
|
+
this.selectionChange.emit({ kind: "range" });
|
|
9578
|
+
this.filtersChange.emit(undefined);
|
|
9579
|
+
}
|
|
9580
|
+
else {
|
|
9581
|
+
this.queryParamsStore.removeFilterByName(agg.name, undefined, { syncUrl: this.syncUrl() });
|
|
9582
|
+
}
|
|
9209
9583
|
this.onClear.emit();
|
|
9210
9584
|
}
|
|
9211
9585
|
}
|
|
@@ -9223,12 +9597,22 @@ class AggregationDateComponent {
|
|
|
9223
9597
|
this.dateRangeDialog()?.open();
|
|
9224
9598
|
}
|
|
9225
9599
|
onRangeSelected(range) {
|
|
9226
|
-
console.log("Selected range:", range);
|
|
9227
9600
|
this.form.patchValue({
|
|
9228
9601
|
option: "custom-range",
|
|
9229
9602
|
customRange: { from: range.from, to: range.to }
|
|
9230
9603
|
});
|
|
9231
9604
|
this.select();
|
|
9605
|
+
// a real date was picked in the picker: emit the custom range live in
|
|
9606
|
+
// controlled mode (the only place a custom range is emitted — never on
|
|
9607
|
+
// toggling the "custom-range" radio)
|
|
9608
|
+
if (this.isControlled()) {
|
|
9609
|
+
try {
|
|
9610
|
+
this.emitCurrentSelection();
|
|
9611
|
+
}
|
|
9612
|
+
catch (err) {
|
|
9613
|
+
warn("Error emitting date selection:", err);
|
|
9614
|
+
}
|
|
9615
|
+
}
|
|
9232
9616
|
}
|
|
9233
9617
|
updateForm(filter) {
|
|
9234
9618
|
if (!filter) {
|
|
@@ -9268,6 +9652,90 @@ class AggregationDateComponent {
|
|
|
9268
9652
|
};
|
|
9269
9653
|
this.form.setValue(formValue);
|
|
9270
9654
|
}
|
|
9655
|
+
/**
|
|
9656
|
+
* Derives the form from a controlled selection.
|
|
9657
|
+
* `{ kind: "option" }` re-selects the date option whose item `value` matches —
|
|
9658
|
+
* no operator/value matching, so presets whose bounds are computed relative to
|
|
9659
|
+
* today survive a round-trip across days; a value unknown to the aggregation
|
|
9660
|
+
* resets the form. `{ kind: "range" }` fills the custom range from the bounds.
|
|
9661
|
+
*/
|
|
9662
|
+
updateFormFromSelection(sel) {
|
|
9663
|
+
if (sel?.kind === "option") {
|
|
9664
|
+
const code = this.itemValueToOption(sel.option);
|
|
9665
|
+
if (code && code !== "custom-range") {
|
|
9666
|
+
this.form.setValue({ option: code, customRange: { from: null, to: null } });
|
|
9667
|
+
}
|
|
9668
|
+
else {
|
|
9669
|
+
this.clear(false);
|
|
9670
|
+
}
|
|
9671
|
+
return;
|
|
9672
|
+
}
|
|
9673
|
+
this.updateForm(this.rangeToLegacyFilter(sel));
|
|
9674
|
+
}
|
|
9675
|
+
/**
|
|
9676
|
+
* The date option codes (what `form.option` holds) are the items' `display`
|
|
9677
|
+
* with a fallback on their `value` (see `translateAggregationToDateOptions`).
|
|
9678
|
+
* The controlled selection persists the item `value` — the real filter value —
|
|
9679
|
+
* so these two helpers convert between the option code and the item value.
|
|
9680
|
+
*/
|
|
9681
|
+
optionToItemValue(option) {
|
|
9682
|
+
const item = this.items().find((it) => (it.display || String(it.value)) === option);
|
|
9683
|
+
return item ? String(item.value) : undefined;
|
|
9684
|
+
}
|
|
9685
|
+
itemValueToOption(value) {
|
|
9686
|
+
const item = this.items().find((it) => String(it.value) === value);
|
|
9687
|
+
return item ? item.display || String(item.value) : undefined;
|
|
9688
|
+
}
|
|
9689
|
+
/**
|
|
9690
|
+
* Converts a controlled `{ kind: "range" }` selection to the `LegacyFilter`
|
|
9691
|
+
* shape `updateForm` expects (which only reads `operator`/`value`/`filters`).
|
|
9692
|
+
* Returns `undefined` for an empty or non-range selection, which resets the form.
|
|
9693
|
+
*/
|
|
9694
|
+
rangeToLegacyFilter(sel) {
|
|
9695
|
+
if (!sel || sel.kind !== "range")
|
|
9696
|
+
return undefined;
|
|
9697
|
+
const { from, to } = sel;
|
|
9698
|
+
if (from && to) {
|
|
9699
|
+
return {
|
|
9700
|
+
operator: "and",
|
|
9701
|
+
filters: [
|
|
9702
|
+
{ operator: "gte", value: from },
|
|
9703
|
+
{ operator: "lte", value: to }
|
|
9704
|
+
]
|
|
9705
|
+
};
|
|
9706
|
+
}
|
|
9707
|
+
if (from)
|
|
9708
|
+
return { operator: "gte", value: from };
|
|
9709
|
+
if (to)
|
|
9710
|
+
return { operator: "lte", value: to };
|
|
9711
|
+
return undefined;
|
|
9712
|
+
}
|
|
9713
|
+
/**
|
|
9714
|
+
* Converts the applied `LegacyFilter` to a `{ kind: "range" }` selection
|
|
9715
|
+
* (the shape emitted through `selectionChange` in controlled mode).
|
|
9716
|
+
* Mirrors the operator handling of `updateForm`. Presets whose operator is not
|
|
9717
|
+
* a range operator (e.g. `eq`) collapse to `from = to = value`.
|
|
9718
|
+
*/
|
|
9719
|
+
toRangeSelection(filter) {
|
|
9720
|
+
switch (filter.operator) {
|
|
9721
|
+
case "gte":
|
|
9722
|
+
return { kind: "range", from: filter.value };
|
|
9723
|
+
case "lte":
|
|
9724
|
+
return { kind: "range", to: filter.value };
|
|
9725
|
+
case "between":
|
|
9726
|
+
return { kind: "range", from: filter.start, to: filter.end };
|
|
9727
|
+
case "and": {
|
|
9728
|
+
const filters = filter.filters ?? [];
|
|
9729
|
+
return {
|
|
9730
|
+
kind: "range",
|
|
9731
|
+
from: filters.find((f) => f.operator === "gte")?.value,
|
|
9732
|
+
to: filters.find((f) => f.operator === "lte")?.value
|
|
9733
|
+
};
|
|
9734
|
+
}
|
|
9735
|
+
default:
|
|
9736
|
+
return { kind: "range", from: filter.value, to: filter.value };
|
|
9737
|
+
}
|
|
9738
|
+
}
|
|
9271
9739
|
toEndOfDay(dateStr) {
|
|
9272
9740
|
return `${new Date(dateStr).toLocaleDateString("en-CA", options)} 23:59:59`;
|
|
9273
9741
|
}
|
|
@@ -9343,7 +9811,7 @@ class AggregationDateComponent {
|
|
|
9343
9811
|
throw new Error("filters.filterInvalid");
|
|
9344
9812
|
}
|
|
9345
9813
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9346
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationDateComponent, isStandalone: true, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "dateRangeDialog", first: true, predicate: AggregationDateRangeDialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"scrollbar-thin flex max-h-[var(--scroll-height,20rem)] snap-y snap-start flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
9814
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationDateComponent, isStandalone: true, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, displayEmptyDistributionIntervals: { classPropertyName: "displayEmptyDistributionIntervals", publicName: "displayEmptyDistributionIntervals", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange", searchText: "searchTextChange" }, host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "dateRangeDialog", first: true, predicate: AggregationDateRangeDialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
9347
9815
|
}
|
|
9348
9816
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationDateComponent, decorators: [{
|
|
9349
9817
|
type: Component,
|
|
@@ -9361,8 +9829,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
9361
9829
|
IconButtonComponent
|
|
9362
9830
|
], host: {
|
|
9363
9831
|
class: "@container"
|
|
9364
|
-
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"
|
|
9365
|
-
}], ctorParameters: () => [], propDecorators: { dateRangeDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateRangeDialogComponent), { isSignal: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
9832
|
+
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
|
|
9833
|
+
}], ctorParameters: () => [], propDecorators: { dateRangeDialog: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateRangeDialogComponent), { isSignal: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], displayEmptyDistributionIntervals: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayEmptyDistributionIntervals", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
9366
9834
|
|
|
9367
9835
|
/**
|
|
9368
9836
|
* Component that allows users to select a date or a date range for filtering search results.
|
|
@@ -9371,7 +9839,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
9371
9839
|
*/
|
|
9372
9840
|
class DateComponent extends AggregationDateComponent {
|
|
9373
9841
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DateComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
9374
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"
|
|
9842
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DateComponent, isStandalone: true, selector: "date-filter,DateFilter", host: { classAttribute: "@container" }, providers: [provideTranslocoScope("filters")], usesInheritance: true, ngImport: i0, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"], dependencies: [{ kind: "directive", type: IconButtonComponent, selector: "button[icon-button], icon-button, IconButton", inputs: ["class", "size"] }, { kind: "directive", type: ListItemComponent, selector: "[role=\"listitem\"], [role=\"option\"]", inputs: ["class", "variant", "decoration"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: AggregationDateRangeDialogComponent, selector: "aggregation-date-range-dialog", inputs: ["min", "max", "lang", "useDateRange"], outputs: ["rangeSelected"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: FilterIcon, selector: "filter-icon, FilterIcon", inputs: ["class"] }, { kind: "component", type: FilterXIcon, selector: "filter-x-icon, FilterXIcon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
9375
9843
|
}
|
|
9376
9844
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DateComponent, decorators: [{
|
|
9377
9845
|
type: Component,
|
|
@@ -9388,7 +9856,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
9388
9856
|
FilterXIcon
|
|
9389
9857
|
], host: {
|
|
9390
9858
|
class: "@container"
|
|
9391
|
-
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"
|
|
9859
|
+
}, template: "<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\r\n <summary\r\n [class.cursor-pointer]=\"collapsible()\"\r\n class=\"m-0 flex h-8 w-full items-center pl-1 font-semibold select-none\"\r\n (click)=\"onHeaderClick($event)\">\r\n <ng-content select=\"label\">\r\n @let icon = aggregation()?.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\r\n }\r\n <span class=\"grow\">{{\r\n aggregation()?.display | syslang | transloco\r\n }}</span>\r\n </ng-content>\r\n\r\n @if (hasFilters()) {\r\n <icon-button\r\n [attr.title]=\"'filters.clearFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\r\n (click)=\"clear()\"\r\n (keydown.enter)=\"clear()\">\r\n <filter-x-icon />\r\n <span class=\"sr-only\">{{ \"filters.clearFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n\r\n @if (!isControlled() && selection() && validSelection()) {\r\n <icon-button\r\n [attr.title]=\"'filters.applyFilters' | transloco\"\r\n [attr.aria-label]=\"'filters.applyFilters' | transloco\"\r\n (click)=\"apply()\"\r\n (keydown.enter)=\"apply()\">\r\n <filter-icon />\r\n <span class=\"sr-only\">{{ \"filters.applyFilters\" | transloco }}</span>\r\n </icon-button>\r\n }\r\n @if (collapsible()) {\r\n <icon-button\r\n title=\"Open/Close\"\r\n class=\"cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\r\n <chevronright />\r\n </icon-button>\r\n }\r\n </summary>\r\n\r\n <!-- content wrapper -->\r\n <form [formGroup]=\"form\">\r\n <ul\r\n class=\"flex max-h-[var(--scroll-height,20rem)] snap-y snap-start scrollbar-thin flex-col gap-1 overflow-auto pt-2\"\r\n role=\"list\">\r\n @for (option of dateOptions(); track $index) {\r\n <li\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"radio.click()\"\r\n [attr.aria-label]=\"option.display | syslang | transloco\"\r\n [class]=\"\r\n cn(\r\n 'flex p-0 px-2 leading-7',\r\n form.get('option')?.value === option.display && 'bg-accent',\r\n option.hidden && 'hidden',\r\n option.disabled && 'disabled pointer-events-none text-neutral-300'\r\n )\r\n \"\r\n [attr.aria-hidden]=\"option.disabled\">\r\n <input\r\n #radio\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-{{ option.display }}\"\r\n [attr.disabled]=\"option.disabled ? true : null\"\r\n [attr.aria-disabled]=\"option.disabled\"\r\n (click)=\"select()\"\r\n value=\"{{ option.display }}\" />\r\n\r\n <label\r\n for=\"date-filter-{{ option.display }}\"\r\n class=\"grow cursor-pointer p-1\">\r\n {{ option.display | syslang | transloco }}\r\n </label>\r\n </li>\r\n }\r\n\r\n @if (allowCustomRange) {\r\n <li\r\n role=\"listitem\"\r\n aria-label=\"open date range picker\"\r\n class=\"flex px-2 leading-7\"\r\n [class.select]=\"form.get('option')?.value === 'custom-range'\">\r\n <input\r\n type=\"radio\"\r\n formControlName=\"option\"\r\n id=\"date-filter-range-dialog\"\r\n value=\"custom-range\"\r\n (click)=\"select()\" />\r\n <div\r\n class=\"@container flex grow justify-end gap-1 p-1 @max-[340px]:flex-wrap\">\r\n <div class=\"flex gap-1\">\r\n <label for=\"datepicker-range-start\" class=\"min-w-10 truncate\">{{\r\n \"filters.from\" | transloco\r\n }}</label>\r\n <input\r\n id=\"datepicker-range-start\"\r\n name=\"start\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeFrom()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n <div class=\"flex gap-1\">\r\n <label\r\n for=\"datepicker-range-end\"\r\n class=\"min-w-10 truncate text-right\"\r\n >{{ \"filters.to\" | transloco }}</label\r\n >\r\n <input\r\n id=\"datepicker-range-end\"\r\n name=\"end\"\r\n type=\"text\"\r\n readonly\r\n class=\"h-8 max-w-[13ch] min-w-[13ch]\"\r\n [value]=\"customRangeTo()\"\r\n (click)=\"selectAndOpenDialog()\" />\r\n </div>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </form>\r\n</details>\r\n\r\n<aggregation-date-range-dialog\r\n [lang]=\"lang()\"\r\n [useDateRange]=\"false\"\r\n [min]=\"form.get('customRange.from')?.value || undefined\"\r\n [max]=\"form.get('customRange.to')?.value || undefined\"\r\n (rangeSelected)=\"onRangeSelected($event)\" />\r\n", styles: [":host{display:block;min-width:200px}ul[role=list]{scrollbar-width:thin}\n"] }]
|
|
9392
9860
|
}] });
|
|
9393
9861
|
|
|
9394
9862
|
class ArticleEntities {
|
|
@@ -13721,6 +14189,14 @@ class AggregationTreeComponent {
|
|
|
13721
14189
|
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
13722
14190
|
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
13723
14191
|
showFiltersCount = input(false, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14192
|
+
/**
|
|
14193
|
+
* Whether to render the per-node checkbox. When `false`, the checkbox is hidden
|
|
14194
|
+
* but selection still toggles on row click — pair it with a custom `aggregationItem`
|
|
14195
|
+
* template that reflects the `selected` context to convey selection.
|
|
14196
|
+
*
|
|
14197
|
+
* @default true
|
|
14198
|
+
*/
|
|
14199
|
+
showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13724
14200
|
expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: (v) => {
|
|
13725
14201
|
const n = numberAttribute(v);
|
|
13726
14202
|
return Number.isNaN(n) ? undefined : n;
|
|
@@ -13737,13 +14213,63 @@ class AggregationTreeComponent {
|
|
|
13737
14213
|
* @default true
|
|
13738
14214
|
*/
|
|
13739
14215
|
syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14216
|
+
/**
|
|
14217
|
+
* Controlled query. When set, the component's own operations (load-more,
|
|
14218
|
+
* search-within-facet, tree-expand) run against this query instead of reading
|
|
14219
|
+
* the shared `QueryParamsStore`. Use to target a query not tied to the current
|
|
14220
|
+
* app, without mutating the shared store or the URL.
|
|
14221
|
+
*
|
|
14222
|
+
* @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
|
|
14223
|
+
*/
|
|
14224
|
+
controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
|
|
14225
|
+
/**
|
|
14226
|
+
* Controlled data. When set, the component renders this aggregation object
|
|
14227
|
+
* instead of reading it from the shared `AggregationsStore`.
|
|
14228
|
+
*
|
|
14229
|
+
* @default undefined — read `AggregationsStore` (current behavior)
|
|
14230
|
+
*/
|
|
14231
|
+
controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
|
|
14232
|
+
/**
|
|
14233
|
+
* Controlled selection. When set, item `$selected` state is derived from this
|
|
14234
|
+
* value (`paths` in the `"/A/B/*"` format) instead of the shared
|
|
14235
|
+
* `QueryParamsStore` filters.
|
|
14236
|
+
*
|
|
14237
|
+
* @default undefined — derive from `QueryParamsStore` filters (current behavior)
|
|
14238
|
+
*/
|
|
14239
|
+
controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
|
|
13740
14240
|
/* outputs */
|
|
13741
14241
|
onSelect = output();
|
|
13742
14242
|
onApply = output();
|
|
13743
14243
|
onClear = output();
|
|
14244
|
+
/**
|
|
14245
|
+
* Emits the new selection (`"/A/B/*"` paths) when the user applies or clears
|
|
14246
|
+
* filters in controlled mode. The counterpart of the `[selection]` input —
|
|
14247
|
+
* emitted before `filtersChange` and `onApply`/`onClear`. In controlled mode
|
|
14248
|
+
* this is the only way changes surface: nothing is written to the shared
|
|
14249
|
+
* stores or the URL.
|
|
14250
|
+
*/
|
|
14251
|
+
selectionChange = output();
|
|
14252
|
+
/**
|
|
14253
|
+
* Emits the applied selection as a structured `Filter` (the exact filter the
|
|
14254
|
+
* default mode would write to the store), ready for `fetchQuery({ filters })`.
|
|
14255
|
+
* `undefined` on clear. Controlled mode only — emitted after `selectionChange`,
|
|
14256
|
+
* before `onApply`/`onClear`.
|
|
14257
|
+
*/
|
|
14258
|
+
filtersChange = output();
|
|
13744
14259
|
/* view queries */
|
|
13745
14260
|
virtualItems = viewChildren("virtualItem", ...(ngDevMode ? [{ debugName: "virtualItems" }] : []));
|
|
13746
14261
|
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
14262
|
+
/* optional host-provided template for the node content/label region */
|
|
14263
|
+
itemDirective = contentChild(AggregationItemDirective, ...(ngDevMode ? [{ debugName: "itemDirective" }] : []));
|
|
14264
|
+
/**
|
|
14265
|
+
* Item content template relayed by the `<Aggregation>` wrapper (which cannot
|
|
14266
|
+
* project content into this component). Also usable directly as a `TemplateRef`
|
|
14267
|
+
* input. A directive projected straight onto this component takes precedence.
|
|
14268
|
+
*/
|
|
14269
|
+
itemTemplateInput = input(undefined, ...(ngDevMode ? [{ debugName: "itemTemplateInput", alias: "itemTemplate" }] : [{
|
|
14270
|
+
alias: "itemTemplate",
|
|
14271
|
+
}]));
|
|
14272
|
+
resolvedItemTemplate = computed(() => this.itemDirective()?.template ?? this.itemTemplateInput(), ...(ngDevMode ? [{ debugName: "resolvedItemTemplate" }] : []));
|
|
13747
14273
|
/* selection state */
|
|
13748
14274
|
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
13749
14275
|
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
@@ -13755,8 +14281,12 @@ class AggregationTreeComponent {
|
|
|
13755
14281
|
column: this.column,
|
|
13756
14282
|
searchText: this.searchText,
|
|
13757
14283
|
syncUrl: this.syncUrl,
|
|
14284
|
+
query: this.controlledQuery,
|
|
14285
|
+
aggregation: this.controlledAggregation,
|
|
14286
|
+
selection: this.controlledSelection,
|
|
13758
14287
|
});
|
|
13759
14288
|
/* spread from base */
|
|
14289
|
+
isControlled = this.base.isControlled;
|
|
13760
14290
|
aggregationsStore = this.base.aggregationsStore;
|
|
13761
14291
|
queryParamsStore = this.base.queryParamsStore;
|
|
13762
14292
|
appStore = this.base.appStore;
|
|
@@ -13783,6 +14313,21 @@ class AggregationTreeComponent {
|
|
|
13783
14313
|
#measureItems = effect(() => this.virtualItems().forEach((el) => this.virtualizer.measureElement(el.nativeElement)), ...(ngDevMode ? [{ debugName: "#measureItems" }] : []));
|
|
13784
14314
|
linkChildren = computed(() => this.appStore.general()?.features?.filterLinkChildren, ...(ngDevMode ? [{ debugName: "linkChildren" }] : []));
|
|
13785
14315
|
aggregation = computed(() => {
|
|
14316
|
+
if (this.isControlled()) {
|
|
14317
|
+
const agg = this.base.controlledAggregation();
|
|
14318
|
+
if (!agg)
|
|
14319
|
+
return null;
|
|
14320
|
+
if (!agg.isTree) {
|
|
14321
|
+
error("The aggregation tree component does not support list aggregations. Please use the <Aggregation /> component instead.");
|
|
14322
|
+
}
|
|
14323
|
+
const enriched = this.aggregationsService.enrichAggregation(agg, this.base.selectionValues() ?? []);
|
|
14324
|
+
const expandedLevel = this.expandedLevel() ?? enriched.expandedLevel;
|
|
14325
|
+
if (expandedLevel) {
|
|
14326
|
+
this.expandItems(enriched.items, expandedLevel);
|
|
14327
|
+
}
|
|
14328
|
+
enriched.searchable = this.searchable() ?? enriched.searchable;
|
|
14329
|
+
return enriched;
|
|
14330
|
+
}
|
|
13786
14331
|
getState(this.aggregationsStore);
|
|
13787
14332
|
const name = this.name();
|
|
13788
14333
|
const column = this.column();
|
|
@@ -13813,7 +14358,10 @@ class AggregationTreeComponent {
|
|
|
13813
14358
|
else if (agg?.items) {
|
|
13814
14359
|
res = this.addCurrentFiltersToItems();
|
|
13815
14360
|
}
|
|
13816
|
-
|
|
14361
|
+
// Controlled mode never touches sessionStorage: selection persistence is the
|
|
14362
|
+
// host's responsibility via the `selection` input, and the `agg-${column}` key
|
|
14363
|
+
// is shared with any default-mode instance on the same column.
|
|
14364
|
+
const sessionAggItem = this.isControlled() ? null : sessionStorage.getItem(`agg-${agg?.column}`);
|
|
13817
14365
|
const sessionAgg = JSON.parse(sessionAggItem || "[]");
|
|
13818
14366
|
return this.processAggregations(sessionAgg.length ? this.setSelected(res, sessionAgg) : res);
|
|
13819
14367
|
}, ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
@@ -13837,13 +14385,16 @@ class AggregationTreeComponent {
|
|
|
13837
14385
|
constructor() {
|
|
13838
14386
|
effect(() => {
|
|
13839
14387
|
getState(this.aggregationsStore);
|
|
14388
|
+
if (this.isControlled())
|
|
14389
|
+
return;
|
|
13840
14390
|
sessionStorage.removeItem(`agg-${this.column()}`);
|
|
13841
14391
|
});
|
|
13842
14392
|
effect(() => {
|
|
13843
14393
|
this.filters.set(this.getFilters());
|
|
13844
14394
|
});
|
|
13845
14395
|
this.destroyRef.onDestroy(() => {
|
|
13846
|
-
|
|
14396
|
+
if (!this.isControlled())
|
|
14397
|
+
sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
|
|
13847
14398
|
if (this.selection()) {
|
|
13848
14399
|
const unselect = (items) => {
|
|
13849
14400
|
items.forEach((item) => {
|
|
@@ -13860,7 +14411,13 @@ class AggregationTreeComponent {
|
|
|
13860
14411
|
clear() {
|
|
13861
14412
|
const agg = this.aggregation();
|
|
13862
14413
|
if (agg) {
|
|
13863
|
-
|
|
14414
|
+
if (this.isControlled()) {
|
|
14415
|
+
this.selectionChange.emit({ kind: "paths", paths: [] });
|
|
14416
|
+
this.filtersChange.emit(undefined);
|
|
14417
|
+
}
|
|
14418
|
+
else {
|
|
14419
|
+
this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
|
|
14420
|
+
}
|
|
13864
14421
|
const unselect = (items) => {
|
|
13865
14422
|
items.forEach((item) => {
|
|
13866
14423
|
item.$selected = false;
|
|
@@ -13870,7 +14427,8 @@ class AggregationTreeComponent {
|
|
|
13870
14427
|
});
|
|
13871
14428
|
};
|
|
13872
14429
|
unselect(this.items());
|
|
13873
|
-
|
|
14430
|
+
if (!this.isControlled())
|
|
14431
|
+
sessionStorage.removeItem(`agg-${this.aggregation()?.column}`);
|
|
13874
14432
|
this.selection.set(false);
|
|
13875
14433
|
this.isAllSelected.set(false);
|
|
13876
14434
|
this.onClear.emit();
|
|
@@ -13891,21 +14449,57 @@ class AggregationTreeComponent {
|
|
|
13891
14449
|
}
|
|
13892
14450
|
}
|
|
13893
14451
|
apply(overrideFilters) {
|
|
13894
|
-
sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
|
|
13895
14452
|
const filters = overrideFilters || this.getFilters();
|
|
13896
14453
|
const agg = this.aggregation();
|
|
13897
|
-
|
|
14454
|
+
if (this.isControlled()) {
|
|
14455
|
+
const paths = filters[0]?.values ?? [];
|
|
14456
|
+
this.selectionChange.emit({ kind: "paths", paths });
|
|
14457
|
+
this.filtersChange.emit(legacyFilterToFilter(this.base.buildAppliedFilter(filters, agg)));
|
|
14458
|
+
}
|
|
14459
|
+
else {
|
|
14460
|
+
sessionStorage.setItem(`agg-${agg?.column}`, JSON.stringify([...this.items()]));
|
|
14461
|
+
this.base.applyFilters(filters, agg, () => this.clear());
|
|
14462
|
+
}
|
|
13898
14463
|
this.searchText.set("");
|
|
13899
14464
|
this.selection.set(false);
|
|
13900
14465
|
this.onApply.emit();
|
|
13901
14466
|
}
|
|
13902
14467
|
loadMore() {
|
|
14468
|
+
// Controlled mode loads into the local state — never the shared AggregationsStore.
|
|
14469
|
+
if (this.isControlled()) {
|
|
14470
|
+
const agg = this.base.controlledAggregation();
|
|
14471
|
+
if (!agg)
|
|
14472
|
+
return;
|
|
14473
|
+
this.aggregationsService
|
|
14474
|
+
.loadMore(this.base.resolvedQuery(), agg, undefined, { count: this.base.resolvedAggregationCount(agg) })
|
|
14475
|
+
.subscribe((aggregation) => {
|
|
14476
|
+
this.base.controlledAggregation.set(aggregation);
|
|
14477
|
+
});
|
|
14478
|
+
return;
|
|
14479
|
+
}
|
|
13903
14480
|
const q = this.queryParamsStore.getQuery();
|
|
13904
14481
|
this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
|
|
13905
14482
|
this.aggregationsStore.updateAggregation(aggregation);
|
|
13906
14483
|
});
|
|
13907
14484
|
}
|
|
13908
14485
|
async open(node) {
|
|
14486
|
+
// Controlled mode expands into the local state via the guard-free fetch —
|
|
14487
|
+
// never the shared AggregationsStore, never QueryService's global result signal.
|
|
14488
|
+
if (this.isControlled()) {
|
|
14489
|
+
const controlled = this.base.controlledAggregation();
|
|
14490
|
+
if (!controlled)
|
|
14491
|
+
return;
|
|
14492
|
+
// Spread before mutating: resolvedQuery() may return the host's query object.
|
|
14493
|
+
const q = { ...this.base.resolvedQuery() };
|
|
14494
|
+
delete q.filters;
|
|
14495
|
+
const agg = await firstValueFrom(this.aggregationsService.open(q, controlled, node, {
|
|
14496
|
+
guardFree: true,
|
|
14497
|
+
}));
|
|
14498
|
+
node.$opened = true;
|
|
14499
|
+
// replaceInTree mutates and returns the same reference — spread so the signal notifies.
|
|
14500
|
+
this.base.controlledAggregation.set({ ...agg });
|
|
14501
|
+
return;
|
|
14502
|
+
}
|
|
13909
14503
|
const q = this.queryParamsStore.getQuery();
|
|
13910
14504
|
delete q.filters;
|
|
13911
14505
|
const agg = await firstValueFrom(this.aggregationsService.open(q, this.aggregation(), node));
|
|
@@ -13917,7 +14511,9 @@ class AggregationTreeComponent {
|
|
|
13917
14511
|
this.onSelect.emit(selectedItems);
|
|
13918
14512
|
this.selection.set(selectedItems.length > 0 || this.hasFilters());
|
|
13919
14513
|
this.verifySelected();
|
|
13920
|
-
|
|
14514
|
+
if (!this.isControlled()) {
|
|
14515
|
+
sessionStorage.setItem(`agg-${this.aggregation()?.column}`, JSON.stringify([...this.items()]));
|
|
14516
|
+
}
|
|
13921
14517
|
}
|
|
13922
14518
|
/* item-level methods — called from the ng-template */
|
|
13923
14519
|
treeItemName(item) {
|
|
@@ -13925,6 +14521,10 @@ class AggregationTreeComponent {
|
|
|
13925
14521
|
return typeof value === "string" ? value : `${value}`;
|
|
13926
14522
|
}
|
|
13927
14523
|
isTreeItemFiltered(item, field) {
|
|
14524
|
+
if (this.isControlled()) {
|
|
14525
|
+
const values = [item.value, `/${item.$path}/*`];
|
|
14526
|
+
return (this.base.selectionValues() ?? []).some((value) => values.some((v) => v === value));
|
|
14527
|
+
}
|
|
13928
14528
|
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.treeItemName(item) });
|
|
13929
14529
|
if (!filters)
|
|
13930
14530
|
return false;
|
|
@@ -13985,7 +14585,10 @@ class AggregationTreeComponent {
|
|
|
13985
14585
|
const notSelectedItems = this.getFlattenTreeItems()
|
|
13986
14586
|
.filter((item) => !item.$selected)
|
|
13987
14587
|
.map((item) => (item.$path ? `/${item.$path}/*` : ""));
|
|
13988
|
-
const
|
|
14588
|
+
const sel = this.controlledSelection();
|
|
14589
|
+
const currentFilters = this.isControlled()
|
|
14590
|
+
? (sel?.kind === "paths" ? sel.paths : [])
|
|
14591
|
+
: this.queryParamsStore.getFilter({ field, name })?.values || [];
|
|
13989
14592
|
if (currentFilters) {
|
|
13990
14593
|
const filteredCurrentFilters = currentFilters.filter((filter) => !notSelectedItems.includes(filter));
|
|
13991
14594
|
if (filteredCurrentFilters.length > 0) {
|
|
@@ -14111,7 +14714,7 @@ class AggregationTreeComponent {
|
|
|
14111
14714
|
return false;
|
|
14112
14715
|
}
|
|
14113
14716
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14114
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationTreeComponent, isStandalone: true, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\", class())" } }, viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the <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" }] });
|
|
14717
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationTreeComponent, isStandalone: true, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, itemTemplateInput: { classPropertyName: "itemTemplateInput", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit] w-[inherit]\", class())" } }, queries: [{ propertyName: "itemDirective", first: true, predicate: AggregationItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "virtualItems", predicate: ["virtualItem"], descendants: true, isSignal: true }, { propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the <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=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: node,\r\n field: field,\r\n selected: node.$selected || node.$selectedVisually || false,\r\n count: node.count,\r\n name: (treeItemName(node) | syslang) ?? '',\r\n searchText: searchText(),\r\n level: treeItemLevel(node),\r\n expanded: node.$opened ?? false,\r\n hasChildren: node.hasChildren ?? false\r\n }\" />\r\n } @else {\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: ChevronRightIcon, selector: "chevron-right, ChevronRight, chevronright, ChevronRightIcon, chevron-right-icon, chevronrighticon", inputs: ["class"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
|
|
14115
14718
|
}
|
|
14116
14719
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationTreeComponent, decorators: [{
|
|
14117
14720
|
type: Component,
|
|
@@ -14129,8 +14732,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14129
14732
|
AggregationPanelComponent,
|
|
14130
14733
|
], standalone: true, host: {
|
|
14131
14734
|
"[class]": 'cn("block h-[inherit] max-h-[inherit] w-[inherit]", class())',
|
|
14132
|
-
}, template: "@if (!aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregationTree component does not support list aggregations. Please use\r\n the <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=\"
|
|
14133
|
-
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], virtualItems: [{ type: i0.ViewChildren, args: ["virtualItem", { isSignal: true }] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
14735
|
+
}, 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=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"tree\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n <div\r\n class=\"absolute top-0 left-0 w-full\"\r\n [style.transform]=\"\r\n 'translateY(' +\r\n (virtualizer.getVirtualItems()[0]\r\n ? virtualizer.getVirtualItems()[0].start\r\n : 0) +\r\n 'px)'\r\n \">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div #virtualItem [attr.data-index]=\"vItem.index\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"treeItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: item,\r\n path: [],\r\n field: aggregation()?.column,\r\n tpl: treeItemTpl,\r\n parent: null\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template\r\n #treeItemTpl\r\n let-node=\"node\"\r\n let-path=\"path\"\r\n let-field=\"field\"\r\n let-tpl=\"tpl\"\r\n let-parent=\"parent\">\r\n <div\r\n role=\"treeitem\"\r\n [style.--level]=\"treeItemLevel(node)\"\r\n [attr.aria-selected]=\"node.$selected ?? false\"\r\n [attr.aria-expanded]=\"\r\n node.hasChildren !== false ? (node.$opened ?? false) : null\r\n \"\r\n [attr.aria-disabled]=\"node.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"node.count === 0 ? 'disabled' : null\">\r\n <a\r\n [attr.aria-label]=\"treeItemName(node) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n node.count === 0 && 'disabled pointer-events-none',\r\n (node.$selected || node.$selectedVisually) && ''\r\n )\r\n \"\r\n (click)=\"selectTreeItem(node, parent, $event, true)\">\r\n <button\r\n variant=\"none\"\r\n [iconOnly]=\"true\"\r\n (click)=\"toggleTreeNode($event, node)\"\r\n class=\"transition-transform ease-in hover:scale-125\"\r\n aria-label=\"Open\">\r\n <ChevronRight\r\n [class]=\"\r\n cn(\r\n 'size-4 translate-x-1',\r\n node.$opened && 'rotate-90',\r\n !node.hasChildren && 'hidden'\r\n )\r\n \"\r\n width=\"16\"\r\n height=\"16\" />\r\n </button>\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ node.value }}\"\r\n [attr.disabled]=\"node.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"node.count === 0\"\r\n (keydown.enter)=\"selectTreeItem(node, parent, $event)\"\r\n [checked]=\"node.$selected || node.$selectedVisually\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: node,\r\n field: field,\r\n selected: node.$selected || node.$selectedVisually || false,\r\n count: node.count,\r\n name: (treeItemName(node) | syslang) ?? '',\r\n searchText: searchText(),\r\n level: treeItemLevel(node),\r\n expanded: node.$opened ?? false,\r\n hasChildren: node.hasChildren ?? false\r\n }\" />\r\n } @else {\r\n @let icon = node.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isTreeItemFiltered(node, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (treeItemName(node) | syslang)\r\n : (treeItemName(node) | syslang)\r\n \"\r\n (click)=\"onTreeItemTextClick(node, parent, $event)\">\r\n @for (\r\n chunk of (treeItemName(node) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && node.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n node.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n @if (node.hasChildren && node.$opened) {\r\n <div role=\"group\">\r\n @for (child of node.items; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n node: child,\r\n path: treeChildrenPath(node, path),\r\n field: field,\r\n tpl: tpl,\r\n parent: node\r\n }\">\r\n </ng-container>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["div[role=treeitem]:has(+div[role=treeitem]){margin-bottom:var(--agg-item-gap, 0)}div[role=treeitem]{display:block;-webkit-user-select:none;user-select:none}div[role=treeitem] a{padding-left:calc(var(--agg-tree-indent, .5rem) * var(--level, 0));line-height:var(--agg-item-height, inherit)}\n"] }]
|
|
14736
|
+
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], virtualItems: [{ type: i0.ViewChildren, args: ["virtualItem", { isSignal: true }] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], itemDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AggregationItemDirective), { isSignal: true }] }], itemTemplateInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
14134
14737
|
|
|
14135
14738
|
class AggregationListComponent {
|
|
14136
14739
|
cn = cn;
|
|
@@ -14143,6 +14746,14 @@ class AggregationListComponent {
|
|
|
14143
14746
|
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
14144
14747
|
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
14145
14748
|
showFiltersCount = input(null, ...(ngDevMode ? [{ debugName: "showFiltersCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14749
|
+
/**
|
|
14750
|
+
* Whether to render the per-item checkbox. When `false`, the checkbox is hidden
|
|
14751
|
+
* but selection still toggles on row click — pair it with a custom `aggregationItem`
|
|
14752
|
+
* template that reflects the `selected` context to convey selection.
|
|
14753
|
+
*
|
|
14754
|
+
* @default true
|
|
14755
|
+
*/
|
|
14756
|
+
showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14146
14757
|
/**
|
|
14147
14758
|
* When `false`, applying or clearing filters updates the store without
|
|
14148
14759
|
* synchronizing the URL (no browser history entry, not shareable/restorable).
|
|
@@ -14150,12 +14761,61 @@ class AggregationListComponent {
|
|
|
14150
14761
|
* @default true
|
|
14151
14762
|
*/
|
|
14152
14763
|
syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14764
|
+
/**
|
|
14765
|
+
* Controlled query. When set, the component's own operations (load-more,
|
|
14766
|
+
* search-within-facet) run against this query instead of reading the shared
|
|
14767
|
+
* `QueryParamsStore`. Use to target a query not tied to the current app,
|
|
14768
|
+
* without mutating the shared store or the URL.
|
|
14769
|
+
*
|
|
14770
|
+
* @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
|
|
14771
|
+
*/
|
|
14772
|
+
controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
|
|
14773
|
+
/**
|
|
14774
|
+
* Controlled data. When set, the component renders this aggregation object
|
|
14775
|
+
* instead of reading it from the shared `AggregationsStore`.
|
|
14776
|
+
*
|
|
14777
|
+
* @default undefined — read `AggregationsStore` (current behavior)
|
|
14778
|
+
*/
|
|
14779
|
+
controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
|
|
14780
|
+
/**
|
|
14781
|
+
* Controlled selection. When set, item `$selected` state is derived from this
|
|
14782
|
+
* value instead of the shared `QueryParamsStore` filters.
|
|
14783
|
+
*
|
|
14784
|
+
* @default undefined — derive from `QueryParamsStore` filters (current behavior)
|
|
14785
|
+
*/
|
|
14786
|
+
controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
|
|
14153
14787
|
/* outputs */
|
|
14154
14788
|
onSelect = output();
|
|
14155
14789
|
onApply = output();
|
|
14156
14790
|
onClear = output();
|
|
14791
|
+
/**
|
|
14792
|
+
* Emits the new selection (item values) when the user applies or clears
|
|
14793
|
+
* filters in controlled mode. The counterpart of the `[selection]` input —
|
|
14794
|
+
* emitted before `filtersChange` and `onApply`/`onClear`. In controlled mode
|
|
14795
|
+
* this is the only way changes surface: nothing is written to the shared
|
|
14796
|
+
* stores or the URL.
|
|
14797
|
+
*/
|
|
14798
|
+
selectionChange = output();
|
|
14799
|
+
/**
|
|
14800
|
+
* Emits the applied selection as a structured `Filter` (the exact filter the
|
|
14801
|
+
* default mode would write to the store), ready for `fetchQuery({ filters })`.
|
|
14802
|
+
* `undefined` on clear. Controlled mode only — emitted after `selectionChange`,
|
|
14803
|
+
* before `onApply`/`onClear`.
|
|
14804
|
+
*/
|
|
14805
|
+
filtersChange = output();
|
|
14157
14806
|
/* view queries */
|
|
14158
14807
|
scrollElement = viewChild("scrollElement", ...(ngDevMode ? [{ debugName: "scrollElement" }] : []));
|
|
14808
|
+
/* optional host-provided template for the item content/label region */
|
|
14809
|
+
itemDirective = contentChild(AggregationItemDirective, ...(ngDevMode ? [{ debugName: "itemDirective" }] : []));
|
|
14810
|
+
/**
|
|
14811
|
+
* Item content template relayed by the `<Aggregation>` wrapper (which cannot
|
|
14812
|
+
* project content into this component). Also usable directly as a `TemplateRef`
|
|
14813
|
+
* input. A directive projected straight onto this component takes precedence.
|
|
14814
|
+
*/
|
|
14815
|
+
itemTemplateInput = input(undefined, ...(ngDevMode ? [{ debugName: "itemTemplateInput", alias: "itemTemplate" }] : [{
|
|
14816
|
+
alias: "itemTemplate",
|
|
14817
|
+
}]));
|
|
14818
|
+
resolvedItemTemplate = computed(() => this.itemDirective()?.template ?? this.itemTemplateInput(), ...(ngDevMode ? [{ debugName: "resolvedItemTemplate" }] : []));
|
|
14159
14819
|
/* selection state */
|
|
14160
14820
|
selection = signal(false, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
14161
14821
|
isAllSelected = signal(false, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
|
|
@@ -14167,8 +14827,12 @@ class AggregationListComponent {
|
|
|
14167
14827
|
column: this.column,
|
|
14168
14828
|
searchText: this.searchText,
|
|
14169
14829
|
syncUrl: this.syncUrl,
|
|
14830
|
+
query: this.controlledQuery,
|
|
14831
|
+
aggregation: this.controlledAggregation,
|
|
14832
|
+
selection: this.controlledSelection,
|
|
14170
14833
|
});
|
|
14171
14834
|
/* spread from base */
|
|
14835
|
+
isControlled = this.base.isControlled;
|
|
14172
14836
|
aggregationsStore = this.base.aggregationsStore;
|
|
14173
14837
|
queryParamsStore = this.base.queryParamsStore;
|
|
14174
14838
|
appStore = this.base.appStore;
|
|
@@ -14192,6 +14856,17 @@ class AggregationListComponent {
|
|
|
14192
14856
|
scrollElement: this.scrollElement(),
|
|
14193
14857
|
}));
|
|
14194
14858
|
aggregation = computed(() => {
|
|
14859
|
+
if (this.isControlled()) {
|
|
14860
|
+
const agg = this.base.controlledAggregation();
|
|
14861
|
+
if (!agg)
|
|
14862
|
+
return null;
|
|
14863
|
+
if (agg.isTree) {
|
|
14864
|
+
error("The aggregation component does not support tree aggregations. Please use the <AggregationTree /> component instead.");
|
|
14865
|
+
}
|
|
14866
|
+
const enriched = this.aggregationsService.enrichAggregation(agg, this.base.selectionValues() ?? []);
|
|
14867
|
+
enriched.searchable = this.searchable() ?? enriched.searchable;
|
|
14868
|
+
return enriched;
|
|
14869
|
+
}
|
|
14195
14870
|
getState(this.aggregationsStore);
|
|
14196
14871
|
const name = this.name();
|
|
14197
14872
|
const column = this.column();
|
|
@@ -14253,7 +14928,13 @@ class AggregationListComponent {
|
|
|
14253
14928
|
clear() {
|
|
14254
14929
|
const agg = this.aggregation();
|
|
14255
14930
|
if (agg) {
|
|
14256
|
-
|
|
14931
|
+
if (this.isControlled()) {
|
|
14932
|
+
this.selectionChange.emit({ kind: "values", values: [] });
|
|
14933
|
+
this.filtersChange.emit(undefined);
|
|
14934
|
+
}
|
|
14935
|
+
else {
|
|
14936
|
+
this.queryParamsStore.removeFilterByName(agg.name, agg.column, { syncUrl: this.syncUrl() });
|
|
14937
|
+
}
|
|
14257
14938
|
this.selection.set(false);
|
|
14258
14939
|
this.isAllSelected.set(false);
|
|
14259
14940
|
}
|
|
@@ -14278,12 +14959,34 @@ class AggregationListComponent {
|
|
|
14278
14959
|
const agg = this.aggregation();
|
|
14279
14960
|
if (!agg)
|
|
14280
14961
|
return;
|
|
14281
|
-
|
|
14962
|
+
if (this.isControlled()) {
|
|
14963
|
+
const values = [...this.items(), ...this.searchedItems()]
|
|
14964
|
+
.filter((item) => item.$selected)
|
|
14965
|
+
.map((item) => String(item.value));
|
|
14966
|
+
this.selectionChange.emit({ kind: "values", values: Array.from(new Set(values)) });
|
|
14967
|
+
this.filtersChange.emit(legacyFilterToFilter(this.base.buildAppliedFilter(this.getFilters(), agg)));
|
|
14968
|
+
}
|
|
14969
|
+
else {
|
|
14970
|
+
this.base.applyFilters(this.getFilters(), agg, () => this.clear());
|
|
14971
|
+
}
|
|
14282
14972
|
this.searchText.set("");
|
|
14283
14973
|
this.selection.set(false);
|
|
14284
14974
|
this.onApply.emit();
|
|
14285
14975
|
}
|
|
14286
14976
|
loadMore() {
|
|
14977
|
+
// Controlled mode loads into the local state — never the shared AggregationsStore.
|
|
14978
|
+
if (this.isControlled()) {
|
|
14979
|
+
const agg = this.base.controlledAggregation();
|
|
14980
|
+
if (!agg)
|
|
14981
|
+
return;
|
|
14982
|
+
this.aggregationsService
|
|
14983
|
+
.loadMore(this.base.resolvedQuery(), agg, undefined, { count: this.base.resolvedAggregationCount(agg) })
|
|
14984
|
+
.subscribe((aggregation) => {
|
|
14985
|
+
this.base.controlledAggregation.set(aggregation);
|
|
14986
|
+
this.cdr.detectChanges();
|
|
14987
|
+
});
|
|
14988
|
+
return;
|
|
14989
|
+
}
|
|
14287
14990
|
const q = this.queryParamsStore.getQuery();
|
|
14288
14991
|
this.aggregationsService.loadMore(q, this.aggregation()).subscribe((aggregation) => {
|
|
14289
14992
|
this.aggregationsStore.updateAggregation(aggregation);
|
|
@@ -14301,6 +15004,9 @@ class AggregationListComponent {
|
|
|
14301
15004
|
return typeof value === "string" ? value : `${value}`;
|
|
14302
15005
|
}
|
|
14303
15006
|
isListItemFiltered(item, field) {
|
|
15007
|
+
if (this.isControlled()) {
|
|
15008
|
+
return (this.base.selectionValues() ?? []).some((value) => value === item.value);
|
|
15009
|
+
}
|
|
14304
15010
|
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
|
|
14305
15011
|
if (!filters)
|
|
14306
15012
|
return false;
|
|
@@ -14330,7 +15036,7 @@ class AggregationListComponent {
|
|
|
14330
15036
|
return this.base.addCurrentFiltersToItems(this.aggregation());
|
|
14331
15037
|
}
|
|
14332
15038
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14333
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationListComponent, isStandalone: true, selector: "AggregationList, aggregation-list, aggregationlist", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class())" } }, viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <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=\"
|
|
15039
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationListComponent, isStandalone: true, selector: "AggregationList, aggregation-list, aggregationlist", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, itemTemplateInput: { classPropertyName: "itemTemplateInput", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange", searchText: "searchTextChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\", class())" } }, queries: [{ propertyName: "itemDirective", first: true, predicate: AggregationItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "scrollElement", first: true, predicate: ["scrollElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <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=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: field,\r\n selected: item.$selected ?? false,\r\n count: item.count,\r\n name: (listItemName(item) | syslang) ?? '',\r\n searchText: searchText()\r\n }\" />\r\n } @else {\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "component", type: TriangleAlertIcon, selector: "triangle-alert-icon, TriangleAlertIcon", inputs: ["class"] }, { kind: "component", type: AggregationPanelComponent, selector: "AggregationPanel, aggregation-panel", inputs: ["id", "collapsible", "collapsed", "isDate", "isEmpty", "aggregation", "showFiltersCount", "filtersCount", "hasFilters", "selection", "isAllSelected", "searchText", "itemsLength", "hasMore", "searchedItemsLength"], outputs: ["searchTextChange", "cleared", "applied", "allSelected", "allUnselected", "loadedMore"] }, { kind: "pipe", type: SyslangPipe, name: "syslang" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: HighlightWordPipe, name: "highlightWord" }] });
|
|
14334
15040
|
}
|
|
14335
15041
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationListComponent, decorators: [{
|
|
14336
15042
|
type: Component,
|
|
@@ -14346,8 +15052,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14346
15052
|
AggregationPanelComponent,
|
|
14347
15053
|
], standalone: true, host: {
|
|
14348
15054
|
"[class]": 'cn("block h-[inherit] max-h-[inherit]", class())',
|
|
14349
|
-
}, template: "@if (aggregation()?.isTree) {\r\n <div class=\"p-2 text-sm text-red-500\">\r\n <triangle-alert-icon class=\"mr-1\" />\r\n The aggregation component no longer supports tree aggregations. Please use\r\n the <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=\"
|
|
14350
|
-
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
15055
|
+
}, 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=\"max-h-(--scroll-height,20rem) w-full scrollbar-thin overflow-auto\">\r\n <div\r\n class=\"relative w-full\"\r\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\"\r\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\r\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\r\n @let item = items()[vItem.index];\r\n <div\r\n class=\"absolute w-full\"\r\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.$selected ?? false\"\r\n [attr.aria-disabled]=\"item.count === 0 ? 'true' : null\"\r\n [attr.disabled]=\"item.count === 0 ? 'disabled' : null\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listItemTpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: aggregation()?.column\r\n }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</aggregation-panel>\r\n\r\n<ng-template #listItemTpl let-item let-field=\"field\">\r\n <a\r\n [attr.aria-label]=\"listItemName(item) | syslang\"\r\n [class]=\"\r\n cn(\r\n 'flex grow items-center gap-2 p-1 leading-7',\r\n item.count === 0 && 'disabled pointer-events-none'\r\n )\r\n \"\r\n (click)=\"selectListItem(item, $event)\">\r\n @if (showCheckbox()) {\r\n <input\r\n type=\"checkbox\"\r\n role=\"checkbox\"\r\n value=\"{{ item.value }}\"\r\n [attr.disabled]=\"item.count === 0 ? true : null\"\r\n [attr.aria-disabled]=\"item.count === 0\"\r\n (keydown.enter)=\"selectListItem(item, $event)\"\r\n [checked]=\"item.$selected\" />\r\n }\r\n @if (resolvedItemTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: item,\r\n field: field,\r\n selected: item.$selected ?? false,\r\n count: item.count,\r\n name: (listItemName(item) | syslang) ?? '',\r\n searchText: searchText()\r\n }\" />\r\n } @else {\r\n @let icon = item.icon;\r\n @if (icon) {\r\n <FaIcon [faClass]=\"icon\" class=\"self-center justify-self-center\" />\r\n }\r\n <span\r\n [class]=\"\r\n cn(\r\n 'line-clamp-1 break-all text-ellipsis',\r\n quickFilter() && 'hover:underline'\r\n )\r\n \"\r\n [title]=\"\r\n quickFilter()\r\n ? ((isListItemFiltered(item, field)\r\n ? 'filters.removeFilter'\r\n : 'filters.addFilter'\r\n ) | transloco) +\r\n ': ' +\r\n (listItemName(item) | syslang)\r\n : (listItemName(item) | syslang)\r\n \"\r\n (click)=\"onListItemTextClick(item, $event)\">\r\n @for (\r\n chunk of (listItemName(item) | syslang) ?? \"\"\r\n | highlightWord: searchText() : 10;\r\n track $index\r\n ) {\r\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\r\n chunk.text\r\n }}</span>\r\n }\r\n </span>\r\n @if (showCount() && item.count > 0) {\r\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\r\n item.count\r\n }}</span>\r\n }\r\n }\r\n </a>\r\n</ng-template>\r\n", styles: ["div[role=option]{display:block;-webkit-user-select:none;user-select:none}div[role=option] a{padding-left:var(--agg-tree-indent, .5rem);line-height:var(--agg-item-height, inherit)}\n"] }]
|
|
15056
|
+
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], scrollElement: [{ type: i0.ViewChild, args: ["scrollElement", { isSignal: true }] }], itemDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AggregationItemDirective), { isSignal: true }] }], itemTemplateInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }] } });
|
|
14351
15057
|
|
|
14352
15058
|
/**
|
|
14353
15059
|
* The `AggregationComponent` is responsible for rendering the appropriate aggregation component based on the type of aggregation (date, tree, or list). It uses the `AggregationsService` to retrieve the aggregation data and determines which component to display based on the column type and whether it is a tree structure. The component also provides inputs for configuring its behavior, such as collapsibility, searchability, and showing filter counts.
|
|
@@ -14372,6 +15078,12 @@ class AggregationComponent {
|
|
|
14372
15078
|
aggregationDate = viewChild(AggregationDateComponent, ...(ngDevMode ? [{ debugName: "aggregationDate" }] : []));
|
|
14373
15079
|
aggregationTree = viewChild(AggregationTreeComponent, ...(ngDevMode ? [{ debugName: "aggregationTree" }] : []));
|
|
14374
15080
|
aggregationList = viewChild(AggregationListComponent, ...(ngDevMode ? [{ debugName: "aggregationList" }] : []));
|
|
15081
|
+
/**
|
|
15082
|
+
* Host-projected `<ng-template aggregationItem>`, relayed to the active list/tree
|
|
15083
|
+
* sub-component (the wrapper has no `<ng-content>`, so the sub-component's own
|
|
15084
|
+
* `contentChild` cannot see it). See {@link AggregationItemDirective}.
|
|
15085
|
+
*/
|
|
15086
|
+
itemDirective = contentChild(AggregationItemDirective, ...(ngDevMode ? [{ debugName: "itemDirective" }] : []));
|
|
14375
15087
|
class = input("", ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
14376
15088
|
/**
|
|
14377
15089
|
* The name of the <details> element. When you provide the same id, the component work as an accordion
|
|
@@ -14415,6 +15127,13 @@ class AggregationComponent {
|
|
|
14415
15127
|
* "Undefined" and not "false" because this input overrides the custom json settings
|
|
14416
15128
|
*/
|
|
14417
15129
|
searchable = input(undefined, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
15130
|
+
/**
|
|
15131
|
+
* Whether to render the per-item checkbox (list/tree). When `false`, the checkbox
|
|
15132
|
+
* is hidden but selection still toggles on row click. Ignored by the date variant.
|
|
15133
|
+
*
|
|
15134
|
+
* @default true
|
|
15135
|
+
*/
|
|
15136
|
+
showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14418
15137
|
expandedLevel = input(undefined, ...(ngDevMode ? [{ debugName: "expandedLevel", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
14419
15138
|
/**
|
|
14420
15139
|
* Controls whether applying or clearing a filter synchronizes the URL.
|
|
@@ -14426,12 +15145,64 @@ class AggregationComponent {
|
|
|
14426
15145
|
* @default true
|
|
14427
15146
|
*/
|
|
14428
15147
|
syncUrl = input(true, ...(ngDevMode ? [{ debugName: "syncUrl", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
15148
|
+
/**
|
|
15149
|
+
* Controlled query. When set, the aggregation's own operations (load-more,
|
|
15150
|
+
* search-within-facet, tree-expand) run against this query instead of reading
|
|
15151
|
+
* the shared `QueryParamsStore`. Use to target a query not tied to the current
|
|
15152
|
+
* app, without mutating the shared store or the URL.
|
|
15153
|
+
*
|
|
15154
|
+
* @default undefined — read `QueryParamsStore.getQuery()` (current behavior)
|
|
15155
|
+
*/
|
|
15156
|
+
controlledQuery = input(undefined, ...(ngDevMode ? [{ debugName: "controlledQuery", alias: "query" }] : [{ alias: "query" }]));
|
|
15157
|
+
/**
|
|
15158
|
+
* Controlled data. When set, the component renders this aggregation object
|
|
15159
|
+
* (name/column/items) instead of reading it from the shared `AggregationsStore`.
|
|
15160
|
+
*
|
|
15161
|
+
* @default undefined — read `AggregationsStore` (current behavior)
|
|
15162
|
+
*/
|
|
15163
|
+
controlledAggregation = input(undefined, ...(ngDevMode ? [{ debugName: "controlledAggregation", alias: "aggregation" }] : [{ alias: "aggregation" }]));
|
|
15164
|
+
/**
|
|
15165
|
+
* Controlled selection. When set, item `$selected` state is derived from this
|
|
15166
|
+
* value (list: item values; tree: `"/A/B/*"` paths; date: the range) instead
|
|
15167
|
+
* of the shared `QueryParamsStore` filters.
|
|
15168
|
+
*
|
|
15169
|
+
* @default undefined — derive from `QueryParamsStore` filters (current behavior)
|
|
15170
|
+
*/
|
|
15171
|
+
controlledSelection = input(undefined, ...(ngDevMode ? [{ debugName: "controlledSelection", alias: "selection" }] : [{ alias: "selection" }]));
|
|
15172
|
+
/**
|
|
15173
|
+
* Which sub-component renders the aggregation.
|
|
15174
|
+
*
|
|
15175
|
+
* `"auto"` (default) keeps the current heuristic: a date column of the app
|
|
15176
|
+
* (`AppStore.isDateColumn`) renders `<AggregationDate>`, a tree aggregation
|
|
15177
|
+
* renders `<AggregationTree>`, anything else `<AggregationList>`. Set an
|
|
15178
|
+
* explicit value in controlled mode when the heuristic cannot apply — e.g. a
|
|
15179
|
+
* date facet of a query not attached to the current app, whose column is
|
|
15180
|
+
* unknown to the app's column map.
|
|
15181
|
+
*
|
|
15182
|
+
* @default "auto"
|
|
15183
|
+
*/
|
|
15184
|
+
type = input("auto", ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
14429
15185
|
/**
|
|
14430
15186
|
* An output event that emits an array of selected `AggregationItem` objects when the user selects items in the aggregation component. This allows parent components to listen for selection changes and react accordingly, such as applying filters or updating the UI based on the selected items.
|
|
14431
15187
|
*/
|
|
14432
15188
|
onSelect = output();
|
|
14433
15189
|
onApply = output();
|
|
14434
15190
|
onClear = output();
|
|
15191
|
+
/**
|
|
15192
|
+
* Emits the new selection when the user applies or clears filters in
|
|
15193
|
+
* controlled mode (list: item values; tree: `"/A/B/*"` paths; date: option or
|
|
15194
|
+
* range). The counterpart of the `[selection]` input — emitted before
|
|
15195
|
+
* `filtersChange` and `onApply`/`onClear`. In controlled mode this is the only
|
|
15196
|
+
* way changes surface: nothing is written to the shared stores or the URL.
|
|
15197
|
+
*/
|
|
15198
|
+
selectionChange = output();
|
|
15199
|
+
/**
|
|
15200
|
+
* Emits the applied selection as a structured `Filter` (the exact filter the
|
|
15201
|
+
* default mode would write to the store), ready for `fetchQuery({ filters })`.
|
|
15202
|
+
* `undefined` on clear. Controlled mode only — emitted after `selectionChange`,
|
|
15203
|
+
* before `onApply`/`onClear`.
|
|
15204
|
+
*/
|
|
15205
|
+
filtersChange = output();
|
|
14435
15206
|
/**
|
|
14436
15207
|
* A computed property that retrieves the items to be displayed in the aggregation component. It checks for the presence of date, tree, or list aggregation components and returns their respective items. If none of these components are present, it returns an empty array. This allows the component to dynamically display the appropriate items based on the type of aggregation being rendered.
|
|
14437
15208
|
*
|
|
@@ -14449,6 +15220,10 @@ class AggregationComponent {
|
|
|
14449
15220
|
selectedItems = computed(() => this.items().filter(item => item.$selected), ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
|
|
14450
15221
|
/* aggregation */
|
|
14451
15222
|
aggregation = computed(() => {
|
|
15223
|
+
// controlled mode: the input is the data source — the store is not read
|
|
15224
|
+
const controlled = this.controlledAggregation();
|
|
15225
|
+
if (controlled)
|
|
15226
|
+
return controlled;
|
|
14452
15227
|
// when the aggegation store updates, we need to check if the aggregation is still valid
|
|
14453
15228
|
getState(this.aggregationsStore);
|
|
14454
15229
|
const agg = this.aggregationsService.getAggregation(this.name(), this.column());
|
|
@@ -14458,6 +15233,20 @@ class AggregationComponent {
|
|
|
14458
15233
|
}
|
|
14459
15234
|
return agg;
|
|
14460
15235
|
}, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
|
|
15236
|
+
/**
|
|
15237
|
+
* The sub-component to render: the explicit `type` input, or the current
|
|
15238
|
+
* date/tree/list heuristic when `type` is `"auto"`.
|
|
15239
|
+
*/
|
|
15240
|
+
resolvedType = computed(() => {
|
|
15241
|
+
const type = this.type();
|
|
15242
|
+
if (type !== "auto")
|
|
15243
|
+
return type;
|
|
15244
|
+
if (this.isDate(this.aggregation()?.column))
|
|
15245
|
+
return "date";
|
|
15246
|
+
if (this.aggregation()?.isTree)
|
|
15247
|
+
return "tree";
|
|
15248
|
+
return "list";
|
|
15249
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedType" }] : []));
|
|
14461
15250
|
/**
|
|
14462
15251
|
* Checks if the specified column is a date filter.
|
|
14463
15252
|
*
|
|
@@ -14471,8 +15260,9 @@ class AggregationComponent {
|
|
|
14471
15260
|
return this.appStore.isDateColumn(column);
|
|
14472
15261
|
}
|
|
14473
15262
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14474
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationComponent, isStandalone: true, selector: "Aggregation, aggregation", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, viewQueries: [{ propertyName: "aggregationDate", first: true, predicate: AggregationDateComponent, descendants: true, isSignal: true }, { propertyName: "aggregationTree", first: true, predicate: AggregationTreeComponent, descendants: true, isSignal: true }, { propertyName: "aggregationList", first: true, predicate: AggregationListComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
14475
|
-
@
|
|
15263
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AggregationComponent, isStandalone: true, selector: "Aggregation, aggregation", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, showFiltersCount: { classPropertyName: "showFiltersCount", publicName: "showFiltersCount", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null }, syncUrl: { classPropertyName: "syncUrl", publicName: "syncUrl", isSignal: true, isRequired: false, transformFunction: null }, controlledQuery: { classPropertyName: "controlledQuery", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, controlledAggregation: { classPropertyName: "controlledAggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, controlledSelection: { classPropertyName: "controlledSelection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect", onApply: "onApply", onClear: "onClear", selectionChange: "selectionChange", filtersChange: "filtersChange" }, host: { properties: { "class": "cn(\"block h-[inherit] max-h-[inherit]\",class())" } }, queries: [{ propertyName: "itemDirective", first: true, predicate: AggregationItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "aggregationDate", first: true, predicate: AggregationDateComponent, descendants: true, isSignal: true }, { propertyName: "aggregationTree", first: true, predicate: AggregationTreeComponent, descendants: true, isSignal: true }, { propertyName: "aggregationList", first: true, predicate: AggregationListComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
15264
|
+
@switch (resolvedType()) {
|
|
15265
|
+
@case ("date") {
|
|
14476
15266
|
<AggregationDate
|
|
14477
15267
|
[id]="id()"
|
|
14478
15268
|
[name]="name()"
|
|
@@ -14482,11 +15272,17 @@ class AggregationComponent {
|
|
|
14482
15272
|
[showFiltersCount]="showFiltersCount()"
|
|
14483
15273
|
[searchable]="searchable()"
|
|
14484
15274
|
[syncUrl]="syncUrl()"
|
|
15275
|
+
[query]="controlledQuery()"
|
|
15276
|
+
[aggregation]="controlledAggregation()"
|
|
15277
|
+
[selection]="controlledSelection()"
|
|
14485
15278
|
[title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
|
|
14486
15279
|
(onApply)="onApply.emit($event)"
|
|
14487
15280
|
(onClear)="onClear.emit($event)"
|
|
15281
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
15282
|
+
(filtersChange)="filtersChange.emit($event)"
|
|
14488
15283
|
/>
|
|
14489
|
-
|
|
15284
|
+
}
|
|
15285
|
+
@case ("tree") {
|
|
14490
15286
|
<AggregationTree
|
|
14491
15287
|
[id]="id()"
|
|
14492
15288
|
[name]="name()"
|
|
@@ -14496,13 +15292,20 @@ class AggregationComponent {
|
|
|
14496
15292
|
[showFiltersCount]="showFiltersCount()"
|
|
14497
15293
|
[searchable]="searchable()"
|
|
14498
15294
|
[syncUrl]="syncUrl()"
|
|
15295
|
+
[query]="controlledQuery()"
|
|
15296
|
+
[aggregation]="controlledAggregation()"
|
|
15297
|
+
[selection]="controlledSelection()"
|
|
15298
|
+
[itemTemplate]="itemDirective()?.template"
|
|
15299
|
+
[showCheckbox]="showCheckbox()"
|
|
14499
15300
|
[expandedLevel]="expandedLevel()"
|
|
14500
15301
|
(onSelect)="onSelect.emit($event)"
|
|
14501
15302
|
(onApply)="onApply.emit($event)"
|
|
14502
15303
|
(onClear)="onClear.emit($event)"
|
|
15304
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
15305
|
+
(filtersChange)="filtersChange.emit($event)"
|
|
14503
15306
|
/>
|
|
14504
|
-
|
|
14505
|
-
|
|
15307
|
+
}
|
|
15308
|
+
@default {
|
|
14506
15309
|
<AggregationList
|
|
14507
15310
|
[id]="id()"
|
|
14508
15311
|
[name]="name()"
|
|
@@ -14512,12 +15315,20 @@ class AggregationComponent {
|
|
|
14512
15315
|
[showFiltersCount]="showFiltersCount()"
|
|
14513
15316
|
[searchable]="searchable()"
|
|
14514
15317
|
[syncUrl]="syncUrl()"
|
|
15318
|
+
[query]="controlledQuery()"
|
|
15319
|
+
[aggregation]="controlledAggregation()"
|
|
15320
|
+
[selection]="controlledSelection()"
|
|
15321
|
+
[itemTemplate]="itemDirective()?.template"
|
|
15322
|
+
[showCheckbox]="showCheckbox()"
|
|
14515
15323
|
(onSelect)="onSelect.emit($event)"
|
|
14516
15324
|
(onApply)="onApply.emit($event)"
|
|
14517
15325
|
(onClear)="onClear.emit($event)"
|
|
15326
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
15327
|
+
(filtersChange)="filtersChange.emit($event)"
|
|
14518
15328
|
/>
|
|
14519
|
-
|
|
14520
|
-
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15331
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AggregationListComponent, selector: "AggregationList, aggregation-list, aggregationlist", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "showCheckbox", "syncUrl", "query", "aggregation", "selection", "itemTemplate", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }, { kind: "component", type: AggregationTreeComponent, selector: "AggregationTree, aggregation-tree, aggregationtree", inputs: ["class", "id", "name", "column", "collapsible", "collapsed", "searchable", "showFiltersCount", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "itemTemplate", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }, { kind: "component", type: AggregationDateComponent, selector: "aggregation-date, AggregationDate, aggregationdate", inputs: ["name", "column", "id", "collapsible", "collapsed", "searchable", "showFiltersCount", "syncUrl", "query", "aggregation", "selection", "title", "displayEmptyDistributionIntervals", "searchText"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange", "searchTextChange"] }] });
|
|
14521
15332
|
}
|
|
14522
15333
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationComponent, decorators: [{
|
|
14523
15334
|
type: Component,
|
|
@@ -14525,7 +15336,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14525
15336
|
selector: "Aggregation, aggregation",
|
|
14526
15337
|
imports: [AggregationListComponent, AggregationTreeComponent, AggregationDateComponent],
|
|
14527
15338
|
template: `
|
|
14528
|
-
@
|
|
15339
|
+
@switch (resolvedType()) {
|
|
15340
|
+
@case ("date") {
|
|
14529
15341
|
<AggregationDate
|
|
14530
15342
|
[id]="id()"
|
|
14531
15343
|
[name]="name()"
|
|
@@ -14535,11 +15347,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14535
15347
|
[showFiltersCount]="showFiltersCount()"
|
|
14536
15348
|
[searchable]="searchable()"
|
|
14537
15349
|
[syncUrl]="syncUrl()"
|
|
15350
|
+
[query]="controlledQuery()"
|
|
15351
|
+
[aggregation]="controlledAggregation()"
|
|
15352
|
+
[selection]="controlledSelection()"
|
|
14538
15353
|
[title]="{ label: 'Date', icon: 'far fa-calendar-day' }"
|
|
14539
15354
|
(onApply)="onApply.emit($event)"
|
|
14540
15355
|
(onClear)="onClear.emit($event)"
|
|
15356
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
15357
|
+
(filtersChange)="filtersChange.emit($event)"
|
|
14541
15358
|
/>
|
|
14542
|
-
|
|
15359
|
+
}
|
|
15360
|
+
@case ("tree") {
|
|
14543
15361
|
<AggregationTree
|
|
14544
15362
|
[id]="id()"
|
|
14545
15363
|
[name]="name()"
|
|
@@ -14549,13 +15367,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14549
15367
|
[showFiltersCount]="showFiltersCount()"
|
|
14550
15368
|
[searchable]="searchable()"
|
|
14551
15369
|
[syncUrl]="syncUrl()"
|
|
15370
|
+
[query]="controlledQuery()"
|
|
15371
|
+
[aggregation]="controlledAggregation()"
|
|
15372
|
+
[selection]="controlledSelection()"
|
|
15373
|
+
[itemTemplate]="itemDirective()?.template"
|
|
15374
|
+
[showCheckbox]="showCheckbox()"
|
|
14552
15375
|
[expandedLevel]="expandedLevel()"
|
|
14553
15376
|
(onSelect)="onSelect.emit($event)"
|
|
14554
15377
|
(onApply)="onApply.emit($event)"
|
|
14555
15378
|
(onClear)="onClear.emit($event)"
|
|
15379
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
15380
|
+
(filtersChange)="filtersChange.emit($event)"
|
|
14556
15381
|
/>
|
|
14557
|
-
|
|
14558
|
-
|
|
15382
|
+
}
|
|
15383
|
+
@default {
|
|
14559
15384
|
<AggregationList
|
|
14560
15385
|
[id]="id()"
|
|
14561
15386
|
[name]="name()"
|
|
@@ -14565,43 +15390,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14565
15390
|
[showFiltersCount]="showFiltersCount()"
|
|
14566
15391
|
[searchable]="searchable()"
|
|
14567
15392
|
[syncUrl]="syncUrl()"
|
|
15393
|
+
[query]="controlledQuery()"
|
|
15394
|
+
[aggregation]="controlledAggregation()"
|
|
15395
|
+
[selection]="controlledSelection()"
|
|
15396
|
+
[itemTemplate]="itemDirective()?.template"
|
|
15397
|
+
[showCheckbox]="showCheckbox()"
|
|
14568
15398
|
(onSelect)="onSelect.emit($event)"
|
|
14569
15399
|
(onApply)="onApply.emit($event)"
|
|
14570
15400
|
(onClear)="onClear.emit($event)"
|
|
15401
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
15402
|
+
(filtersChange)="filtersChange.emit($event)"
|
|
14571
15403
|
/>
|
|
14572
|
-
|
|
15404
|
+
}
|
|
15405
|
+
}
|
|
14573
15406
|
`,
|
|
14574
15407
|
host: {
|
|
14575
15408
|
"[class]": 'cn("block h-[inherit] max-h-[inherit]",class())'
|
|
14576
15409
|
}
|
|
14577
15410
|
}]
|
|
14578
|
-
}], propDecorators: { aggregationDate: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateComponent), { isSignal: true }] }], aggregationTree: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationTreeComponent), { isSignal: true }] }], aggregationList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationListComponent), { isSignal: true }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }] } });
|
|
15411
|
+
}], propDecorators: { aggregationDate: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationDateComponent), { isSignal: true }] }], aggregationTree: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationTreeComponent), { isSignal: true }] }], aggregationList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AggregationListComponent), { isSignal: true }] }], itemDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AggregationItemDirective), { isSignal: true }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], showFiltersCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFiltersCount", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], expandedLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedLevel", required: false }] }], syncUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncUrl", required: false }] }], controlledQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], controlledAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: false }] }], controlledSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }], onApply: [{ type: i0.Output, args: ["onApply"] }], onClear: [{ type: i0.Output, args: ["onClear"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }] } });
|
|
14579
15412
|
|
|
14580
15413
|
class AsideFiltersComponent {
|
|
14581
15414
|
cn = cn;
|
|
14582
15415
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
14583
|
-
|
|
15416
|
+
/**
|
|
15417
|
+
* Explicit, developer-defined list of aggregations to display.
|
|
15418
|
+
*
|
|
15419
|
+
* Each entry is an aggregation **name** (preferred) or **column** — e.g.
|
|
15420
|
+
* `['Authors', 'Sources', 'Geo']`. Although typed as `string[]`, this is the list of
|
|
15421
|
+
* aggregations to render, not a list of raw column keys.
|
|
15422
|
+
*
|
|
15423
|
+
* When provided, it fully bypasses the JSON `filters` configuration (neither
|
|
15424
|
+
* `position`, `getAuthorized`, nor the admin `hidden` rule are applied): the listed
|
|
15425
|
+
* aggregations are rendered as-is, in the given order, provided they exist in the
|
|
15426
|
+
* {@link AggregationsStore}. When empty (default), the component falls back to the
|
|
15427
|
+
* JSON-driven behavior (`position: 'left' | 'both'`, excluding `hidden` aggregations).
|
|
15428
|
+
*
|
|
15429
|
+
* Resolution tries by `name` first — the canonical, unambiguous identifier, since a
|
|
15430
|
+
* `column` can be shared by several aggregations with distinct names — then falls back
|
|
15431
|
+
* to the first aggregation matching the given column. Prefer passing names.
|
|
15432
|
+
*/
|
|
15433
|
+
aggregations = input([], ...(ngDevMode ? [{ debugName: "aggregations" }] : []));
|
|
15434
|
+
/**
|
|
15435
|
+
* When `true` (default), every section shares the same `id`, producing a native
|
|
15436
|
+
* `<details name>` accordion where a single section is open at a time. When `false`,
|
|
15437
|
+
* each section gets a unique id (based on the aggregation `name`) and expands
|
|
15438
|
+
* independently — closer to a typical SBA v2 "Facets List".
|
|
15439
|
+
*/
|
|
15440
|
+
exclusive = input(true, ...(ngDevMode ? [{ debugName: "exclusive", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14584
15441
|
appStore = inject(AppStore);
|
|
15442
|
+
aggregationsStore = inject(AggregationsStore);
|
|
15443
|
+
aggregationsService = inject(AggregationsService);
|
|
14585
15444
|
asideFilters = computed(() => {
|
|
14586
|
-
|
|
15445
|
+
const explicit = this.aggregations();
|
|
15446
|
+
// Explicit developer-defined list → render exactly as passed, bypassing the JSON config.
|
|
15447
|
+
if (explicit.length > 0) {
|
|
15448
|
+
return explicit
|
|
15449
|
+
.map((key) => this.aggregationsStore.getAggregation(key, "name") ?? this.aggregationsStore.getAggregation(key, "column"))
|
|
15450
|
+
.filter((agg) => !!agg)
|
|
15451
|
+
.map((agg) => ({ name: agg.name, column: agg.column }));
|
|
15452
|
+
}
|
|
15453
|
+
// Default JSON-driven behavior:
|
|
15454
|
+
// `left` filters live only in the drawer, `both` filters live in the drawer and the bar.
|
|
15455
|
+
// Admin-hidden aggregations are excluded (the rule lives in the service).
|
|
14587
15456
|
const asideFilters = this.appStore.filters().filter((f) => f.position === "left" || f.position === "both");
|
|
14588
|
-
return this.appStore.getAuthorized(asideFilters);
|
|
15457
|
+
return this.aggregationsService.rejectHidden(this.appStore.getAuthorized(asideFilters));
|
|
14589
15458
|
}, ...(ngDevMode ? [{ debugName: "asideFilters" }] : []));
|
|
14590
|
-
/**
|
|
14591
|
-
* Checks if the specified column is a date filter.
|
|
14592
|
-
*
|
|
14593
|
-
* @param column The column name to check.
|
|
14594
|
-
* @returns True if the column is a date filter, false otherwise.
|
|
14595
|
-
*/
|
|
14596
|
-
isDate(column) {
|
|
14597
|
-
return this.appStore.isDateColumn(column);
|
|
14598
|
-
}
|
|
14599
15459
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AsideFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14600
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AsideFiltersComponent, isStandalone: true, selector: "aside-filters, AsideFilters, asidefilters", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null },
|
|
15460
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AsideFiltersComponent, isStandalone: true, selector: "aside-filters, AsideFilters, asidefilters", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, exclusive: { classPropertyName: "exclusive", publicName: "exclusive", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "cn('flex flex-col gap-2 overflow-auto', class())" } }, ngImport: i0, template: `
|
|
14601
15461
|
<div class="flex flex-col gap-2">
|
|
14602
15462
|
@for (agg of asideFilters(); track agg.name) {
|
|
14603
15463
|
<Aggregation
|
|
14604
|
-
id="aside-filters"
|
|
15464
|
+
[id]="exclusive() ? 'aside-filters' : 'aside-filters-' + agg.name"
|
|
14605
15465
|
[name]="agg.name"
|
|
14606
15466
|
[column]="agg.column"
|
|
14607
15467
|
[collapsible]="true"
|
|
@@ -14609,7 +15469,7 @@ class AsideFiltersComponent {
|
|
|
14609
15469
|
showFiltersCount />
|
|
14610
15470
|
}
|
|
14611
15471
|
</div>
|
|
14612
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel", "syncUrl"], outputs: ["onSelect", "onApply", "onClear"] }] });
|
|
15472
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }] });
|
|
14613
15473
|
}
|
|
14614
15474
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AsideFiltersComponent, decorators: [{
|
|
14615
15475
|
type: Component,
|
|
@@ -14620,7 +15480,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14620
15480
|
<div class="flex flex-col gap-2">
|
|
14621
15481
|
@for (agg of asideFilters(); track agg.name) {
|
|
14622
15482
|
<Aggregation
|
|
14623
|
-
id="aside-filters"
|
|
15483
|
+
[id]="exclusive() ? 'aside-filters' : 'aside-filters-' + agg.name"
|
|
14624
15484
|
[name]="agg.name"
|
|
14625
15485
|
[column]="agg.column"
|
|
14626
15486
|
[collapsible]="true"
|
|
@@ -14633,7 +15493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14633
15493
|
"[class]": "cn('flex flex-col gap-2 overflow-auto', class())"
|
|
14634
15494
|
}
|
|
14635
15495
|
}]
|
|
14636
|
-
}], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }],
|
|
15496
|
+
}], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], exclusive: [{ type: i0.Input, args: [{ isSignal: true, alias: "exclusive", required: false }] }] } });
|
|
14637
15497
|
|
|
14638
15498
|
class FilterButtonComponent {
|
|
14639
15499
|
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
@@ -14760,7 +15620,7 @@ class FilterButtonComponent {
|
|
|
14760
15620
|
}
|
|
14761
15621
|
</PopoverContent>
|
|
14762
15622
|
</Popover>
|
|
14763
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel", "syncUrl"], outputs: ["onSelect", "onApply", "onClear"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size", "aria-label"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: OperatorPipe, name: "operator" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
15623
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: PopoverComponent, selector: "popover, Popover", inputs: ["disabled", "closeOnScroll"], outputs: ["closed"] }, { kind: "directive", type: PopoverContentComponent, selector: "popover-content, PopoverContent, popovercontent", inputs: ["class", "position", "keepOpen", "offset", "strategy"], exportAs: ["popoverContent"] }, { kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "scheme", "size", "aria-label"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass", "class"] }, { kind: "pipe", type: OperatorPipe, name: "operator" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
|
|
14764
15624
|
}
|
|
14765
15625
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: FilterButtonComponent, decorators: [{
|
|
14766
15626
|
type: Component,
|
|
@@ -14844,92 +15704,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14844
15704
|
const FILTERS_BREAKPOINT = new InjectionToken('FILTERS_BREAKPOINT', { factory: () => 5 });
|
|
14845
15705
|
|
|
14846
15706
|
class MoreComponent {
|
|
14847
|
-
cn = cn;
|
|
14848
15707
|
count = input(2, ...(ngDevMode ? [{ debugName: "count" }] : []));
|
|
14849
15708
|
includedFilters = input([], ...(ngDevMode ? [{ debugName: "includedFilters" }] : []));
|
|
14850
15709
|
excludedFilters = input([], ...(ngDevMode ? [{ debugName: "excludedFilters" }] : []));
|
|
14851
15710
|
aggregations = input(...(ngDevMode ? [undefined, { debugName: "aggregations" }] : []));
|
|
14852
15711
|
homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
14853
|
-
appStore = inject(AppStore);
|
|
14854
|
-
aggregationsStore = inject(AggregationsStore);
|
|
14855
|
-
queryParamsStore = inject(QueryParamsStore);
|
|
14856
15712
|
aggregationsService = inject(AggregationsService);
|
|
14857
15713
|
/**
|
|
14858
|
-
*
|
|
15714
|
+
* The overflow filters rendered as accordions: the authorized filters beyond the
|
|
15715
|
+
* first `count` (which are already shown in the bar).
|
|
14859
15716
|
*
|
|
14860
15717
|
* @remarks
|
|
14861
|
-
*
|
|
14862
|
-
*
|
|
14863
|
-
*
|
|
14864
|
-
*/
|
|
14865
|
-
filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
14866
|
-
/**
|
|
14867
|
-
* Computed property that returns an array of filters that are currently visible.
|
|
14868
|
-
* Filters out any filters where the `hidden` property is set to true.
|
|
14869
|
-
*
|
|
14870
|
-
* @returns An array of filters that are not hidden
|
|
15718
|
+
* Pure display wiring — authorization (and the admin `hidden` rule) is handled by
|
|
15719
|
+
* {@link AggregationsService.getAuthorizedFilters}; `toSpliced` just drops the
|
|
15720
|
+
* filters already shown in the bar.
|
|
14871
15721
|
*/
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
}, ...(ngDevMode ? [{ debugName: "visibleFilters" }] : []));
|
|
14876
|
-
constructor() {
|
|
14877
|
-
effect(() => {
|
|
14878
|
-
const count = this.count();
|
|
14879
|
-
const authorizedFilters = this.aggregationsService
|
|
14880
|
-
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
|
|
14881
|
-
.toSpliced(0, count);
|
|
14882
|
-
const f = authorizedFilters.map((agg) => {
|
|
14883
|
-
const { icon = "far fa-list", hidden = false } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
|
|
14884
|
-
return {
|
|
14885
|
-
name: agg.name,
|
|
14886
|
-
column: agg.column,
|
|
14887
|
-
icon,
|
|
14888
|
-
count: 0,
|
|
14889
|
-
isTree: false,
|
|
14890
|
-
disabled: false,
|
|
14891
|
-
hidden
|
|
14892
|
-
};
|
|
14893
|
-
});
|
|
14894
|
-
this.filters.set(f);
|
|
14895
|
-
this.updateFilters();
|
|
14896
|
-
});
|
|
14897
|
-
}
|
|
14898
|
-
clearFilter(field) {
|
|
14899
|
-
this.queryParamsStore.removeFilter(field);
|
|
14900
|
-
}
|
|
14901
|
-
updateFilters() {
|
|
14902
|
-
this.filters.update((filters) => {
|
|
14903
|
-
return filters.map((filter) => {
|
|
14904
|
-
const agg = this.aggregationsStore.getAggregation(filter.name);
|
|
14905
|
-
const f = this.queryParamsStore.getFilter({ name: agg?.name, field: agg?.column });
|
|
14906
|
-
const count = f?.count || 0;
|
|
14907
|
-
const { display = agg?.isTree ? undefined : filter.display } = this.appStore.getAggregationCustomization(filter.column) || {};
|
|
14908
|
-
return {
|
|
14909
|
-
...filter,
|
|
14910
|
-
name: agg?.name || filter.name,
|
|
14911
|
-
display,
|
|
14912
|
-
isTree: agg?.isTree || false,
|
|
14913
|
-
count,
|
|
14914
|
-
disabled: !!(!agg?.items || agg.items?.length === 0) // Disable if no items
|
|
14915
|
-
};
|
|
14916
|
-
});
|
|
14917
|
-
});
|
|
14918
|
-
}
|
|
14919
|
-
/**
|
|
14920
|
-
* Checks whether there are active filters for the current aggregation column.
|
|
14921
|
-
*
|
|
14922
|
-
* if True, the clear button is shown.
|
|
14923
|
-
*
|
|
14924
|
-
* @returns {boolean} `true` if the filter count for the aggregation column is greater than 0, otherwise `false`.
|
|
14925
|
-
*/
|
|
14926
|
-
hasFilters(aggregation) {
|
|
14927
|
-
const { count = 0 } = this.queryParamsStore.getFilter({ field: aggregation.column, name: aggregation.name }) || {};
|
|
14928
|
-
return count > 0;
|
|
14929
|
-
}
|
|
15722
|
+
filters = computed(() => this.aggregationsService
|
|
15723
|
+
.getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
|
|
15724
|
+
.toSpliced(0, this.count()), ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
14930
15725
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14931
15726
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", 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: `
|
|
14932
|
-
@for (filter of
|
|
15727
|
+
@for (filter of filters(); track $index) {
|
|
14933
15728
|
<Aggregation
|
|
14934
15729
|
class="w-60 max-w-80 px-1 [--height:15lh]"
|
|
14935
15730
|
id="more-filters"
|
|
@@ -14940,12 +15735,12 @@ class MoreComponent {
|
|
|
14940
15735
|
[name]="filter.name"
|
|
14941
15736
|
[column]="filter.column" />
|
|
14942
15737
|
}
|
|
14943
|
-
`, isInline: true, styles: [":host{scrollbar-width:none}\n"], dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "expandedLevel", "syncUrl"], outputs: ["onSelect", "onApply", "onClear"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
15738
|
+
`, isInline: true, styles: [":host{scrollbar-width:none}\n"], dependencies: [{ kind: "component", type: AggregationComponent, selector: "Aggregation, aggregation", inputs: ["class", "id", "name", "column", "showFiltersCount", "collapsible", "collapsed", "searchable", "showCheckbox", "expandedLevel", "syncUrl", "query", "aggregation", "selection", "type"], outputs: ["onSelect", "onApply", "onClear", "selectionChange", "filtersChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
14944
15739
|
}
|
|
14945
15740
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MoreComponent, decorators: [{
|
|
14946
15741
|
type: Component,
|
|
14947
15742
|
args: [{ selector: "more, More", standalone: true, imports: [AggregationComponent, TranslocoPipe], template: `
|
|
14948
|
-
@for (filter of
|
|
15743
|
+
@for (filter of filters(); track $index) {
|
|
14949
15744
|
<Aggregation
|
|
14950
15745
|
class="w-60 max-w-80 px-1 [--height:15lh]"
|
|
14951
15746
|
id="more-filters"
|
|
@@ -14959,7 +15754,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14959
15754
|
`, host: {
|
|
14960
15755
|
class: "divide-y divide-muted-foreground/18"
|
|
14961
15756
|
}, styles: [":host{scrollbar-width:none}\n"] }]
|
|
14962
|
-
}],
|
|
15757
|
+
}], 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 }] }] } });
|
|
14963
15758
|
|
|
14964
15759
|
class MoreButtonComponent {
|
|
14965
15760
|
appStore = inject(AppStore);
|
|
@@ -16798,5 +17593,5 @@ const queryNameResolver = () => {
|
|
|
16798
17593
|
* Generated bundle index. Do not edit.
|
|
16799
17594
|
*/
|
|
16800
17595
|
|
|
16801
|
-
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeatureFlagsDialogComponent, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
17596
|
+
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeatureFlagsDialogComponent, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, legacyFilterToFilter, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
16802
17597
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|