@sinequa/atomic-angular 2.2.0 → 2.3.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.
@@ -1,8 +1,8 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, HostBinding, HostListener, Inject, Injectable, InjectionToken, Injector, LOCALE_ID, Optional, Pipe, Service, TemplateRef, ViewEncapsulation, afterNextRender, afterRenderEffect, assertInInjectionContext, booleanAttribute, computed, contentChild, contentChildren, effect, inject, input, isDevMode, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, provideEnvironmentInitializer, resource, runInInjectionContext, signal, untracked, viewChild, viewChildren } from "@angular/core";
3
- import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, shareReplay, switchMap, tap, throwError } from "rxjs";
3
+ import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, scan, shareReplay, switchMap, tap, throwError } from "rxjs";
4
4
  import { TranslocoPipe, TranslocoService, provideTranslocoScope } from "@jsverse/transloco";
5
- import { ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef } from "@sinequa/galactik";
5
+ import { ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef } from "@sinequa/galactik";
6
6
  import { withDevtools } from "@angular-architects/ngrx-toolkit";
7
7
  import { getState, patchState, signalStore, signalStoreFeature, withComputed, withMethods, withState } from "@ngrx/signals";
8
8
  import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
@@ -20658,9 +20658,7 @@ i0.ɵɵngDeclareClassMetadata({
20658
20658
  }] }
20659
20659
  });
20660
20660
  const FeatureFlags = createCallable(FeatureFlagsDialogComponent);
20661
- var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
20662
- call = injectCallRef();
20663
- dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
20661
+ var KeyboardShortcutsListComponent = class KeyboardShortcutsListComponent {
20664
20662
  keyboard = inject(KeyboardShortcutsService);
20665
20663
  groups = computed(() => {
20666
20664
  const listed = this.keyboard.shortcuts().filter((shortcut) => !shortcut.hidden && shortcut.labelKey);
@@ -20675,6 +20673,146 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
20675
20673
  items
20676
20674
  }));
20677
20675
  }, ...ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []);
20676
+ static ɵfac = i0.ɵɵngDeclareFactory({
20677
+ minVersion: "12.0.0",
20678
+ version: "22.2.1",
20679
+ ngImport: i0,
20680
+ type: KeyboardShortcutsListComponent,
20681
+ deps: [],
20682
+ target: i0.ɵɵFactoryTarget.Component
20683
+ });
20684
+ static ɵcmp = i0.ɵɵngDeclareComponent({
20685
+ minVersion: "17.0.0",
20686
+ version: "22.2.1",
20687
+ type: KeyboardShortcutsListComponent,
20688
+ isStandalone: true,
20689
+ selector: "keyboard-shortcuts-list",
20690
+ providers: [provideTranslocoScope("dialogs")],
20691
+ ngImport: i0,
20692
+ template: `
20693
+ @if (groups().length === 0) {
20694
+ <p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
20695
+ }
20696
+
20697
+ @for (group of groups(); track group.titleKey ?? "") {
20698
+ @if (group.titleKey) {
20699
+ <h3 class="mt-(--spacing-md) mb-(--spacing-4xs) text-(length:--size-lg) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
20700
+ }
20701
+ <dl>
20702
+ @for (shortcut of group.items; track shortcut) {
20703
+ <div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
20704
+ <dt>{{ shortcut.labelKey! | transloco }}</dt>
20705
+ <dd class="flex items-center gap-(--spacing-3xs)">
20706
+ <!-- One group per combination: a shortcut may answer to several (Alt+0 and
20707
+ Shift+?). The group carries the whole combination as its name and the keys
20708
+ are hidden, otherwise a screen reader reads out every key and separator as
20709
+ a separate item. -->
20710
+ <!-- comboIndex is captured explicitly: inside the nested loop $index would
20711
+ refer to the keys, not to the combination.
20712
+ (No backticks in this comment: the template is a template literal.) -->
20713
+ @for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
20714
+ @if (!first) {
20715
+ <span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
20716
+ }
20717
+ <KbdGroup [attr.aria-label]="aria">
20718
+ @for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
20719
+ @if (!firstPart) {
20720
+ <span aria-hidden="true">+</span>
20721
+ }
20722
+ <kbd aria-hidden="true">{{ part }}</kbd>
20723
+ }
20724
+ </KbdGroup>
20725
+ }
20726
+ </dd>
20727
+ </div>
20728
+ }
20729
+ </dl>
20730
+ }
20731
+ `,
20732
+ isInline: true,
20733
+ dependencies: [
20734
+ {
20735
+ kind: "directive",
20736
+ type: KbdComponent,
20737
+ selector: "kbd, Kbd",
20738
+ inputs: ["class", "size"]
20739
+ },
20740
+ {
20741
+ kind: "directive",
20742
+ type: KbdGroupComponent,
20743
+ selector: "kbd-group, kbdgroup, KbdGroup"
20744
+ },
20745
+ {
20746
+ kind: "pipe",
20747
+ type: TranslocoPipe,
20748
+ name: "transloco"
20749
+ }
20750
+ ],
20751
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
20752
+ });
20753
+ };
20754
+ i0.ɵɵngDeclareClassMetadata({
20755
+ minVersion: "12.0.0",
20756
+ version: "22.2.1",
20757
+ ngImport: i0,
20758
+ type: KeyboardShortcutsListComponent,
20759
+ decorators: [{
20760
+ type: Component,
20761
+ args: [{
20762
+ selector: "keyboard-shortcuts-list",
20763
+ changeDetection: ChangeDetectionStrategy.OnPush,
20764
+ imports: [
20765
+ TranslocoPipe,
20766
+ KbdComponent,
20767
+ KbdGroupComponent
20768
+ ],
20769
+ providers: [provideTranslocoScope("dialogs")],
20770
+ template: `
20771
+ @if (groups().length === 0) {
20772
+ <p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
20773
+ }
20774
+
20775
+ @for (group of groups(); track group.titleKey ?? "") {
20776
+ @if (group.titleKey) {
20777
+ <h3 class="mt-(--spacing-md) mb-(--spacing-4xs) text-(length:--size-lg) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
20778
+ }
20779
+ <dl>
20780
+ @for (shortcut of group.items; track shortcut) {
20781
+ <div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
20782
+ <dt>{{ shortcut.labelKey! | transloco }}</dt>
20783
+ <dd class="flex items-center gap-(--spacing-3xs)">
20784
+ <!-- One group per combination: a shortcut may answer to several (Alt+0 and
20785
+ Shift+?). The group carries the whole combination as its name and the keys
20786
+ are hidden, otherwise a screen reader reads out every key and separator as
20787
+ a separate item. -->
20788
+ <!-- comboIndex is captured explicitly: inside the nested loop $index would
20789
+ refer to the keys, not to the combination.
20790
+ (No backticks in this comment: the template is a template literal.) -->
20791
+ @for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
20792
+ @if (!first) {
20793
+ <span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
20794
+ }
20795
+ <KbdGroup [attr.aria-label]="aria">
20796
+ @for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
20797
+ @if (!firstPart) {
20798
+ <span aria-hidden="true">+</span>
20799
+ }
20800
+ <kbd aria-hidden="true">{{ part }}</kbd>
20801
+ }
20802
+ </KbdGroup>
20803
+ }
20804
+ </dd>
20805
+ </div>
20806
+ }
20807
+ </dl>
20808
+ }
20809
+ `
20810
+ }]
20811
+ }]
20812
+ });
20813
+ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
20814
+ call = injectCallRef();
20815
+ dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
20678
20816
  constructor() {
20679
20817
  afterNextRender(() => this.dialog()?.showModal());
20680
20818
  }
@@ -20687,7 +20825,7 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
20687
20825
  target: i0.ɵɵFactoryTarget.Component
20688
20826
  });
20689
20827
  static ɵcmp = i0.ɵɵngDeclareComponent({
20690
- minVersion: "17.0.0",
20828
+ minVersion: "17.2.0",
20691
20829
  version: "22.2.1",
20692
20830
  type: KeyboardShortcutsDialogComponent,
20693
20831
  isStandalone: true,
@@ -20709,44 +20847,7 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
20709
20847
  </DialogHeader>
20710
20848
 
20711
20849
  <DialogBody>
20712
- @if (groups().length === 0) {
20713
- <p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
20714
- }
20715
-
20716
- @for (group of groups(); track group.titleKey ?? "") {
20717
- @if (group.titleKey) {
20718
- <h3 class="mt-(--spacing-md) mb-(--spacing-4xs) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
20719
- }
20720
- <dl>
20721
- @for (shortcut of group.items; track shortcut) {
20722
- <div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
20723
- <dt>{{ shortcut.labelKey! | transloco }}</dt>
20724
- <dd class="flex items-center gap-(--spacing-3xs)">
20725
- <!-- One group per combination: a shortcut may answer to several (Alt+0 and
20726
- Shift+?). The group carries the whole combination as its name and the keys
20727
- are hidden, otherwise a screen reader reads out every key and separator as
20728
- a separate item. -->
20729
- <!-- comboIndex is captured explicitly: inside the nested loop $index would
20730
- refer to the keys, not to the combination.
20731
- (No backticks in this comment: the template is a template literal.) -->
20732
- @for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
20733
- @if (!first) {
20734
- <span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
20735
- }
20736
- <KbdGroup [attr.aria-label]="aria">
20737
- @for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
20738
- @if (!firstPart) {
20739
- <span aria-hidden="true">+</span>
20740
- }
20741
- <kbd aria-hidden="true">{{ part }}</kbd>
20742
- }
20743
- </KbdGroup>
20744
- }
20745
- </dd>
20746
- </div>
20747
- }
20748
- </dl>
20749
- }
20850
+ <keyboard-shortcuts-list />
20750
20851
  </DialogBody>
20751
20852
 
20752
20853
  <DialogFooter>
@@ -20812,15 +20913,9 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
20812
20913
  inputs: ["class"]
20813
20914
  },
20814
20915
  {
20815
- kind: "directive",
20816
- type: KbdComponent,
20817
- selector: "kbd, Kbd",
20818
- inputs: ["class", "size"]
20819
- },
20820
- {
20821
- kind: "directive",
20822
- type: KbdGroupComponent,
20823
- selector: "kbd-group, kbdgroup, KbdGroup"
20916
+ kind: "component",
20917
+ type: KeyboardShortcutsListComponent,
20918
+ selector: "keyboard-shortcuts-list"
20824
20919
  },
20825
20920
  {
20826
20921
  kind: "pipe",
@@ -20850,8 +20945,7 @@ i0.ɵɵngDeclareClassMetadata({
20850
20945
  DialogTitleComponent,
20851
20946
  DialogBodyComponent,
20852
20947
  DialogFooterComponent,
20853
- KbdComponent,
20854
- KbdGroupComponent
20948
+ KeyboardShortcutsListComponent
20855
20949
  ],
20856
20950
  providers: [provideTranslocoScope("dialogs")],
20857
20951
  template: `
@@ -20862,44 +20956,7 @@ i0.ɵɵngDeclareClassMetadata({
20862
20956
  </DialogHeader>
20863
20957
 
20864
20958
  <DialogBody>
20865
- @if (groups().length === 0) {
20866
- <p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
20867
- }
20868
-
20869
- @for (group of groups(); track group.titleKey ?? "") {
20870
- @if (group.titleKey) {
20871
- <h3 class="mt-(--spacing-md) mb-(--spacing-4xs) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
20872
- }
20873
- <dl>
20874
- @for (shortcut of group.items; track shortcut) {
20875
- <div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
20876
- <dt>{{ shortcut.labelKey! | transloco }}</dt>
20877
- <dd class="flex items-center gap-(--spacing-3xs)">
20878
- <!-- One group per combination: a shortcut may answer to several (Alt+0 and
20879
- Shift+?). The group carries the whole combination as its name and the keys
20880
- are hidden, otherwise a screen reader reads out every key and separator as
20881
- a separate item. -->
20882
- <!-- comboIndex is captured explicitly: inside the nested loop $index would
20883
- refer to the keys, not to the combination.
20884
- (No backticks in this comment: the template is a template literal.) -->
20885
- @for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
20886
- @if (!first) {
20887
- <span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
20888
- }
20889
- <KbdGroup [attr.aria-label]="aria">
20890
- @for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
20891
- @if (!firstPart) {
20892
- <span aria-hidden="true">+</span>
20893
- }
20894
- <kbd aria-hidden="true">{{ part }}</kbd>
20895
- }
20896
- </KbdGroup>
20897
- }
20898
- </dd>
20899
- </div>
20900
- }
20901
- </dl>
20902
- }
20959
+ <keyboard-shortcuts-list />
20903
20960
  </DialogBody>
20904
20961
 
20905
20962
  <DialogFooter>
@@ -31045,11 +31102,6 @@ i0.ɵɵngDeclareClassMetadata({
31045
31102
  }]
31046
31103
  }] }
31047
31104
  });
31048
- const SUPPORTED_LANGUAGES = [
31049
- "en",
31050
- "fr",
31051
- "de"
31052
- ];
31053
31105
  const sameKeys = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
31054
31106
  const userProfileSchema = (editing) => schema((p) => {
31055
31107
  applyEach(p.data, (field) => readonly(field, { when: ({ key }) => `data.${key()}` !== editing() }));
@@ -31057,7 +31109,6 @@ const userProfileSchema = (editing) => schema((p) => {
31057
31109
  });
31058
31110
  var UserProfileFormComponent = class UserProfileFormComponent {
31059
31111
  principalStore = inject(PrincipalStore);
31060
- userSettingsStore = inject(UserSettingsStore);
31061
31112
  userProfileService = inject(UserProfileService);
31062
31113
  appStore = inject(AppStore);
31063
31114
  client = inject(ATOMIC_CLIENT);
@@ -31105,26 +31156,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
31105
31156
  initials = computed(() => this.principalStore.initials(), ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
31106
31157
  username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []);
31107
31158
  propertyToEdit = signal(void 0, ...ngDevMode ? [{ debugName: "propertyToEdit" }] : /* istanbul ignore next */ []);
31108
- currentLanguage = model(null, ...ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []);
31109
- allLanguages = [
31110
- {
31111
- code: "en",
31112
- label: "English",
31113
- icon: FlagEnglishIcon
31114
- },
31115
- {
31116
- code: "fr",
31117
- label: "Français",
31118
- icon: FlagFrenchIcon
31119
- },
31120
- {
31121
- code: "de",
31122
- label: "Deutsch",
31123
- icon: FlagGermanIcon
31124
- }
31125
- ];
31126
- languageCodes = [...SUPPORTED_LANGUAGES];
31127
- languageLabel = (code) => this.allLanguages.find((lang) => lang.code === code)?.label ?? code;
31128
31159
  changingPassword = signal(false, ...ngDevMode ? [{ debugName: "changingPassword" }] : /* istanbul ignore next */ []);
31129
31160
  allowChangePassword = computed(() => {
31130
31161
  const useCredentials = this.client.config.authMode?.kind === "credentials";
@@ -31155,7 +31186,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
31155
31186
  submission: { action: async () => this.saveEditedProperty() }
31156
31187
  });
31157
31188
  constructor() {
31158
- this.currentLanguage.set(this.transloco.getActiveLang());
31159
31189
  effect(() => {
31160
31190
  if (this.userProfileResource.error()?.status === 404) this.createUserProfile();
31161
31191
  });
@@ -31293,17 +31323,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
31293
31323
  reader.readAsDataURL(element.files[0]);
31294
31324
  reader.onload = () => void this.patchProperty("data", "profilePhoto", reader.result);
31295
31325
  }
31296
- onLanguageChange(code) {
31297
- if (!code) return;
31298
- this.currentLanguage.set(code);
31299
- this.changeLanguage();
31300
- }
31301
- changeLanguage() {
31302
- const language = this.currentLanguage();
31303
- if (!language) return;
31304
- this.userSettingsStore.updateLanguage(language);
31305
- if (this.transloco.getActiveLang() !== language) this.transloco.setActiveLang(language);
31306
- }
31307
31326
  static ɵfac = i0.ɵɵngDeclareFactory({
31308
31327
  minVersion: "12.0.0",
31309
31328
  version: "22.2.1",
@@ -31318,18 +31337,10 @@ var UserProfileFormComponent = class UserProfileFormComponent {
31318
31337
  type: UserProfileFormComponent,
31319
31338
  isStandalone: true,
31320
31339
  selector: "user-profile-form, UserProfileForm, userprofileform",
31321
- inputs: { currentLanguage: {
31322
- classPropertyName: "currentLanguage",
31323
- publicName: "currentLanguage",
31324
- isSignal: true,
31325
- isRequired: false,
31326
- transformFunction: null
31327
- } },
31328
- outputs: { currentLanguage: "currentLanguageChange" },
31329
31340
  host: { classAttribute: "flex flex-col h-full overflow-hidden" },
31330
31341
  providers: [provideTranslocoScope("user-profile", "login")],
31331
31342
  ngImport: i0,
31332
- template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE — outside the <form> below: it is a user setting applied on the spot, not a\n profile property submitted with the rest. The trigger is projected rather than left to the\n default one so it can carry an id, which is what makes the <label for> name the combobox —\n SelectMenu's own `fieldLabel` renders a <span>, which names nothing.\n The label goes in the `.select-label` slot: the DS wrap paints it (same size and weight as\n the property labels below) and provides the label-to-field gap, so this needs no wrapper. -->\n <SelectMenu\n class=\"w-fit\"\n size=\"md\"\n [options]=\"languageCodes\"\n [label]=\"languageLabel\"\n [value]=\"currentLanguage()\"\n (valueChange)=\"onLanguageChange($event)\">\n <label class=\"select-label\" for=\"user-profile-language\">{{\n \"userProfile.language\" | transloco\n }}</label>\n <button\n selectTrigger\n type=\"button\"\n id=\"user-profile-language\"\n class=\"select-trigger\">\n <span class=\"select-trigger-label\">{{\n languageLabel(currentLanguage() ?? \"\")\n }}</span>\n <chevron-down-icon class=\"select-chevron\" />\n </button>\n </SelectMenu>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n",
31343
+ template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n",
31333
31344
  dependencies: [
31334
31345
  {
31335
31346
  kind: "directive",
@@ -31422,45 +31433,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
31422
31433
  "size"
31423
31434
  ]
31424
31435
  },
31425
- {
31426
- kind: "component",
31427
- type: SelectComponent,
31428
- selector: "SelectMenu,select-menu",
31429
- inputs: [
31430
- "value",
31431
- "options",
31432
- "placement",
31433
- "size",
31434
- "fieldLabel",
31435
- "aria-label",
31436
- "placeholder",
31437
- "hint",
31438
- "disabled",
31439
- "class",
31440
- "selectionMode",
31441
- "multiple",
31442
- "wrap",
31443
- "key",
31444
- "label",
31445
- "optionDisabled",
31446
- "noOptionsText",
31447
- "contentClass"
31448
- ],
31449
- outputs: ["valueChange", "touch"],
31450
- exportAs: ["select"]
31451
- },
31452
- {
31453
- kind: "directive",
31454
- type: SelectTriggerDirective,
31455
- selector: "[selectTrigger]",
31456
- exportAs: ["selectTrigger"]
31457
- },
31458
- {
31459
- kind: "component",
31460
- type: ChevronDownIcon,
31461
- selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
31462
- inputs: ["class"]
31463
- },
31464
31436
  {
31465
31437
  kind: "directive",
31466
31438
  type: TextareaComponent,
@@ -31530,29 +31502,15 @@ i0.ɵɵngDeclareClassMetadata({
31530
31502
  UserIcon,
31531
31503
  AvatarImageComponent,
31532
31504
  ButtonComponent,
31533
- SelectComponent,
31534
- SelectTriggerDirective,
31535
- ChevronDownIcon,
31536
31505
  TextareaComponent,
31537
31506
  TextareaControlDirective,
31538
31507
  ChangePasswordComponent
31539
31508
  ],
31540
31509
  providers: [provideTranslocoScope("user-profile", "login")],
31541
- template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE — outside the <form> below: it is a user setting applied on the spot, not a\n profile property submitted with the rest. The trigger is projected rather than left to the\n default one so it can carry an id, which is what makes the <label for> name the combobox —\n SelectMenu's own `fieldLabel` renders a <span>, which names nothing.\n The label goes in the `.select-label` slot: the DS wrap paints it (same size and weight as\n the property labels below) and provides the label-to-field gap, so this needs no wrapper. -->\n <SelectMenu\n class=\"w-fit\"\n size=\"md\"\n [options]=\"languageCodes\"\n [label]=\"languageLabel\"\n [value]=\"currentLanguage()\"\n (valueChange)=\"onLanguageChange($event)\">\n <label class=\"select-label\" for=\"user-profile-language\">{{\n \"userProfile.language\" | transloco\n }}</label>\n <button\n selectTrigger\n type=\"button\"\n id=\"user-profile-language\"\n class=\"select-trigger\">\n <span class=\"select-trigger-label\">{{\n languageLabel(currentLanguage() ?? \"\")\n }}</span>\n <chevron-down-icon class=\"select-chevron\" />\n </button>\n </SelectMenu>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n"
31510
+ template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n"
31542
31511
  }]
31543
31512
  }],
31544
- ctorParameters: () => [],
31545
- propDecorators: { currentLanguage: [{
31546
- type: i0.Input,
31547
- args: [{
31548
- isSignal: true,
31549
- alias: "currentLanguage",
31550
- required: false
31551
- }]
31552
- }, {
31553
- type: i0.Output,
31554
- args: ["currentLanguageChange"]
31555
- }] }
31513
+ ctorParameters: () => []
31556
31514
  });
31557
31515
  function toRecord(keys, source) {
31558
31516
  return Object.fromEntries(keys.map((key) => [key, String(source?.[key] ?? "")]));
@@ -31564,92 +31522,732 @@ function splitPropertyId(id) {
31564
31522
  key: id.slice(separator + 1)
31565
31523
  };
31566
31524
  }
31567
- var UserProfileDialog = class UserProfileDialog {
31568
- call = injectCallRef();
31569
- dialog = viewChild.required(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
31570
- constructor() {
31571
- afterNextRender(() => this.dialog().open());
31525
+ var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
31526
+ principalStore = inject(PrincipalStore);
31527
+ appStore = inject(AppStore);
31528
+ client = inject(ATOMIC_CLIENT);
31529
+ userOverride = inject(UserOverrideService);
31530
+ injector = inject(Injector);
31531
+ profileEnabled = computed(() => this.appStore.general()?.features?.userProfile?.enabled ?? false, ...ngDevMode ? [{ debugName: "profileEnabled" }] : /* istanbul ignore next */ []);
31532
+ username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []);
31533
+ allowChangePassword = computed(() => {
31534
+ const useCredentials = this.client.config.authMode?.kind === "credentials";
31535
+ const { allowChangePassword = false } = this.appStore.general()?.features || {};
31536
+ return allowChangePassword && useCredentials && (this.principalStore.editablePartition?.() || false);
31537
+ }, ...ngDevMode ? [{ debugName: "allowChangePassword" }] : /* istanbul ignore next */ []);
31538
+ resetSettings() {
31539
+ ResetUserSettings.call(void 0, { injector: this.injector });
31540
+ }
31541
+ overrideUser() {
31542
+ OverrideUser.call(void 0, { injector: this.injector });
31543
+ }
31544
+ revertOverride() {
31545
+ this.userOverride.override();
31572
31546
  }
31573
31547
  static ɵfac = i0.ɵɵngDeclareFactory({
31574
31548
  minVersion: "12.0.0",
31575
31549
  version: "22.2.1",
31576
31550
  ngImport: i0,
31577
- type: UserProfileDialog,
31551
+ type: SettingsAccountPanelComponent,
31578
31552
  deps: [],
31579
31553
  target: i0.ɵɵFactoryTarget.Component
31580
31554
  });
31581
31555
  static ɵcmp = i0.ɵɵngDeclareComponent({
31582
- minVersion: "17.2.0",
31556
+ minVersion: "17.0.0",
31583
31557
  version: "22.2.1",
31584
- type: UserProfileDialog,
31558
+ type: SettingsAccountPanelComponent,
31585
31559
  isStandalone: true,
31586
- selector: "user-profile-dialog, userprofiledialog, UserProfileDialog",
31587
- providers: [provideTranslocoScope("user-profile")],
31588
- viewQueries: [{
31589
- propertyName: "dialog",
31590
- first: true,
31591
- predicate: DialogComponent,
31592
- descendants: true,
31593
- isSignal: true
31594
- }],
31560
+ selector: "settings-account-panel",
31561
+ providers: [provideTranslocoScope("settings")],
31595
31562
  ngImport: i0,
31596
31563
  template: `
31597
- <div dialog #dialog="dialog" (closed)="call.end($event)">
31598
- <DialogContent size="md">
31599
- <DialogHeader [closeLabel]="'userProfile.close' | transloco">
31600
- <DialogTitle>{{ 'userProfile.title' | transloco }}</DialogTitle>
31601
- </DialogHeader>
31564
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.account.title" | transloco }}</h2>
31565
+ <p class="text-(--font-primary-muted)">{{ "settings.account.description" | transloco }}</p>
31566
+
31567
+ @if (profileEnabled()) {
31568
+ <user-profile-form class="mt-(--spacing-md) min-h-0 flex-1" />
31569
+ } @else if (allowChangePassword()) {
31570
+ <!-- No profile to show: the password is all the account offers, as it was in the user menu. -->
31571
+ <change-password
31572
+ class="mt-(--spacing-md)"
31573
+ [surface]="false"
31574
+ [username]="username()"
31575
+ [redirectAfterSuccess]="false"
31576
+ [redirectAfterCancel]="false" />
31577
+ }
31602
31578
 
31603
- <DialogBody class="flex-1">
31604
- <user-profile-form class="flex flex-1 flex-col min-h-0" />
31605
- </DialogBody>
31606
- </DialogContent>
31579
+ <!-- Account-wide actions, one row each: what it does on the left, the action on the right. -->
31580
+ <div class="mt-(--spacing-md) flex flex-col border-t border-(--stroke-primary-light)">
31581
+ @if (principalStore.isOverridingUser()) {
31582
+ <div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
31583
+ <div>
31584
+ <p id="settings-revert-override-title" class="font-semibold">{{ "settings.account.revertOverride.title" | transloco }}</p>
31585
+ <p id="settings-revert-override-description" class="text-(--font-primary-muted)">{{ "settings.account.revertOverride.description" | transloco }}</p>
31586
+ </div>
31587
+ <button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-revert-override-description" (click)="revertOverride()">
31588
+ {{ "settings.account.revertOverride.action" | transloco }}
31589
+ </button>
31590
+ </div>
31591
+ } @else if (principalStore.allowUserOverride()) {
31592
+ <div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
31593
+ <div>
31594
+ <p id="settings-override-title" class="font-semibold">{{ "settings.account.overrideUser.title" | transloco }}</p>
31595
+ <p id="settings-override-description" class="text-(--font-primary-muted)">{{ "settings.account.overrideUser.description" | transloco }}</p>
31596
+ </div>
31597
+ <button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-override-description" (click)="overrideUser()">
31598
+ {{ "settings.account.overrideUser.action" | transloco }}
31599
+ </button>
31600
+ </div>
31601
+ }
31602
+
31603
+ <div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-md)">
31604
+ <div>
31605
+ <p id="settings-clear-data-title" class="font-semibold">{{ "settings.account.clearData.title" | transloco }}</p>
31606
+ <p id="settings-clear-data-description" class="text-(--font-primary-muted)">{{ "settings.account.clearData.description" | transloco }}</p>
31607
+ </div>
31608
+ <!-- Destructive: the secondary variant re-coloured with the error tokens. Every state of the
31609
+ variant (hover, active, focus) sets its own colours, so each one is overridden here — a
31610
+ plain border/text colour would only win while the button is at rest. -->
31611
+ <button
31612
+ type="button"
31613
+ variant="secondary"
31614
+ class="shrink-0 border-(--stroke-error-base) text-(--font-error-base) enabled:hover:bg-(--bg-error-lightest) enabled:hover:text-(--font-error-base) enabled:active:border-(--stroke-error-base) enabled:active:bg-(--bg-error-lightest) enabled:active:text-(--font-error-base) enabled:active:shadow-[inset_0_0_0_1px_var(--stroke-error-base)] enabled:focus-visible:border-(--stroke-error-base) enabled:focus-visible:text-(--font-error-base)"
31615
+ aria-describedby="settings-clear-data-description"
31616
+ (click)="resetSettings()">
31617
+ {{ "settings.account.clearData.action" | transloco }}
31618
+ </button>
31619
+ </div>
31607
31620
  </div>
31608
31621
  `,
31609
31622
  isInline: true,
31610
31623
  dependencies: [
31611
31624
  {
31612
- kind: "component",
31613
- type: DialogComponent,
31614
- selector: "dialog, [dialog]",
31615
- inputs: ["closeLabel"],
31616
- outputs: ["closed"],
31617
- exportAs: ["dialog"]
31625
+ kind: "directive",
31626
+ type: ButtonComponent,
31627
+ selector: "button",
31628
+ inputs: [
31629
+ "class",
31630
+ "variant",
31631
+ "scheme",
31632
+ "iconOnly",
31633
+ "size"
31634
+ ]
31618
31635
  },
31619
31636
  {
31620
31637
  kind: "component",
31621
- type: DialogHeaderComponent,
31622
- selector: "DialogHeader",
31623
- inputs: ["closeLabel", "class"]
31638
+ type: UserProfileFormComponent,
31639
+ selector: "user-profile-form, UserProfileForm, userprofileform"
31624
31640
  },
31625
31641
  {
31626
- kind: "directive",
31627
- type: DialogContentComponent,
31628
- selector: "DialogContent",
31642
+ kind: "component",
31643
+ type: ChangePasswordComponent,
31644
+ selector: "change-password, ChangePassword, changepassword",
31629
31645
  inputs: [
31630
- "size",
31631
- "labelledby",
31632
- "class"
31633
- ]
31646
+ "username",
31647
+ "alert",
31648
+ "redirectAfterSuccess",
31649
+ "redirectAfterCancel",
31650
+ "surface",
31651
+ "passwords"
31652
+ ],
31653
+ outputs: [
31654
+ "success",
31655
+ "cancel",
31656
+ "passwordsChange"
31657
+ ]
31658
+ },
31659
+ {
31660
+ kind: "pipe",
31661
+ type: TranslocoPipe,
31662
+ name: "transloco"
31663
+ }
31664
+ ]
31665
+ });
31666
+ };
31667
+ i0.ɵɵngDeclareClassMetadata({
31668
+ minVersion: "12.0.0",
31669
+ version: "22.2.1",
31670
+ ngImport: i0,
31671
+ type: SettingsAccountPanelComponent,
31672
+ decorators: [{
31673
+ type: Component,
31674
+ args: [{
31675
+ selector: "settings-account-panel",
31676
+ imports: [
31677
+ TranslocoPipe,
31678
+ ButtonComponent,
31679
+ UserProfileFormComponent,
31680
+ ChangePasswordComponent
31681
+ ],
31682
+ providers: [provideTranslocoScope("settings")],
31683
+ template: `
31684
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.account.title" | transloco }}</h2>
31685
+ <p class="text-(--font-primary-muted)">{{ "settings.account.description" | transloco }}</p>
31686
+
31687
+ @if (profileEnabled()) {
31688
+ <user-profile-form class="mt-(--spacing-md) min-h-0 flex-1" />
31689
+ } @else if (allowChangePassword()) {
31690
+ <!-- No profile to show: the password is all the account offers, as it was in the user menu. -->
31691
+ <change-password
31692
+ class="mt-(--spacing-md)"
31693
+ [surface]="false"
31694
+ [username]="username()"
31695
+ [redirectAfterSuccess]="false"
31696
+ [redirectAfterCancel]="false" />
31697
+ }
31698
+
31699
+ <!-- Account-wide actions, one row each: what it does on the left, the action on the right. -->
31700
+ <div class="mt-(--spacing-md) flex flex-col border-t border-(--stroke-primary-light)">
31701
+ @if (principalStore.isOverridingUser()) {
31702
+ <div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
31703
+ <div>
31704
+ <p id="settings-revert-override-title" class="font-semibold">{{ "settings.account.revertOverride.title" | transloco }}</p>
31705
+ <p id="settings-revert-override-description" class="text-(--font-primary-muted)">{{ "settings.account.revertOverride.description" | transloco }}</p>
31706
+ </div>
31707
+ <button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-revert-override-description" (click)="revertOverride()">
31708
+ {{ "settings.account.revertOverride.action" | transloco }}
31709
+ </button>
31710
+ </div>
31711
+ } @else if (principalStore.allowUserOverride()) {
31712
+ <div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
31713
+ <div>
31714
+ <p id="settings-override-title" class="font-semibold">{{ "settings.account.overrideUser.title" | transloco }}</p>
31715
+ <p id="settings-override-description" class="text-(--font-primary-muted)">{{ "settings.account.overrideUser.description" | transloco }}</p>
31716
+ </div>
31717
+ <button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-override-description" (click)="overrideUser()">
31718
+ {{ "settings.account.overrideUser.action" | transloco }}
31719
+ </button>
31720
+ </div>
31721
+ }
31722
+
31723
+ <div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-md)">
31724
+ <div>
31725
+ <p id="settings-clear-data-title" class="font-semibold">{{ "settings.account.clearData.title" | transloco }}</p>
31726
+ <p id="settings-clear-data-description" class="text-(--font-primary-muted)">{{ "settings.account.clearData.description" | transloco }}</p>
31727
+ </div>
31728
+ <!-- Destructive: the secondary variant re-coloured with the error tokens. Every state of the
31729
+ variant (hover, active, focus) sets its own colours, so each one is overridden here — a
31730
+ plain border/text colour would only win while the button is at rest. -->
31731
+ <button
31732
+ type="button"
31733
+ variant="secondary"
31734
+ class="shrink-0 border-(--stroke-error-base) text-(--font-error-base) enabled:hover:bg-(--bg-error-lightest) enabled:hover:text-(--font-error-base) enabled:active:border-(--stroke-error-base) enabled:active:bg-(--bg-error-lightest) enabled:active:text-(--font-error-base) enabled:active:shadow-[inset_0_0_0_1px_var(--stroke-error-base)] enabled:focus-visible:border-(--stroke-error-base) enabled:focus-visible:text-(--font-error-base)"
31735
+ aria-describedby="settings-clear-data-description"
31736
+ (click)="resetSettings()">
31737
+ {{ "settings.account.clearData.action" | transloco }}
31738
+ </button>
31739
+ </div>
31740
+ </div>
31741
+ `
31742
+ }]
31743
+ }]
31744
+ });
31745
+ const THEMES$1 = [
31746
+ "light",
31747
+ "dark",
31748
+ "system"
31749
+ ];
31750
+ const LANGUAGES = [
31751
+ "en",
31752
+ "fr",
31753
+ "de"
31754
+ ];
31755
+ const isTheme = (value) => THEMES$1.includes(value);
31756
+ const isSupportedLanguage = (value) => LANGUAGES.includes(value);
31757
+ var SettingsAppearancePanelComponent = class SettingsAppearancePanelComponent {
31758
+ userSettingsStore = inject(UserSettingsStore);
31759
+ transloco = inject(TranslocoService);
31760
+ document = inject(DOCUMENT);
31761
+ lang = toSignal(this.transloco.langChanges$, { initialValue: this.transloco.getActiveLang() });
31762
+ loadedTranslations = toSignal(this.transloco.events$.pipe(filter((event) => event.type === "translationLoadSuccess"), scan((count) => count + 1, 0)), { initialValue: 0 });
31763
+ themeCodes = [...THEMES$1];
31764
+ languageCodes = [...LANGUAGES];
31765
+ themeIcons = {
31766
+ light: SunBrightIcon,
31767
+ dark: MoonIcon,
31768
+ system: DesktopIcon
31769
+ };
31770
+ languageIcons = {
31771
+ en: FlagEnglishIcon,
31772
+ fr: FlagFrenchIcon,
31773
+ de: FlagGermanIcon
31774
+ };
31775
+ languageLabels = {
31776
+ en: "English",
31777
+ fr: "Français",
31778
+ de: "Deutsch"
31779
+ };
31780
+ currentTheme = computed(() => this.userSettingsStore.userTheme() ?? "system", ...ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []);
31781
+ currentLanguage = computed(() => this.lang(), ...ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []);
31782
+ themeLabel = (code) => {
31783
+ this.lang();
31784
+ this.loadedTranslations();
31785
+ return isTheme(code) ? this.transloco.translate(`settings.appearance.${code}Mode`) : "";
31786
+ };
31787
+ languageLabel = (code) => isSupportedLanguage(code) ? this.languageLabels[code] : code ?? "";
31788
+ themeIcon(code) {
31789
+ return isTheme(code) ? this.themeIcons[code] : null;
31790
+ }
31791
+ languageIcon(code) {
31792
+ return isSupportedLanguage(code) ? this.languageIcons[code] : null;
31793
+ }
31794
+ async onThemeChange(code) {
31795
+ if (!isTheme(code)) return;
31796
+ try {
31797
+ await this.userSettingsStore.setUserTheme(code);
31798
+ } catch (err) {
31799
+ error("set user theme failed", err);
31800
+ return;
31801
+ }
31802
+ const dark = code === "dark" || code === "system" && window.matchMedia?.("(prefers-color-scheme: dark)").matches;
31803
+ this.document.documentElement.classList.toggle("dark", dark);
31804
+ }
31805
+ onLanguageChange(code) {
31806
+ if (!isSupportedLanguage(code)) return;
31807
+ this.userSettingsStore.updateLanguage(code).catch((err) => error("update language failed", err));
31808
+ if (this.transloco.getActiveLang() !== code) this.transloco.setActiveLang(code);
31809
+ }
31810
+ static ɵfac = i0.ɵɵngDeclareFactory({
31811
+ minVersion: "12.0.0",
31812
+ version: "22.2.1",
31813
+ ngImport: i0,
31814
+ type: SettingsAppearancePanelComponent,
31815
+ deps: [],
31816
+ target: i0.ɵɵFactoryTarget.Component
31817
+ });
31818
+ static ɵcmp = i0.ɵɵngDeclareComponent({
31819
+ minVersion: "14.0.0",
31820
+ version: "22.2.1",
31821
+ type: SettingsAppearancePanelComponent,
31822
+ isStandalone: true,
31823
+ selector: "settings-appearance-panel",
31824
+ providers: [provideTranslocoScope("settings")],
31825
+ ngImport: i0,
31826
+ template: `
31827
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.appearance.title" | transloco }}</h2>
31828
+ <p class="text-(--font-primary-muted)">{{ "settings.appearance.description" | transloco }}</p>
31829
+
31830
+ <div class="mt-(--spacing-lg) flex items-start justify-between gap-(--spacing-md)">
31831
+ <div>
31832
+ <label id="settings-theme-label" class="font-semibold" for="settings-theme">{{ "settings.appearance.theme" | transloco }}</label>
31833
+ <p id="settings-theme-description" class="text-(--font-primary-muted)">{{ "settings.appearance.themeDescription" | transloco }}</p>
31834
+ </div>
31835
+ <SelectMenu class="w-fit shrink-0" size="md" [options]="themeCodes" [label]="themeLabel" [value]="currentTheme()" (valueChange)="onThemeChange($event)">
31836
+ <button selectTrigger type="button" id="settings-theme" class="select-trigger" aria-describedby="settings-theme-description">
31837
+ <ng-container *ngComponentOutlet="themeIcon(currentTheme())" />
31838
+ <span class="select-trigger-label">{{ themeLabel(currentTheme()) }}</span>
31839
+ <chevron-down-icon class="select-chevron" />
31840
+ </button>
31841
+ <!-- The default option rendering is the label alone: the icon needs its own template. -->
31842
+ <ng-template #selectOption let-option>
31843
+ <span class="flex items-center gap-(--spacing-xs)">
31844
+ <ng-container *ngComponentOutlet="themeIcon(option)" />
31845
+ {{ themeLabel(option) }}
31846
+ </span>
31847
+ </ng-template>
31848
+ </SelectMenu>
31849
+ </div>
31850
+
31851
+ <hr class="my-(--spacing-md) border-(--stroke-primary-light)" />
31852
+
31853
+ <div class="flex items-start justify-between gap-(--spacing-md)">
31854
+ <div>
31855
+ <label id="settings-language-label" class="font-semibold" for="settings-language">{{ "settings.appearance.language" | transloco }}</label>
31856
+ <p id="settings-language-description" class="text-(--font-primary-muted)">{{ "settings.appearance.languageDescription" | transloco }}</p>
31857
+ </div>
31858
+ <SelectMenu
31859
+ class="w-fit shrink-0"
31860
+ size="md"
31861
+ [options]="languageCodes"
31862
+ [label]="languageLabel"
31863
+ [value]="currentLanguage()"
31864
+ (valueChange)="onLanguageChange($event)">
31865
+ <button selectTrigger type="button" id="settings-language" class="select-trigger" aria-describedby="settings-language-description">
31866
+ <ng-container *ngComponentOutlet="languageIcon(currentLanguage())" />
31867
+ <span class="select-trigger-label">{{ languageLabel(currentLanguage()) }}</span>
31868
+ <chevron-down-icon class="select-chevron" />
31869
+ </button>
31870
+ <ng-template #selectOption let-option>
31871
+ <span class="flex items-center gap-(--spacing-xs)">
31872
+ <ng-container *ngComponentOutlet="languageIcon(option)" />
31873
+ {{ languageLabel(option) }}
31874
+ </span>
31875
+ </ng-template>
31876
+ </SelectMenu>
31877
+ </div>
31878
+ `,
31879
+ isInline: true,
31880
+ dependencies: [
31881
+ {
31882
+ kind: "directive",
31883
+ type: NgComponentOutlet,
31884
+ selector: "[ngComponentOutlet]",
31885
+ inputs: [
31886
+ "ngComponentOutlet",
31887
+ "ngComponentOutletInputs",
31888
+ "ngComponentOutletInjector",
31889
+ "ngComponentOutletEnvironmentInjector",
31890
+ "ngComponentOutletContent",
31891
+ "ngComponentOutletNgModule"
31892
+ ],
31893
+ exportAs: ["ngComponentOutlet"]
31894
+ },
31895
+ {
31896
+ kind: "component",
31897
+ type: SelectComponent,
31898
+ selector: "SelectMenu,select-menu",
31899
+ inputs: [
31900
+ "value",
31901
+ "options",
31902
+ "placement",
31903
+ "size",
31904
+ "fieldLabel",
31905
+ "aria-label",
31906
+ "placeholder",
31907
+ "hint",
31908
+ "disabled",
31909
+ "class",
31910
+ "selectionMode",
31911
+ "multiple",
31912
+ "wrap",
31913
+ "key",
31914
+ "label",
31915
+ "optionDisabled",
31916
+ "noOptionsText",
31917
+ "contentClass"
31918
+ ],
31919
+ outputs: ["valueChange", "touch"],
31920
+ exportAs: ["select"]
31634
31921
  },
31635
31922
  {
31636
31923
  kind: "directive",
31637
- type: DialogBodyComponent,
31638
- selector: "DialogBody",
31924
+ type: SelectTriggerDirective,
31925
+ selector: "[selectTrigger]",
31926
+ exportAs: ["selectTrigger"]
31927
+ },
31928
+ {
31929
+ kind: "component",
31930
+ type: ChevronDownIcon,
31931
+ selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
31639
31932
  inputs: ["class"]
31640
31933
  },
31934
+ {
31935
+ kind: "pipe",
31936
+ type: TranslocoPipe,
31937
+ name: "transloco"
31938
+ }
31939
+ ]
31940
+ });
31941
+ };
31942
+ i0.ɵɵngDeclareClassMetadata({
31943
+ minVersion: "12.0.0",
31944
+ version: "22.2.1",
31945
+ ngImport: i0,
31946
+ type: SettingsAppearancePanelComponent,
31947
+ decorators: [{
31948
+ type: Component,
31949
+ args: [{
31950
+ selector: "settings-appearance-panel",
31951
+ imports: [
31952
+ NgComponentOutlet,
31953
+ TranslocoPipe,
31954
+ SelectComponent,
31955
+ SelectTriggerDirective,
31956
+ ChevronDownIcon
31957
+ ],
31958
+ providers: [provideTranslocoScope("settings")],
31959
+ template: `
31960
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.appearance.title" | transloco }}</h2>
31961
+ <p class="text-(--font-primary-muted)">{{ "settings.appearance.description" | transloco }}</p>
31962
+
31963
+ <div class="mt-(--spacing-lg) flex items-start justify-between gap-(--spacing-md)">
31964
+ <div>
31965
+ <label id="settings-theme-label" class="font-semibold" for="settings-theme">{{ "settings.appearance.theme" | transloco }}</label>
31966
+ <p id="settings-theme-description" class="text-(--font-primary-muted)">{{ "settings.appearance.themeDescription" | transloco }}</p>
31967
+ </div>
31968
+ <SelectMenu class="w-fit shrink-0" size="md" [options]="themeCodes" [label]="themeLabel" [value]="currentTheme()" (valueChange)="onThemeChange($event)">
31969
+ <button selectTrigger type="button" id="settings-theme" class="select-trigger" aria-describedby="settings-theme-description">
31970
+ <ng-container *ngComponentOutlet="themeIcon(currentTheme())" />
31971
+ <span class="select-trigger-label">{{ themeLabel(currentTheme()) }}</span>
31972
+ <chevron-down-icon class="select-chevron" />
31973
+ </button>
31974
+ <!-- The default option rendering is the label alone: the icon needs its own template. -->
31975
+ <ng-template #selectOption let-option>
31976
+ <span class="flex items-center gap-(--spacing-xs)">
31977
+ <ng-container *ngComponentOutlet="themeIcon(option)" />
31978
+ {{ themeLabel(option) }}
31979
+ </span>
31980
+ </ng-template>
31981
+ </SelectMenu>
31982
+ </div>
31983
+
31984
+ <hr class="my-(--spacing-md) border-(--stroke-primary-light)" />
31985
+
31986
+ <div class="flex items-start justify-between gap-(--spacing-md)">
31987
+ <div>
31988
+ <label id="settings-language-label" class="font-semibold" for="settings-language">{{ "settings.appearance.language" | transloco }}</label>
31989
+ <p id="settings-language-description" class="text-(--font-primary-muted)">{{ "settings.appearance.languageDescription" | transloco }}</p>
31990
+ </div>
31991
+ <SelectMenu
31992
+ class="w-fit shrink-0"
31993
+ size="md"
31994
+ [options]="languageCodes"
31995
+ [label]="languageLabel"
31996
+ [value]="currentLanguage()"
31997
+ (valueChange)="onLanguageChange($event)">
31998
+ <button selectTrigger type="button" id="settings-language" class="select-trigger" aria-describedby="settings-language-description">
31999
+ <ng-container *ngComponentOutlet="languageIcon(currentLanguage())" />
32000
+ <span class="select-trigger-label">{{ languageLabel(currentLanguage()) }}</span>
32001
+ <chevron-down-icon class="select-chevron" />
32002
+ </button>
32003
+ <ng-template #selectOption let-option>
32004
+ <span class="flex items-center gap-(--spacing-xs)">
32005
+ <ng-container *ngComponentOutlet="languageIcon(option)" />
32006
+ {{ languageLabel(option) }}
32007
+ </span>
32008
+ </ng-template>
32009
+ </SelectMenu>
32010
+ </div>
32011
+ `
32012
+ }]
32013
+ }]
32014
+ });
32015
+ var SettingsShortcutsPanelComponent = class SettingsShortcutsPanelComponent {
32016
+ static ɵfac = i0.ɵɵngDeclareFactory({
32017
+ minVersion: "12.0.0",
32018
+ version: "22.2.1",
32019
+ ngImport: i0,
32020
+ type: SettingsShortcutsPanelComponent,
32021
+ deps: [],
32022
+ target: i0.ɵɵFactoryTarget.Component
32023
+ });
32024
+ static ɵcmp = i0.ɵɵngDeclareComponent({
32025
+ minVersion: "14.0.0",
32026
+ version: "22.2.1",
32027
+ type: SettingsShortcutsPanelComponent,
32028
+ isStandalone: true,
32029
+ selector: "settings-shortcuts-panel",
32030
+ providers: [provideTranslocoScope("settings")],
32031
+ ngImport: i0,
32032
+ template: `
32033
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.shortcuts.title" | transloco }}</h2>
32034
+ <p class="mb-(--spacing-md) text-(--font-primary-muted)">{{ "settings.shortcuts.description" | transloco }}</p>
32035
+ <keyboard-shortcuts-list />
32036
+ `,
32037
+ isInline: true,
32038
+ dependencies: [{
32039
+ kind: "component",
32040
+ type: KeyboardShortcutsListComponent,
32041
+ selector: "keyboard-shortcuts-list"
32042
+ }, {
32043
+ kind: "pipe",
32044
+ type: TranslocoPipe,
32045
+ name: "transloco"
32046
+ }]
32047
+ });
32048
+ };
32049
+ i0.ɵɵngDeclareClassMetadata({
32050
+ minVersion: "12.0.0",
32051
+ version: "22.2.1",
32052
+ ngImport: i0,
32053
+ type: SettingsShortcutsPanelComponent,
32054
+ decorators: [{
32055
+ type: Component,
32056
+ args: [{
32057
+ selector: "settings-shortcuts-panel",
32058
+ imports: [TranslocoPipe, KeyboardShortcutsListComponent],
32059
+ providers: [provideTranslocoScope("settings")],
32060
+ template: `
32061
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.shortcuts.title" | transloco }}</h2>
32062
+ <p class="mb-(--spacing-md) text-(--font-primary-muted)">{{ "settings.shortcuts.description" | transloco }}</p>
32063
+ <keyboard-shortcuts-list />
32064
+ `
32065
+ }]
32066
+ }]
32067
+ });
32068
+ let active;
32069
+ var SettingsDialogComponent = class SettingsDialogComponent {
32070
+ call = injectCallRef();
32071
+ dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
32072
+ items = [
32073
+ {
32074
+ id: "account",
32075
+ labelKey: "settings.account.nav"
32076
+ },
32077
+ {
32078
+ id: "appearance",
32079
+ labelKey: "settings.appearance.nav"
32080
+ },
32081
+ {
32082
+ id: "shortcuts",
32083
+ labelKey: "settings.shortcuts.nav"
32084
+ }
32085
+ ];
32086
+ panel = signal(this.call.props()?.panel ?? "account", ...ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []);
32087
+ duplicate = active !== void 0;
32088
+ constructor() {
32089
+ if (active) {
32090
+ active.panel.set(this.panel());
32091
+ afterNextRender(() => this.call.end("dialog-close"));
32092
+ return;
32093
+ }
32094
+ active = this;
32095
+ inject(DestroyRef).onDestroy(() => {
32096
+ if (active === this) active = void 0;
32097
+ });
32098
+ afterNextRender(() => this.dialog()?.open());
32099
+ }
32100
+ static ɵfac = i0.ɵɵngDeclareFactory({
32101
+ minVersion: "12.0.0",
32102
+ version: "22.2.1",
32103
+ ngImport: i0,
32104
+ type: SettingsDialogComponent,
32105
+ deps: [],
32106
+ target: i0.ɵɵFactoryTarget.Component
32107
+ });
32108
+ static ɵcmp = i0.ɵɵngDeclareComponent({
32109
+ minVersion: "17.0.0",
32110
+ version: "22.2.1",
32111
+ type: SettingsDialogComponent,
32112
+ isStandalone: true,
32113
+ selector: "settings-dialog",
32114
+ providers: [provideTranslocoScope("settings")],
32115
+ viewQueries: [{
32116
+ propertyName: "dialog",
32117
+ first: true,
32118
+ predicate: DialogComponent,
32119
+ descendants: true,
32120
+ isSignal: true
32121
+ }],
32122
+ ngImport: i0,
32123
+ template: `
32124
+ @if (!duplicate) {
32125
+ <div dialog #dialog="dialog" [closeLabel]="'settings.close' | transloco" (closed)="call.end($event)">
32126
+ <DialogContent size="xl" class="h-[min(48rem,calc(100dvh-2rem))] flex-row! items-stretch!">
32127
+ <div class="flex w-64 shrink-0 flex-col gap-(--spacing-3xs) bg-(--layer-page-on-layer) p-(--spacing-sm)">
32128
+ <button
32129
+ type="button"
32130
+ variant="none"
32131
+ class="modal-close mb-(--spacing-sm)"
32132
+ [attr.aria-label]="'settings.close' | transloco"
32133
+ (click)="dialog.close()">
32134
+ <XMarkIcon aria-hidden="true" />
32135
+ </button>
32136
+
32137
+ <!-- Panels are a navigation, not a tablist: each one is a button, the current one is
32138
+ flagged with aria-current, which is what a screen reader reads out. -->
32139
+ <nav [attr.aria-label]="'settings.title' | transloco" class="flex flex-col gap-(--spacing-3xs)">
32140
+ @for (item of items; track item.id) {
32141
+ <button
32142
+ type="button"
32143
+ variant="none"
32144
+ class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
32145
+ [attr.aria-current]="panel() === item.id ? 'page' : null"
32146
+ (click)="panel.set(item.id)">
32147
+ @switch (item.id) {
32148
+ @case ("account") {
32149
+ <user-icon aria-hidden="true" />
32150
+ }
32151
+ @case ("appearance") {
32152
+ <palette-icon aria-hidden="true" />
32153
+ }
32154
+ @case ("shortcuts") {
32155
+ <keyboard-icon aria-hidden="true" />
32156
+ }
32157
+ }
32158
+ {{ item.labelKey | transloco }}
32159
+ </button>
32160
+ }
32161
+ </nav>
32162
+ </div>
32163
+
32164
+ <div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
32165
+ @switch (panel()) {
32166
+ @case ("account") {
32167
+ <settings-account-panel class="flex min-h-0 flex-1 flex-col" />
32168
+ }
32169
+ @case ("appearance") {
32170
+ <settings-appearance-panel />
32171
+ }
32172
+ @case ("shortcuts") {
32173
+ <settings-shortcuts-panel />
32174
+ }
32175
+ }
32176
+ </div>
32177
+ </DialogContent>
32178
+ </div>
32179
+ }
32180
+ `,
32181
+ isInline: true,
32182
+ dependencies: [
32183
+ {
32184
+ kind: "directive",
32185
+ type: ButtonComponent,
32186
+ selector: "button",
32187
+ inputs: [
32188
+ "class",
32189
+ "variant",
32190
+ "scheme",
32191
+ "iconOnly",
32192
+ "size"
32193
+ ]
32194
+ },
31641
32195
  {
31642
32196
  kind: "component",
31643
- type: UserProfileFormComponent,
31644
- selector: "user-profile-form, UserProfileForm, userprofileform",
31645
- inputs: ["currentLanguage"],
31646
- outputs: ["currentLanguageChange"]
32197
+ type: DialogComponent,
32198
+ selector: "dialog, [dialog]",
32199
+ inputs: ["closeLabel"],
32200
+ outputs: ["closed"],
32201
+ exportAs: ["dialog"]
31647
32202
  },
31648
32203
  {
31649
32204
  kind: "directive",
31650
- type: DialogTitleComponent,
31651
- selector: "DialogTitle",
31652
- inputs: ["id", "class"]
32205
+ type: DialogContentComponent,
32206
+ selector: "DialogContent",
32207
+ inputs: [
32208
+ "size",
32209
+ "labelledby",
32210
+ "class"
32211
+ ]
32212
+ },
32213
+ {
32214
+ kind: "component",
32215
+ type: UserIcon,
32216
+ selector: "user, user-icon, User, UserIcon",
32217
+ inputs: ["class"]
32218
+ },
32219
+ {
32220
+ kind: "component",
32221
+ type: PaletteIcon,
32222
+ selector: "palette, palette-icon, Palette, PaletteIcon",
32223
+ inputs: ["class"]
32224
+ },
32225
+ {
32226
+ kind: "component",
32227
+ type: KeyboardIcon,
32228
+ selector: "keyboard, keyboard-icon, Keyboard, KeyboardIcon",
32229
+ inputs: ["class"]
32230
+ },
32231
+ {
32232
+ kind: "component",
32233
+ type: XMarkIcon,
32234
+ selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
32235
+ inputs: ["class"]
32236
+ },
32237
+ {
32238
+ kind: "component",
32239
+ type: SettingsAccountPanelComponent,
32240
+ selector: "settings-account-panel"
32241
+ },
32242
+ {
32243
+ kind: "component",
32244
+ type: SettingsAppearancePanelComponent,
32245
+ selector: "settings-appearance-panel"
32246
+ },
32247
+ {
32248
+ kind: "component",
32249
+ type: SettingsShortcutsPanelComponent,
32250
+ selector: "settings-shortcuts-panel"
31653
32251
  },
31654
32252
  {
31655
32253
  kind: "pipe",
@@ -31663,33 +32261,82 @@ i0.ɵɵngDeclareClassMetadata({
31663
32261
  minVersion: "12.0.0",
31664
32262
  version: "22.2.1",
31665
32263
  ngImport: i0,
31666
- type: UserProfileDialog,
32264
+ type: SettingsDialogComponent,
31667
32265
  decorators: [{
31668
32266
  type: Component,
31669
32267
  args: [{
31670
- selector: "user-profile-dialog, userprofiledialog, UserProfileDialog",
32268
+ selector: "settings-dialog",
31671
32269
  imports: [
32270
+ TranslocoPipe,
32271
+ ButtonComponent,
31672
32272
  DialogComponent,
31673
- DialogHeaderComponent,
31674
32273
  DialogContentComponent,
31675
- DialogBodyComponent,
31676
- UserProfileFormComponent,
31677
- DialogTitleComponent,
31678
- TranslocoPipe
32274
+ UserIcon,
32275
+ PaletteIcon,
32276
+ KeyboardIcon,
32277
+ XMarkIcon,
32278
+ SettingsAccountPanelComponent,
32279
+ SettingsAppearancePanelComponent,
32280
+ SettingsShortcutsPanelComponent
31679
32281
  ],
31680
- providers: [provideTranslocoScope("user-profile")],
32282
+ providers: [provideTranslocoScope("settings")],
31681
32283
  template: `
31682
- <div dialog #dialog="dialog" (closed)="call.end($event)">
31683
- <DialogContent size="md">
31684
- <DialogHeader [closeLabel]="'userProfile.close' | transloco">
31685
- <DialogTitle>{{ 'userProfile.title' | transloco }}</DialogTitle>
31686
- </DialogHeader>
32284
+ @if (!duplicate) {
32285
+ <div dialog #dialog="dialog" [closeLabel]="'settings.close' | transloco" (closed)="call.end($event)">
32286
+ <DialogContent size="xl" class="h-[min(48rem,calc(100dvh-2rem))] flex-row! items-stretch!">
32287
+ <div class="flex w-64 shrink-0 flex-col gap-(--spacing-3xs) bg-(--layer-page-on-layer) p-(--spacing-sm)">
32288
+ <button
32289
+ type="button"
32290
+ variant="none"
32291
+ class="modal-close mb-(--spacing-sm)"
32292
+ [attr.aria-label]="'settings.close' | transloco"
32293
+ (click)="dialog.close()">
32294
+ <XMarkIcon aria-hidden="true" />
32295
+ </button>
31687
32296
 
31688
- <DialogBody class="flex-1">
31689
- <user-profile-form class="flex flex-1 flex-col min-h-0" />
31690
- </DialogBody>
31691
- </DialogContent>
31692
- </div>
32297
+ <!-- Panels are a navigation, not a tablist: each one is a button, the current one is
32298
+ flagged with aria-current, which is what a screen reader reads out. -->
32299
+ <nav [attr.aria-label]="'settings.title' | transloco" class="flex flex-col gap-(--spacing-3xs)">
32300
+ @for (item of items; track item.id) {
32301
+ <button
32302
+ type="button"
32303
+ variant="none"
32304
+ class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
32305
+ [attr.aria-current]="panel() === item.id ? 'page' : null"
32306
+ (click)="panel.set(item.id)">
32307
+ @switch (item.id) {
32308
+ @case ("account") {
32309
+ <user-icon aria-hidden="true" />
32310
+ }
32311
+ @case ("appearance") {
32312
+ <palette-icon aria-hidden="true" />
32313
+ }
32314
+ @case ("shortcuts") {
32315
+ <keyboard-icon aria-hidden="true" />
32316
+ }
32317
+ }
32318
+ {{ item.labelKey | transloco }}
32319
+ </button>
32320
+ }
32321
+ </nav>
32322
+ </div>
32323
+
32324
+ <div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
32325
+ @switch (panel()) {
32326
+ @case ("account") {
32327
+ <settings-account-panel class="flex min-h-0 flex-1 flex-col" />
32328
+ }
32329
+ @case ("appearance") {
32330
+ <settings-appearance-panel />
32331
+ }
32332
+ @case ("shortcuts") {
32333
+ <settings-shortcuts-panel />
32334
+ }
32335
+ }
32336
+ </div>
32337
+ </DialogContent>
32338
+ </div>
32339
+ }
31693
32340
  `
31694
32341
  }]
31695
32342
  }],
@@ -31699,7 +32346,7 @@ i0.ɵɵngDeclareClassMetadata({
31699
32346
  args: [i0.forwardRef(() => DialogComponent), { isSignal: true }]
31700
32347
  }] }
31701
32348
  });
31702
- const UserProfile = createCallable(UserProfileDialog);
32349
+ const Settings = createCallable(SettingsDialogComponent);
31703
32350
  const auditInterceptorFn = (request, next) => {
31704
32351
  if (!request.url.includes("api/v1")) return next(request);
31705
32352
  if (isJsonable(request.body) && !(request.body instanceof HttpParams)) addAuditAdditionalInfo(request.body);
@@ -32150,6 +32797,6 @@ function provideSessionExpiry(options = {}) {
32150
32797
  const queryNameResolver = () => {
32151
32798
  return inject(AppStore).getDefaultQuery()?.name || "";
32152
32799
  };
32153
- export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, 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, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfile, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
32800
+ export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, 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, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, SettingsAppearancePanelComponent, SettingsDialogComponent, SettingsShortcutsPanelComponent, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
32154
32801
 
32155
32802
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map