@dereekb/dbx-firebase 13.5.0 → 13.6.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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { inject, Injectable, provideAppInitializer, makeEnvironmentProviders, InjectionToken, Component, model, computed, ChangeDetectionStrategy, signal, Directive, EventEmitter, input, output, Injector, viewChild, HostListener, NgModule, ElementRef, forwardRef, effect, DestroyRef, Inject, Optional, Pipe } from '@angular/core';
3
3
  import { DbxAnalyticsService } from '@dereekb/dbx-analytics';
4
4
  import { asObservable, timeoutStartWith, filterMaybe, isNot, SubscriptionObject, lazyFrom, switchMapWhileTrue, loadingStateFromObs, distinctUntilKeysChange, cleanupDestroyable, iterationHasNextAndCanLoadMore, pageItemAccumulatorCurrentPage, accumulatorFlattenPageListLoadingState, useFirst, itemAccumulatorNextPageUntilResultsCount, iteratorNextPageUntilPage, iteratorNextPageUntilMaxPageLoadLimit, pageLoadingStateFromObs, useAsObservable, filterMaybeArray, mapEachAsync, invertObservableDecision, filterItemsWithObservableDecision, switchMapMaybe, beginLoading, mapLoadingStateValueWithOperator, valueFromFinishedLoadingState, skipInitialMaybe, distinctUntilModelKeyChange, successResult, errorResult, isLoadingStateLoading, cleanup, mapLoadingState, throwErrorFromLoadingStateError, maybeValueFromObservableOrValue, distinctUntilHasDifferentValues, MultiSubscriptionObject, startWithBeginLoading, skipAllInitialMaybe } from '@dereekb/rxjs';
5
- import { switchMap, of, shareReplay, map, distinctUntilChanged, EMPTY, catchError, firstValueFrom, BehaviorSubject, combineLatest, first, from, tap, interval, exhaustMap, filter, take, startWith, Subject, throttleTime, NEVER, defaultIfEmpty, combineLatestWith, mergeMap, Observable } from 'rxjs';
5
+ import { switchMap, of, shareReplay, map, Subject, merge, distinctUntilChanged, EMPTY, catchError, firstValueFrom, tap, BehaviorSubject, combineLatest, first, from, interval, exhaustMap, filter, take, startWith, throttleTime, NEVER, defaultIfEmpty, combineLatestWith, mergeMap, Observable } from 'rxjs';
6
6
  import * as i2 from '@dereekb/dbx-core';
7
7
  import { loggedInObsFromIsLoggedIn, loggedOutObsFromIsLoggedIn, authUserIdentifier, DbxInjectionContext, DBX_INJECTION_COMPONENT_DATA, DbxInjectionComponent, AbstractForwardDbxInjectionContextDirective, DbxInjectionContextDirective, DbxAuthService, DbxActionButtonDirective, completeOnDestroy, cleanSubscription, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionAutoTriggerDirective, clean, cleanLoadingContext, provideDbxRouteModelIdDirectiveDelegate, provideDbxRouteModelKeyDirectiveDelegate, AbstractIfDirective, LockSetComponentStore, newWithInjector, CutTextPipe, DbxActionContextStoreSourceInstance, DbxActionHandlerInstance, cleanSubscriptionWithLockSet, SimpleStorageAccessorFactory, dbxRouteParamReaderInstance, DbxRouteParamDefaultRedirectInstance } from '@dereekb/dbx-core';
8
8
  import { Auth, authState, idToken, signInWithPopup, linkWithPopup, linkWithCredential, unlink, createUserWithEmailAndPassword, signInWithEmailAndPassword, signInAnonymously, reauthenticateWithPopup, OAuthProvider, FacebookAuthProvider, GithubAuthProvider, GoogleAuthProvider, TwitterAuthProvider, provideAuth, getAuth, connectAuthEmulator } from '@angular/fire/auth';
@@ -141,7 +141,14 @@ class DbxFirebaseAuthService {
141
141
  firebaseAuth = inject(Auth);
142
142
  delegate = inject(DbxFirebaseAuthServiceDelegate, { optional: true }) ?? DEFAULT_DBX_FIREBASE_AUTH_SERVICE_DELEGATE;
143
143
  _authState$ = authState(this.firebaseAuth);
144
- currentAuthUser$ = this._authState$.pipe(timeoutStartWith(null, 1000), distinctUntilChanged(), shareReplay(1));
144
+ /**
145
+ * Subject that triggers a re-emission of the current auth user.
146
+ *
147
+ * Useful after operations that mutate the {@link User} object in place (e.g., linking/unlinking providers)
148
+ * without triggering a new {@link authState} emission.
149
+ */
150
+ _authUpdate$ = new Subject();
151
+ currentAuthUser$ = merge(this._authState$, this._authUpdate$.pipe(map(() => this.firebaseAuth.currentUser))).pipe(timeoutStartWith(null, 1000), shareReplay(1));
145
152
  currentAuthUserInfo$ = this.currentAuthUser$.pipe(map((x) => (x ? authUserInfoFromAuthUser(x) : undefined)));
146
153
  authUser$ = this.currentAuthUser$.pipe(filterMaybe());
147
154
  authUserInfo$ = this.authUser$.pipe(map(authUserInfoFromAuthUser));
@@ -250,7 +257,7 @@ class DbxFirebaseAuthService {
250
257
  else {
251
258
  throw new Error('User is not logged in currently.');
252
259
  }
253
- })));
260
+ }), tap(() => this._authUpdate$.next())));
254
261
  }
255
262
  /**
256
263
  * Links a credential to the current user. Useful for merging accounts
@@ -272,7 +279,7 @@ class DbxFirebaseAuthService {
272
279
  else {
273
280
  throw new Error('User is not logged in currently.');
274
281
  }
275
- })));
282
+ }), tap(() => this._authUpdate$.next())));
276
283
  }
277
284
  /**
278
285
  * Unlinks an authentication provider from the current user.
@@ -293,7 +300,7 @@ class DbxFirebaseAuthService {
293
300
  else {
294
301
  throw new Error('User is not logged in currently.');
295
302
  }
296
- })));
303
+ }), tap(() => this._authUpdate$.next())));
297
304
  }
298
305
  registerWithEmailAndPassword(email, password) {
299
306
  return createUserWithEmailAndPassword(this.firebaseAuth, email, password);
@@ -726,6 +733,73 @@ const OAUTH_FIREBASE_LOGIN_METHOD_CATEGORY = 'oauth';
726
733
  class DbxFirebaseLoginContext extends DbxInjectionContext {
727
734
  }
728
735
 
736
+ /**
737
+ * Map of Firebase provider IDs to known login method types.
738
+ *
739
+ * @example
740
+ * ```ts
741
+ * FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP['google.com']; // 'google'
742
+ * ```
743
+ */
744
+ const FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP = {
745
+ 'google.com': 'google',
746
+ 'facebook.com': 'facebook',
747
+ 'github.com': 'github',
748
+ 'twitter.com': 'twitter',
749
+ 'apple.com': 'apple',
750
+ 'microsoft.com': 'microsoft',
751
+ phone: 'phone',
752
+ password: 'email'
753
+ };
754
+ /**
755
+ * Map of known login method types to Firebase provider IDs.
756
+ *
757
+ * @example
758
+ * ```ts
759
+ * LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP['google']; // 'google.com'
760
+ * ```
761
+ */
762
+ const LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP = {
763
+ google: 'google.com',
764
+ facebook: 'facebook.com',
765
+ github: 'github.com',
766
+ twitter: 'twitter.com',
767
+ apple: 'apple.com',
768
+ microsoft: 'microsoft.com',
769
+ phone: 'phone',
770
+ email: 'password'
771
+ };
772
+ /**
773
+ * Converts a Firebase provider ID (e.g., 'google.com') to its corresponding login method type (e.g., 'google').
774
+ *
775
+ * @param providerId - The Firebase provider ID.
776
+ * @returns The matching login method type, or undefined if unknown.
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * firebaseProviderIdToLoginMethodType('google.com'); // 'google'
781
+ * firebaseProviderIdToLoginMethodType('unknown.com'); // undefined
782
+ * ```
783
+ */
784
+ function firebaseProviderIdToLoginMethodType(providerId) {
785
+ return FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP[providerId];
786
+ }
787
+ /**
788
+ * Converts a login method type (e.g., 'google') to its corresponding Firebase provider ID (e.g., 'google.com').
789
+ *
790
+ * @param type - The login method type.
791
+ * @returns The matching Firebase provider ID, or undefined if unknown.
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * loginMethodTypeToFirebaseProviderId('google'); // 'google.com'
796
+ * loginMethodTypeToFirebaseProviderId('unknown'); // undefined
797
+ * ```
798
+ */
799
+ function loginMethodTypeToFirebaseProviderId(type) {
800
+ return LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP[type];
801
+ }
802
+
729
803
  /**
730
804
  * Renders a styled login button that triggers a login action handler on click.
731
805
  *
@@ -737,8 +811,10 @@ class DbxFirebaseLoginButtonComponent {
737
811
  iconSignal = computed(() => this.config()?.icon, ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
738
812
  iconFilterSignal = computed(() => this.config()?.iconFilter, ...(ngDevMode ? [{ debugName: "iconFilterSignal" }] : /* istanbul ignore next */ []));
739
813
  textSignal = computed(() => this.config()?.text ?? '', ...(ngDevMode ? [{ debugName: "textSignal" }] : /* istanbul ignore next */ []));
740
- buttonColorSignal = computed(() => this.config()?.buttonColor ?? 'transparent', ...(ngDevMode ? [{ debugName: "buttonColorSignal" }] : /* istanbul ignore next */ []));
814
+ buttonColorSignal = computed(() => this.config()?.buttonColor ?? (this.config()?.color ? undefined : 'transparent'), ...(ngDevMode ? [{ debugName: "buttonColorSignal" }] : /* istanbul ignore next */ []));
741
815
  buttonTextColorSignal = computed(() => this.config()?.buttonTextColor, ...(ngDevMode ? [{ debugName: "buttonTextColorSignal" }] : /* istanbul ignore next */ []));
816
+ colorSignal = computed(() => this.config()?.color, ...(ngDevMode ? [{ debugName: "colorSignal" }] : /* istanbul ignore next */ []));
817
+ confirmConfigSignal = computed(() => this.config()?.confirmConfig, ...(ngDevMode ? [{ debugName: "confirmConfigSignal" }] : /* istanbul ignore next */ []));
742
818
  setConfig(config) {
743
819
  this.config.set(config);
744
820
  }
@@ -757,8 +833,8 @@ class DbxFirebaseLoginButtonComponent {
757
833
  };
758
834
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseLoginButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
759
835
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseLoginButtonComponent, isStandalone: true, selector: "dbx-firebase-login-button", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { config: "configChange" }, host: { classAttribute: "dbx-firebase-login-button" }, ngImport: i0, template: `
760
- <ng-container dbxAction [dbxActionHandler]="handleAction" dbxActionValue [dbxActionSuccessHandler]="onActionSuccess">
761
- <dbx-button dbxActionButton [customTextColor]="buttonTextColorSignal()" [customButtonColor]="buttonColorSignal()" [raised]="true">
836
+ <ng-container dbxAction [dbxActionHandler]="handleAction" [dbxActionSuccessHandler]="onActionSuccess" [dbxActionConfirm]="confirmConfigSignal()" [dbxActionConfirmSkip]="!confirmConfigSignal()">
837
+ <dbx-button dbxActionButton [customTextColor]="buttonTextColorSignal()" [customButtonColor]="buttonColorSignal()" [raised]="true" [color]="colorSignal()" [attr.aria-label]="textSignal()">
762
838
  <div class="dbx-firebase-login-button-content">
763
839
  <span class="dbx-firebase-login-button-icon dbx-icon-spacer">
764
840
  @if (iconUrlSignal()) {
@@ -772,15 +848,15 @@ class DbxFirebaseLoginButtonComponent {
772
848
  </div>
773
849
  </dbx-button>
774
850
  </ng-container>
775
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i2.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i2.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i2.DbxActionValueDirective, selector: "dbxActionValue,[dbxActionValue]", inputs: ["dbxActionValue"] }, { kind: "directive", type: i2.DbxActionSuccessHandlerDirective, selector: "[dbxActionSuccessHandler]", inputs: ["dbxActionSuccessHandler"] }, { kind: "directive", type: i2.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1$1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
851
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i2.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i2.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i2.DbxActionSuccessHandlerDirective, selector: "[dbxActionSuccessHandler]", inputs: ["dbxActionSuccessHandler"] }, { kind: "directive", type: i2.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "directive", type: i1$1.DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm", "dbxActionConfirmSkip"] }, { kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1$1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
776
852
  }
777
853
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseLoginButtonComponent, decorators: [{
778
854
  type: Component,
779
855
  args: [{
780
856
  selector: 'dbx-firebase-login-button',
781
857
  template: `
782
- <ng-container dbxAction [dbxActionHandler]="handleAction" dbxActionValue [dbxActionSuccessHandler]="onActionSuccess">
783
- <dbx-button dbxActionButton [customTextColor]="buttonTextColorSignal()" [customButtonColor]="buttonColorSignal()" [raised]="true">
858
+ <ng-container dbxAction [dbxActionHandler]="handleAction" [dbxActionSuccessHandler]="onActionSuccess" [dbxActionConfirm]="confirmConfigSignal()" [dbxActionConfirmSkip]="!confirmConfigSignal()">
859
+ <dbx-button dbxActionButton [customTextColor]="buttonTextColorSignal()" [customButtonColor]="buttonColorSignal()" [raised]="true" [color]="colorSignal()" [attr.aria-label]="textSignal()">
784
860
  <div class="dbx-firebase-login-button-content">
785
861
  <span class="dbx-firebase-login-button-icon dbx-icon-spacer">
786
862
  @if (iconUrlSignal()) {
@@ -876,13 +952,16 @@ class AbstractConfiguredDbxFirebaseLoginButtonDirective {
876
952
  ngOnInit() {
877
953
  const assets = this.assetConfig;
878
954
  const text = this._textForMode(assets);
955
+ const isUnlink = this.effectiveLoginMode === 'unlink';
879
956
  this._config.set({
880
957
  text,
881
- icon: assets.loginIcon,
882
- iconUrl: assets.logoUrl,
883
- iconFilter: assets.logoFilter,
884
- buttonColor: assets.backgroundColor,
885
- buttonTextColor: assets.textColor,
958
+ icon: isUnlink ? 'link_off' : assets.loginIcon,
959
+ iconUrl: isUnlink ? undefined : assets.logoUrl,
960
+ iconFilter: isUnlink ? undefined : assets.logoFilter,
961
+ buttonColor: isUnlink ? undefined : assets.backgroundColor,
962
+ buttonTextColor: isUnlink ? undefined : assets.textColor,
963
+ color: isUnlink ? 'warn' : undefined,
964
+ confirmConfig: isUnlink ? { title: `Disconnect ${assets.providerName ?? 'Provider'}`, prompt: `Are you sure you want to disconnect ${assets.providerName ?? 'this provider'}?` } : undefined,
886
965
  handleLogin: () => this._handleAction()
887
966
  });
888
967
  }
@@ -895,6 +974,19 @@ class AbstractConfiguredDbxFirebaseLoginButtonDirective {
895
974
  handleLink() {
896
975
  throw new Error(`Linking is not supported for the "${this.loginProvider}" provider.`);
897
976
  }
977
+ /**
978
+ * Handles the unlink action by removing the provider from the current user.
979
+ * Uses the {@link LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP} to resolve the Firebase provider ID.
980
+ *
981
+ * @returns A promise that resolves when the unlink action completes.
982
+ */
983
+ handleUnlink() {
984
+ const providerId = loginMethodTypeToFirebaseProviderId(this.loginProvider);
985
+ if (!providerId) {
986
+ throw new Error(`Unknown provider ID for login method type "${this.loginProvider}".`);
987
+ }
988
+ return this.dbxFirebaseAuthService.unlinkProvider(providerId);
989
+ }
898
990
  get providerConfig() {
899
991
  return this.dbxFirebaseAuthLoginService.getLoginProvider(this.loginProvider);
900
992
  }
@@ -906,21 +998,31 @@ class AbstractConfiguredDbxFirebaseLoginButtonDirective {
906
998
  }
907
999
  _textForMode(assets) {
908
1000
  let text;
909
- if (this.effectiveLoginMode === 'link') {
910
- text = assets.linkText ?? (assets.providerName ? `Connect ${assets.providerName}` : '<linkText not configured>');
911
- }
912
- else {
913
- text = assets.loginText ?? '<loginText not configured>';
1001
+ switch (this.effectiveLoginMode) {
1002
+ case 'link':
1003
+ text = assets.linkText ?? (assets.providerName ? `Connect ${assets.providerName}` : '<linkText not configured>');
1004
+ break;
1005
+ case 'unlink':
1006
+ text = assets.unlinkText ?? (assets.providerName ? `Disconnect ${assets.providerName}` : '<unlinkText not configured>');
1007
+ break;
1008
+ default:
1009
+ text = assets.loginText ?? '<loginText not configured>';
1010
+ break;
914
1011
  }
915
1012
  return text;
916
1013
  }
917
1014
  _handleAction() {
918
1015
  let promise;
919
- if (this.effectiveLoginMode === 'link') {
920
- promise = this.handleLink();
921
- }
922
- else {
923
- promise = this.handleLogin();
1016
+ switch (this.effectiveLoginMode) {
1017
+ case 'link':
1018
+ promise = this.handleLink();
1019
+ break;
1020
+ case 'unlink':
1021
+ promise = this.handleUnlink();
1022
+ break;
1023
+ default:
1024
+ promise = this.handleLogin();
1025
+ break;
924
1026
  }
925
1027
  return promise.catch((error) => {
926
1028
  throw firebaseAuthErrorToReadableError(error);
@@ -1124,11 +1226,11 @@ class DbxFirebaseLoginEmailContentComponent {
1124
1226
  this.doneOrCancelled.next(false);
1125
1227
  }
1126
1228
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseLoginEmailContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1127
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseLoginEmailContentComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<div class=\"dbx-firebase-login-email-content\">\n @switch (emailModeSignal()) {\n @case ('login') {\n <ng-container *ngTemplateOutlet=\"loginView\"></ng-container>\n }\n @case ('recover') {\n <ng-container *ngTemplateOutlet=\"resetPassword\"></ng-container>\n }\n @case ('recoversent') {\n <ng-container *ngTemplateOutlet=\"resetPasswordSent\"></ng-container>\n }\n }\n</div>\n\n<!-- Login View -->\n<ng-template #loginView>\n <ng-container dbxAction [dbxActionHandler]=\"handleLoginAction\">\n <dbx-firebase-email-form [config]=\"formConfig\" dbxActionForm [dbxFormSource]=\"emailFormValueSignal()\"></dbx-firebase-email-form>\n @if (isLoginMode) {\n <div class=\"dbx-firebase-login-email-forgot-prompt\">\n <dbx-link [anchor]=\"forgotAnchor\">Forgot Password?</dbx-link>\n </div>\n }\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" [text]=\"buttonText\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <dbx-button-spacer></dbx-button-spacer>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancel()\">Cancel</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </ng-container>\n</ng-template>\n\n<!-- Reset Password View -->\n<ng-template #resetPassword>\n <div class=\"dbx-firebase-login-email-content-recovery\" dbxAction [dbxActionHandler]=\"handleRecoveryAction\" [dbxActionSuccessHandler]=\"handleRecoverySuccess\">\n <dbx-firebase-email-recovery-form dbxActionForm [dbxFormSource]=\"recoveryFormValueSignal()\"></dbx-firebase-email-recovery-form>\n <p class=\"dbx-hint\">An email will be sent to the above address to help you reset your password.</p>\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" text=\"Send Recovery Email\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancelReset()\">Cancel Recovery</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </div>\n</ng-template>\n\n<!-- Reset Password Sent -->\n<ng-template #resetPasswordSent>\n <div class=\"dbx-firebase-login-email-content-recovery-sent\">\n <p class=\"dbx-hint\">A recovery email was sent to the specified address. Please check your email for next steps.</p>\n <button mat-raised-button (click)=\"clickedRecoveryAcknowledged()\">Ok</button>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxLinkComponent, selector: "dbx-link", inputs: ["ref", "href", "anchor"] }, { kind: "directive", type: DbxActionErrorDirective, selector: "[dbxActionError]" }, { kind: "directive", type: DbxActionFormDirective, selector: "[dbxActionForm]", inputs: ["dbxActionFormDisabledOnWorking", "dbxActionFormIsValid", "dbxActionFormIsEqual", "dbxActionFormIsModified", "dbxActionFormMapValue"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i2.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i2.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i2.DbxActionSuccessHandlerDirective, selector: "[dbxActionSuccessHandler]", inputs: ["dbxActionSuccessHandler"] }, { kind: "directive", type: i2.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxButtonSpacerDirective, selector: "dbx-button-spacer,[dbxButtonSpacer]" }, { kind: "component", type: DbxFirebaseEmailFormComponent, selector: "dbx-firebase-email-form" }, { kind: "component", type: DbxFirebaseEmailRecoveryFormComponent, selector: "dbx-firebase-email-recovery-form" }, { kind: "directive", type: DbxFormSourceDirective, selector: "[dbxFormSource]", inputs: ["dbxFormSourceMode", "dbxFormSource"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseLoginEmailContentComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<div class=\"dbx-firebase-login-email-content\" aria-live=\"polite\">\n @switch (emailModeSignal()) {\n @case ('login') {\n <ng-container *ngTemplateOutlet=\"loginView\"></ng-container>\n }\n @case ('recover') {\n <ng-container *ngTemplateOutlet=\"resetPassword\"></ng-container>\n }\n @case ('recoversent') {\n <ng-container *ngTemplateOutlet=\"resetPasswordSent\"></ng-container>\n }\n }\n</div>\n\n<!-- Login View -->\n<ng-template #loginView>\n <ng-container dbxAction [dbxActionHandler]=\"handleLoginAction\">\n <dbx-firebase-email-form [config]=\"formConfig\" dbxActionForm [dbxFormSource]=\"emailFormValueSignal()\"></dbx-firebase-email-form>\n @if (isLoginMode) {\n <div class=\"dbx-firebase-login-email-forgot-prompt\">\n <dbx-link [anchor]=\"forgotAnchor\">Forgot Password?</dbx-link>\n </div>\n }\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" [text]=\"buttonText\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <dbx-button-spacer></dbx-button-spacer>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancel()\">Cancel</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </ng-container>\n</ng-template>\n\n<!-- Reset Password View -->\n<ng-template #resetPassword>\n <div class=\"dbx-firebase-login-email-content-recovery\" dbxAction [dbxActionHandler]=\"handleRecoveryAction\" [dbxActionSuccessHandler]=\"handleRecoverySuccess\">\n <dbx-firebase-email-recovery-form dbxActionForm [dbxFormSource]=\"recoveryFormValueSignal()\"></dbx-firebase-email-recovery-form>\n <p class=\"dbx-hint\">An email will be sent to the above address to help you reset your password.</p>\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" text=\"Send Recovery Email\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancelReset()\">Cancel Recovery</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </div>\n</ng-template>\n\n<!-- Reset Password Sent -->\n<ng-template #resetPasswordSent>\n <div class=\"dbx-firebase-login-email-content-recovery-sent\" role=\"status\">\n <p class=\"dbx-hint\">A recovery email was sent to the specified address. Please check your email for next steps.</p>\n <button mat-raised-button (click)=\"clickedRecoveryAcknowledged()\">Ok</button>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxLinkComponent, selector: "dbx-link", inputs: ["ref", "href", "anchor"] }, { kind: "directive", type: DbxActionErrorDirective, selector: "[dbxActionError]" }, { kind: "directive", type: DbxActionFormDirective, selector: "[dbxActionForm]", inputs: ["dbxActionFormDisabledOnWorking", "dbxActionFormIsValid", "dbxActionFormIsEqual", "dbxActionFormIsModified", "dbxActionFormMapValue"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i2.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i2.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i2.DbxActionSuccessHandlerDirective, selector: "[dbxActionSuccessHandler]", inputs: ["dbxActionSuccessHandler"] }, { kind: "directive", type: i2.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxButtonSpacerDirective, selector: "dbx-button-spacer,[dbxButtonSpacer]" }, { kind: "component", type: DbxFirebaseEmailFormComponent, selector: "dbx-firebase-email-form" }, { kind: "component", type: DbxFirebaseEmailRecoveryFormComponent, selector: "dbx-firebase-email-recovery-form" }, { kind: "directive", type: DbxFormSourceDirective, selector: "[dbxFormSource]", inputs: ["dbxFormSourceMode", "dbxFormSource"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1128
1230
  }
1129
1231
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseLoginEmailContentComponent, decorators: [{
1130
1232
  type: Component,
1131
- args: [{ imports: [NgTemplateOutlet, DbxErrorComponent, DbxLinkComponent, DbxActionErrorDirective, DbxActionFormDirective, MatButtonModule, DbxActionModule, DbxButtonComponent, DbxButtonSpacerDirective, DbxFirebaseEmailFormComponent, DbxFirebaseEmailRecoveryFormComponent, DbxFormSourceDirective], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"dbx-firebase-login-email-content\">\n @switch (emailModeSignal()) {\n @case ('login') {\n <ng-container *ngTemplateOutlet=\"loginView\"></ng-container>\n }\n @case ('recover') {\n <ng-container *ngTemplateOutlet=\"resetPassword\"></ng-container>\n }\n @case ('recoversent') {\n <ng-container *ngTemplateOutlet=\"resetPasswordSent\"></ng-container>\n }\n }\n</div>\n\n<!-- Login View -->\n<ng-template #loginView>\n <ng-container dbxAction [dbxActionHandler]=\"handleLoginAction\">\n <dbx-firebase-email-form [config]=\"formConfig\" dbxActionForm [dbxFormSource]=\"emailFormValueSignal()\"></dbx-firebase-email-form>\n @if (isLoginMode) {\n <div class=\"dbx-firebase-login-email-forgot-prompt\">\n <dbx-link [anchor]=\"forgotAnchor\">Forgot Password?</dbx-link>\n </div>\n }\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" [text]=\"buttonText\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <dbx-button-spacer></dbx-button-spacer>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancel()\">Cancel</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </ng-container>\n</ng-template>\n\n<!-- Reset Password View -->\n<ng-template #resetPassword>\n <div class=\"dbx-firebase-login-email-content-recovery\" dbxAction [dbxActionHandler]=\"handleRecoveryAction\" [dbxActionSuccessHandler]=\"handleRecoverySuccess\">\n <dbx-firebase-email-recovery-form dbxActionForm [dbxFormSource]=\"recoveryFormValueSignal()\"></dbx-firebase-email-recovery-form>\n <p class=\"dbx-hint\">An email will be sent to the above address to help you reset your password.</p>\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" text=\"Send Recovery Email\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancelReset()\">Cancel Recovery</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </div>\n</ng-template>\n\n<!-- Reset Password Sent -->\n<ng-template #resetPasswordSent>\n <div class=\"dbx-firebase-login-email-content-recovery-sent\">\n <p class=\"dbx-hint\">A recovery email was sent to the specified address. Please check your email for next steps.</p>\n <button mat-raised-button (click)=\"clickedRecoveryAcknowledged()\">Ok</button>\n </div>\n</ng-template>\n" }]
1233
+ args: [{ imports: [NgTemplateOutlet, DbxErrorComponent, DbxLinkComponent, DbxActionErrorDirective, DbxActionFormDirective, MatButtonModule, DbxActionModule, DbxButtonComponent, DbxButtonSpacerDirective, DbxFirebaseEmailFormComponent, DbxFirebaseEmailRecoveryFormComponent, DbxFormSourceDirective], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"dbx-firebase-login-email-content\" aria-live=\"polite\">\n @switch (emailModeSignal()) {\n @case ('login') {\n <ng-container *ngTemplateOutlet=\"loginView\"></ng-container>\n }\n @case ('recover') {\n <ng-container *ngTemplateOutlet=\"resetPassword\"></ng-container>\n }\n @case ('recoversent') {\n <ng-container *ngTemplateOutlet=\"resetPasswordSent\"></ng-container>\n }\n }\n</div>\n\n<!-- Login View -->\n<ng-template #loginView>\n <ng-container dbxAction [dbxActionHandler]=\"handleLoginAction\">\n <dbx-firebase-email-form [config]=\"formConfig\" dbxActionForm [dbxFormSource]=\"emailFormValueSignal()\"></dbx-firebase-email-form>\n @if (isLoginMode) {\n <div class=\"dbx-firebase-login-email-forgot-prompt\">\n <dbx-link [anchor]=\"forgotAnchor\">Forgot Password?</dbx-link>\n </div>\n }\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" [text]=\"buttonText\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <dbx-button-spacer></dbx-button-spacer>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancel()\">Cancel</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </ng-container>\n</ng-template>\n\n<!-- Reset Password View -->\n<ng-template #resetPassword>\n <div class=\"dbx-firebase-login-email-content-recovery\" dbxAction [dbxActionHandler]=\"handleRecoveryAction\" [dbxActionSuccessHandler]=\"handleRecoverySuccess\">\n <dbx-firebase-email-recovery-form dbxActionForm [dbxFormSource]=\"recoveryFormValueSignal()\"></dbx-firebase-email-recovery-form>\n <p class=\"dbx-hint\">An email will be sent to the above address to help you reset your password.</p>\n <div class=\"dbx-flex\">\n <dbx-button class=\"dbx-button-wide\" text=\"Send Recovery Email\" [raised]=\"true\" color=\"primary\" dbxActionButton></dbx-button>\n <span class=\"dbx-spacer\"></span>\n <button mat-flat-button (click)=\"onCancelReset()\">Cancel Recovery</button>\n </div>\n <dbx-error dbxActionError></dbx-error>\n </div>\n</ng-template>\n\n<!-- Reset Password Sent -->\n<ng-template #resetPasswordSent>\n <div class=\"dbx-firebase-login-email-content-recovery-sent\" role=\"status\">\n <p class=\"dbx-hint\">A recovery email was sent to the specified address. Please check your email for next steps.</p>\n <button mat-raised-button (click)=\"clickedRecoveryAcknowledged()\">Ok</button>\n </div>\n</ng-template>\n" }]
1132
1234
  }] });
1133
1235
 
1134
1236
  /**
@@ -1432,14 +1534,45 @@ function provideDbxFirebaseLogin(config) {
1432
1534
  */
1433
1535
  class DbxFirebaseLoginListComponent {
1434
1536
  dbxFirebaseAuthLoginService = inject(DbxFirebaseAuthLoginService);
1537
+ dbxFirebaseAuthService = inject(DbxFirebaseAuthService);
1538
+ _linkedProviderIds = toSignal(this.dbxFirebaseAuthService.currentLinkedProviderIds$, { initialValue: [] });
1539
+ /**
1540
+ * The login method types currently linked to the authenticated user.
1541
+ */
1542
+ linkedMethodTypesSignal = computed(() => {
1543
+ return filterMaybeArrayValues(this._linkedProviderIds().map(firebaseProviderIdToLoginMethodType));
1544
+ }, ...(ngDevMode ? [{ debugName: "linkedMethodTypesSignal" }] : /* istanbul ignore next */ []));
1435
1545
  loginMode = input('login', ...(ngDevMode ? [{ debugName: "loginMode" }] : /* istanbul ignore next */ []));
1436
1546
  providerTypes = input(...(ngDevMode ? [undefined, { debugName: "providerTypes" }] : /* istanbul ignore next */ []));
1437
1547
  omitProviderTypes = input(...(ngDevMode ? [undefined, { debugName: "omitProviderTypes" }] : /* istanbul ignore next */ []));
1438
1548
  providerCategories = input(...(ngDevMode ? [undefined, { debugName: "providerCategories" }] : /* istanbul ignore next */ []));
1549
+ loginModeAriaLabelSignal = computed(() => {
1550
+ switch (this.loginMode()) {
1551
+ case 'register':
1552
+ return 'Registration options';
1553
+ case 'link':
1554
+ return 'Link account options';
1555
+ case 'unlink':
1556
+ return 'Unlink account options';
1557
+ default:
1558
+ return 'Login options';
1559
+ }
1560
+ }, ...(ngDevMode ? [{ debugName: "loginModeAriaLabelSignal" }] : /* istanbul ignore next */ []));
1561
+ get loginModeAriaLabel() {
1562
+ return this.loginModeAriaLabelSignal();
1563
+ }
1439
1564
  providerTypesSignal = computed(() => {
1440
1565
  const providerTypes = this.providerTypes();
1441
1566
  const omitProviderTypes = this.omitProviderTypes();
1442
- const baseTypes = providerTypes ? asArray(providerTypes) : this.dbxFirebaseAuthLoginService.getEnabledTypes();
1567
+ const loginMode = this.loginMode();
1568
+ let baseTypes;
1569
+ if (loginMode === 'unlink') {
1570
+ // In unlink mode, show only currently linked providers
1571
+ baseTypes = this.linkedMethodTypesSignal();
1572
+ }
1573
+ else {
1574
+ baseTypes = providerTypes ? asArray(providerTypes) : this.dbxFirebaseAuthLoginService.getEnabledTypes();
1575
+ }
1443
1576
  return omitProviderTypes ? excludeValuesFromArray(baseTypes, asArray(omitProviderTypes)) : baseTypes;
1444
1577
  }, ...(ngDevMode ? [{ debugName: "providerTypesSignal" }] : /* istanbul ignore next */ []));
1445
1578
  providersSignal = computed(() => {
@@ -1464,6 +1597,10 @@ class DbxFirebaseLoginListComponent {
1464
1597
  providers = providers.filter((x) => x.allowLinking !== false);
1465
1598
  mapFn = (x) => ({ componentClass: x.componentClass, loginMethodType: x.loginMethodType, data: { loginMode } });
1466
1599
  break;
1600
+ case 'unlink':
1601
+ providers = providers.filter((x) => x.allowLinking !== false);
1602
+ mapFn = (x) => ({ componentClass: x.componentClass, loginMethodType: x.loginMethodType, data: { loginMode } });
1603
+ break;
1467
1604
  default:
1468
1605
  mapFn = (x) => ({ componentClass: x.componentClass, loginMethodType: x.loginMethodType, data: { loginMode } });
1469
1606
  break;
@@ -1471,9 +1608,9 @@ class DbxFirebaseLoginListComponent {
1471
1608
  return providers.map(mapFn);
1472
1609
  }, ...(ngDevMode ? [{ debugName: "providersInjectionConfigsSignal" }] : /* istanbul ignore next */ []));
1473
1610
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseLoginListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1474
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseLoginListComponent, isStandalone: true, selector: "dbx-firebase-login-list", inputs: { loginMode: { classPropertyName: "loginMode", publicName: "loginMode", isSignal: true, isRequired: false, transformFunction: null }, providerTypes: { classPropertyName: "providerTypes", publicName: "providerTypes", isSignal: true, isRequired: false, transformFunction: null }, omitProviderTypes: { classPropertyName: "omitProviderTypes", publicName: "omitProviderTypes", isSignal: true, isRequired: false, transformFunction: null }, providerCategories: { classPropertyName: "providerCategories", publicName: "providerCategories", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "dbx-firebase-login-list" }, ngImport: i0, template: `
1611
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseLoginListComponent, isStandalone: true, selector: "dbx-firebase-login-list", inputs: { loginMode: { classPropertyName: "loginMode", publicName: "loginMode", isSignal: true, isRequired: false, transformFunction: null }, providerTypes: { classPropertyName: "providerTypes", publicName: "providerTypes", isSignal: true, isRequired: false, transformFunction: null }, omitProviderTypes: { classPropertyName: "omitProviderTypes", publicName: "omitProviderTypes", isSignal: true, isRequired: false, transformFunction: null }, providerCategories: { classPropertyName: "providerCategories", publicName: "providerCategories", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "list" }, properties: { "attr.aria-label": "loginModeAriaLabel" }, classAttribute: "dbx-firebase-login-list dbx-button-column" }, ngImport: i0, template: `
1475
1612
  @for (config of providersInjectionConfigsSignal(); track config.loginMethodType) {
1476
- <div class="dbx-firebase-login-item">
1613
+ <div class="dbx-firebase-login-item" role="listitem">
1477
1614
  <dbx-injection [config]="config"></dbx-injection>
1478
1615
  </div>
1479
1616
  }
@@ -1485,13 +1622,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
1485
1622
  selector: 'dbx-firebase-login-list',
1486
1623
  template: `
1487
1624
  @for (config of providersInjectionConfigsSignal(); track config.loginMethodType) {
1488
- <div class="dbx-firebase-login-item">
1625
+ <div class="dbx-firebase-login-item" role="listitem">
1489
1626
  <dbx-injection [config]="config"></dbx-injection>
1490
1627
  </div>
1491
1628
  }
1492
1629
  `,
1493
1630
  host: {
1494
- class: 'dbx-firebase-login-list'
1631
+ class: 'dbx-firebase-login-list dbx-button-column',
1632
+ role: 'list',
1633
+ '[attr.aria-label]': 'loginModeAriaLabel'
1495
1634
  },
1496
1635
  standalone: true,
1497
1636
  imports: [DbxInjectionComponent]
@@ -1638,73 +1777,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
1638
1777
  }]
1639
1778
  }] });
1640
1779
 
1641
- /**
1642
- * Map of Firebase provider IDs to known login method types.
1643
- *
1644
- * @example
1645
- * ```ts
1646
- * FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP['google.com']; // 'google'
1647
- * ```
1648
- */
1649
- const FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP = {
1650
- 'google.com': 'google',
1651
- 'facebook.com': 'facebook',
1652
- 'github.com': 'github',
1653
- 'twitter.com': 'twitter',
1654
- 'apple.com': 'apple',
1655
- 'microsoft.com': 'microsoft',
1656
- phone: 'phone',
1657
- password: 'email'
1658
- };
1659
- /**
1660
- * Map of known login method types to Firebase provider IDs.
1661
- *
1662
- * @example
1663
- * ```ts
1664
- * LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP['google']; // 'google.com'
1665
- * ```
1666
- */
1667
- const LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP = {
1668
- google: 'google.com',
1669
- facebook: 'facebook.com',
1670
- github: 'github.com',
1671
- twitter: 'twitter.com',
1672
- apple: 'apple.com',
1673
- microsoft: 'microsoft.com',
1674
- phone: 'phone',
1675
- email: 'password'
1676
- };
1677
- /**
1678
- * Converts a Firebase provider ID (e.g., 'google.com') to its corresponding login method type (e.g., 'google').
1679
- *
1680
- * @param providerId - The Firebase provider ID.
1681
- * @returns The matching login method type, or undefined if unknown.
1682
- *
1683
- * @example
1684
- * ```ts
1685
- * firebaseProviderIdToLoginMethodType('google.com'); // 'google'
1686
- * firebaseProviderIdToLoginMethodType('unknown.com'); // undefined
1687
- * ```
1688
- */
1689
- function firebaseProviderIdToLoginMethodType(providerId) {
1690
- return FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP[providerId];
1691
- }
1692
- /**
1693
- * Converts a login method type (e.g., 'google') to its corresponding Firebase provider ID (e.g., 'google.com').
1694
- *
1695
- * @param type - The login method type.
1696
- * @returns The matching Firebase provider ID, or undefined if unknown.
1697
- *
1698
- * @example
1699
- * ```ts
1700
- * loginMethodTypeToFirebaseProviderId('google'); // 'google.com'
1701
- * loginMethodTypeToFirebaseProviderId('unknown'); // undefined
1702
- * ```
1703
- */
1704
- function loginMethodTypeToFirebaseProviderId(type) {
1705
- return LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP[type];
1706
- }
1707
-
1708
1780
  /**
1709
1781
  * Component for managing linked authentication providers on a user account.
1710
1782
  *
@@ -1721,47 +1793,19 @@ class DbxFirebaseManageAuthProvidersComponent {
1721
1793
  dbxFirebaseAuthService = inject(DbxFirebaseAuthService);
1722
1794
  dbxFirebaseAuthLoginService = inject(DbxFirebaseAuthLoginService);
1723
1795
  _linkedProviderIds = toSignal(this.dbxFirebaseAuthService.currentLinkedProviderIds$, { initialValue: [] });
1724
- linkedProvidersSignal = computed(() => {
1725
- const providerIds = this._linkedProviderIds();
1726
- return providerIds.map((providerId) => {
1727
- const loginMethodType = firebaseProviderIdToLoginMethodType(providerId);
1728
- const assets = loginMethodType ? this.dbxFirebaseAuthLoginService.getProviderAssets(loginMethodType) : undefined;
1729
- const providerName = assets?.providerName ?? providerId;
1730
- const unlinkText = assets?.unlinkText ?? `Disconnect ${providerName}`;
1731
- return { providerId, loginMethodType, providerName, unlinkText, assets };
1732
- });
1733
- }, ...(ngDevMode ? [{ debugName: "linkedProvidersSignal" }] : /* istanbul ignore next */ []));
1734
1796
  linkedMethodTypesSignal = computed(() => {
1735
- return filterMaybeArrayValues(this.linkedProvidersSignal().map((p) => p.loginMethodType));
1797
+ return filterMaybeArrayValues(this._linkedProviderIds().map(firebaseProviderIdToLoginMethodType));
1736
1798
  }, ...(ngDevMode ? [{ debugName: "linkedMethodTypesSignal" }] : /* istanbul ignore next */ []));
1737
1799
  showLinkSectionSignal = computed(() => {
1738
1800
  const linkedTypes = new Set(this.linkedMethodTypesSignal());
1739
1801
  const oauthProviders = this.dbxFirebaseAuthLoginService.getLinkProviders(this.dbxFirebaseAuthLoginService.getEnabledTypes());
1740
1802
  return oauthProviders.some((p) => p.category === OAUTH_FIREBASE_LOGIN_METHOD_CATEGORY && !linkedTypes.has(p.loginMethodType));
1741
1803
  }, ...(ngDevMode ? [{ debugName: "showLinkSectionSignal" }] : /* istanbul ignore next */ []));
1742
- /**
1743
- * Creates a work handler for unlinking a specific provider.
1744
- *
1745
- * @param providerId - The Firebase provider ID to unlink (e.g., 'google.com').
1746
- * @returns A {@link WorkUsingContext} handler that unlinking the provider on execution.
1747
- */
1748
- makeUnlinkHandler(providerId) {
1749
- return (_, context) => {
1750
- const promise = this.dbxFirebaseAuthService.unlinkProvider(providerId);
1751
- context.startWorkingWithPromise(promise);
1752
- };
1753
- }
1754
1804
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseManageAuthProvidersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1755
1805
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseManageAuthProvidersComponent, isStandalone: true, selector: "dbx-firebase-manage-auth-providers", host: { classAttribute: "d-block dbx-firebase-manage-auth-providers" }, ngImport: i0, template: `
1756
- @if (linkedProvidersSignal().length) {
1806
+ @if (linkedMethodTypesSignal().length) {
1757
1807
  <dbx-section header="Connected Providers">
1758
- @for (provider of linkedProvidersSignal(); track provider.providerId) {
1759
- <div class="dbx-firebase-manage-provider-item">
1760
- <ng-container dbxAction [dbxActionHandler]="makeUnlinkHandler(provider.providerId)" dbxActionValue>
1761
- <dbx-button dbxActionButton [text]="provider.unlinkText" icon="link_off" color="warn"></dbx-button>
1762
- </ng-container>
1763
- </div>
1764
- }
1808
+ <dbx-firebase-login loginMode="unlink" providerCategories="oauth"></dbx-firebase-login>
1765
1809
  </dbx-section>
1766
1810
  }
1767
1811
  @if (showLinkSectionSignal()) {
@@ -1769,24 +1813,18 @@ class DbxFirebaseManageAuthProvidersComponent {
1769
1813
  <dbx-firebase-login loginMode="link" [omitProviderTypes]="linkedMethodTypesSignal()" providerCategories="oauth"></dbx-firebase-login>
1770
1814
  </dbx-section>
1771
1815
  }
1772
- `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseLoginComponent, selector: "dbx-firebase-login", inputs: ["loginMode", "providerTypes", "omitProviderTypes", "providerCategories"] }, { kind: "component", type: DbxSectionComponent, selector: "dbx-section", inputs: ["elevate"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i2.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i2.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i2.DbxActionValueDirective, selector: "dbxActionValue,[dbxActionValue]", inputs: ["dbxActionValue"] }, { kind: "directive", type: i2.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1$1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1816
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseLoginComponent, selector: "dbx-firebase-login", inputs: ["loginMode", "providerTypes", "omitProviderTypes", "providerCategories"] }, { kind: "component", type: DbxSectionComponent, selector: "dbx-section", inputs: ["elevate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1773
1817
  }
1774
1818
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseManageAuthProvidersComponent, decorators: [{
1775
1819
  type: Component,
1776
1820
  args: [{
1777
1821
  selector: 'dbx-firebase-manage-auth-providers',
1778
1822
  standalone: true,
1779
- imports: [DbxFirebaseLoginComponent, DbxSectionComponent, MatIconModule, DbxActionModule, DbxButtonModule],
1823
+ imports: [DbxFirebaseLoginComponent, DbxSectionComponent],
1780
1824
  template: `
1781
- @if (linkedProvidersSignal().length) {
1825
+ @if (linkedMethodTypesSignal().length) {
1782
1826
  <dbx-section header="Connected Providers">
1783
- @for (provider of linkedProvidersSignal(); track provider.providerId) {
1784
- <div class="dbx-firebase-manage-provider-item">
1785
- <ng-container dbxAction [dbxActionHandler]="makeUnlinkHandler(provider.providerId)" dbxActionValue>
1786
- <dbx-button dbxActionButton [text]="provider.unlinkText" icon="link_off" color="warn"></dbx-button>
1787
- </ng-container>
1788
- </div>
1789
- }
1827
+ <dbx-firebase-login loginMode="unlink" providerCategories="oauth"></dbx-firebase-login>
1790
1828
  </dbx-section>
1791
1829
  }
1792
1830
  @if (showLinkSectionSignal()) {
@@ -4196,7 +4234,7 @@ class DbxFirebaseDocumentStoreContextStore extends ComponentStore {
4196
4234
  lastStoresChangeAt$ = this.select((state) => state.lastStoresChangeAt).pipe(distinctUntilChanged(isSameDate), shareReplay(1));
4197
4235
  entriesGroupedByIdentity$ = this.stores$.pipe(switchMap((stores) => {
4198
4236
  let entriesObs;
4199
- const allEntries = [...stores.values()];
4237
+ const allEntries = Array.from(stores.values());
4200
4238
  const { included: hasIdentity, excluded: noIdentity } = separateValues(allEntries, (x) => x.modelIdentity != null);
4201
4239
  if (noIdentity.length > 0) {
4202
4240
  entriesObs = combineLatest(noIdentity.map((entryWithoutCachedIdentity) => entryWithoutCachedIdentity.store.modelIdentity$.pipe(first(), map((z) => {
@@ -5641,7 +5679,7 @@ class DbxFirebaseNotificationItemListViewItemComponent extends AbstractDbxValueL
5641
5679
  }
5642
5680
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseNotificationItemListViewItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5643
5681
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.3", type: DbxFirebaseNotificationItemListViewItemComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: `
5644
- <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item">
5682
+ <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item" role="article" [attr.aria-label]="subject">
5645
5683
  <div class="item-left">
5646
5684
  <span class="notificationitem-subject">{{ subject }}</span>
5647
5685
  <span class="notificationitem-message item-details">{{ message | cutText: 90 }}</span>
@@ -5654,7 +5692,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
5654
5692
  type: Component,
5655
5693
  args: [{
5656
5694
  template: `
5657
- <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item">
5695
+ <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item" role="article" [attr.aria-label]="subject">
5658
5696
  <div class="item-left">
5659
5697
  <span class="notificationitem-subject">{{ subject }}</span>
5660
5698
  <span class="notificationitem-message item-details">{{ message | cutText: 90 }}</span>
@@ -5676,11 +5714,11 @@ class DbxFirebaseNotificationItemContentComponent {
5676
5714
  message = input(...(ngDevMode ? [undefined, { debugName: "message" }] : /* istanbul ignore next */ []));
5677
5715
  date = input(...(ngDevMode ? [undefined, { debugName: "date" }] : /* istanbul ignore next */ []));
5678
5716
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseNotificationItemContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5679
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseNotificationItemContentComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-content", inputs: { subject: { classPropertyName: "subject", publicName: "subject", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"dbx-firebase-notificationitem-content\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5717
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: DbxFirebaseNotificationItemContentComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-content", inputs: { subject: { classPropertyName: "subject", publicName: "subject", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"dbx-firebase-notificationitem-content\" role=\"article\" [attr.aria-label]=\"subject()\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5680
5718
  }
5681
5719
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseNotificationItemContentComponent, decorators: [{
5682
5720
  type: Component,
5683
- args: [{ selector: 'dbx-firebase-notificationitem-content', imports: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"dbx-firebase-notificationitem-content\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"] }]
5721
+ args: [{ selector: 'dbx-firebase-notificationitem-content', imports: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"dbx-firebase-notificationitem-content\" role=\"article\" [attr.aria-label]=\"subject()\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"] }]
5684
5722
  }], propDecorators: { subject: [{ type: i0.Input, args: [{ isSignal: true, alias: "subject", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }] } });
5685
5723
 
5686
5724
  /**
@@ -5912,7 +5950,7 @@ class DbxFirebaseNotificationItemStorePopoverButtonComponent extends AbstractPop
5912
5950
  }
5913
5951
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseNotificationItemStorePopoverButtonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5914
5952
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.3", type: DbxFirebaseNotificationItemStorePopoverButtonComponent, isStandalone: true, selector: "dbx-firebase-notification-item-store-popover-button", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "buttonElement", first: true, predicate: ["button"], descendants: true, read: ElementRef, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
5915
- <dbx-icon-button #button (buttonClick)="showNotificationsPopover()" icon="notifications"></dbx-icon-button>
5953
+ <dbx-icon-button #button (buttonClick)="showNotificationsPopover()" icon="notifications" aria-label="Notifications"></dbx-icon-button>
5916
5954
  `, isInline: true, dependencies: [{ kind: "component", type: DbxIconButtonComponent, selector: "dbx-icon-button" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5917
5955
  }
5918
5956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: DbxFirebaseNotificationItemStorePopoverButtonComponent, decorators: [{
@@ -5920,7 +5958,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
5920
5958
  args: [{
5921
5959
  selector: 'dbx-firebase-notification-item-store-popover-button',
5922
5960
  template: `
5923
- <dbx-icon-button #button (buttonClick)="showNotificationsPopover()" icon="notifications"></dbx-icon-button>
5961
+ <dbx-icon-button #button (buttonClick)="showNotificationsPopover()" icon="notifications" aria-label="Notifications"></dbx-icon-button>
5924
5962
  `,
5925
5963
  standalone: true,
5926
5964
  imports: [DbxIconButtonComponent],