@sinequa/atomic-angular 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3540,16 +3540,33 @@ class AggregationsService {
3540
3540
  }
3541
3541
  /* aggregations helpers fot the filters components */
3542
3542
  getAuthorizedFilters(aggregations, includedFilters = [], excludedFilters = [], homepageOnly = false) {
3543
+ // An explicit list is a deliberate developer choice: it bypasses the `hidden`
3544
+ // admin rule. The rule only applies to the default, config-driven path.
3545
+ const explicit = aggregations !== undefined;
3543
3546
  const agg = aggregations || this.appStore.getAuthorizedFilters();
3544
3547
  const authorizedFilters = agg
3545
3548
  .filter((f) => this.getFilterCriteria()(f)) // filter only the filters present
3546
3549
  .filter((f) => !homepageOnly || this.getHomepageFilterCriteria()(f)) // when requested, keep only filters flagged `homepage: true`
3547
3550
  .filter((f) => !this.getLeftOnlyFilterCriteria()(f)) // exclude left-only filters (rendered in the left drawer, not the bar)
3551
+ .filter((f) => explicit || !this.getHiddenFilterCriteria()(f)) // exclude admin-hidden filters (default path only)
3548
3552
  .filter((f) => !excludedFilters.includes(f.name))
3549
3553
  .filter((f) => !includedFilters.length || includedFilters.includes(f.name))
3550
3554
  .map((f) => ({ field: f.column, column: f.column, name: f.name })); // field is needed for filters constructions
3551
3555
  return authorizedFilters;
3552
3556
  }
3557
+ /**
3558
+ * Removes the aggregations flagged `hidden: true` in the `filters` custom JSON
3559
+ * configuration (the administrator does not want them displayed).
3560
+ *
3561
+ * Exposed for consumers that build their own aggregation list without going through
3562
+ * {@link getAuthorizedFilters} (e.g. the aside filters drawer).
3563
+ *
3564
+ * @param aggregations - The aggregations to filter.
3565
+ * @returns The aggregations that are not flagged hidden.
3566
+ */
3567
+ rejectHidden(aggregations) {
3568
+ return aggregations.filter((agg) => !this.getHiddenFilterCriteria()(agg));
3569
+ }
3553
3570
  /**
3554
3571
  * Determines whether a custom JSON filter refers to the given aggregation.
3555
3572
  *
@@ -3593,6 +3610,10 @@ class AggregationsService {
3593
3610
  // `position: 'both'` (and an undefined position) stay in the bar.
3594
3611
  return (agg) => this.appStore.filters().some((filter) => filter.position === "left" && this.matchesAggregation(filter, agg));
3595
3612
  };
3613
+ getHiddenFilterCriteria = () => {
3614
+ // an aggregation is hidden when a matching filter in the custom JSON has `hidden: true`
3615
+ return (agg) => this.appStore.filters().some((filter) => filter.hidden && this.matchesAggregation(filter, agg));
3616
+ };
3596
3617
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3597
3618
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AggregationsService, providedIn: "root" });
3598
3619
  }
@@ -15392,28 +15413,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15392
15413
  class AsideFiltersComponent {
15393
15414
  cn = cn;
15394
15415
  class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
15395
- position = input(...(ngDevMode ? [undefined, { debugName: "position" }] : []));
15416
+ /**
15417
+ * Explicit, developer-defined list of aggregations to display.
15418
+ *
15419
+ * Each entry is an aggregation **name** (preferred) or **column** — e.g.
15420
+ * `['Authors', 'Sources', 'Geo']`. Although typed as `string[]`, this is the list of
15421
+ * aggregations to render, not a list of raw column keys.
15422
+ *
15423
+ * When provided, it fully bypasses the JSON `filters` configuration (neither
15424
+ * `position`, `getAuthorized`, nor the admin `hidden` rule are applied): the listed
15425
+ * aggregations are rendered as-is, in the given order, provided they exist in the
15426
+ * {@link AggregationsStore}. When empty (default), the component falls back to the
15427
+ * JSON-driven behavior (`position: 'left' | 'both'`, excluding `hidden` aggregations).
15428
+ *
15429
+ * Resolution tries by `name` first — the canonical, unambiguous identifier, since a
15430
+ * `column` can be shared by several aggregations with distinct names — then falls back
15431
+ * to the first aggregation matching the given column. Prefer passing names.
15432
+ */
15433
+ aggregations = input([], ...(ngDevMode ? [{ debugName: "aggregations" }] : []));
15434
+ /**
15435
+ * When `true` (default), every section shares the same `id`, producing a native
15436
+ * `<details name>` accordion where a single section is open at a time. When `false`,
15437
+ * each section gets a unique id (based on the aggregation `name`) and expands
15438
+ * independently — closer to a typical SBA v2 "Facets List".
15439
+ */
15440
+ exclusive = input(true, ...(ngDevMode ? [{ debugName: "exclusive", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
15396
15441
  appStore = inject(AppStore);
15442
+ aggregationsStore = inject(AggregationsStore);
15443
+ aggregationsService = inject(AggregationsService);
15397
15444
  asideFilters = computed(() => {
15398
- // `left` filters live only in the drawer, `both` filters live in the drawer and the bar
15445
+ const explicit = this.aggregations();
15446
+ // Explicit developer-defined list → render exactly as passed, bypassing the JSON config.
15447
+ if (explicit.length > 0) {
15448
+ return explicit
15449
+ .map((key) => this.aggregationsStore.getAggregation(key, "name") ?? this.aggregationsStore.getAggregation(key, "column"))
15450
+ .filter((agg) => !!agg)
15451
+ .map((agg) => ({ name: agg.name, column: agg.column }));
15452
+ }
15453
+ // Default JSON-driven behavior:
15454
+ // `left` filters live only in the drawer, `both` filters live in the drawer and the bar.
15455
+ // Admin-hidden aggregations are excluded (the rule lives in the service).
15399
15456
  const asideFilters = this.appStore.filters().filter((f) => f.position === "left" || f.position === "both");
15400
- return this.appStore.getAuthorized(asideFilters);
15457
+ return this.aggregationsService.rejectHidden(this.appStore.getAuthorized(asideFilters));
15401
15458
  }, ...(ngDevMode ? [{ debugName: "asideFilters" }] : []));
15402
- /**
15403
- * Checks if the specified column is a date filter.
15404
- *
15405
- * @param column The column name to check.
15406
- * @returns True if the column is a date filter, false otherwise.
15407
- */
15408
- isDate(column) {
15409
- return this.appStore.isDateColumn(column);
15410
- }
15411
15459
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AsideFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15412
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AsideFiltersComponent, isStandalone: true, selector: "aside-filters, AsideFilters, asidefilters", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "cn('flex flex-col gap-2 overflow-auto', class())" } }, ngImport: i0, template: `
15460
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AsideFiltersComponent, isStandalone: true, selector: "aside-filters, AsideFilters, asidefilters", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, exclusive: { classPropertyName: "exclusive", publicName: "exclusive", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "cn('flex flex-col gap-2 overflow-auto', class())" } }, ngImport: i0, template: `
15413
15461
  <div class="flex flex-col gap-2">
15414
15462
  @for (agg of asideFilters(); track agg.name) {
15415
15463
  <Aggregation
15416
- id="aside-filters"
15464
+ [id]="exclusive() ? 'aside-filters' : 'aside-filters-' + agg.name"
15417
15465
  [name]="agg.name"
15418
15466
  [column]="agg.column"
15419
15467
  [collapsible]="true"
@@ -15432,7 +15480,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15432
15480
  <div class="flex flex-col gap-2">
15433
15481
  @for (agg of asideFilters(); track agg.name) {
15434
15482
  <Aggregation
15435
- id="aside-filters"
15483
+ [id]="exclusive() ? 'aside-filters' : 'aside-filters-' + agg.name"
15436
15484
  [name]="agg.name"
15437
15485
  [column]="agg.column"
15438
15486
  [collapsible]="true"
@@ -15445,7 +15493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15445
15493
  "[class]": "cn('flex flex-col gap-2 overflow-auto', class())"
15446
15494
  }
15447
15495
  }]
15448
- }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }] } });
15496
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], exclusive: [{ type: i0.Input, args: [{ isSignal: true, alias: "exclusive", required: false }] }] } });
15449
15497
 
15450
15498
  class FilterButtonComponent {
15451
15499
  name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
@@ -15656,92 +15704,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15656
15704
  const FILTERS_BREAKPOINT = new InjectionToken('FILTERS_BREAKPOINT', { factory: () => 5 });
15657
15705
 
15658
15706
  class MoreComponent {
15659
- cn = cn;
15660
15707
  count = input(2, ...(ngDevMode ? [{ debugName: "count" }] : []));
15661
15708
  includedFilters = input([], ...(ngDevMode ? [{ debugName: "includedFilters" }] : []));
15662
15709
  excludedFilters = input([], ...(ngDevMode ? [{ debugName: "excludedFilters" }] : []));
15663
15710
  aggregations = input(...(ngDevMode ? [undefined, { debugName: "aggregations" }] : []));
15664
15711
  homepage = input(false, ...(ngDevMode ? [{ debugName: "homepage", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
15665
- appStore = inject(AppStore);
15666
- aggregationsStore = inject(AggregationsStore);
15667
- queryParamsStore = inject(QueryParamsStore);
15668
15712
  aggregationsService = inject(AggregationsService);
15669
15713
  /**
15670
- * Signal containing an array of extended filter objects.
15714
+ * The overflow filters rendered as accordions: the authorized filters beyond the
15715
+ * first `count` (which are already shown in the bar).
15671
15716
  *
15672
15717
  * @remarks
15673
- * This signal holds the current state of filters that can be applied to data.
15674
- * The filters are of type CFilterEx[], which extends the base filter functionality.
15675
- *
15718
+ * Pure display wiring — authorization (and the admin `hidden` rule) is handled by
15719
+ * {@link AggregationsService.getAuthorizedFilters}; `toSpliced` just drops the
15720
+ * filters already shown in the bar.
15676
15721
  */
15677
- filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
15678
- /**
15679
- * Computed property that returns an array of filters that are currently visible.
15680
- * Filters out any filters where the `hidden` property is set to true.
15681
- *
15682
- * @returns An array of filters that are not hidden
15683
- */
15684
- visibleFilters = computed(() => {
15685
- const filters = this.filters().filter((f) => !f.hidden);
15686
- return filters;
15687
- }, ...(ngDevMode ? [{ debugName: "visibleFilters" }] : []));
15688
- constructor() {
15689
- effect(() => {
15690
- const count = this.count();
15691
- const authorizedFilters = this.aggregationsService
15692
- .getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
15693
- .toSpliced(0, count);
15694
- const f = authorizedFilters.map((agg) => {
15695
- const { icon = "far fa-list", hidden = false } = this.appStore.getAggregationCustomization(agg.column, agg.name) || {};
15696
- return {
15697
- name: agg.name,
15698
- column: agg.column,
15699
- icon,
15700
- count: 0,
15701
- isTree: false,
15702
- disabled: false,
15703
- hidden
15704
- };
15705
- });
15706
- this.filters.set(f);
15707
- this.updateFilters();
15708
- });
15709
- }
15710
- clearFilter(field) {
15711
- this.queryParamsStore.removeFilter(field);
15712
- }
15713
- updateFilters() {
15714
- this.filters.update((filters) => {
15715
- return filters.map((filter) => {
15716
- const agg = this.aggregationsStore.getAggregation(filter.name);
15717
- const f = this.queryParamsStore.getFilter({ name: agg?.name, field: agg?.column });
15718
- const count = f?.count || 0;
15719
- const { display = agg?.isTree ? undefined : filter.display } = this.appStore.getAggregationCustomization(filter.column) || {};
15720
- return {
15721
- ...filter,
15722
- name: agg?.name || filter.name,
15723
- display,
15724
- isTree: agg?.isTree || false,
15725
- count,
15726
- disabled: !!(!agg?.items || agg.items?.length === 0) // Disable if no items
15727
- };
15728
- });
15729
- });
15730
- }
15731
- /**
15732
- * Checks whether there are active filters for the current aggregation column.
15733
- *
15734
- * if True, the clear button is shown.
15735
- *
15736
- * @returns {boolean} `true` if the filter count for the aggregation column is greater than 0, otherwise `false`.
15737
- */
15738
- hasFilters(aggregation) {
15739
- const { count = 0 } = this.queryParamsStore.getFilter({ field: aggregation.column, name: aggregation.name }) || {};
15740
- return count > 0;
15741
- }
15722
+ filters = computed(() => this.aggregationsService
15723
+ .getAuthorizedFilters(this.aggregations(), this.includedFilters(), this.excludedFilters(), this.homepage())
15724
+ .toSpliced(0, this.count()), ...(ngDevMode ? [{ debugName: "filters" }] : []));
15742
15725
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15743
15726
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: MoreComponent, isStandalone: true, selector: "more, More", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, includedFilters: { classPropertyName: "includedFilters", publicName: "includedFilters", isSignal: true, isRequired: false, transformFunction: null }, excludedFilters: { classPropertyName: "excludedFilters", publicName: "excludedFilters", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, homepage: { classPropertyName: "homepage", publicName: "homepage", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "divide-y divide-muted-foreground/18" }, ngImport: i0, template: `
15744
- @for (filter of visibleFilters(); track $index) {
15727
+ @for (filter of filters(); track $index) {
15745
15728
  <Aggregation
15746
15729
  class="w-60 max-w-80 px-1 [--height:15lh]"
15747
15730
  id="more-filters"
@@ -15757,7 +15740,7 @@ class MoreComponent {
15757
15740
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MoreComponent, decorators: [{
15758
15741
  type: Component,
15759
15742
  args: [{ selector: "more, More", standalone: true, imports: [AggregationComponent, TranslocoPipe], template: `
15760
- @for (filter of visibleFilters(); track $index) {
15743
+ @for (filter of filters(); track $index) {
15761
15744
  <Aggregation
15762
15745
  class="w-60 max-w-80 px-1 [--height:15lh]"
15763
15746
  id="more-filters"
@@ -15771,7 +15754,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
15771
15754
  `, host: {
15772
15755
  class: "divide-y divide-muted-foreground/18"
15773
15756
  }, styles: [":host{scrollbar-width:none}\n"] }]
15774
- }], ctorParameters: () => [], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], includedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includedFilters", required: false }] }], excludedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludedFilters", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], homepage: [{ type: i0.Input, args: [{ isSignal: true, alias: "homepage", required: false }] }] } });
15757
+ }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], includedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "includedFilters", required: false }] }], excludedFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludedFilters", required: false }] }], aggregations: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregations", required: false }] }], homepage: [{ type: i0.Input, args: [{ isSignal: true, alias: "homepage", required: false }] }] } });
15775
15758
 
15776
15759
  class MoreButtonComponent {
15777
15760
  appStore = inject(AppStore);