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