@sinequa/atomic-angular 1.0.9 → 1.0.11

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, ChangeDetectionStrategy, resource, ViewContainerRef, viewChildren, numberAttribute, afterEveryRender } from '@angular/core';
2
+ import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, ChangeDetectionStrategy, resource, ViewContainerRef, viewChildren, numberAttribute, afterRenderEffect, afterEveryRender } from '@angular/core';
3
3
  import { BehaviorSubject, Subscription, catchError, EMPTY, firstValueFrom, map, Subject, of, tap, throwError, filter, shareReplay, fromEvent, debounceTime, from, switchMap } from 'rxjs';
4
4
  import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
5
5
  import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SwitchComponent, SelectOptionDirective, DialogService, TabsComponent, TabsListComponent, TabComponent, ChevronLeftIconComponent, ChevronsLeftIconComponent, ChevronsRightIconComponent, Separator, SheetCloseDirective, SheetService, DateRangePickerDirective, DatepickerDirective, ButtonGroup, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, SearchIcon, FilterIcon, LoadingCircleIconComponent, CircleCheckIconComponent, PopoverComponent, CardComponent, CardHeaderComponent, CardContentComponent, CardFooterComponent, BookmarkIcon, PopoverContentComponent, UserIcon, TrashIcon, FolderIcon, VerticalDividerComponent, BreakpointObserverService, HorizontalDividerComponent, FlagEnglishIconComponent, FlagFrenchIconComponent, EditIcon, UndoIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
@@ -2809,19 +2809,45 @@ function withSavedSearchesFeatures() {
2809
2809
  })));
2810
2810
  }
2811
2811
 
2812
+ /**
2813
+ * Canonical default shape of the user settings state.
2814
+ *
2815
+ * Shared between `initialize()` (overlaid under the fetched settings) and `reset()`
2816
+ * (used as the replacement state) so the two cannot drift apart. Keep in sync with
2817
+ * the `UserSettingsState` type.
2818
+ */
2819
+ const USER_SETTINGS_DEFAULTS = {
2820
+ bookmarks: [],
2821
+ recentSearches: [],
2822
+ savedSearches: [],
2823
+ baskets: [],
2824
+ alerts: [],
2825
+ assistants: {},
2826
+ language: undefined,
2827
+ collapseAssistant: undefined,
2828
+ userTheme: undefined,
2829
+ agents: { isDebugMode: false }
2830
+ };
2812
2831
  function withUserSettingsFeatures() {
2813
2832
  return signalStoreFeature(withState({ language: undefined, collapseAssistant: undefined }), withMethods(store => ({
2814
2833
  /**
2815
2834
  * Initializes the user settings store by fetching the user settings from the backend API
2816
2835
  * and patching the store with the retrieved settings.
2817
2836
  *
2837
+ * The fetched settings are overlaid on top of {@link USER_SETTINGS_DEFAULTS} so that any
2838
+ * key absent from the new user's settings resets to its default instead of retaining the
2839
+ * previous user's in-memory value. This is required because `initialize()` runs both on
2840
+ * first login and on every user override (impersonation): a plain merge would leak the
2841
+ * previous user's data (e.g. `recentSearches`) into the new user's session and backend.
2842
+ *
2818
2843
  * @returns {Promise<void>} A promise that resolves when the initialization is complete.
2819
2844
  */
2820
2845
  async initialize() {
2821
- // Fetch the user settings from the backend API and patch the store with the result
2846
+ // Fetch the user settings from the backend API and overlay them on the defaults,
2847
+ // so missing keys reset rather than keep the previous user's values.
2822
2848
  try {
2823
2849
  const settings = await fetchUserSettings();
2824
- patchState(store, settings);
2850
+ patchState(store, { ...USER_SETTINGS_DEFAULTS, ...settings });
2825
2851
  }
2826
2852
  catch (err) {
2827
2853
  error('Error fetching user settings:', err);
@@ -2839,17 +2865,7 @@ function withUserSettingsFeatures() {
2839
2865
  async reset() {
2840
2866
  // Reset the user settings to the initial state
2841
2867
  await deleteUserSettings();
2842
- patchState(store, {
2843
- bookmarks: [],
2844
- recentSearches: [],
2845
- savedSearches: [],
2846
- baskets: [],
2847
- alerts: [],
2848
- assistants: {},
2849
- language: undefined,
2850
- collapseAssistant: undefined,
2851
- agents: { isDebugMode: false }
2852
- });
2868
+ patchState(store, USER_SETTINGS_DEFAULTS);
2853
2869
  }
2854
2870
  })));
2855
2871
  }
@@ -6909,8 +6925,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
6909
6925
  }]
6910
6926
  }] });
6911
6927
  /**
6912
- * Directive to be used on the element that should be considered as the limit
6913
- * for the overflow manager.
6928
+ * Directive to be used on the "more" trigger element. The overflow manager
6929
+ * reserves this element's size when not all items fit, so the last visible
6930
+ * item never overlaps it. Its position is not used for measurement.
6914
6931
  */
6915
6932
  class OverflowStopDirective {
6916
6933
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverflowStopDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -6924,10 +6941,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
6924
6941
  }]
6925
6942
  }] });
6926
6943
  /**
6927
- * Directive that takes items and a stop element to count the number of items
6928
- * that can fit before the stop element.
6929
- * The directive will apply a `visibility: hidden` on the items that are not
6930
- * visible.
6944
+ * Directive that counts how many items fit inside the container and hides the
6945
+ * overflowing ones. The boundary is the container's own content edge, which
6946
+ * keeps the measurement correct even when the host lives inside a flex layout
6947
+ * (we never depend on a sibling's position). The stop element is used only to
6948
+ * reserve space for the "more" trigger when not all items fit.
6949
+ *
6950
+ * The directive applies `display: none` on the items that do not fit.
6931
6951
  *
6932
6952
  * You can specify a target element to observe for resize events, otherwise the
6933
6953
  * directive will observe the element itself.
@@ -6966,10 +6986,26 @@ class OverflowManagerDirective {
6966
6986
  target = input(...(ngDevMode ? [undefined, { debugName: "target" }] : []));
6967
6987
  margin = input(4, ...(ngDevMode ? [{ debugName: "margin" }] : []));
6968
6988
  direction = input("horizontal", ...(ngDevMode ? [{ debugName: "direction" }] : []));
6989
+ /**
6990
+ * Always reserve the stop element's size, even when every item fits inside
6991
+ * the container. Use it when the stop trigger is permanently visible (e.g.
6992
+ * the "more" button also gives access to items that are never rendered in
6993
+ * the container), so the last item never overlaps it.
6994
+ */
6995
+ reserveStop = input(false, ...(ngDevMode ? [{ debugName: "reserveStop", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
6969
6996
  count = output();
6970
6997
  el = inject(ElementRef).nativeElement;
6971
6998
  destroyRef = inject(DestroyRef);
6972
6999
  resizeObserver = new ResizeObserver(() => this.countItems());
7000
+ // Recompute when an individual item's size changes (label update, async
7001
+ // translation, font load…), not only when the container resizes. Items we
7002
+ // hid with `display: none` report a 0×0 size; ignore those notifications so
7003
+ // our own show/hide toggling doesn't trigger redundant recounts.
7004
+ itemsResizeObserver = new ResizeObserver((entries) => {
7005
+ const visibleItemResized = entries.some((entry) => entry.target.style.display !== "none");
7006
+ if (visibleItemResized)
7007
+ this.countItems();
7008
+ });
6973
7009
  countSub;
6974
7010
  _lastCount;
6975
7011
  constructor() {
@@ -6981,21 +7017,37 @@ class OverflowManagerDirective {
6981
7017
  this.countItems();
6982
7018
  }
6983
7019
  });
7020
+ // (re)observe every item whenever the projected list changes, so that
7021
+ // added/removed items and per-item size changes both trigger a recount
7022
+ effect(() => {
7023
+ const items = this.items();
7024
+ this.itemsResizeObserver.disconnect();
7025
+ items.forEach((item) => this.itemsResizeObserver.observe(item.nativeElement));
7026
+ });
6984
7027
  // listens to the count output and toggles the visibility of the items
6985
7028
  this.countSub = this.count.subscribe((count) => this.toggleToCount(count));
6986
7029
  this.destroyRef.onDestroy(() => {
6987
7030
  this.resizeObserver.disconnect();
7031
+ this.itemsResizeObserver.disconnect();
6988
7032
  this.countSub?.unsubscribe();
6989
7033
  this.countSub = undefined;
6990
7034
  });
6991
7035
  }
6992
7036
  /**
6993
- * Counts the number of items that can fit before the stop element.
7037
+ * Counts the number of items that can fit inside the container.
7038
+ *
7039
+ * The boundary is the container's own content edge (not the position of the
7040
+ * stop element). This is what makes the measurement robust when the host is
7041
+ * placed inside a flex layout: we never rely on a sibling staying where we
7042
+ * expect it. The stop element is only used for its *size*, to reserve space
7043
+ * for the "more" trigger when not all items fit.
7044
+ *
6994
7045
  * Emits the count if it has changed.
6995
7046
  */
6996
7047
  countItems() {
6997
7048
  if (!this.items() || this.items().length === 0 || !this.stop())
6998
7049
  return;
7050
+ const horizontal = this.direction() === "horizontal";
6999
7051
  // Reset all items to their natural size before measuring so that previously
7000
7052
  // hidden items (display: none) don't corrupt the layout and position of
7001
7053
  // their siblings.
@@ -7003,19 +7055,33 @@ class OverflowManagerDirective {
7003
7055
  item.nativeElement.style.display = "";
7004
7056
  });
7005
7057
  // getBoundingClientRect() forces a synchronous reflow, so positions are
7006
- // accurate after the reset above.
7058
+ // accurate after the reset above. Using rects (not offsetWidth) means the
7059
+ // inter-item gap is accounted for automatically.
7060
+ const containerRect = this.el.getBoundingClientRect();
7007
7061
  const stopRect = this.stop().nativeElement.getBoundingClientRect();
7008
7062
  const itemsRects = this.items().map((item) => item.nativeElement.getBoundingClientRect());
7009
- let count = 0;
7010
- for (const rect of itemsRects) {
7011
- // if the direction is horizontal, we check the right side of the item
7012
- if (this.direction() === "horizontal" && rect.right + this.margin() <= stopRect.left)
7013
- count++;
7014
- // if the direction is vertical, we check the bottom side of the item
7015
- else if (this.direction() === "vertical" && rect.bottom + this.margin() <= stopRect.top)
7016
- count++;
7017
- else
7018
- break;
7063
+ // The container's content edge, with a small slack margin.
7064
+ const containerEnd = (horizontal ? containerRect.right : containerRect.bottom) - this.margin();
7065
+ const itemEnd = (rect) => (horizontal ? rect.right : rect.bottom);
7066
+ let count;
7067
+ // If every item fits within the container, no "more" trigger is needed and
7068
+ // we don't reserve any space for it unless the trigger is permanently
7069
+ // visible (reserveStop), in which case its space is always reserved.
7070
+ if (!this.reserveStop() && itemEnd(itemsRects[itemsRects.length - 1]) <= containerEnd) {
7071
+ count = itemsRects.length;
7072
+ }
7073
+ else {
7074
+ // Otherwise reserve the stop element's own size so the last visible item
7075
+ // never overlaps the "more" trigger.
7076
+ const reserve = horizontal ? stopRect.width : stopRect.height;
7077
+ const limit = containerEnd - reserve;
7078
+ count = 0;
7079
+ for (const rect of itemsRects) {
7080
+ if (itemEnd(rect) <= limit)
7081
+ count++;
7082
+ else
7083
+ break;
7084
+ }
7019
7085
  }
7020
7086
  if (this._lastCount !== count) {
7021
7087
  this._lastCount = count;
@@ -7038,7 +7104,7 @@ class OverflowManagerDirective {
7038
7104
  });
7039
7105
  }
7040
7106
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverflowManagerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7041
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.18", type: OverflowManagerDirective, isStandalone: true, selector: "[overflowManager]", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { count: "count" }, queries: [{ propertyName: "items", predicate: OverflowItemDirective, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "stop", first: true, predicate: OverflowStopDirective, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0 });
7107
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.18", type: OverflowManagerDirective, isStandalone: true, selector: "[overflowManager]", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, reserveStop: { classPropertyName: "reserveStop", publicName: "reserveStop", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { count: "count" }, queries: [{ propertyName: "items", predicate: OverflowItemDirective, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "stop", first: true, predicate: OverflowStopDirective, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0 });
7042
7108
  }
7043
7109
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverflowManagerDirective, decorators: [{
7044
7110
  type: Directive,
@@ -7046,7 +7112,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
7046
7112
  selector: "[overflowManager]",
7047
7113
  standalone: true
7048
7114
  }]
7049
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => OverflowItemDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], stop: [{ type: i0.ContentChild, args: [i0.forwardRef(() => OverflowStopDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], count: [{ type: i0.Output, args: ["count"] }] } });
7115
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => OverflowItemDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], stop: [{ type: i0.ContentChild, args: [i0.forwardRef(() => OverflowStopDirective), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], reserveStop: [{ type: i0.Input, args: [{ isSignal: true, alias: "reserveStop", required: false }] }], count: [{ type: i0.Output, args: ["count"] }] } });
7050
7116
 
7051
7117
  /**
7052
7118
  * Directive that selects an article on click.
@@ -7283,7 +7349,7 @@ class NavbarTabsComponent {
7283
7349
  </Menu>
7284
7350
  </div>
7285
7351
  }
7286
- `, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { kind: "component", type: MenuComponent, selector: "menu, Menu", inputs: ["disabled"] }, { kind: "directive", type: MenuItemComponent, selector: "menu-item, menuitem, MenuItem", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: MenuContentComponent, selector: "MenuContent, menucontent, menu-content", inputs: ["class", "position"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "noTruncate", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "component", type: EllipsisIcon, selector: "ellipsis-icon, EllipsisIcon, ellipsisicon", inputs: ["class", "orientation"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
7352
+ `, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { kind: "component", type: MenuComponent, selector: "menu, Menu", inputs: ["disabled"] }, { kind: "directive", type: MenuItemComponent, selector: "menu-item, menuitem, MenuItem", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: MenuContentComponent, selector: "MenuContent, menucontent, menu-content", inputs: ["class", "position"] }, { kind: "directive", type: TabsComponent, selector: "tabs, Tabs", inputs: ["class"] }, { kind: "directive", type: TabsListComponent, selector: "tabs-list, TabsList", inputs: ["class", "variant", "size"] }, { kind: "directive", type: TabComponent, selector: "tab, Tab", inputs: ["class", "variant", "size", "noTruncate", "value", "active"], outputs: ["clicked"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction", "reserveStop"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "directive", type: BadgeComponent, selector: "badge, Badge", inputs: ["class", "variant", "size"] }, { kind: "component", type: EllipsisIcon, selector: "ellipsis-icon, EllipsisIcon, ellipsisicon", inputs: ["class", "orientation"] }, { kind: "component", type: FaIconComponent, selector: "fa-icon, FaIcon", inputs: ["faClass"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }, { kind: "pipe", type: SyslangPipe, name: "syslang" }] });
7287
7353
  }
7288
7354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NavbarTabsComponent, decorators: [{
7289
7355
  type: Component,
@@ -14594,50 +14660,76 @@ class FiltersBarComponent {
14594
14660
  return this.aggregationsStore.aggregations().length > 0;
14595
14661
  return false;
14596
14662
  }, ...(ngDevMode ? [{ debugName: "hasAggregations" }] : []));
14663
+ /**
14664
+ * The full list of authorized filters, NOT capped by `filtersCount`.
14665
+ *
14666
+ * This computed signal performs the following operations:
14667
+ * 1. Retrieves aggregations from either the component's aggregations input or the app store
14668
+ * 2. Filters aggregations based on the route's filter criteria configuration
14669
+ * 3. Excludes filters specified in the `excludeFilters` list
14670
+ * 4. If `includeFilters` is not empty, only includes filters present in that list
14671
+ * 5. Maps the filtered aggregations to objects containing only `name` and `column` properties
14672
+ */
14673
+ allAuthorizedFilters = computed(() => {
14674
+ return this.aggregationsService
14675
+ .getAuthorizedFilters(this.aggregations(), this.includeFilters(), this.excludeFilters(), this.homepage())
14676
+ .map((f) => ({ name: f.name, column: f.column }));
14677
+ }, ...(ngDevMode ? [{ debugName: "allAuthorizedFilters" }] : []));
14597
14678
  /**
14598
14679
  * Computes the list of additional filters that can be displayed in the "more filters" popover.
14599
14680
  *
14600
- * This computed property filters the authorized filters from the AppStore, excluding those
14601
- * specified in the `excludeFilters` input. It then maps these filters to their corresponding
14602
- * aggregations from the AggregationsStore, limited to the number defined by `moreFilterCount`.
14681
+ * Derived from the FULL authorized list (not the one capped by `filtersCount`), so the
14682
+ * filters beyond `filtersCount` which are never rendered in the bar — are still counted.
14683
+ * Otherwise, when every rendered filter fits in the container, this list would be empty and
14684
+ * the "more" button would be hidden even though more filters exist beyond the cap.
14603
14685
  *
14604
- * This property manages the visibility and content of the "more filters" popover in the UI.
14686
+ * This property manages the visibility of the "more filters" button in the UI.
14605
14687
  *
14606
14688
  * @returns An array of Aggregation objects representing the additional filters available.
14607
14689
  */
14608
14690
  hasMoreFilters = computed(() => {
14609
- const moreFiltersAggregations = this.authorizedFilters()
14610
- .filter((f) => !this.excludeFilters().includes(f.name)) // filter out the excluded filters
14611
- .filter((f) => !this.includeFilters().length || this.includeFilters().includes(f.name)) // exclude filters not included in includeFilters if not empty
14612
- .map((f) => ({ column: f.column, name: f.name }))
14691
+ const moreFiltersAggregations = this.allAuthorizedFilters()
14613
14692
  .toSpliced(0, this.visibleFiltersCount())
14614
14693
  .map((f) => this.aggregationsStore.getAggregation(f.column, "column"));
14615
14694
  return moreFiltersAggregations;
14616
14695
  }, ...(ngDevMode ? [{ debugName: "hasMoreFilters" }] : []));
14617
14696
  /**
14618
- * Computed property that returns a filtered and processed list of authorized filters.
14619
- *
14620
- * This computed signal performs the following operations:
14621
- * 1. Retrieves aggregations from either the component's aggregations input or the app store
14622
- * 2. Filters aggregations based on the route's filter criteria configuration
14623
- * 3. Excludes filters specified in the `excludeFilters` list
14624
- * 4. If `includeFilters` is not empty, only includes filters present in that list
14625
- * 5. Maps the filtered aggregations to objects containing only `name` and `column` properties
14626
- * 6. Limits the result to the number specified by `filtersCount`
14697
+ * The authorized filters rendered as buttons in the bar, limited to the number
14698
+ * specified by `filtersCount`.
14627
14699
  *
14628
14700
  * @returns An array of authorized filter objects, each containing `name` and `column` properties
14629
14701
  */
14630
14702
  authorizedFilters = computed(() => {
14631
- const authorizedFilters = this.aggregationsService
14632
- .getAuthorizedFilters(this.aggregations(), this.includeFilters(), this.excludeFilters(), this.homepage())
14633
- .map((f) => ({ name: f.name, column: f.column }))
14634
- .toSpliced(this.filtersCount());
14635
- return authorizedFilters;
14703
+ return this.allAuthorizedFilters().toSpliced(this.filtersCount());
14636
14704
  }, ...(ngDevMode ? [{ debugName: "authorizedFilters" }] : []));
14705
+ /**
14706
+ * Whether some authorized filters exist beyond the `filtersCount` cap.
14707
+ *
14708
+ * Those filters are never rendered in the bar and are only reachable through
14709
+ * the "more" button, which is therefore permanently visible: the overflow
14710
+ * manager must always reserve its space so the last filter button never
14711
+ * overlaps it (`reserveStop`).
14712
+ */
14713
+ hasCappedFilters = computed(() => this.allAuthorizedFilters().length > this.filtersCount(), ...(ngDevMode ? [{ debugName: "hasCappedFilters" }] : []));
14637
14714
  constructor() {
14638
14715
  this.transloco.events$
14639
14716
  .pipe(takeUntilDestroyed(this.destroyRef), debounceTime(100))
14640
14717
  .subscribe(() => this.overflowManagerRef()?.countItems());
14718
+ // Recount the overflow whenever the applied filters or basket change (e.g.
14719
+ // a filter modified or removed from the "more filters" popover). A
14720
+ // FilterButton hidden by the overflow manager (display: none) emits no
14721
+ // resize notification when its natural width changes, so it could fit in
14722
+ // the bar again without the manager knowing. afterRenderEffect guarantees
14723
+ // the DOM already reflects the new state when we measure.
14724
+ afterRenderEffect({
14725
+ read: () => {
14726
+ // track filters and basket changes (getState is reactive here)
14727
+ const { filters, basket } = getState(this.queryParamsStore);
14728
+ void filters;
14729
+ void basket;
14730
+ this.overflowManagerRef()?.countItems();
14731
+ }
14732
+ });
14641
14733
  }
14642
14734
  /**
14643
14735
  * Clears all filters (included baskets) by invoking the clearFilters method on the queryParamsStore.
@@ -14686,8 +14778,8 @@ class FiltersBarComponent {
14686
14778
  });
14687
14779
  }
14688
14780
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: FiltersBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14689
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: FiltersBarComponent, isStandalone: true, selector: "filters-bar, FiltersBar, filtersbar", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, morePosition: { classPropertyName: "morePosition", publicName: "morePosition", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, includeFilters: { classPropertyName: "includeFilters", publicName: "includeFilters", isSignal: true, isRequired: false, transformFunction: null }, excludeFilters: { classPropertyName: "excludeFilters", publicName: "excludeFilters", isSignal: true, isRequired: false, transformFunction: null }, filtersCount: { classPropertyName: "filtersCount", publicName: "filtersCount", isSignal: true, isRequired: false, transformFunction: null }, showMoreFiltersButton: { classPropertyName: "showMoreFiltersButton", publicName: "showMoreFiltersButton", isSignal: true, isRequired: false, transformFunction: null }, homepage: { classPropertyName: "homepage", publicName: "homepage", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClearFilters: "onClearFilters", onClearBasket: "onClearBasket" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "cn('block relative', class())" } }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "moreButtonRef", first: true, predicate: MoreButtonComponent, descendants: true, isSignal: true }, { propertyName: "filterButtonRefs", predicate: FilterButtonComponent, descendants: true, isSignal: true }, { propertyName: "overflowManagerRef", first: true, predicate: OverflowManagerDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
14690
- <div overflowManager [direction]="direction()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
14781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: FiltersBarComponent, isStandalone: true, selector: "filters-bar, FiltersBar, filtersbar", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, morePosition: { classPropertyName: "morePosition", publicName: "morePosition", isSignal: true, isRequired: false, transformFunction: null }, aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, includeFilters: { classPropertyName: "includeFilters", publicName: "includeFilters", isSignal: true, isRequired: false, transformFunction: null }, excludeFilters: { classPropertyName: "excludeFilters", publicName: "excludeFilters", isSignal: true, isRequired: false, transformFunction: null }, filtersCount: { classPropertyName: "filtersCount", publicName: "filtersCount", isSignal: true, isRequired: false, transformFunction: null }, showMoreFiltersButton: { classPropertyName: "showMoreFiltersButton", publicName: "showMoreFiltersButton", isSignal: true, isRequired: false, transformFunction: null }, homepage: { classPropertyName: "homepage", publicName: "homepage", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, expandedLevel: { classPropertyName: "expandedLevel", publicName: "expandedLevel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClearFilters: "onClearFilters", onClearBasket: "onClearBasket" }, host: { listeners: { "click": "handleClick($event)" }, properties: { "class": "cn('block relative min-w-0', class())" } }, providers: [provideTranslocoScope("filters")], viewQueries: [{ propertyName: "moreButtonRef", first: true, predicate: MoreButtonComponent, descendants: true, isSignal: true }, { propertyName: "filterButtonRefs", predicate: FilterButtonComponent, descendants: true, isSignal: true }, { propertyName: "overflowManagerRef", first: true, predicate: OverflowManagerDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
14782
+ <div overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
14691
14783
  @if (hasFilters()) {
14692
14784
  <button
14693
14785
  variant="destructive"
@@ -14739,7 +14831,7 @@ class FiltersBarComponent {
14739
14831
  }
14740
14832
  }
14741
14833
  </div>
14742
- `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { kind: "component", type: MoreButtonComponent, selector: "more-button, MoreButton", inputs: ["count", "position", "includedFilters", "excludedFilters", "aggregations", "homepage"] }, { kind: "component", type: FilterButtonComponent, selector: "filter-button, FilterButton", inputs: ["name", "column", "position", "offset", "expandedLevel"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14834
+ `, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { kind: "component", type: MoreButtonComponent, selector: "more-button, MoreButton", inputs: ["count", "position", "includedFilters", "excludedFilters", "aggregations", "homepage"] }, { kind: "component", type: FilterButtonComponent, selector: "filter-button, FilterButton", inputs: ["name", "column", "position", "offset", "expandedLevel"] }, { kind: "directive", type: OverflowManagerDirective, selector: "[overflowManager]", inputs: ["target", "margin", "direction", "reserveStop"], outputs: ["count"] }, { kind: "directive", type: OverflowItemDirective, selector: "[overflowItem]" }, { kind: "directive", type: OverflowStopDirective, selector: "[overflowStop]" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
14743
14835
  }
14744
14836
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: FiltersBarComponent, decorators: [{
14745
14837
  type: Component,
@@ -14757,7 +14849,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
14757
14849
  ],
14758
14850
  providers: [provideTranslocoScope("filters")],
14759
14851
  template: `
14760
- <div overflowManager [direction]="direction()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
14852
+ <div overflowManager [direction]="direction()" [reserveStop]="hasCappedFilters()" (count)="adjustFiltersCount($event)" class="flex items-end gap-2 rounded-[inherit] bg-inherit">
14761
14853
  @if (hasFilters()) {
14762
14854
  <button
14763
14855
  variant="destructive"
@@ -14811,7 +14903,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
14811
14903
  </div>
14812
14904
  `,
14813
14905
  host: {
14814
- "[class]": "cn('block relative', class())",
14906
+ "[class]": "cn('block relative min-w-0', class())",
14815
14907
  "(click)": "handleClick($event)"
14816
14908
  }
14817
14909
  }]