@firebase-oss/ui-angular 0.0.1 → 0.0.2-exp.ed61394

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,14 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, HostBinding, Component, computed, InjectionToken, inject, makeEnvironmentProviders, signal, effect, output, viewChild, model, isDevMode } from '@angular/core';
3
- import { getBehavior, getTranslation, createSignInAuthFormSchema, createSignUpAuthFormSchema, createForgotPasswordAuthFormSchema, createEmailLinkAuthFormSchema, createPhoneAuthNumberFormSchema, createPhoneAuthVerifyFormSchema, createMultiFactorPhoneAuthNumberFormSchema, createMultiFactorPhoneAuthVerifyFormSchema, createMultiFactorTotpAuthNumberFormSchema, createMultiFactorTotpAuthVerifyFormSchema, sendSignInLinkToEmail, FirebaseUIError, completeEmailLinkSignIn, sendPasswordResetEmail, verifyPhoneNumber, signInWithMultiFactorAssertion, countryData, formatPhoneNumber, confirmPhoneNumber, signInWithEmailAndPassword, hasBehavior, createUserWithEmailAndPassword, signInWithProvider, registerFramework } from '@firebase-oss/ui-core';
4
- import { CommonModule } from '@angular/common';
2
+ import { input, HostBinding, Component, computed, InjectionToken, Optional, inject, makeEnvironmentProviders, signal, effect, PLATFORM_ID, EventEmitter, Output, viewChild, model, output, isDevMode } from '@angular/core';
3
+ import { getBehavior, getTranslation, createSignInAuthFormSchema, createSignUpAuthFormSchema, createForgotPasswordAuthFormSchema, createEmailLinkAuthFormSchema, createPhoneAuthNumberFormSchema, createPhoneAuthVerifyFormSchema, createMultiFactorPhoneAuthNumberFormSchema, createMultiFactorPhoneAuthAssertionFormSchema, createMultiFactorPhoneAuthVerifyFormSchema, createMultiFactorTotpAuthNumberFormSchema, createMultiFactorTotpAuthVerifyFormSchema, sendSignInLinkToEmail, FirebaseUIError, completeEmailLinkSignIn, sendPasswordResetEmail, verifyPhoneNumber, signInWithMultiFactorAssertion, formatPhoneNumber, enrollWithMultiFactorAssertion, generateTotpSecret, generateTotpQrCode, countryData, confirmPhoneNumber, signInWithEmailAndPassword, hasBehavior, createUserWithEmailAndPassword, signInWithProvider, registerFramework } from '@firebase-oss/ui-core';
4
+ import { isPlatformBrowser, CommonModule } from '@angular/common';
5
5
  import { injectField, injectForm, injectStore, TanStackField, TanStackAppField } from '@tanstack/angular-form';
6
6
  import { buttonVariant } from '@firebase-oss/ui-styles';
7
7
  import { FirebaseApps } from '@angular/fire/app';
8
- import { PhoneAuthProvider, PhoneMultiFactorGenerator, TotpMultiFactorGenerator } from 'firebase/auth';
8
+ import { Auth, authState, GoogleAuthProvider, FacebookAuthProvider, OAuthProvider, TwitterAuthProvider, GithubAuthProvider } from '@angular/fire/auth';
9
+ import { PhoneAuthProvider, PhoneMultiFactorGenerator, TotpMultiFactorGenerator, multiFactor, FactorId } from 'firebase/auth';
9
10
  import * as i1 from '@angular/forms';
10
11
  import { FormsModule } from '@angular/forms';
11
- import { GoogleAuthProvider, FacebookAuthProvider, OAuthProvider, TwitterAuthProvider, GithubAuthProvider } from '@angular/fire/auth';
12
12
 
13
13
  /**
14
14
  * Copyright 2025 Google LLC
@@ -25,7 +25,11 @@ import { GoogleAuthProvider, FacebookAuthProvider, OAuthProvider, TwitterAuthPro
25
25
  * See the License for the specific language governing permissions and
26
26
  * limitations under the License.
27
27
  */
28
+ /**
29
+ * A customizable button component with multiple variants.
30
+ */
28
31
  class ButtonComponent {
32
+ /** The visual variant of the button. */
29
33
  variant = input(...(ngDevMode ? [undefined, { debugName: "variant" }] : []));
30
34
  get getButtonClasses() {
31
35
  return buttonVariant({ variant: this.variant() });
@@ -45,16 +49,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
45
49
  args: ["class"]
46
50
  }] } });
47
51
 
52
+ /**
53
+ * Copyright 2025 Google LLC
54
+ *
55
+ * Licensed under the Apache License, Version 2.0 (the "License");
56
+ * you may not use this file except in compliance with the License.
57
+ * You may obtain a copy of the License at
58
+ *
59
+ * http://www.apache.org/licenses/LICENSE-2.0
60
+ *
61
+ * Unless required by applicable law or agreed to in writing, software
62
+ * distributed under the License is distributed on an "AS IS" BASIS,
63
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
64
+ * See the License for the specific language governing permissions and
65
+ * limitations under the License.
66
+ */
67
+ /**
68
+ * A component that displays form field metadata, such as validation errors.
69
+ */
48
70
  class FormMetadataComponent {
71
+ /** The form field API instance. */
49
72
  field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
50
73
  errors = computed(() => this.field()
51
74
  .state.meta.errors.map((error) => error.message)
52
75
  .join(", "), ...(ngDevMode ? [{ debugName: "errors" }] : []));
53
76
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FormMetadataComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
54
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: FormMetadataComponent, isStandalone: true, selector: "fui-form-metadata", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
77
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: FormMetadataComponent, isStandalone: true, selector: "fui-form-metadata", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
55
78
  @if (field().state.meta.isTouched && errors().length > 0) {
56
79
  <div>
57
- <div role="alert" aria-live="polite" class="fui-form__error">
80
+ <div role="alert" aria-live="polite" class="fui-error">
58
81
  {{ errors() }}
59
82
  </div>
60
83
  </div>
@@ -66,10 +89,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
66
89
  args: [{
67
90
  selector: "fui-form-metadata",
68
91
  standalone: true,
92
+ host: {
93
+ style: "display: block;",
94
+ },
69
95
  template: `
70
96
  @if (field().state.meta.isTouched && errors().length > 0) {
71
97
  <div>
72
- <div role="alert" aria-live="polite" class="fui-form__error">
98
+ <div role="alert" aria-live="polite" class="fui-error">
73
99
  {{ errors() }}
74
100
  </div>
75
101
  </div>
@@ -77,17 +103,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
77
103
  `,
78
104
  }]
79
105
  }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }] } });
106
+ /**
107
+ * A form input component with label, description, and validation support.
108
+ */
80
109
  class FormInputComponent {
81
110
  field = injectField();
111
+ /** The label text for the input field. */
82
112
  label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
113
+ /** The input type (e.g., "text", "email", "password"). */
83
114
  type = input("text", ...(ngDevMode ? [{ debugName: "type" }] : []));
115
+ /** Optional description text displayed below the label. */
116
+ description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
84
117
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FormInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
85
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: FormInputComponent, isStandalone: true, selector: "fui-form-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
118
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: FormInputComponent, isStandalone: true, selector: "fui-form-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
86
119
  <label [for]="field.api.name">
87
120
  <div data-input-label>
88
121
  <div>{{ label() }}</div>
89
122
  <div><ng-content select="input-action" /></div>
90
123
  </div>
124
+ @if (description()) {
125
+ <div data-input-description>{{ description() }}</div>
126
+ }
91
127
  <div data-input-group>
92
128
  <ng-content select="input-before" />
93
129
  <input
@@ -111,12 +147,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
111
147
  selector: "fui-form-input",
112
148
  standalone: true,
113
149
  imports: [FormMetadataComponent],
150
+ host: {
151
+ style: "display: block;",
152
+ },
114
153
  template: `
115
154
  <label [for]="field.api.name">
116
155
  <div data-input-label>
117
156
  <div>{{ label() }}</div>
118
157
  <div><ng-content select="input-action" /></div>
119
158
  </div>
159
+ @if (description()) {
160
+ <div data-input-description>{{ description() }}</div>
161
+ }
120
162
  <div data-input-group>
121
163
  <ng-content select="input-before" />
122
164
  <input
@@ -134,7 +176,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
134
176
  </label>
135
177
  `,
136
178
  }]
137
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }] } });
179
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
180
+ /**
181
+ * A button component for form actions (e.g., "Forgot Password?" link).
182
+ */
138
183
  class FormActionComponent {
139
184
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FormActionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
140
185
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: FormActionComponent, isStandalone: true, selector: "button[fui-form-action]", host: { attributes: { "type": "button" }, classAttribute: "fui-form__action" }, ngImport: i0, template: `<ng-content></ng-content> `, isInline: true });
@@ -151,12 +196,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
151
196
  template: `<ng-content></ng-content> `,
152
197
  }]
153
198
  }] });
199
+ /**
200
+ * A submit button component for forms.
201
+ *
202
+ * Automatically disables when the form is submitting.
203
+ */
154
204
  class FormSubmitComponent {
205
+ /** Optional additional CSS classes. */
155
206
  class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
207
+ /** The form state for tracking submission status. */
156
208
  state = input.required(...(ngDevMode ? [{ debugName: "state" }] : []));
157
209
  isSubmitting = computed(() => this.state().isSubmitting, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : []));
158
210
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FormSubmitComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: FormSubmitComponent, isStandalone: true, selector: "fui-form-submit", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "type": "submit" } }, ngImport: i0, template: `
211
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: FormSubmitComponent, isStandalone: true, selector: "fui-form-submit", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "type": "submit" }, styleAttribute: "display: block;" }, ngImport: i0, template: `
160
212
  <button fui-button class="fui-form__action" [class]="class()" [disabled]="isSubmitting()">
161
213
  <ng-content></ng-content>
162
214
  </button>
@@ -170,6 +222,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
170
222
  imports: [ButtonComponent],
171
223
  host: {
172
224
  type: "submit",
225
+ style: "display: block;",
173
226
  },
174
227
  template: `
175
228
  <button fui-button class="fui-form__action" [class]="class()" [disabled]="isSubmitting()">
@@ -178,7 +231,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
178
231
  `,
179
232
  }]
180
233
  }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }] } });
234
+ /**
235
+ * A component that displays form-level error messages.
236
+ *
237
+ * Shows errors from form submission, not validation errors.
238
+ */
181
239
  class FormErrorMessageComponent {
240
+ /** The form state containing error information. */
182
241
  state = input.required(...(ngDevMode ? [{ debugName: "state" }] : []));
183
242
  errorMessage = computed(() => {
184
243
  const error = this.state().errorMap?.onSubmit;
@@ -189,9 +248,9 @@ class FormErrorMessageComponent {
189
248
  return undefined;
190
249
  }, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
191
250
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FormErrorMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
192
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: FormErrorMessageComponent, isStandalone: true, selector: "fui-form-error-message", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
251
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: FormErrorMessageComponent, isStandalone: true, selector: "fui-form-error-message", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
193
252
  @if (errorMessage()) {
194
- <div class="fui-form__error">
253
+ <div class="fui-error">
195
254
  {{ errorMessage() }}
196
255
  </div>
197
256
  }
@@ -202,9 +261,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
202
261
  args: [{
203
262
  selector: "fui-form-error-message",
204
263
  standalone: true,
264
+ host: {
265
+ style: "display: block;",
266
+ },
205
267
  template: `
206
268
  @if (errorMessage()) {
207
- <div class="fui-form__error">
269
+ <div class="fui-error">
208
270
  {{ errorMessage() }}
209
271
  </div>
210
272
  }
@@ -229,12 +291,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
229
291
  */
230
292
  const FIREBASE_UI_STORE = new InjectionToken("firebaseui.store");
231
293
  const FIREBASE_UI_POLICIES = new InjectionToken("firebaseui.policies");
294
+ /**
295
+ * Provides FirebaseUI configuration for the Angular application.
296
+ *
297
+ * This function must be called in your application's providers array to enable FirebaseUI functionality.
298
+ *
299
+ * @param uiFactory - Factory function that creates a FirebaseUIStore from Firebase apps.
300
+ * @returns Environment providers for FirebaseUI.
301
+ */
232
302
  function provideFirebaseUI(uiFactory) {
233
303
  const providers = [
234
304
  // TODO: This should depend on the FirebaseAuth provider via deps,
235
305
  // see https://github.com/angular/angularfire/blob/35e0a9859299010488852b1826e4083abe56528f/src/firestore/firestore.module.ts#L76
236
306
  {
237
307
  provide: FIREBASE_UI_STORE,
308
+ deps: [FirebaseApps, [new Optional(), Auth]],
238
309
  useFactory: () => {
239
310
  const apps = inject(FirebaseApps);
240
311
  if (!apps || apps.length === 0) {
@@ -246,10 +317,23 @@ function provideFirebaseUI(uiFactory) {
246
317
  ];
247
318
  return makeEnvironmentProviders(providers);
248
319
  }
320
+ /**
321
+ * Provides policy configuration (terms of service and privacy policy URLs) for FirebaseUI components.
322
+ *
323
+ * @param factory - Factory function that returns the policy configuration.
324
+ * @returns Environment providers for FirebaseUI policies.
325
+ */
249
326
  function provideFirebaseUIPolicies(factory) {
250
327
  const providers = [{ provide: FIREBASE_UI_POLICIES, useFactory: factory }];
251
328
  return makeEnvironmentProviders(providers);
252
329
  }
330
+ /**
331
+ * Injects the FirebaseUI store as a reactive signal.
332
+ *
333
+ * Returns a readonly signal that updates when the UI state changes.
334
+ *
335
+ * @returns A readonly signal containing the current FirebaseUI state.
336
+ */
253
337
  function injectUI() {
254
338
  const store = inject(FIREBASE_UI_STORE);
255
339
  const ui = signal(store.get(), ...(ngDevMode ? [{ debugName: "ui" }] : []));
@@ -258,9 +342,42 @@ function injectUI() {
258
342
  });
259
343
  return ui.asReadonly();
260
344
  }
345
+ /**
346
+ * Injects a callback that is called when a user is authenticated.
347
+ *
348
+ * The callback is only triggered for non-anonymous users.
349
+ *
350
+ * @param onAuthenticated - Callback function called when a user is authenticated.
351
+ */
352
+ function injectUserAuthenticated(onAuthenticated) {
353
+ const auth = inject(Auth);
354
+ const state = authState(auth);
355
+ effect((onCleanup) => {
356
+ const subscription = state.subscribe((user) => {
357
+ if (user && !user.isAnonymous) {
358
+ onAuthenticated(user);
359
+ }
360
+ });
361
+ onCleanup(() => {
362
+ subscription.unsubscribe();
363
+ });
364
+ });
365
+ }
366
+ /**
367
+ * Injects a reCAPTCHA verifier for phone authentication.
368
+ *
369
+ * Automatically renders the reCAPTCHA widget in the provided element when available.
370
+ *
371
+ * @param element - Function that returns the element reference where reCAPTCHA should be rendered.
372
+ * @returns A computed signal containing the reCAPTCHA verifier instance, or null if not available.
373
+ */
261
374
  function injectRecaptchaVerifier(element) {
262
375
  const ui = injectUI();
376
+ const platformId = inject(PLATFORM_ID);
263
377
  const verifier = computed(() => {
378
+ if (!isPlatformBrowser(platformId)) {
379
+ return null;
380
+ }
264
381
  const elementRef = element();
265
382
  if (!elementRef) {
266
383
  return null;
@@ -275,61 +392,151 @@ function injectRecaptchaVerifier(element) {
275
392
  });
276
393
  return verifier;
277
394
  }
395
+ /**
396
+ * Injects a translation string as a reactive signal.
397
+ *
398
+ * The signal updates when the UI locale changes.
399
+ *
400
+ * @param category - The translation category (e.g., "labels", "errors", "prompts").
401
+ * @param key - The translation key within the category.
402
+ * @returns A computed signal containing the translated string.
403
+ */
278
404
  function injectTranslation(category, key) {
279
405
  const ui = injectUI();
280
406
  return computed(() => getTranslation(ui(), category, key));
281
407
  }
408
+ /**
409
+ * Injects the sign-in authentication form schema as a reactive signal.
410
+ *
411
+ * @returns A computed signal containing the sign-in form schema.
412
+ */
282
413
  function injectSignInAuthFormSchema() {
283
414
  const ui = injectUI();
284
415
  return computed(() => createSignInAuthFormSchema(ui()));
285
416
  }
417
+ /**
418
+ * Injects the sign-up authentication form schema as a reactive signal.
419
+ *
420
+ * @returns A computed signal containing the sign-up form schema.
421
+ */
286
422
  function injectSignUpAuthFormSchema() {
287
423
  const ui = injectUI();
288
424
  return computed(() => createSignUpAuthFormSchema(ui()));
289
425
  }
426
+ /**
427
+ * Injects the forgot password authentication form schema as a reactive signal.
428
+ *
429
+ * @returns A computed signal containing the forgot password form schema.
430
+ */
290
431
  function injectForgotPasswordAuthFormSchema() {
291
432
  const ui = injectUI();
292
433
  return computed(() => createForgotPasswordAuthFormSchema(ui()));
293
434
  }
435
+ /**
436
+ * Injects the email link authentication form schema as a reactive signal.
437
+ *
438
+ * @returns A computed signal containing the email link auth form schema.
439
+ */
294
440
  function injectEmailLinkAuthFormSchema() {
295
441
  const ui = injectUI();
296
442
  return computed(() => createEmailLinkAuthFormSchema(ui()));
297
443
  }
444
+ /**
445
+ * Injects the phone authentication number form schema as a reactive signal.
446
+ *
447
+ * @returns A computed signal containing the phone auth number form schema.
448
+ */
298
449
  function injectPhoneAuthFormSchema() {
299
450
  const ui = injectUI();
300
451
  return computed(() => createPhoneAuthNumberFormSchema(ui()));
301
452
  }
453
+ /**
454
+ * Injects the phone authentication verification form schema as a reactive signal.
455
+ *
456
+ * @returns A computed signal containing the phone auth verification form schema.
457
+ */
302
458
  function injectPhoneAuthVerifyFormSchema() {
303
459
  const ui = injectUI();
304
460
  return computed(() => createPhoneAuthVerifyFormSchema(ui()));
305
461
  }
462
+ /**
463
+ * Injects the multi-factor phone authentication number form schema as a reactive signal.
464
+ *
465
+ * @returns A computed signal containing the MFA phone auth number form schema.
466
+ */
306
467
  function injectMultiFactorPhoneAuthNumberFormSchema() {
307
468
  const ui = injectUI();
308
469
  return computed(() => createMultiFactorPhoneAuthNumberFormSchema(ui()));
309
470
  }
471
+ /**
472
+ * Injects the multi-factor phone authentication assertion form schema as a reactive signal.
473
+ *
474
+ * @returns A computed signal containing the MFA phone auth assertion form schema.
475
+ */
476
+ function injectMultiFactorPhoneAuthAssertionFormSchema() {
477
+ const ui = injectUI();
478
+ return computed(() => createMultiFactorPhoneAuthAssertionFormSchema(ui()));
479
+ }
480
+ /**
481
+ * Injects the multi-factor phone authentication verification form schema as a reactive signal.
482
+ *
483
+ * @returns A computed signal containing the MFA phone auth verification form schema.
484
+ */
310
485
  function injectMultiFactorPhoneAuthVerifyFormSchema() {
311
486
  const ui = injectUI();
312
487
  return computed(() => createMultiFactorPhoneAuthVerifyFormSchema(ui()));
313
488
  }
489
+ /**
490
+ * Injects the multi-factor TOTP authentication number form schema as a reactive signal.
491
+ *
492
+ * @returns A computed signal containing the MFA TOTP auth number form schema.
493
+ */
314
494
  function injectMultiFactorTotpAuthNumberFormSchema() {
315
495
  const ui = injectUI();
316
496
  return computed(() => createMultiFactorTotpAuthNumberFormSchema(ui()));
317
497
  }
498
+ /**
499
+ * Injects the multi-factor TOTP authentication verification form schema as a reactive signal.
500
+ *
501
+ * @returns A computed signal containing the MFA TOTP auth verification form schema.
502
+ */
318
503
  function injectMultiFactorTotpAuthVerifyFormSchema() {
319
504
  const ui = injectUI();
320
505
  return computed(() => createMultiFactorTotpAuthVerifyFormSchema(ui()));
321
506
  }
507
+ /**
508
+ * Injects the policy configuration (terms of service and privacy policy URLs).
509
+ *
510
+ * @returns The policy configuration, or null if not provided.
511
+ */
322
512
  function injectPolicies() {
323
513
  return inject(FIREBASE_UI_POLICIES, { optional: true });
324
514
  }
515
+ /**
516
+ * Injects the list of allowed countries for phone authentication as a reactive signal.
517
+ *
518
+ * @returns A computed signal containing the array of allowed country data.
519
+ */
325
520
  function injectCountries() {
326
521
  const ui = injectUI();
327
522
  return computed(() => getBehavior(ui(), "countryCodes")().allowedCountries);
328
523
  }
524
+ /**
525
+ * Injects the default country for phone authentication as a reactive signal.
526
+ *
527
+ * @returns A computed signal containing the default country data.
528
+ */
329
529
  function injectDefaultCountry() {
330
530
  const ui = injectUI();
331
531
  return computed(() => getBehavior(ui(), "countryCodes")().defaultCountry);
332
532
  }
533
+ /**
534
+ * Injects the redirect error message as a reactive signal.
535
+ *
536
+ * Returns the error message if a redirect error occurred, undefined otherwise.
537
+ *
538
+ * @returns A computed signal containing the redirect error message, or undefined if no error.
539
+ */
333
540
  function injectRedirectError() {
334
541
  const ui = injectUI();
335
542
  return computed(() => {
@@ -356,6 +563,11 @@ function injectRedirectError() {
356
563
  * See the License for the specific language governing permissions and
357
564
  * limitations under the License.
358
565
  */
566
+ /**
567
+ * A component that displays terms of service and privacy policy links.
568
+ *
569
+ * Parses the terms and privacy policy template and renders clickable links.
570
+ */
359
571
  class PoliciesComponent {
360
572
  policies = injectPolicies();
361
573
  termsText = injectTranslation("labels", "termsOfService");
@@ -364,6 +576,9 @@ class PoliciesComponent {
364
576
  tosUrl = this.policies?.termsOfServiceUrl;
365
577
  privacyPolicyUrl = this.policies?.privacyPolicyUrl;
366
578
  shouldShow = computed(() => this.policies !== null, ...(ngDevMode ? [{ debugName: "shouldShow" }] : []));
579
+ get displayStyle() {
580
+ return this.shouldShow() ? "block" : "none";
581
+ }
367
582
  policyParts = computed(() => {
368
583
  if (!this.shouldShow()) {
369
584
  return [];
@@ -394,23 +609,21 @@ class PoliciesComponent {
394
609
  });
395
610
  }, ...(ngDevMode ? [{ debugName: "policyParts" }] : []));
396
611
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PoliciesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
397
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PoliciesComponent, isStandalone: true, selector: "fui-policies", ngImport: i0, template: `
612
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PoliciesComponent, isStandalone: true, selector: "fui-policies", host: { properties: { "style.display": "this.displayStyle" }, classAttribute: "fui-policies" }, ngImport: i0, template: `
398
613
  @if (shouldShow()) {
399
- <div class="fui-policies">
400
- @for (part of policyParts(); track $index) {
401
- @if (part.type === "tos") {
402
- <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
403
- {{ part.text }}
404
- </a>
405
- } @else if (part.type === "privacy") {
406
- <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
407
- {{ part.text }}
408
- </a>
409
- } @else {
410
- <span>{{ part.content }}</span>
411
- }
614
+ @for (part of policyParts(); track $index) {
615
+ @if (part.type === "tos") {
616
+ <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
617
+ {{ part.text }}
618
+ </a>
619
+ } @else if (part.type === "privacy") {
620
+ <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
621
+ {{ part.text }}
622
+ </a>
623
+ } @else {
624
+ <span>{{ part.content }}</span>
412
625
  }
413
- </div>
626
+ }
414
627
  }
415
628
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
416
629
  }
@@ -418,29 +631,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
418
631
  type: Component,
419
632
  args: [{
420
633
  selector: "fui-policies",
634
+ host: {
635
+ class: "fui-policies",
636
+ },
421
637
  standalone: true,
422
638
  imports: [CommonModule],
423
639
  template: `
424
640
  @if (shouldShow()) {
425
- <div class="fui-policies">
426
- @for (part of policyParts(); track $index) {
427
- @if (part.type === "tos") {
428
- <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
429
- {{ part.text }}
430
- </a>
431
- } @else if (part.type === "privacy") {
432
- <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
433
- {{ part.text }}
434
- </a>
435
- } @else {
436
- <span>{{ part.content }}</span>
437
- }
641
+ @for (part of policyParts(); track $index) {
642
+ @if (part.type === "tos") {
643
+ <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
644
+ {{ part.text }}
645
+ </a>
646
+ } @else if (part.type === "privacy") {
647
+ <a [attr.href]="part.url" target="_blank" rel="noopener noreferrer">
648
+ {{ part.text }}
649
+ </a>
650
+ } @else {
651
+ <span>{{ part.content }}</span>
438
652
  }
439
- </div>
653
+ }
440
654
  }
441
655
  `,
442
656
  }]
443
- }] });
657
+ }], propDecorators: { displayStyle: [{
658
+ type: HostBinding,
659
+ args: ["style.display"]
660
+ }] } });
444
661
 
445
662
  /**
446
663
  * Copyright 2025 Google LLC
@@ -457,6 +674,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
457
674
  * See the License for the specific language governing permissions and
458
675
  * limitations under the License.
459
676
  */
677
+ /**
678
+ * A form component for email link authentication.
679
+ *
680
+ * Sends a sign-in link to the user's email address and automatically completes sign-in
681
+ * if the user arrives via an email link.
682
+ */
460
683
  class EmailLinkAuthFormComponent {
461
684
  ui = injectUI();
462
685
  formSchema = injectEmailLinkAuthFormSchema();
@@ -465,8 +688,10 @@ class EmailLinkAuthFormComponent {
465
688
  sendSignInLinkLabel = injectTranslation("labels", "sendSignInLink");
466
689
  emailSentMessage = injectTranslation("messages", "signInLinkSent");
467
690
  unknownErrorLabel = injectTranslation("errors", "unknownError");
468
- emailSent = output();
469
- signIn = output();
691
+ /** Event emitter fired when sign-in link email is sent. */
692
+ emailSent = new EventEmitter();
693
+ /** Event emitter for successful sign-in. */
694
+ signIn = new EventEmitter();
470
695
  form = injectForm({
471
696
  defaultValues: {
472
697
  email: "",
@@ -484,18 +709,18 @@ class EmailLinkAuthFormComponent {
484
709
  this.form.update({
485
710
  validators: {
486
711
  onBlur: this.formSchema(),
487
- onSubmit: this.formSchema(),
488
712
  onSubmitAsync: async ({ value }) => {
489
713
  try {
490
714
  await sendSignInLinkToEmail(this.ui(), value.email);
491
715
  this.emailSentState.set(true);
492
- this.emailSent?.emit();
716
+ this.emailSent.emit();
493
717
  return;
494
718
  }
495
719
  catch (error) {
496
720
  if (error instanceof FirebaseUIError) {
497
721
  return error.message;
498
722
  }
723
+ console.error(error);
499
724
  return this.unknownErrorLabel();
500
725
  }
501
726
  },
@@ -506,13 +731,13 @@ class EmailLinkAuthFormComponent {
506
731
  async completeSignIn() {
507
732
  const credential = await completeEmailLinkSignIn(this.ui(), window.location.href);
508
733
  if (credential) {
509
- this.signIn?.emit(credential);
734
+ this.signIn.emit(credential);
510
735
  }
511
736
  }
512
737
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: EmailLinkAuthFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
513
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: EmailLinkAuthFormComponent, isStandalone: true, selector: "fui-email-link-auth-form", outputs: { emailSent: "emailSent", signIn: "signIn" }, ngImport: i0, template: `
738
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: EmailLinkAuthFormComponent, isStandalone: true, selector: "fui-email-link-auth-form", outputs: { emailSent: "emailSent", signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
514
739
  @if (emailSentState()) {
515
- <div class="fui-form__success">
740
+ <div class="fui-success">
516
741
  {{ emailSentMessage() }}
517
742
  </div>
518
743
  }
@@ -533,13 +758,16 @@ class EmailLinkAuthFormComponent {
533
758
  </fieldset>
534
759
  </form>
535
760
  }
536
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
761
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
537
762
  }
538
763
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: EmailLinkAuthFormComponent, decorators: [{
539
764
  type: Component,
540
765
  args: [{
541
766
  selector: "fui-email-link-auth-form",
542
767
  standalone: true,
768
+ host: {
769
+ style: "display: block;",
770
+ },
543
771
  imports: [
544
772
  CommonModule,
545
773
  TanStackField,
@@ -551,7 +779,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
551
779
  ],
552
780
  template: `
553
781
  @if (emailSentState()) {
554
- <div class="fui-form__success">
782
+ <div class="fui-success">
555
783
  {{ emailSentMessage() }}
556
784
  </div>
557
785
  }
@@ -574,7 +802,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
574
802
  }
575
803
  `,
576
804
  }]
577
- }], ctorParameters: () => [], propDecorators: { emailSent: [{ type: i0.Output, args: ["emailSent"] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
805
+ }], ctorParameters: () => [], propDecorators: { emailSent: [{
806
+ type: Output
807
+ }], signIn: [{
808
+ type: Output
809
+ }] } });
578
810
 
579
811
  /**
580
812
  * Copyright 2025 Google LLC
@@ -591,6 +823,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
591
823
  * See the License for the specific language governing permissions and
592
824
  * limitations under the License.
593
825
  */
826
+ /**
827
+ * A form component for requesting a password reset email.
828
+ *
829
+ * Displays a success message after the email is sent.
830
+ */
594
831
  class ForgotPasswordAuthFormComponent {
595
832
  ui = injectUI();
596
833
  formSchema = injectForgotPasswordAuthFormSchema();
@@ -600,8 +837,10 @@ class ForgotPasswordAuthFormComponent {
600
837
  backToSignInLabel = injectTranslation("labels", "backToSignIn");
601
838
  checkEmailForResetMessage = injectTranslation("messages", "checkEmailForReset");
602
839
  unknownErrorLabel = injectTranslation("errors", "unknownError");
603
- passwordSent = output();
604
- backToSignIn = output();
840
+ /** Event emitter for back to sign in action. */
841
+ backToSignIn = input(...(ngDevMode ? [undefined, { debugName: "backToSignIn" }] : []));
842
+ /** Event emitter fired when password reset email is sent. */
843
+ passwordSent = new EventEmitter();
605
844
  form = injectForm({
606
845
  defaultValues: {
607
846
  email: "",
@@ -618,18 +857,18 @@ class ForgotPasswordAuthFormComponent {
618
857
  this.form.update({
619
858
  validators: {
620
859
  onBlur: this.formSchema(),
621
- onSubmit: this.formSchema(),
622
860
  onSubmitAsync: async ({ value }) => {
623
861
  try {
624
862
  await sendPasswordResetEmail(this.ui(), value.email);
625
863
  this.emailSent.set(true);
626
- this.passwordSent?.emit();
864
+ this.passwordSent.emit();
627
865
  return;
628
866
  }
629
867
  catch (error) {
630
868
  if (error instanceof FirebaseUIError) {
631
869
  return error.message;
632
870
  }
871
+ console.error(error);
633
872
  return this.unknownErrorLabel();
634
873
  }
635
874
  },
@@ -638,9 +877,9 @@ class ForgotPasswordAuthFormComponent {
638
877
  });
639
878
  }
640
879
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ForgotPasswordAuthFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
641
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ForgotPasswordAuthFormComponent, isStandalone: true, selector: "fui-forgot-password-auth-form", outputs: { passwordSent: "passwordSent", backToSignIn: "backToSignIn" }, ngImport: i0, template: `
880
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ForgotPasswordAuthFormComponent, isStandalone: true, selector: "fui-forgot-password-auth-form", inputs: { backToSignIn: { classPropertyName: "backToSignIn", publicName: "backToSignIn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { passwordSent: "passwordSent" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
642
881
  @if (emailSent()) {
643
- <div class="fui-form__success">
882
+ <div class="fui-success">
644
883
  {{ checkEmailForResetMessage() }}
645
884
  </div>
646
885
  }
@@ -660,18 +899,21 @@ class ForgotPasswordAuthFormComponent {
660
899
  <fui-form-error-message [state]="state()" />
661
900
  </fieldset>
662
901
 
663
- @if (backToSignIn) {
664
- <button fui-form-action (click)="backToSignIn.emit()">{{ backToSignInLabel() }} &rarr;</button>
902
+ @if (backToSignIn()?.observed) {
903
+ <button fui-form-action (click)="backToSignIn()?.emit()">{{ backToSignInLabel() }} &rarr;</button>
665
904
  }
666
905
  </form>
667
906
  }
668
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: FormActionComponent, selector: "button[fui-form-action]" }] });
907
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: FormActionComponent, selector: "button[fui-form-action]" }] });
669
908
  }
670
909
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ForgotPasswordAuthFormComponent, decorators: [{
671
910
  type: Component,
672
911
  args: [{
673
912
  selector: "fui-forgot-password-auth-form",
674
913
  standalone: true,
914
+ host: {
915
+ style: "display: block;",
916
+ },
675
917
  imports: [
676
918
  CommonModule,
677
919
  TanStackField,
@@ -684,7 +926,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
684
926
  ],
685
927
  template: `
686
928
  @if (emailSent()) {
687
- <div class="fui-form__success">
929
+ <div class="fui-success">
688
930
  {{ checkEmailForResetMessage() }}
689
931
  </div>
690
932
  }
@@ -704,14 +946,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
704
946
  <fui-form-error-message [state]="state()" />
705
947
  </fieldset>
706
948
 
707
- @if (backToSignIn) {
708
- <button fui-form-action (click)="backToSignIn.emit()">{{ backToSignInLabel() }} &rarr;</button>
949
+ @if (backToSignIn()?.observed) {
950
+ <button fui-form-action (click)="backToSignIn()?.emit()">{{ backToSignInLabel() }} &rarr;</button>
709
951
  }
710
952
  </form>
711
953
  }
712
954
  `,
713
955
  }]
714
- }], ctorParameters: () => [], propDecorators: { passwordSent: [{ type: i0.Output, args: ["passwordSent"] }], backToSignIn: [{ type: i0.Output, args: ["backToSignIn"] }] } });
956
+ }], ctorParameters: () => [], propDecorators: { backToSignIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "backToSignIn", required: false }] }], passwordSent: [{
957
+ type: Output
958
+ }] } });
715
959
 
716
960
  /**
717
961
  * Copyright 2025 Google LLC
@@ -727,51 +971,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
727
971
  * See the License for the specific language governing permissions and
728
972
  * limitations under the License.
729
973
  */
974
+ /**
975
+ * A form component for requesting SMS verification code during MFA assertion.
976
+ */
730
977
  class SmsMultiFactorAssertionPhoneFormComponent {
731
978
  ui = injectUI();
732
- formSchema = injectMultiFactorPhoneAuthNumberFormSchema();
979
+ /** The multi-factor info hint containing phone number details. */
733
980
  hint = input.required(...(ngDevMode ? [{ debugName: "hint" }] : []));
734
- onSubmit = output();
735
- phoneNumberLabel = injectTranslation("labels", "phoneNumber");
981
+ /** Event emitter fired when verification ID is received. */
982
+ onSubmit = new EventEmitter();
736
983
  sendCodeLabel = injectTranslation("labels", "sendCode");
737
- unknownErrorLabel = injectTranslation("errors", "unknownError");
738
984
  recaptchaContainer = viewChild.required("recaptchaContainer");
739
985
  phoneNumber = computed(() => {
740
986
  const hint = this.hint();
741
987
  return hint.phoneNumber || "";
742
988
  }, ...(ngDevMode ? [{ debugName: "phoneNumber" }] : []));
989
+ mfaSmsAssertionPrompt = computed(() => {
990
+ return getTranslation(this.ui(), "messages", "mfaSmsAssertionPrompt", { phoneNumber: this.phoneNumber() });
991
+ }, ...(ngDevMode ? [{ debugName: "mfaSmsAssertionPrompt" }] : []));
743
992
  recaptchaVerifier = injectRecaptchaVerifier(() => this.recaptchaContainer());
744
993
  form = injectForm({
745
- defaultValues: {
746
- phoneNumber: "",
747
- },
994
+ defaultValues: {},
748
995
  });
749
996
  state = injectStore(this.form, (state) => state);
750
997
  constructor() {
751
- effect(() => {
752
- // Set the phone number value from the hint
753
- this.form.setFieldValue("phoneNumber", this.phoneNumber());
754
- });
755
998
  effect(() => {
756
999
  this.form.update({
757
1000
  validators: {
758
- onBlur: this.formSchema(),
759
- onSubmit: this.formSchema(),
760
1001
  onSubmitAsync: async () => {
761
1002
  try {
762
1003
  const verifier = this.recaptchaVerifier();
763
1004
  if (!verifier) {
764
- return this.unknownErrorLabel();
1005
+ return "Recaptcha verifier not available";
765
1006
  }
766
1007
  const verificationId = await verifyPhoneNumber(this.ui(), "", verifier, undefined, this.hint());
767
1008
  this.onSubmit.emit(verificationId);
768
1009
  return;
769
1010
  }
770
1011
  catch (error) {
771
- if (error instanceof FirebaseUIError) {
772
- return error.message;
773
- }
774
- return this.unknownErrorLabel();
1012
+ return error instanceof FirebaseUIError ? error.message : String(error);
775
1013
  }
776
1014
  },
777
1015
  },
@@ -792,16 +1030,14 @@ class SmsMultiFactorAssertionPhoneFormComponent {
792
1030
  this.form.handleSubmit();
793
1031
  }
794
1032
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorAssertionPhoneFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
795
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.7", type: SmsMultiFactorAssertionPhoneFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-assertion-phone-form", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, viewQueries: [{ propertyName: "recaptchaContainer", first: true, predicate: ["recaptchaContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
1033
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.7", type: SmsMultiFactorAssertionPhoneFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-assertion-phone-form", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, host: { styleAttribute: "display: block;" }, viewQueries: [{ propertyName: "recaptchaContainer", first: true, predicate: ["recaptchaContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
796
1034
  <form (submit)="handleSubmit($event)" class="fui-form">
797
1035
  <fieldset>
798
- <fui-form-input
799
- name="phoneNumber"
800
- tanstack-app-field
801
- [tanstackField]="form"
802
- label="{{ phoneNumberLabel() }}"
803
- type="tel"
804
- ></fui-form-input>
1036
+ <label>
1037
+ <div data-input-description>
1038
+ {{ mfaSmsAssertionPrompt() }}
1039
+ </div>
1040
+ </label>
805
1041
  </fieldset>
806
1042
  <fieldset>
807
1043
  <div class="fui-recaptcha-container" #recaptchaContainer></div>
@@ -813,31 +1049,25 @@ class SmsMultiFactorAssertionPhoneFormComponent {
813
1049
  <fui-form-error-message [state]="state()" />
814
1050
  </fieldset>
815
1051
  </form>
816
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
1052
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
817
1053
  }
818
1054
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorAssertionPhoneFormComponent, decorators: [{
819
1055
  type: Component,
820
1056
  args: [{
821
1057
  selector: "fui-sms-multi-factor-assertion-phone-form",
822
1058
  standalone: true,
823
- imports: [
824
- CommonModule,
825
- TanStackField,
826
- TanStackAppField,
827
- FormInputComponent,
828
- FormSubmitComponent,
829
- FormErrorMessageComponent,
830
- ],
1059
+ imports: [CommonModule, FormSubmitComponent, FormErrorMessageComponent],
1060
+ host: {
1061
+ style: "display: block;",
1062
+ },
831
1063
  template: `
832
1064
  <form (submit)="handleSubmit($event)" class="fui-form">
833
1065
  <fieldset>
834
- <fui-form-input
835
- name="phoneNumber"
836
- tanstack-app-field
837
- [tanstackField]="form"
838
- label="{{ phoneNumberLabel() }}"
839
- type="tel"
840
- ></fui-form-input>
1066
+ <label>
1067
+ <div data-input-description>
1068
+ {{ mfaSmsAssertionPrompt() }}
1069
+ </div>
1070
+ </label>
841
1071
  </fieldset>
842
1072
  <fieldset>
843
1073
  <div class="fui-recaptcha-container" #recaptchaContainer></div>
@@ -851,14 +1081,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
851
1081
  </form>
852
1082
  `,
853
1083
  }]
854
- }], ctorParameters: () => [], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: true }] }], onSubmit: [{ type: i0.Output, args: ["onSubmit"] }], recaptchaContainer: [{ type: i0.ViewChild, args: ["recaptchaContainer", { isSignal: true }] }] } });
1084
+ }], ctorParameters: () => [], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: true }] }], onSubmit: [{
1085
+ type: Output
1086
+ }], recaptchaContainer: [{ type: i0.ViewChild, args: ["recaptchaContainer", { isSignal: true }] }] } });
1087
+ /**
1088
+ * A form component for verifying SMS code during MFA assertion.
1089
+ */
855
1090
  class SmsMultiFactorAssertionVerifyFormComponent {
856
1091
  ui = injectUI();
857
1092
  formSchema = injectMultiFactorPhoneAuthVerifyFormSchema();
1093
+ /** The verification ID received from the phone form. */
858
1094
  verificationId = input.required(...(ngDevMode ? [{ debugName: "verificationId" }] : []));
859
- onSuccess = output();
1095
+ /** Event emitter for successful MFA assertion. */
1096
+ onSuccess = new EventEmitter();
860
1097
  verificationCodeLabel = injectTranslation("labels", "verificationCode");
861
1098
  verifyCodeLabel = injectTranslation("labels", "verifyCode");
1099
+ smsVerificationPrompt = injectTranslation("prompts", "smsVerificationPrompt");
862
1100
  unknownErrorLabel = injectTranslation("errors", "unknownError");
863
1101
  form = injectForm({
864
1102
  defaultValues: {
@@ -880,15 +1118,12 @@ class SmsMultiFactorAssertionVerifyFormComponent {
880
1118
  try {
881
1119
  const credential = PhoneAuthProvider.credential(value.verificationId, value.verificationCode);
882
1120
  const assertion = PhoneMultiFactorGenerator.assertion(credential);
883
- await signInWithMultiFactorAssertion(this.ui(), assertion);
884
- this.onSuccess.emit();
1121
+ const result = await signInWithMultiFactorAssertion(this.ui(), assertion);
1122
+ this.onSuccess.emit(result);
885
1123
  return;
886
1124
  }
887
1125
  catch (error) {
888
- if (error instanceof FirebaseUIError) {
889
- return error.message;
890
- }
891
- return this.unknownErrorLabel();
1126
+ return error instanceof FirebaseUIError ? error.message : String(error);
892
1127
  }
893
1128
  },
894
1129
  },
@@ -901,14 +1136,15 @@ class SmsMultiFactorAssertionVerifyFormComponent {
901
1136
  this.form.handleSubmit();
902
1137
  }
903
1138
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorAssertionVerifyFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
904
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: SmsMultiFactorAssertionVerifyFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-assertion-verify-form", inputs: { verificationId: { classPropertyName: "verificationId", publicName: "verificationId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSuccess: "onSuccess" }, ngImport: i0, template: `
1139
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: SmsMultiFactorAssertionVerifyFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-assertion-verify-form", inputs: { verificationId: { classPropertyName: "verificationId", publicName: "verificationId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSuccess: "onSuccess" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
905
1140
  <form (submit)="handleSubmit($event)" class="fui-form">
906
1141
  <fieldset>
907
1142
  <fui-form-input
908
1143
  name="verificationCode"
909
1144
  tanstack-app-field
910
1145
  [tanstackField]="form"
911
- label="{{ verificationCodeLabel() }}"
1146
+ [label]="verificationCodeLabel()"
1147
+ [description]="smsVerificationPrompt()"
912
1148
  type="text"
913
1149
  ></fui-form-input>
914
1150
  </fieldset>
@@ -919,13 +1155,16 @@ class SmsMultiFactorAssertionVerifyFormComponent {
919
1155
  <fui-form-error-message [state]="state()" />
920
1156
  </fieldset>
921
1157
  </form>
922
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
1158
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
923
1159
  }
924
1160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorAssertionVerifyFormComponent, decorators: [{
925
1161
  type: Component,
926
1162
  args: [{
927
1163
  selector: "fui-sms-multi-factor-assertion-verify-form",
928
1164
  standalone: true,
1165
+ host: {
1166
+ style: "display: block;",
1167
+ },
929
1168
  imports: [
930
1169
  CommonModule,
931
1170
  TanStackField,
@@ -941,7 +1180,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
941
1180
  name="verificationCode"
942
1181
  tanstack-app-field
943
1182
  [tanstackField]="form"
944
- label="{{ verificationCodeLabel() }}"
1183
+ [label]="verificationCodeLabel()"
1184
+ [description]="smsVerificationPrompt()"
945
1185
  type="text"
946
1186
  ></fui-form-input>
947
1187
  </fieldset>
@@ -954,21 +1194,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
954
1194
  </form>
955
1195
  `,
956
1196
  }]
957
- }], ctorParameters: () => [], propDecorators: { verificationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "verificationId", required: true }] }], onSuccess: [{ type: i0.Output, args: ["onSuccess"] }] } });
1197
+ }], ctorParameters: () => [], propDecorators: { verificationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "verificationId", required: true }] }], onSuccess: [{
1198
+ type: Output
1199
+ }] } });
1200
+ /**
1201
+ * A form component for SMS multi-factor authentication assertion.
1202
+ *
1203
+ * Manages the flow between requesting and verifying SMS codes for MFA.
1204
+ */
958
1205
  class SmsMultiFactorAssertionFormComponent {
1206
+ /** The multi-factor info hint containing phone number details. */
959
1207
  hint = input.required(...(ngDevMode ? [{ debugName: "hint" }] : []));
960
- onSuccess = output();
1208
+ /** Event emitter for successful MFA assertion. */
1209
+ onSuccess = new EventEmitter();
961
1210
  verification = signal(null, ...(ngDevMode ? [{ debugName: "verification" }] : []));
962
1211
  handlePhoneSubmit(verificationId) {
963
1212
  this.verification.set({ verificationId });
964
1213
  }
965
1214
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorAssertionFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
966
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SmsMultiFactorAssertionFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-assertion-form", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSuccess: "onSuccess" }, ngImport: i0, template: `
1215
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SmsMultiFactorAssertionFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-assertion-form", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSuccess: "onSuccess" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
967
1216
  <div class="fui-content">
968
1217
  @if (verification()) {
969
1218
  <fui-sms-multi-factor-assertion-verify-form
970
1219
  [verificationId]="verification()!.verificationId"
971
- (onSuccess)="onSuccess.emit()"
1220
+ (onSuccess)="onSuccess.emit($event)"
972
1221
  />
973
1222
  } @else {
974
1223
  <fui-sms-multi-factor-assertion-phone-form [hint]="hint()" (onSubmit)="handlePhoneSubmit($event)" />
@@ -982,12 +1231,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
982
1231
  selector: "fui-sms-multi-factor-assertion-form",
983
1232
  standalone: true,
984
1233
  imports: [CommonModule, SmsMultiFactorAssertionPhoneFormComponent, SmsMultiFactorAssertionVerifyFormComponent],
1234
+ host: {
1235
+ style: "display: block;",
1236
+ },
985
1237
  template: `
986
1238
  <div class="fui-content">
987
1239
  @if (verification()) {
988
1240
  <fui-sms-multi-factor-assertion-verify-form
989
1241
  [verificationId]="verification()!.verificationId"
990
- (onSuccess)="onSuccess.emit()"
1242
+ (onSuccess)="onSuccess.emit($event)"
991
1243
  />
992
1244
  } @else {
993
1245
  <fui-sms-multi-factor-assertion-phone-form [hint]="hint()" (onSubmit)="handlePhoneSubmit($event)" />
@@ -995,7 +1247,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
995
1247
  </div>
996
1248
  `,
997
1249
  }]
998
- }], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: true }] }], onSuccess: [{ type: i0.Output, args: ["onSuccess"] }] } });
1250
+ }], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: true }] }], onSuccess: [{
1251
+ type: Output
1252
+ }] } });
999
1253
 
1000
1254
  /**
1001
1255
  * Copyright 2025 Google LLC
@@ -1011,14 +1265,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1011
1265
  * See the License for the specific language governing permissions and
1012
1266
  * limitations under the License.
1013
1267
  */
1268
+ /**
1269
+ * A form component for TOTP multi-factor authentication assertion.
1270
+ *
1271
+ * Allows users to enter a TOTP code from their authenticator app.
1272
+ */
1014
1273
  class TotpMultiFactorAssertionFormComponent {
1015
1274
  ui = injectUI();
1016
1275
  formSchema = injectMultiFactorTotpAuthVerifyFormSchema();
1276
+ /** The multi-factor info hint containing TOTP details. */
1017
1277
  hint = input.required(...(ngDevMode ? [{ debugName: "hint" }] : []));
1018
- onSuccess = output();
1278
+ /** Event emitter for successful MFA assertion. */
1279
+ onSuccess = new EventEmitter();
1019
1280
  verificationCodeLabel = injectTranslation("labels", "verificationCode");
1020
1281
  verifyCodeLabel = injectTranslation("labels", "verifyCode");
1021
- unknownErrorLabel = injectTranslation("errors", "unknownError");
1282
+ enterVerificationCodePrompt = injectTranslation("prompts", "enterVerificationCode");
1022
1283
  form = injectForm({
1023
1284
  defaultValues: {
1024
1285
  verificationCode: "",
@@ -1030,19 +1291,15 @@ class TotpMultiFactorAssertionFormComponent {
1030
1291
  this.form.update({
1031
1292
  validators: {
1032
1293
  onBlur: this.formSchema(),
1033
- onSubmit: this.formSchema(),
1034
1294
  onSubmitAsync: async ({ value }) => {
1035
1295
  try {
1036
1296
  const assertion = TotpMultiFactorGenerator.assertionForSignIn(this.hint().uid, value.verificationCode);
1037
- await signInWithMultiFactorAssertion(this.ui(), assertion);
1038
- this.onSuccess.emit();
1297
+ const result = await signInWithMultiFactorAssertion(this.ui(), assertion);
1298
+ this.onSuccess.emit(result);
1039
1299
  return;
1040
1300
  }
1041
1301
  catch (error) {
1042
- if (error instanceof FirebaseUIError) {
1043
- return error.message;
1044
- }
1045
- return this.unknownErrorLabel();
1302
+ return error instanceof FirebaseUIError ? error.message : String(error);
1046
1303
  }
1047
1304
  },
1048
1305
  },
@@ -1055,14 +1312,15 @@ class TotpMultiFactorAssertionFormComponent {
1055
1312
  this.form.handleSubmit();
1056
1313
  }
1057
1314
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorAssertionFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1058
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: TotpMultiFactorAssertionFormComponent, isStandalone: true, selector: "fui-totp-multi-factor-assertion-form", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSuccess: "onSuccess" }, ngImport: i0, template: `
1315
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: TotpMultiFactorAssertionFormComponent, isStandalone: true, selector: "fui-totp-multi-factor-assertion-form", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSuccess: "onSuccess" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1059
1316
  <form (submit)="handleSubmit($event)" class="fui-form">
1060
1317
  <fieldset>
1061
1318
  <fui-form-input
1062
1319
  name="verificationCode"
1063
1320
  tanstack-app-field
1064
1321
  [tanstackField]="form"
1065
- label="{{ verificationCodeLabel() }}"
1322
+ [label]="verificationCodeLabel()"
1323
+ [description]="enterVerificationCodePrompt()"
1066
1324
  type="text"
1067
1325
  placeholder="123456"
1068
1326
  maxlength="6"
@@ -1075,13 +1333,16 @@ class TotpMultiFactorAssertionFormComponent {
1075
1333
  <fui-form-error-message [state]="state()" />
1076
1334
  </fieldset>
1077
1335
  </form>
1078
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
1336
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
1079
1337
  }
1080
1338
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorAssertionFormComponent, decorators: [{
1081
1339
  type: Component,
1082
1340
  args: [{
1083
1341
  selector: "fui-totp-multi-factor-assertion-form",
1084
1342
  standalone: true,
1343
+ host: {
1344
+ style: "display: block;",
1345
+ },
1085
1346
  imports: [
1086
1347
  CommonModule,
1087
1348
  TanStackField,
@@ -1097,7 +1358,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1097
1358
  name="verificationCode"
1098
1359
  tanstack-app-field
1099
1360
  [tanstackField]="form"
1100
- label="{{ verificationCodeLabel() }}"
1361
+ [label]="verificationCodeLabel()"
1362
+ [description]="enterVerificationCodePrompt()"
1101
1363
  type="text"
1102
1364
  placeholder="123456"
1103
1365
  maxlength="6"
@@ -1112,7 +1374,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1112
1374
  </form>
1113
1375
  `,
1114
1376
  }]
1115
- }], ctorParameters: () => [], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: true }] }], onSuccess: [{ type: i0.Output, args: ["onSuccess"] }] } });
1377
+ }], ctorParameters: () => [], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: true }] }], onSuccess: [{
1378
+ type: Output
1379
+ }] } });
1116
1380
 
1117
1381
  /**
1118
1382
  * Copyright 2025 Google LLC
@@ -1129,8 +1393,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1129
1393
  * See the License for the specific language governing permissions and
1130
1394
  * limitations under the License.
1131
1395
  */
1396
+ /**
1397
+ * A form component for multi-factor authentication assertion.
1398
+ *
1399
+ * Allows users to select and complete MFA verification using SMS or TOTP.
1400
+ */
1132
1401
  class MultiFactorAuthAssertionFormComponent {
1133
1402
  ui = injectUI();
1403
+ constructor() {
1404
+ effect((onCleanup) => {
1405
+ // Cleanup the multi-factor resolver when the component unmounts.
1406
+ onCleanup(() => {
1407
+ this.ui().setMultiFactorResolver();
1408
+ });
1409
+ });
1410
+ }
1411
+ /** Event emitter for successful MFA assertion. */
1412
+ onSuccess = new EventEmitter();
1134
1413
  resolver = computed(() => {
1135
1414
  const resolver = this.ui().multiFactorResolver;
1136
1415
  if (!resolver) {
@@ -1139,30 +1418,31 @@ class MultiFactorAuthAssertionFormComponent {
1139
1418
  return resolver;
1140
1419
  }, ...(ngDevMode ? [{ debugName: "resolver" }] : []));
1141
1420
  selectedHint = signal(this.resolver().hints.length === 1 ? this.resolver().hints[0] : undefined, ...(ngDevMode ? [{ debugName: "selectedHint" }] : []));
1142
- phoneFactorId = computed(() => PhoneMultiFactorGenerator.FACTOR_ID, ...(ngDevMode ? [{ debugName: "phoneFactorId" }] : []));
1143
- totpFactorId = computed(() => TotpMultiFactorGenerator.FACTOR_ID, ...(ngDevMode ? [{ debugName: "totpFactorId" }] : []));
1421
+ phoneFactorId = PhoneMultiFactorGenerator.FACTOR_ID;
1422
+ totpFactorId = TotpMultiFactorGenerator.FACTOR_ID;
1144
1423
  smsVerificationLabel = injectTranslation("labels", "mfaSmsVerification");
1145
1424
  totpVerificationLabel = injectTranslation("labels", "mfaTotpVerification");
1425
+ mfaAssertionFactorPrompt = injectTranslation("prompts", "mfaAssertionFactorPrompt");
1146
1426
  selectHint(hint) {
1147
1427
  this.selectedHint.set(hint);
1148
1428
  }
1149
1429
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthAssertionFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1150
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MultiFactorAuthAssertionFormComponent, isStandalone: true, selector: "fui-multi-factor-auth-assertion-form", ngImport: i0, template: `
1430
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MultiFactorAuthAssertionFormComponent, isStandalone: true, selector: "fui-multi-factor-auth-assertion-form", outputs: { onSuccess: "onSuccess" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1151
1431
  <div class="fui-content">
1152
1432
  @if (selectedHint()) {
1153
- @if (selectedHint()!.factorId === phoneFactorId()) {
1154
- <fui-sms-multi-factor-assertion-form [hint]="selectedHint()!" />
1155
- } @else if (selectedHint()!.factorId === totpFactorId()) {
1156
- <fui-totp-multi-factor-assertion-form [hint]="selectedHint()!" />
1433
+ @if (selectedHint()!.factorId === phoneFactorId) {
1434
+ <fui-sms-multi-factor-assertion-form [hint]="selectedHint()!" (onSuccess)="onSuccess.emit($event)" />
1435
+ } @else if (selectedHint()!.factorId === totpFactorId) {
1436
+ <fui-totp-multi-factor-assertion-form [hint]="selectedHint()!" (onSuccess)="onSuccess.emit($event)" />
1157
1437
  }
1158
1438
  } @else {
1159
- <p>TODO: Select a multi-factor authentication method</p>
1439
+ <p>{{ mfaAssertionFactorPrompt() }}</p>
1160
1440
  @for (hint of resolver().hints; track hint.factorId) {
1161
- @if (hint.factorId === totpFactorId()) {
1441
+ @if (hint.factorId === totpFactorId) {
1162
1442
  <button fui-button (click)="selectHint(hint)">
1163
1443
  {{ totpVerificationLabel() }}
1164
1444
  </button>
1165
- } @else if (hint.factorId === phoneFactorId()) {
1445
+ } @else if (hint.factorId === phoneFactorId) {
1166
1446
  <button fui-button (click)="selectHint(hint)">
1167
1447
  {{ smsVerificationLabel() }}
1168
1448
  </button>
@@ -1178,22 +1458,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1178
1458
  selector: "fui-multi-factor-auth-assertion-form",
1179
1459
  standalone: true,
1180
1460
  imports: [CommonModule, SmsMultiFactorAssertionFormComponent, TotpMultiFactorAssertionFormComponent, ButtonComponent],
1461
+ host: {
1462
+ style: "display: block;",
1463
+ },
1181
1464
  template: `
1182
1465
  <div class="fui-content">
1183
1466
  @if (selectedHint()) {
1184
- @if (selectedHint()!.factorId === phoneFactorId()) {
1185
- <fui-sms-multi-factor-assertion-form [hint]="selectedHint()!" />
1186
- } @else if (selectedHint()!.factorId === totpFactorId()) {
1187
- <fui-totp-multi-factor-assertion-form [hint]="selectedHint()!" />
1467
+ @if (selectedHint()!.factorId === phoneFactorId) {
1468
+ <fui-sms-multi-factor-assertion-form [hint]="selectedHint()!" (onSuccess)="onSuccess.emit($event)" />
1469
+ } @else if (selectedHint()!.factorId === totpFactorId) {
1470
+ <fui-totp-multi-factor-assertion-form [hint]="selectedHint()!" (onSuccess)="onSuccess.emit($event)" />
1188
1471
  }
1189
1472
  } @else {
1190
- <p>TODO: Select a multi-factor authentication method</p>
1473
+ <p>{{ mfaAssertionFactorPrompt() }}</p>
1191
1474
  @for (hint of resolver().hints; track hint.factorId) {
1192
- @if (hint.factorId === totpFactorId()) {
1475
+ @if (hint.factorId === totpFactorId) {
1193
1476
  <button fui-button (click)="selectHint(hint)">
1194
1477
  {{ totpVerificationLabel() }}
1195
1478
  </button>
1196
- } @else if (hint.factorId === phoneFactorId()) {
1479
+ } @else if (hint.factorId === phoneFactorId) {
1197
1480
  <button fui-button (click)="selectHint(hint)">
1198
1481
  {{ smsVerificationLabel() }}
1199
1482
  </button>
@@ -1203,7 +1486,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1203
1486
  </div>
1204
1487
  `,
1205
1488
  }]
1206
- }] });
1489
+ }], ctorParameters: () => [], propDecorators: { onSuccess: [{
1490
+ type: Output
1491
+ }] } });
1207
1492
 
1208
1493
  /**
1209
1494
  * Copyright 2025 Google LLC
@@ -1220,9 +1505,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1220
1505
  * See the License for the specific language governing permissions and
1221
1506
  * limitations under the License.
1222
1507
  */
1508
+ /**
1509
+ * A country selector component for phone number input.
1510
+ *
1511
+ * Displays a dropdown with country flags, dial codes, and names for selecting a country.
1512
+ */
1223
1513
  class CountrySelectorComponent {
1224
1514
  countries = injectCountries();
1225
1515
  defaultCountry = injectDefaultCountry();
1516
+ /** The selected country code (two-way binding). */
1226
1517
  value = model(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
1227
1518
  selected = computed(() => {
1228
1519
  if (!this.value()) {
@@ -1237,7 +1528,7 @@ class CountrySelectorComponent {
1237
1528
  }
1238
1529
  }
1239
1530
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CountrySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1240
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: CountrySelectorComponent, isStandalone: true, selector: "fui-country-selector", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
1531
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: CountrySelectorComponent, isStandalone: true, selector: "fui-country-selector", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1241
1532
  <div class="fui-country-selector">
1242
1533
  <div class="fui-country-selector__wrapper">
1243
1534
  <span class="fui-country-selector__flag">{{ selected().emoji }}</span>
@@ -1248,7 +1539,7 @@ class CountrySelectorComponent {
1248
1539
  [ngModel]="selected().code"
1249
1540
  (ngModelChange)="handleCountryChange($event)"
1250
1541
  >
1251
- @for (country of countries(); track country.code) {
1542
+ @for (country of countries(); track $index) {
1252
1543
  <option [value]="country.code">{{ country.dialCode }} ({{ country.name }})</option>
1253
1544
  }
1254
1545
  </select>
@@ -1263,6 +1554,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1263
1554
  selector: "fui-country-selector",
1264
1555
  standalone: true,
1265
1556
  imports: [CommonModule, FormsModule],
1557
+ host: {
1558
+ style: "display: block;",
1559
+ },
1266
1560
  template: `
1267
1561
  <div class="fui-country-selector">
1268
1562
  <div class="fui-country-selector__wrapper">
@@ -1274,7 +1568,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1274
1568
  [ngModel]="selected().code"
1275
1569
  (ngModelChange)="handleCountryChange($event)"
1276
1570
  >
1277
- @for (country of countries(); track country.code) {
1571
+ @for (country of countries(); track $index) {
1278
1572
  <option [value]="country.code">{{ country.dialCode }} ({{ country.name }})</option>
1279
1573
  }
1280
1574
  </select>
@@ -1300,42 +1594,712 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1300
1594
  * See the License for the specific language governing permissions and
1301
1595
  * limitations under the License.
1302
1596
  */
1303
- class PhoneNumberFormComponent {
1597
+ /**
1598
+ * A form component for SMS multi-factor authentication enrollment.
1599
+ *
1600
+ * Manages the flow between phone number entry and verification code entry for MFA enrollment.
1601
+ */
1602
+ class SmsMultiFactorEnrollmentFormComponent {
1304
1603
  ui = injectUI();
1305
- formSchema = injectPhoneAuthFormSchema();
1306
- onSubmit = output();
1307
- country = signal(countryData[0].code, ...(ngDevMode ? [{ debugName: "country" }] : []));
1604
+ phoneFormSchema = injectMultiFactorPhoneAuthNumberFormSchema();
1605
+ verificationFormSchema = injectMultiFactorPhoneAuthVerifyFormSchema();
1606
+ defaultCountry = injectDefaultCountry();
1607
+ verificationId = signal(null, ...(ngDevMode ? [{ debugName: "verificationId" }] : []));
1608
+ country = signal(this.defaultCountry().code, ...(ngDevMode ? [{ debugName: "country" }] : []));
1609
+ displayName = signal("", ...(ngDevMode ? [{ debugName: "displayName" }] : []));
1610
+ displayNameLabel = injectTranslation("labels", "displayName");
1308
1611
  phoneNumberLabel = injectTranslation("labels", "phoneNumber");
1309
1612
  sendCodeLabel = injectTranslation("labels", "sendCode");
1310
- unknownErrorLabel = injectTranslation("errors", "unknownError");
1613
+ verificationCodeLabel = injectTranslation("labels", "verificationCode");
1614
+ verifyCodeLabel = injectTranslation("labels", "verifyCode");
1615
+ smsVerificationPrompt = injectTranslation("prompts", "smsVerificationPrompt");
1616
+ /** Event emitter fired when MFA enrollment is completed. */
1617
+ onEnrollment = new EventEmitter();
1311
1618
  recaptchaContainer = viewChild.required("recaptchaContainer");
1312
1619
  recaptchaVerifier = injectRecaptchaVerifier(() => this.recaptchaContainer());
1313
- form = injectForm({
1620
+ phoneForm = injectForm({
1314
1621
  defaultValues: {
1622
+ displayName: "",
1315
1623
  phoneNumber: "",
1316
1624
  },
1317
1625
  });
1318
- state = injectStore(this.form, (state) => state);
1626
+ verificationForm = injectForm({
1627
+ defaultValues: {
1628
+ verificationCode: "",
1629
+ },
1630
+ });
1631
+ phoneState = injectStore(this.phoneForm, (state) => state);
1632
+ verificationState = injectStore(this.verificationForm, (state) => state);
1319
1633
  constructor() {
1634
+ if (!this.ui().auth.currentUser) {
1635
+ throw new Error("User must be authenticated to enroll with multi-factor authentication");
1636
+ }
1320
1637
  effect(() => {
1321
- this.form.update({
1638
+ this.phoneForm.update({
1322
1639
  validators: {
1323
- onBlur: this.formSchema(),
1324
- onSubmit: this.formSchema(),
1640
+ onBlur: this.phoneFormSchema(),
1641
+ onSubmit: this.phoneFormSchema(),
1325
1642
  onSubmitAsync: async ({ value }) => {
1326
- const selectedCountry = countryData.find((c) => c.code === this.country());
1327
- const formattedNumber = formatPhoneNumber(value.phoneNumber, selectedCountry);
1328
1643
  try {
1329
1644
  const verifier = this.recaptchaVerifier();
1330
1645
  if (!verifier) {
1331
- return this.unknownErrorLabel();
1646
+ return "Recaptcha verifier not available";
1332
1647
  }
1333
- const verificationId = await verifyPhoneNumber(this.ui(), formattedNumber, verifier);
1334
- this.onSubmit.emit({ verificationId, phoneNumber: formattedNumber });
1648
+ const currentUser = this.ui().auth.currentUser;
1649
+ const mfaUser = multiFactor(currentUser);
1650
+ const formattedPhoneNumber = formatPhoneNumber(value.phoneNumber, this.defaultCountry());
1651
+ const verificationId = await verifyPhoneNumber(this.ui(), formattedPhoneNumber, verifier, mfaUser);
1652
+ this.displayName.set(value.displayName);
1653
+ this.verificationId.set(verificationId);
1335
1654
  return;
1336
1655
  }
1337
1656
  catch (error) {
1338
- if (error instanceof FirebaseUIError) {
1657
+ return error instanceof FirebaseUIError ? error.message : String(error);
1658
+ }
1659
+ },
1660
+ },
1661
+ });
1662
+ });
1663
+ effect(() => {
1664
+ this.verificationForm.update({
1665
+ validators: {
1666
+ onBlur: this.verificationFormSchema(),
1667
+ onSubmit: this.verificationFormSchema(),
1668
+ onSubmitAsync: async ({ value }) => {
1669
+ try {
1670
+ const credential = PhoneAuthProvider.credential(this.verificationId(), value.verificationCode);
1671
+ const assertion = PhoneMultiFactorGenerator.assertion(credential);
1672
+ await enrollWithMultiFactorAssertion(this.ui(), assertion, this.displayName());
1673
+ this.onEnrollment.emit();
1674
+ return;
1675
+ }
1676
+ catch (error) {
1677
+ return error instanceof FirebaseUIError ? error.message : String(error);
1678
+ }
1679
+ },
1680
+ },
1681
+ });
1682
+ });
1683
+ }
1684
+ async handlePhoneSubmit(event) {
1685
+ event.preventDefault();
1686
+ event.stopPropagation();
1687
+ this.phoneForm.handleSubmit();
1688
+ }
1689
+ async handleVerificationSubmit(event) {
1690
+ event.preventDefault();
1691
+ event.stopPropagation();
1692
+ this.verificationForm.handleSubmit();
1693
+ }
1694
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorEnrollmentFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1695
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SmsMultiFactorEnrollmentFormComponent, isStandalone: true, selector: "fui-sms-multi-factor-enrollment-form", outputs: { onEnrollment: "onEnrollment" }, host: { styleAttribute: "display: block;" }, viewQueries: [{ propertyName: "recaptchaContainer", first: true, predicate: ["recaptchaContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
1696
+ <div class="fui-form-container">
1697
+ @if (!verificationId()) {
1698
+ <form (submit)="handlePhoneSubmit($event)" class="fui-form">
1699
+ <fieldset>
1700
+ <fui-form-input
1701
+ name="displayName"
1702
+ tanstack-app-field
1703
+ [tanstackField]="phoneForm"
1704
+ [label]="displayNameLabel()"
1705
+ type="text"
1706
+ />
1707
+ </fieldset>
1708
+ <fieldset>
1709
+ <fui-form-input
1710
+ name="phoneNumber"
1711
+ tanstack-app-field
1712
+ [tanstackField]="phoneForm"
1713
+ [label]="phoneNumberLabel()"
1714
+ type="tel"
1715
+ >
1716
+ <fui-country-selector [(value)]="country" ngProjectAs="input-before" />
1717
+ </fui-form-input>
1718
+ </fieldset>
1719
+ <fieldset>
1720
+ <div class="fui-recaptcha-container" #recaptchaContainer></div>
1721
+ </fieldset>
1722
+ <fieldset>
1723
+ <fui-form-submit [state]="phoneState()">
1724
+ {{ sendCodeLabel() }}
1725
+ </fui-form-submit>
1726
+ <fui-form-error-message [state]="phoneState()" />
1727
+ </fieldset>
1728
+ </form>
1729
+ } @else {
1730
+ <form (submit)="handleVerificationSubmit($event)" class="fui-form">
1731
+ <fieldset>
1732
+ <fui-form-input
1733
+ name="verificationCode"
1734
+ tanstack-app-field
1735
+ [tanstackField]="verificationForm"
1736
+ [label]="verificationCodeLabel()"
1737
+ [description]="smsVerificationPrompt()"
1738
+ type="text"
1739
+ ></fui-form-input>
1740
+ </fieldset>
1741
+ <fieldset>
1742
+ <fui-form-submit [state]="verificationState()">
1743
+ {{ verifyCodeLabel() }}
1744
+ </fui-form-submit>
1745
+ <fui-form-error-message [state]="verificationState()" />
1746
+ </fieldset>
1747
+ </form>
1748
+ }
1749
+ </div>
1750
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: CountrySelectorComponent, selector: "fui-country-selector", inputs: ["value"], outputs: ["valueChange"] }] });
1751
+ }
1752
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SmsMultiFactorEnrollmentFormComponent, decorators: [{
1753
+ type: Component,
1754
+ args: [{
1755
+ selector: "fui-sms-multi-factor-enrollment-form",
1756
+ standalone: true,
1757
+ host: {
1758
+ style: "display: block;",
1759
+ },
1760
+ imports: [
1761
+ CommonModule,
1762
+ TanStackField,
1763
+ TanStackAppField,
1764
+ FormInputComponent,
1765
+ FormSubmitComponent,
1766
+ FormErrorMessageComponent,
1767
+ CountrySelectorComponent,
1768
+ ],
1769
+ template: `
1770
+ <div class="fui-form-container">
1771
+ @if (!verificationId()) {
1772
+ <form (submit)="handlePhoneSubmit($event)" class="fui-form">
1773
+ <fieldset>
1774
+ <fui-form-input
1775
+ name="displayName"
1776
+ tanstack-app-field
1777
+ [tanstackField]="phoneForm"
1778
+ [label]="displayNameLabel()"
1779
+ type="text"
1780
+ />
1781
+ </fieldset>
1782
+ <fieldset>
1783
+ <fui-form-input
1784
+ name="phoneNumber"
1785
+ tanstack-app-field
1786
+ [tanstackField]="phoneForm"
1787
+ [label]="phoneNumberLabel()"
1788
+ type="tel"
1789
+ >
1790
+ <fui-country-selector [(value)]="country" ngProjectAs="input-before" />
1791
+ </fui-form-input>
1792
+ </fieldset>
1793
+ <fieldset>
1794
+ <div class="fui-recaptcha-container" #recaptchaContainer></div>
1795
+ </fieldset>
1796
+ <fieldset>
1797
+ <fui-form-submit [state]="phoneState()">
1798
+ {{ sendCodeLabel() }}
1799
+ </fui-form-submit>
1800
+ <fui-form-error-message [state]="phoneState()" />
1801
+ </fieldset>
1802
+ </form>
1803
+ } @else {
1804
+ <form (submit)="handleVerificationSubmit($event)" class="fui-form">
1805
+ <fieldset>
1806
+ <fui-form-input
1807
+ name="verificationCode"
1808
+ tanstack-app-field
1809
+ [tanstackField]="verificationForm"
1810
+ [label]="verificationCodeLabel()"
1811
+ [description]="smsVerificationPrompt()"
1812
+ type="text"
1813
+ ></fui-form-input>
1814
+ </fieldset>
1815
+ <fieldset>
1816
+ <fui-form-submit [state]="verificationState()">
1817
+ {{ verifyCodeLabel() }}
1818
+ </fui-form-submit>
1819
+ <fui-form-error-message [state]="verificationState()" />
1820
+ </fieldset>
1821
+ </form>
1822
+ }
1823
+ </div>
1824
+ `,
1825
+ }]
1826
+ }], ctorParameters: () => [], propDecorators: { onEnrollment: [{
1827
+ type: Output
1828
+ }], recaptchaContainer: [{ type: i0.ViewChild, args: ["recaptchaContainer", { isSignal: true }] }] } });
1829
+
1830
+ /**
1831
+ * Copyright 2025 Google LLC
1832
+ *
1833
+ * Licensed under the Apache License, Version 2.0 (the "License");
1834
+ * you may not use this file except in compliance with the License.
1835
+ * You may obtain a copy of the License at
1836
+ *
1837
+ * http://www.apache.org/licenses/LICENSE-2.0
1838
+ *
1839
+ * Unless required by applicable law or agreed to in writing, software
1840
+ * distributed under the License is distributed on an "AS IS" BASIS,
1841
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1842
+ * See the License for the specific language governing permissions and
1843
+ * limitations under the License.
1844
+ */
1845
+ /**
1846
+ * A form component for generating a TOTP secret and display name during MFA enrollment.
1847
+ */
1848
+ class TotpMultiFactorSecretGenerationFormComponent {
1849
+ ui = injectUI();
1850
+ formSchema = injectMultiFactorTotpAuthNumberFormSchema();
1851
+ /** Event emitter fired when TOTP secret is generated. */
1852
+ onSubmit = new EventEmitter();
1853
+ displayNameLabel = injectTranslation("labels", "displayName");
1854
+ generateQrCodeLabel = injectTranslation("labels", "generateQrCode");
1855
+ form = injectForm({
1856
+ defaultValues: {
1857
+ displayName: "",
1858
+ },
1859
+ });
1860
+ state = injectStore(this.form, (state) => state);
1861
+ constructor() {
1862
+ effect(() => {
1863
+ this.form.update({
1864
+ validators: {
1865
+ onBlur: this.formSchema(),
1866
+ onSubmit: this.formSchema(),
1867
+ onSubmitAsync: async ({ value }) => {
1868
+ try {
1869
+ const secret = await generateTotpSecret(this.ui());
1870
+ this.onSubmit.emit({ secret, displayName: value.displayName });
1871
+ return;
1872
+ }
1873
+ catch (error) {
1874
+ return error instanceof FirebaseUIError ? error.message : String(error);
1875
+ }
1876
+ },
1877
+ },
1878
+ });
1879
+ });
1880
+ }
1881
+ async handleSubmit(event) {
1882
+ event.preventDefault();
1883
+ event.stopPropagation();
1884
+ this.form.handleSubmit();
1885
+ }
1886
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorSecretGenerationFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1887
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: TotpMultiFactorSecretGenerationFormComponent, isStandalone: true, selector: "fui-totp-multi-factor-secret-generation-form", outputs: { onSubmit: "onSubmit" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1888
+ <form (submit)="handleSubmit($event)" class="fui-form">
1889
+ <fieldset>
1890
+ <fui-form-input
1891
+ name="displayName"
1892
+ tanstack-app-field
1893
+ [tanstackField]="form"
1894
+ [label]="displayNameLabel()"
1895
+ type="text"
1896
+ ></fui-form-input>
1897
+ </fieldset>
1898
+ <fieldset>
1899
+ <fui-form-submit [state]="state()">
1900
+ {{ generateQrCodeLabel() }}
1901
+ </fui-form-submit>
1902
+ <fui-form-error-message [state]="state()" />
1903
+ </fieldset>
1904
+ </form>
1905
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
1906
+ }
1907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorSecretGenerationFormComponent, decorators: [{
1908
+ type: Component,
1909
+ args: [{
1910
+ selector: "fui-totp-multi-factor-secret-generation-form",
1911
+ standalone: true,
1912
+ host: {
1913
+ style: "display: block;",
1914
+ },
1915
+ imports: [
1916
+ CommonModule,
1917
+ TanStackField,
1918
+ TanStackAppField,
1919
+ FormInputComponent,
1920
+ FormSubmitComponent,
1921
+ FormErrorMessageComponent,
1922
+ ],
1923
+ template: `
1924
+ <form (submit)="handleSubmit($event)" class="fui-form">
1925
+ <fieldset>
1926
+ <fui-form-input
1927
+ name="displayName"
1928
+ tanstack-app-field
1929
+ [tanstackField]="form"
1930
+ [label]="displayNameLabel()"
1931
+ type="text"
1932
+ ></fui-form-input>
1933
+ </fieldset>
1934
+ <fieldset>
1935
+ <fui-form-submit [state]="state()">
1936
+ {{ generateQrCodeLabel() }}
1937
+ </fui-form-submit>
1938
+ <fui-form-error-message [state]="state()" />
1939
+ </fieldset>
1940
+ </form>
1941
+ `,
1942
+ }]
1943
+ }], ctorParameters: () => [], propDecorators: { onSubmit: [{
1944
+ type: Output
1945
+ }] } });
1946
+ /**
1947
+ * A form component for verifying TOTP code during MFA enrollment.
1948
+ *
1949
+ * Displays a QR code and allows users to verify their authenticator app setup.
1950
+ */
1951
+ class TotpMultiFactorVerificationFormComponent {
1952
+ ui = injectUI();
1953
+ formSchema = injectMultiFactorTotpAuthVerifyFormSchema();
1954
+ /** The TOTP secret generated in the previous step. */
1955
+ secret = input.required(...(ngDevMode ? [{ debugName: "secret" }] : []));
1956
+ /** The display name for the TOTP factor. */
1957
+ displayName = input.required(...(ngDevMode ? [{ debugName: "displayName" }] : []));
1958
+ /** Event emitter fired when MFA enrollment is completed. */
1959
+ onEnrollment = new EventEmitter();
1960
+ verificationCodeLabel = injectTranslation("labels", "verificationCode");
1961
+ verifyCodeLabel = injectTranslation("labels", "verifyCode");
1962
+ mfaTotpQrCodePrompt = injectTranslation("prompts", "mfaTotpQrCodePrompt");
1963
+ mfaTotpEnrollmentVerificationPrompt = injectTranslation("prompts", "mfaTotpEnrollmentVerificationPrompt");
1964
+ form = injectForm({
1965
+ defaultValues: {
1966
+ verificationCode: "",
1967
+ },
1968
+ });
1969
+ state = injectStore(this.form, (state) => state);
1970
+ qrCodeDataUrl = computed(() => {
1971
+ return generateTotpQrCode(this.ui(), this.secret(), this.displayName());
1972
+ }, ...(ngDevMode ? [{ debugName: "qrCodeDataUrl" }] : []));
1973
+ constructor() {
1974
+ effect(() => {
1975
+ this.form.update({
1976
+ validators: {
1977
+ onBlur: this.formSchema(),
1978
+ onSubmit: this.formSchema(),
1979
+ onSubmitAsync: async ({ value }) => {
1980
+ try {
1981
+ const assertion = TotpMultiFactorGenerator.assertionForEnrollment(this.secret(), value.verificationCode);
1982
+ await enrollWithMultiFactorAssertion(this.ui(), assertion, this.displayName());
1983
+ this.onEnrollment.emit();
1984
+ return;
1985
+ }
1986
+ catch (error) {
1987
+ return error instanceof FirebaseUIError ? error.message : String(error);
1988
+ }
1989
+ },
1990
+ },
1991
+ });
1992
+ });
1993
+ }
1994
+ async handleSubmit(event) {
1995
+ event.preventDefault();
1996
+ event.stopPropagation();
1997
+ this.form.handleSubmit();
1998
+ }
1999
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorVerificationFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2000
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: TotpMultiFactorVerificationFormComponent, isStandalone: true, selector: "fui-totp-multi-factor-verification-form", inputs: { secret: { classPropertyName: "secret", publicName: "secret", isSignal: true, isRequired: true, transformFunction: null }, displayName: { classPropertyName: "displayName", publicName: "displayName", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onEnrollment: "onEnrollment" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
2001
+ <div class="fui-qr-code-container">
2002
+ <img [src]="qrCodeDataUrl()" alt="TOTP QR Code" />
2003
+ <code>{{ secret().secretKey.toString() }}</code>
2004
+ <p>{{ mfaTotpQrCodePrompt() }}</p>
2005
+ </div>
2006
+ <form (submit)="handleSubmit($event)" class="fui-form">
2007
+ <fieldset>
2008
+ <fui-form-input
2009
+ name="verificationCode"
2010
+ tanstack-app-field
2011
+ [tanstackField]="form"
2012
+ [label]="verificationCodeLabel()"
2013
+ [description]="mfaTotpEnrollmentVerificationPrompt()"
2014
+ type="text"
2015
+ ></fui-form-input>
2016
+ </fieldset>
2017
+ <fieldset>
2018
+ <fui-form-submit [state]="state()">
2019
+ {{ verifyCodeLabel() }}
2020
+ </fui-form-submit>
2021
+ <fui-form-error-message [state]="state()" />
2022
+ </fieldset>
2023
+ </form>
2024
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
2025
+ }
2026
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorVerificationFormComponent, decorators: [{
2027
+ type: Component,
2028
+ args: [{
2029
+ selector: "fui-totp-multi-factor-verification-form",
2030
+ standalone: true,
2031
+ host: {
2032
+ style: "display: block;",
2033
+ },
2034
+ imports: [
2035
+ CommonModule,
2036
+ TanStackField,
2037
+ TanStackAppField,
2038
+ FormInputComponent,
2039
+ FormSubmitComponent,
2040
+ FormErrorMessageComponent,
2041
+ ],
2042
+ template: `
2043
+ <div class="fui-qr-code-container">
2044
+ <img [src]="qrCodeDataUrl()" alt="TOTP QR Code" />
2045
+ <code>{{ secret().secretKey.toString() }}</code>
2046
+ <p>{{ mfaTotpQrCodePrompt() }}</p>
2047
+ </div>
2048
+ <form (submit)="handleSubmit($event)" class="fui-form">
2049
+ <fieldset>
2050
+ <fui-form-input
2051
+ name="verificationCode"
2052
+ tanstack-app-field
2053
+ [tanstackField]="form"
2054
+ [label]="verificationCodeLabel()"
2055
+ [description]="mfaTotpEnrollmentVerificationPrompt()"
2056
+ type="text"
2057
+ ></fui-form-input>
2058
+ </fieldset>
2059
+ <fieldset>
2060
+ <fui-form-submit [state]="state()">
2061
+ {{ verifyCodeLabel() }}
2062
+ </fui-form-submit>
2063
+ <fui-form-error-message [state]="state()" />
2064
+ </fieldset>
2065
+ </form>
2066
+ `,
2067
+ }]
2068
+ }], ctorParameters: () => [], propDecorators: { secret: [{ type: i0.Input, args: [{ isSignal: true, alias: "secret", required: true }] }], displayName: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayName", required: true }] }], onEnrollment: [{
2069
+ type: Output
2070
+ }] } });
2071
+ /**
2072
+ * A form component for TOTP multi-factor authentication enrollment.
2073
+ *
2074
+ * Manages the flow between secret generation and verification for TOTP MFA enrollment.
2075
+ */
2076
+ class TotpMultiFactorEnrollmentFormComponent {
2077
+ ui = injectUI();
2078
+ enrollment = signal(null, ...(ngDevMode ? [{ debugName: "enrollment" }] : []));
2079
+ /** Event emitter fired when MFA enrollment is completed. */
2080
+ onEnrollment = new EventEmitter();
2081
+ constructor() {
2082
+ if (!this.ui().auth.currentUser) {
2083
+ throw new Error("User must be authenticated to enroll with multi-factor authentication");
2084
+ }
2085
+ }
2086
+ handleSecretGeneration(data) {
2087
+ this.enrollment.set(data);
2088
+ }
2089
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorEnrollmentFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2090
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: TotpMultiFactorEnrollmentFormComponent, isStandalone: true, selector: "fui-totp-multi-factor-enrollment-form", outputs: { onEnrollment: "onEnrollment" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
2091
+ <div class="fui-form-container">
2092
+ @if (!enrollment()) {
2093
+ <fui-totp-multi-factor-secret-generation-form (onSubmit)="handleSecretGeneration($event)" />
2094
+ } @else {
2095
+ <fui-totp-multi-factor-verification-form
2096
+ [secret]="enrollment()!.secret"
2097
+ [displayName]="enrollment()!.displayName"
2098
+ (onEnrollment)="onEnrollment.emit()"
2099
+ />
2100
+ }
2101
+ </div>
2102
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TotpMultiFactorSecretGenerationFormComponent, selector: "fui-totp-multi-factor-secret-generation-form", outputs: ["onSubmit"] }, { kind: "component", type: TotpMultiFactorVerificationFormComponent, selector: "fui-totp-multi-factor-verification-form", inputs: ["secret", "displayName"], outputs: ["onEnrollment"] }] });
2103
+ }
2104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TotpMultiFactorEnrollmentFormComponent, decorators: [{
2105
+ type: Component,
2106
+ args: [{
2107
+ selector: "fui-totp-multi-factor-enrollment-form",
2108
+ standalone: true,
2109
+ imports: [CommonModule, TotpMultiFactorSecretGenerationFormComponent, TotpMultiFactorVerificationFormComponent],
2110
+ host: {
2111
+ style: "display: block;",
2112
+ },
2113
+ template: `
2114
+ <div class="fui-form-container">
2115
+ @if (!enrollment()) {
2116
+ <fui-totp-multi-factor-secret-generation-form (onSubmit)="handleSecretGeneration($event)" />
2117
+ } @else {
2118
+ <fui-totp-multi-factor-verification-form
2119
+ [secret]="enrollment()!.secret"
2120
+ [displayName]="enrollment()!.displayName"
2121
+ (onEnrollment)="onEnrollment.emit()"
2122
+ />
2123
+ }
2124
+ </div>
2125
+ `,
2126
+ }]
2127
+ }], ctorParameters: () => [], propDecorators: { onEnrollment: [{
2128
+ type: Output
2129
+ }] } });
2130
+
2131
+ /**
2132
+ * Copyright 2025 Google LLC
2133
+ *
2134
+ * Licensed under the Apache License, Version 2.0 (the "License");
2135
+ * you may not use this file except in compliance with the License.
2136
+ * You may obtain a copy of the License at
2137
+ *
2138
+ * http://www.apache.org/licenses/LICENSE-2.0
2139
+ *
2140
+ * Unless required by applicable law or agreed to in writing, software
2141
+ * distributed under the License is distributed on an "AS IS" BASIS,
2142
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2143
+ * See the License for the specific language governing permissions and
2144
+ * limitations under the License.
2145
+ */
2146
+ /**
2147
+ * A form component for multi-factor authentication enrollment.
2148
+ *
2149
+ * Allows users to enroll in MFA using SMS or TOTP methods.
2150
+ */
2151
+ class MultiFactorAuthEnrollmentFormComponent {
2152
+ /** The available MFA factor types for enrollment. */
2153
+ hints = input([FactorId.TOTP, FactorId.PHONE], ...(ngDevMode ? [{ debugName: "hints" }] : []));
2154
+ /** Event emitter fired when MFA enrollment is completed. */
2155
+ onEnrollment = new EventEmitter();
2156
+ selectedHint = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedHint" }] : []));
2157
+ phoneFactorId = FactorId.PHONE;
2158
+ totpFactorId = FactorId.TOTP;
2159
+ smsVerificationLabel = injectTranslation("labels", "mfaSmsVerification");
2160
+ totpVerificationLabel = injectTranslation("labels", "mfaTotpVerification");
2161
+ validatedHint = computed(() => {
2162
+ const hint = this.selectedHint();
2163
+ if (hint && hint !== this.phoneFactorId && hint !== this.totpFactorId) {
2164
+ throw new Error(`Unknown multi-factor enrollment type: ${hint}`);
2165
+ }
2166
+ return hint;
2167
+ }, ...(ngDevMode ? [{ debugName: "validatedHint" }] : []));
2168
+ ngOnInit() {
2169
+ const hints = this.hints();
2170
+ if (hints.length === 0) {
2171
+ throw new Error("MultiFactorAuthEnrollmentForm must have at least one hint");
2172
+ }
2173
+ // Auto-select single hint after component initialization
2174
+ if (hints.length === 1) {
2175
+ this.selectedHint.set(hints[0]);
2176
+ }
2177
+ }
2178
+ selectHint(hint) {
2179
+ this.selectedHint.set(hint);
2180
+ }
2181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthEnrollmentFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2182
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MultiFactorAuthEnrollmentFormComponent, isStandalone: true, selector: "fui-multi-factor-auth-enrollment-form", inputs: { hints: { classPropertyName: "hints", publicName: "hints", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onEnrollment: "onEnrollment" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
2183
+ <div class="fui-content">
2184
+ @if (validatedHint()) {
2185
+ @if (validatedHint() === phoneFactorId) {
2186
+ <fui-sms-multi-factor-enrollment-form (onEnrollment)="onEnrollment.emit()" />
2187
+ } @else if (validatedHint() === totpFactorId) {
2188
+ <fui-totp-multi-factor-enrollment-form (onEnrollment)="onEnrollment.emit()" />
2189
+ }
2190
+ } @else {
2191
+ @for (hint of hints(); track hint) {
2192
+ @if (hint === totpFactorId) {
2193
+ <button fui-button [variant]="'secondary'" (click)="selectHint(hint)">
2194
+ {{ totpVerificationLabel() }}
2195
+ </button>
2196
+ } @else if (hint === phoneFactorId) {
2197
+ <button fui-button [variant]="'secondary'" (click)="selectHint(hint)">
2198
+ {{ smsVerificationLabel() }}
2199
+ </button>
2200
+ }
2201
+ }
2202
+ }
2203
+ </div>
2204
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SmsMultiFactorEnrollmentFormComponent, selector: "fui-sms-multi-factor-enrollment-form", outputs: ["onEnrollment"] }, { kind: "component", type: TotpMultiFactorEnrollmentFormComponent, selector: "fui-totp-multi-factor-enrollment-form", outputs: ["onEnrollment"] }, { kind: "component", type: ButtonComponent, selector: "button[fui-button]", inputs: ["variant"] }] });
2205
+ }
2206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthEnrollmentFormComponent, decorators: [{
2207
+ type: Component,
2208
+ args: [{
2209
+ selector: "fui-multi-factor-auth-enrollment-form",
2210
+ standalone: true,
2211
+ host: {
2212
+ style: "display: block;",
2213
+ },
2214
+ imports: [
2215
+ CommonModule,
2216
+ SmsMultiFactorEnrollmentFormComponent,
2217
+ TotpMultiFactorEnrollmentFormComponent,
2218
+ ButtonComponent,
2219
+ ],
2220
+ template: `
2221
+ <div class="fui-content">
2222
+ @if (validatedHint()) {
2223
+ @if (validatedHint() === phoneFactorId) {
2224
+ <fui-sms-multi-factor-enrollment-form (onEnrollment)="onEnrollment.emit()" />
2225
+ } @else if (validatedHint() === totpFactorId) {
2226
+ <fui-totp-multi-factor-enrollment-form (onEnrollment)="onEnrollment.emit()" />
2227
+ }
2228
+ } @else {
2229
+ @for (hint of hints(); track hint) {
2230
+ @if (hint === totpFactorId) {
2231
+ <button fui-button [variant]="'secondary'" (click)="selectHint(hint)">
2232
+ {{ totpVerificationLabel() }}
2233
+ </button>
2234
+ } @else if (hint === phoneFactorId) {
2235
+ <button fui-button [variant]="'secondary'" (click)="selectHint(hint)">
2236
+ {{ smsVerificationLabel() }}
2237
+ </button>
2238
+ }
2239
+ }
2240
+ }
2241
+ </div>
2242
+ `,
2243
+ }]
2244
+ }], propDecorators: { hints: [{ type: i0.Input, args: [{ isSignal: true, alias: "hints", required: false }] }], onEnrollment: [{
2245
+ type: Output
2246
+ }] } });
2247
+
2248
+ /**
2249
+ * Copyright 2025 Google LLC
2250
+ *
2251
+ * Licensed under the Apache License, Version 2.0 (the "License");
2252
+ * you may not use this file except in compliance with the License.
2253
+ * You may obtain a copy of the License at
2254
+ *
2255
+ * http://www.apache.org/licenses/LICENSE-2.0
2256
+ *
2257
+ * Unless required by applicable law or agreed to in writing, software
2258
+ * distributed under the License is distributed on an "AS IS" BASIS,
2259
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2260
+ * See the License for the specific language governing permissions and
2261
+ * limitations under the License.
2262
+ */
2263
+ /**
2264
+ * A form component for entering a phone number and requesting a verification code.
2265
+ */
2266
+ class PhoneNumberFormComponent {
2267
+ ui = injectUI();
2268
+ formSchema = injectPhoneAuthFormSchema();
2269
+ /** Event emitter fired when phone number is verified and verification ID is received. */
2270
+ onSubmit = new EventEmitter();
2271
+ /** The selected country code for phone number formatting. */
2272
+ country = signal(countryData[0].code, ...(ngDevMode ? [{ debugName: "country" }] : []));
2273
+ phoneNumberLabel = injectTranslation("labels", "phoneNumber");
2274
+ sendCodeLabel = injectTranslation("labels", "sendCode");
2275
+ unknownErrorLabel = injectTranslation("errors", "unknownError");
2276
+ recaptchaContainer = viewChild.required("recaptchaContainer");
2277
+ recaptchaVerifier = injectRecaptchaVerifier(() => this.recaptchaContainer());
2278
+ form = injectForm({
2279
+ defaultValues: {
2280
+ phoneNumber: "",
2281
+ },
2282
+ });
2283
+ state = injectStore(this.form, (state) => state);
2284
+ constructor() {
2285
+ effect(() => {
2286
+ this.form.update({
2287
+ validators: {
2288
+ onBlur: this.formSchema(),
2289
+ onSubmitAsync: async ({ value }) => {
2290
+ const selectedCountry = countryData.find((c) => c.code === this.country());
2291
+ const formattedNumber = formatPhoneNumber(value.phoneNumber, selectedCountry);
2292
+ try {
2293
+ const verifier = this.recaptchaVerifier();
2294
+ if (!verifier) {
2295
+ return this.unknownErrorLabel();
2296
+ }
2297
+ const verificationId = await verifyPhoneNumber(this.ui(), formattedNumber, verifier);
2298
+ this.onSubmit.emit({ verificationId, phoneNumber: formattedNumber });
2299
+ return;
2300
+ }
2301
+ catch (error) {
2302
+ if (error instanceof FirebaseUIError) {
1339
2303
  return error.message;
1340
2304
  }
1341
2305
  return this.unknownErrorLabel();
@@ -1359,16 +2323,18 @@ class PhoneNumberFormComponent {
1359
2323
  this.form.handleSubmit();
1360
2324
  }
1361
2325
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PhoneNumberFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1362
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.7", type: PhoneNumberFormComponent, isStandalone: true, selector: "fui-phone-number-form", outputs: { onSubmit: "onSubmit" }, viewQueries: [{ propertyName: "recaptchaContainer", first: true, predicate: ["recaptchaContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
2326
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.7", type: PhoneNumberFormComponent, isStandalone: true, selector: "fui-phone-number-form", outputs: { onSubmit: "onSubmit" }, host: { styleAttribute: "display: block;" }, viewQueries: [{ propertyName: "recaptchaContainer", first: true, predicate: ["recaptchaContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
1363
2327
  <form (submit)="handleSubmit($event)" class="fui-form">
1364
2328
  <fieldset>
1365
- <fui-country-selector [(value)]="country"></fui-country-selector>
1366
2329
  <fui-form-input
1367
2330
  name="phoneNumber"
1368
2331
  tanstack-app-field
1369
2332
  [tanstackField]="form"
1370
- label="{{ phoneNumberLabel() }}"
1371
- ></fui-form-input>
2333
+ [label]="phoneNumberLabel()"
2334
+ type="tel"
2335
+ >
2336
+ <fui-country-selector [(value)]="country" ngProjectAs="input-before" />
2337
+ </fui-form-input>
1372
2338
  </fieldset>
1373
2339
  <fieldset>
1374
2340
  <div class="fui-recaptcha-container" #recaptchaContainer></div>
@@ -1381,13 +2347,16 @@ class PhoneNumberFormComponent {
1381
2347
  <fui-form-error-message [state]="state()" />
1382
2348
  </fieldset>
1383
2349
  </form>
1384
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: CountrySelectorComponent, selector: "fui-country-selector", inputs: ["value"], outputs: ["valueChange"] }] });
2350
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: CountrySelectorComponent, selector: "fui-country-selector", inputs: ["value"], outputs: ["valueChange"] }] });
1385
2351
  }
1386
2352
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PhoneNumberFormComponent, decorators: [{
1387
2353
  type: Component,
1388
2354
  args: [{
1389
2355
  selector: "fui-phone-number-form",
1390
2356
  standalone: true,
2357
+ host: {
2358
+ style: "display: block;",
2359
+ },
1391
2360
  imports: [
1392
2361
  CommonModule,
1393
2362
  TanStackField,
@@ -1401,13 +2370,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1401
2370
  template: `
1402
2371
  <form (submit)="handleSubmit($event)" class="fui-form">
1403
2372
  <fieldset>
1404
- <fui-country-selector [(value)]="country"></fui-country-selector>
1405
2373
  <fui-form-input
1406
2374
  name="phoneNumber"
1407
2375
  tanstack-app-field
1408
2376
  [tanstackField]="form"
1409
- label="{{ phoneNumberLabel() }}"
1410
- ></fui-form-input>
2377
+ [label]="phoneNumberLabel()"
2378
+ type="tel"
2379
+ >
2380
+ <fui-country-selector [(value)]="country" ngProjectAs="input-before" />
2381
+ </fui-form-input>
1411
2382
  </fieldset>
1412
2383
  <fieldset>
1413
2384
  <div class="fui-recaptcha-container" #recaptchaContainer></div>
@@ -1422,14 +2393,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1422
2393
  </form>
1423
2394
  `,
1424
2395
  }]
1425
- }], ctorParameters: () => [], propDecorators: { onSubmit: [{ type: i0.Output, args: ["onSubmit"] }], recaptchaContainer: [{ type: i0.ViewChild, args: ["recaptchaContainer", { isSignal: true }] }] } });
2396
+ }], ctorParameters: () => [], propDecorators: { onSubmit: [{
2397
+ type: Output
2398
+ }], recaptchaContainer: [{ type: i0.ViewChild, args: ["recaptchaContainer", { isSignal: true }] }] } });
2399
+ /**
2400
+ * A form component for entering and verifying the SMS verification code.
2401
+ */
1426
2402
  class VerificationFormComponent {
1427
2403
  ui = injectUI();
1428
2404
  formSchema = injectPhoneAuthVerifyFormSchema();
2405
+ /** The verification ID received from the phone number form. */
1429
2406
  verificationId = input.required(...(ngDevMode ? [{ debugName: "verificationId" }] : []));
1430
- signIn = output();
2407
+ /** Event emitter for successful sign-in. */
2408
+ signIn = new EventEmitter();
1431
2409
  verificationCodeLabel = injectTranslation("labels", "verificationCode");
1432
2410
  verifyCodeLabel = injectTranslation("labels", "verifyCode");
2411
+ smsVerificationPrompt = injectTranslation("prompts", "smsVerificationPrompt");
1433
2412
  unknownErrorLabel = injectTranslation("errors", "unknownError");
1434
2413
  form = injectForm({
1435
2414
  defaultValues: {
@@ -1446,7 +2425,6 @@ class VerificationFormComponent {
1446
2425
  this.form.update({
1447
2426
  validators: {
1448
2427
  onBlur: this.formSchema(),
1449
- onSubmit: this.formSchema(),
1450
2428
  onSubmitAsync: async ({ value }) => {
1451
2429
  try {
1452
2430
  const credential = await confirmPhoneNumber(this.ui(), this.verificationId(), value.verificationCode);
@@ -1470,14 +2448,16 @@ class VerificationFormComponent {
1470
2448
  this.form.handleSubmit();
1471
2449
  }
1472
2450
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: VerificationFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1473
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: VerificationFormComponent, isStandalone: true, selector: "fui-verification-form", inputs: { verificationId: { classPropertyName: "verificationId", publicName: "verificationId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { signIn: "signIn" }, ngImport: i0, template: `
2451
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: VerificationFormComponent, isStandalone: true, selector: "fui-verification-form", inputs: { verificationId: { classPropertyName: "verificationId", publicName: "verificationId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1474
2452
  <form (submit)="handleSubmit($event)" class="fui-form">
1475
2453
  <fieldset>
1476
2454
  <fui-form-input
1477
2455
  name="verificationCode"
1478
2456
  tanstack-app-field
1479
2457
  [tanstackField]="form"
1480
- label="{{ verificationCodeLabel() }}"
2458
+ [label]="verificationCodeLabel()"
2459
+ [description]="smsVerificationPrompt()"
2460
+ type="text"
1481
2461
  ></fui-form-input>
1482
2462
  </fieldset>
1483
2463
 
@@ -1490,13 +2470,16 @@ class VerificationFormComponent {
1490
2470
  <fui-form-error-message [state]="state()" />
1491
2471
  </fieldset>
1492
2472
  </form>
1493
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
2473
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }] });
1494
2474
  }
1495
2475
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: VerificationFormComponent, decorators: [{
1496
2476
  type: Component,
1497
2477
  args: [{
1498
2478
  selector: "fui-verification-form",
1499
2479
  standalone: true,
2480
+ host: {
2481
+ style: "display: block;",
2482
+ },
1500
2483
  imports: [
1501
2484
  CommonModule,
1502
2485
  TanStackField,
@@ -1513,7 +2496,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1513
2496
  name="verificationCode"
1514
2497
  tanstack-app-field
1515
2498
  [tanstackField]="form"
1516
- label="{{ verificationCodeLabel() }}"
2499
+ [label]="verificationCodeLabel()"
2500
+ [description]="smsVerificationPrompt()"
2501
+ type="text"
1517
2502
  ></fui-form-input>
1518
2503
  </fieldset>
1519
2504
 
@@ -1528,15 +2513,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1528
2513
  </form>
1529
2514
  `,
1530
2515
  }]
1531
- }], ctorParameters: () => [], propDecorators: { verificationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "verificationId", required: true }] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
2516
+ }], ctorParameters: () => [], propDecorators: { verificationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "verificationId", required: true }] }], signIn: [{
2517
+ type: Output
2518
+ }] } });
2519
+ /**
2520
+ * A form component for phone number authentication.
2521
+ *
2522
+ * Manages the flow between phone number entry and verification code entry.
2523
+ */
1532
2524
  class PhoneAuthFormComponent {
1533
2525
  verificationId = signal(null, ...(ngDevMode ? [{ debugName: "verificationId" }] : []));
1534
- signIn = output();
2526
+ /** Event emitter for successful sign-in. */
2527
+ signIn = new EventEmitter();
1535
2528
  handlePhoneSubmit(data) {
1536
2529
  this.verificationId.set(data.verificationId);
1537
2530
  }
1538
2531
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PhoneAuthFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1539
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PhoneAuthFormComponent, isStandalone: true, selector: "fui-phone-auth-form", outputs: { signIn: "signIn" }, ngImport: i0, template: `
2532
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PhoneAuthFormComponent, isStandalone: true, selector: "fui-phone-auth-form", outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1540
2533
  <div class="fui-form-container">
1541
2534
  @if (verificationId()) {
1542
2535
  <fui-verification-form [verificationId]="verificationId()!" (signIn)="signIn.emit($event)" />
@@ -1552,6 +2545,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1552
2545
  selector: "fui-phone-auth-form",
1553
2546
  standalone: true,
1554
2547
  imports: [CommonModule, PhoneNumberFormComponent, VerificationFormComponent],
2548
+ host: {
2549
+ style: "display: block;",
2550
+ },
1555
2551
  template: `
1556
2552
  <div class="fui-form-container">
1557
2553
  @if (verificationId()) {
@@ -1562,7 +2558,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1562
2558
  </div>
1563
2559
  `,
1564
2560
  }]
1565
- }], propDecorators: { signIn: [{ type: i0.Output, args: ["signIn"] }] } });
2561
+ }], propDecorators: { signIn: [{
2562
+ type: Output
2563
+ }] } });
1566
2564
 
1567
2565
  /**
1568
2566
  * Copyright 2025 Google LLC
@@ -1579,6 +2577,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1579
2577
  * See the License for the specific language governing permissions and
1580
2578
  * limitations under the License.
1581
2579
  */
2580
+ /**
2581
+ * A form component for signing in with email and password.
2582
+ */
1582
2583
  class SignInAuthFormComponent {
1583
2584
  ui = injectUI();
1584
2585
  formSchema = injectSignInAuthFormSchema();
@@ -1589,9 +2590,12 @@ class SignInAuthFormComponent {
1589
2590
  noAccountLabel = injectTranslation("prompts", "noAccount");
1590
2591
  signUpLabel = injectTranslation("labels", "signUp");
1591
2592
  unknownErrorLabel = injectTranslation("errors", "unknownError");
1592
- forgotPassword = output();
1593
- signUp = output();
1594
- signIn = output();
2593
+ /** Event emitter for forgot password action. */
2594
+ forgotPassword = input(...(ngDevMode ? [undefined, { debugName: "forgotPassword" }] : []));
2595
+ /** Event emitter for sign up action. */
2596
+ signUp = input(...(ngDevMode ? [undefined, { debugName: "signUp" }] : []));
2597
+ /** Event emitter for successful sign-in. */
2598
+ signIn = new EventEmitter();
1595
2599
  form = injectForm({
1596
2600
  defaultValues: {
1597
2601
  email: "",
@@ -1608,19 +2612,18 @@ class SignInAuthFormComponent {
1608
2612
  effect(() => {
1609
2613
  this.form.update({
1610
2614
  validators: {
1611
- onChange: this.formSchema(),
1612
2615
  onBlur: this.formSchema(),
1613
- onSubmit: this.formSchema(),
1614
2616
  onSubmitAsync: async ({ value }) => {
1615
2617
  try {
1616
2618
  const credential = await signInWithEmailAndPassword(this.ui(), value.email, value.password);
1617
- this.signIn?.emit(credential);
2619
+ this.signIn.emit(credential);
1618
2620
  return;
1619
2621
  }
1620
2622
  catch (error) {
1621
2623
  if (error instanceof FirebaseUIError) {
1622
2624
  return error.message;
1623
2625
  }
2626
+ console.error(error);
1624
2627
  return this.unknownErrorLabel();
1625
2628
  }
1626
2629
  },
@@ -1629,14 +2632,15 @@ class SignInAuthFormComponent {
1629
2632
  });
1630
2633
  }
1631
2634
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignInAuthFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1632
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignInAuthFormComponent, isStandalone: true, selector: "fui-sign-in-auth-form", outputs: { forgotPassword: "forgotPassword", signUp: "signUp", signIn: "signIn" }, ngImport: i0, template: `
2635
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignInAuthFormComponent, isStandalone: true, selector: "fui-sign-in-auth-form", inputs: { forgotPassword: { classPropertyName: "forgotPassword", publicName: "forgotPassword", isSignal: true, isRequired: false, transformFunction: null }, signUp: { classPropertyName: "signUp", publicName: "signUp", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1633
2636
  <form (submit)="handleSubmit($event)" class="fui-form">
1634
2637
  <fieldset>
1635
2638
  <fui-form-input
1636
2639
  name="email"
1637
2640
  tanstack-app-field
1638
2641
  [tanstackField]="form"
1639
- label="{{ emailLabel() }}"
2642
+ [label]="emailLabel()"
2643
+ type="email"
1640
2644
  ></fui-form-input>
1641
2645
  </fieldset>
1642
2646
  <fieldset>
@@ -1644,11 +2648,11 @@ class SignInAuthFormComponent {
1644
2648
  name="password"
1645
2649
  tanstack-app-field
1646
2650
  [tanstackField]="form"
1647
- label="{{ passwordLabel() }}"
2651
+ [label]="passwordLabel()"
1648
2652
  type="password"
1649
2653
  >
1650
- @if (forgotPassword) {
1651
- <button ngProjectAs="input-action" fui-form-action (click)="forgotPassword.emit()">
2654
+ @if (forgotPassword()?.observed) {
2655
+ <button ngProjectAs="input-action" fui-form-action (click)="forgotPassword()?.emit()">
1652
2656
  {{ forgotPasswordLabel() }}
1653
2657
  </button>
1654
2658
  }
@@ -1664,17 +2668,20 @@ class SignInAuthFormComponent {
1664
2668
  <fui-form-error-message [state]="state()" />
1665
2669
  </fieldset>
1666
2670
 
1667
- @if (signUp) {
1668
- <button fui-form-action (click)="signUp.emit()">{{ noAccountLabel() }} {{ signUpLabel() }}</button>
2671
+ @if (signUp()?.observed) {
2672
+ <button fui-form-action (click)="signUp()?.emit()">{{ noAccountLabel() }} {{ signUpLabel() }}</button>
1669
2673
  }
1670
2674
  </form>
1671
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: FormActionComponent, selector: "button[fui-form-action]" }] });
2675
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: FormActionComponent, selector: "button[fui-form-action]" }] });
1672
2676
  }
1673
2677
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignInAuthFormComponent, decorators: [{
1674
2678
  type: Component,
1675
2679
  args: [{
1676
2680
  selector: "fui-sign-in-auth-form",
1677
2681
  standalone: true,
2682
+ host: {
2683
+ style: "display: block;",
2684
+ },
1678
2685
  imports: [
1679
2686
  CommonModule,
1680
2687
  TanStackField,
@@ -1692,7 +2699,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1692
2699
  name="email"
1693
2700
  tanstack-app-field
1694
2701
  [tanstackField]="form"
1695
- label="{{ emailLabel() }}"
2702
+ [label]="emailLabel()"
2703
+ type="email"
1696
2704
  ></fui-form-input>
1697
2705
  </fieldset>
1698
2706
  <fieldset>
@@ -1700,11 +2708,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1700
2708
  name="password"
1701
2709
  tanstack-app-field
1702
2710
  [tanstackField]="form"
1703
- label="{{ passwordLabel() }}"
2711
+ [label]="passwordLabel()"
1704
2712
  type="password"
1705
2713
  >
1706
- @if (forgotPassword) {
1707
- <button ngProjectAs="input-action" fui-form-action (click)="forgotPassword.emit()">
2714
+ @if (forgotPassword()?.observed) {
2715
+ <button ngProjectAs="input-action" fui-form-action (click)="forgotPassword()?.emit()">
1708
2716
  {{ forgotPasswordLabel() }}
1709
2717
  </button>
1710
2718
  }
@@ -1720,13 +2728,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1720
2728
  <fui-form-error-message [state]="state()" />
1721
2729
  </fieldset>
1722
2730
 
1723
- @if (signUp) {
1724
- <button fui-form-action (click)="signUp.emit()">{{ noAccountLabel() }} {{ signUpLabel() }}</button>
2731
+ @if (signUp()?.observed) {
2732
+ <button fui-form-action (click)="signUp()?.emit()">{{ noAccountLabel() }} {{ signUpLabel() }}</button>
1725
2733
  }
1726
2734
  </form>
1727
2735
  `,
1728
2736
  }]
1729
- }], ctorParameters: () => [], propDecorators: { forgotPassword: [{ type: i0.Output, args: ["forgotPassword"] }], signUp: [{ type: i0.Output, args: ["signUp"] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
2737
+ }], ctorParameters: () => [], propDecorators: { forgotPassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "forgotPassword", required: false }] }], signUp: [{ type: i0.Input, args: [{ isSignal: true, alias: "signUp", required: false }] }], signIn: [{
2738
+ type: Output
2739
+ }] } });
1730
2740
 
1731
2741
  /**
1732
2742
  * Copyright 2025 Google LLC
@@ -1743,6 +2753,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1743
2753
  * See the License for the specific language governing permissions and
1744
2754
  * limitations under the License.
1745
2755
  */
2756
+ /**
2757
+ * A form component for signing up with email and password.
2758
+ *
2759
+ * Optionally includes a display name field if the requireDisplayName behavior is enabled.
2760
+ */
1746
2761
  class SignUpAuthFormComponent {
1747
2762
  ui = injectUI();
1748
2763
  formSchema = injectSignUpAuthFormSchema();
@@ -1756,8 +2771,10 @@ class SignUpAuthFormComponent {
1756
2771
  haveAccountLabel = injectTranslation("prompts", "haveAccount");
1757
2772
  signInLabel = injectTranslation("labels", "signIn");
1758
2773
  unknownErrorLabel = injectTranslation("errors", "unknownError");
1759
- signUp = output();
1760
- signIn = output();
2774
+ /** Event emitter for sign in action. */
2775
+ signIn = input(...(ngDevMode ? [undefined, { debugName: "signIn" }] : []));
2776
+ /** Event emitter for successful sign-up. */
2777
+ signUp = new EventEmitter();
1761
2778
  form = injectForm({
1762
2779
  defaultValues: {
1763
2780
  email: "",
@@ -1776,17 +2793,17 @@ class SignUpAuthFormComponent {
1776
2793
  this.form.update({
1777
2794
  validators: {
1778
2795
  onBlur: this.formSchema(),
1779
- onSubmit: this.formSchema(),
1780
2796
  onSubmitAsync: async ({ value }) => {
1781
2797
  try {
1782
2798
  const credential = await createUserWithEmailAndPassword(this.ui(), value.email, value.password, value.displayName);
1783
- this.signUp?.emit(credential);
2799
+ this.signUp.emit(credential);
1784
2800
  return;
1785
2801
  }
1786
2802
  catch (error) {
1787
2803
  if (error instanceof FirebaseUIError) {
1788
2804
  return error.message;
1789
2805
  }
2806
+ console.error(error);
1790
2807
  return this.unknownErrorLabel();
1791
2808
  }
1792
2809
  },
@@ -1795,27 +2812,22 @@ class SignUpAuthFormComponent {
1795
2812
  });
1796
2813
  }
1797
2814
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignUpAuthFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1798
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignUpAuthFormComponent, isStandalone: true, selector: "fui-sign-up-auth-form", outputs: { signUp: "signUp", signIn: "signIn" }, ngImport: i0, template: `
2815
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignUpAuthFormComponent, isStandalone: true, selector: "fui-sign-up-auth-form", inputs: { signIn: { classPropertyName: "signIn", publicName: "signIn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signUp: "signUp" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1799
2816
  <form (submit)="handleSubmit($event)" class="fui-form">
1800
2817
  @if (requireDisplayNameField()) {
1801
2818
  <fieldset>
1802
- <fui-form-input
1803
- name="displayName"
1804
- tanstack-app-field
1805
- [tanstackField]="form"
1806
- label="{{ displayNameLabel() }}"
1807
- />
2819
+ <fui-form-input name="displayName" tanstack-app-field [tanstackField]="form" [label]="displayNameLabel()" />
1808
2820
  </fieldset>
1809
2821
  }
1810
2822
  <fieldset>
1811
- <fui-form-input name="email" tanstack-app-field [tanstackField]="form" label="{{ emailLabel() }}" />
2823
+ <fui-form-input name="email" tanstack-app-field [tanstackField]="form" [label]="emailLabel()" type="email" />
1812
2824
  </fieldset>
1813
2825
  <fieldset>
1814
2826
  <fui-form-input
1815
2827
  name="password"
1816
2828
  tanstack-app-field
1817
2829
  [tanstackField]="form"
1818
- label="{{ passwordLabel() }}"
2830
+ [label]="passwordLabel()"
1819
2831
  type="password"
1820
2832
  />
1821
2833
  </fieldset>
@@ -1827,16 +2839,20 @@ class SignUpAuthFormComponent {
1827
2839
  <fui-form-error-message [state]="state()" />
1828
2840
  </fieldset>
1829
2841
 
1830
- @if (signIn) {
1831
- <button fui-form-action (click)="signIn.emit()">{{ haveAccountLabel() }} {{ signInLabel() }} &rarr;</button>
2842
+ @if (signIn()?.observed) {
2843
+ <button fui-form-action (click)="signIn()?.emit()">{{ haveAccountLabel() }} {{ signInLabel() }}</button>
1832
2844
  }
1833
2845
  </form>
1834
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: FormActionComponent, selector: "button[fui-form-action]" }] });
2846
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TanStackField, selector: "[tanstackField]", inputs: ["name", "defaultValue", "asyncDebounceMs", "asyncAlways", "tanstackField", "validators", "listeners", "defaultMeta", "mode", "disableErrorFlat"], exportAs: ["field"] }, { kind: "directive", type: TanStackAppField, selector: "[tanstack-app-field]" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: FormInputComponent, selector: "fui-form-input", inputs: ["label", "type", "description"] }, { kind: "component", type: FormSubmitComponent, selector: "fui-form-submit", inputs: ["class", "state"] }, { kind: "component", type: FormErrorMessageComponent, selector: "fui-form-error-message", inputs: ["state"] }, { kind: "component", type: FormActionComponent, selector: "button[fui-form-action]" }] });
1835
2847
  }
1836
2848
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignUpAuthFormComponent, decorators: [{
1837
2849
  type: Component,
1838
2850
  args: [{
1839
2851
  selector: "fui-sign-up-auth-form",
2852
+ standalone: true,
2853
+ host: {
2854
+ style: "display: block;",
2855
+ },
1840
2856
  imports: [
1841
2857
  CommonModule,
1842
2858
  TanStackField,
@@ -1851,23 +2867,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1851
2867
  <form (submit)="handleSubmit($event)" class="fui-form">
1852
2868
  @if (requireDisplayNameField()) {
1853
2869
  <fieldset>
1854
- <fui-form-input
1855
- name="displayName"
1856
- tanstack-app-field
1857
- [tanstackField]="form"
1858
- label="{{ displayNameLabel() }}"
1859
- />
2870
+ <fui-form-input name="displayName" tanstack-app-field [tanstackField]="form" [label]="displayNameLabel()" />
1860
2871
  </fieldset>
1861
2872
  }
1862
2873
  <fieldset>
1863
- <fui-form-input name="email" tanstack-app-field [tanstackField]="form" label="{{ emailLabel() }}" />
2874
+ <fui-form-input name="email" tanstack-app-field [tanstackField]="form" [label]="emailLabel()" type="email" />
1864
2875
  </fieldset>
1865
2876
  <fieldset>
1866
2877
  <fui-form-input
1867
2878
  name="password"
1868
2879
  tanstack-app-field
1869
2880
  [tanstackField]="form"
1870
- label="{{ passwordLabel() }}"
2881
+ [label]="passwordLabel()"
1871
2882
  type="password"
1872
2883
  />
1873
2884
  </fieldset>
@@ -1879,14 +2890,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1879
2890
  <fui-form-error-message [state]="state()" />
1880
2891
  </fieldset>
1881
2892
 
1882
- @if (signIn) {
1883
- <button fui-form-action (click)="signIn.emit()">{{ haveAccountLabel() }} {{ signInLabel() }} &rarr;</button>
2893
+ @if (signIn()?.observed) {
2894
+ <button fui-form-action (click)="signIn()?.emit()">{{ haveAccountLabel() }} {{ signInLabel() }}</button>
1884
2895
  }
1885
2896
  </form>
1886
2897
  `,
1887
- standalone: true,
1888
2898
  }]
1889
- }], ctorParameters: () => [], propDecorators: { signUp: [{ type: i0.Output, args: ["signUp"] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
2899
+ }], ctorParameters: () => [], propDecorators: { signIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "signIn", required: false }] }], signUp: [{
2900
+ type: Output
2901
+ }] } });
1890
2902
 
1891
2903
  /**
1892
2904
  * Copyright 2025 Google LLC
@@ -1903,15 +2915,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1903
2915
  * See the License for the specific language governing permissions and
1904
2916
  * limitations under the License.
1905
2917
  */
2918
+ /**
2919
+ * A generic OAuth button component for signing in with any OAuth provider.
2920
+ */
1906
2921
  class OAuthButtonComponent {
1907
2922
  ui = injectUI();
1908
- unknownErrorLabel = injectTranslation("errors", "unknownError");
2923
+ /** The OAuth provider to use for sign-in. */
1909
2924
  provider = input.required(...(ngDevMode ? [{ debugName: "provider" }] : []));
1910
- error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : []));
2925
+ /** Whether to use themed styling. */
2926
+ themed = input(...(ngDevMode ? [undefined, { debugName: "themed" }] : []));
2927
+ error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
2928
+ /** Event emitter for successful sign-in. */
2929
+ signIn = output();
2930
+ buttonVariant = computed(() => {
2931
+ return this.themed() ? "primary" : "secondary";
2932
+ }, ...(ngDevMode ? [{ debugName: "buttonVariant" }] : []));
1911
2933
  async handleOAuthSignIn() {
1912
- this.error.set(undefined);
2934
+ this.error.set(null);
1913
2935
  try {
1914
- await signInWithProvider(this.ui(), this.provider());
2936
+ const credential = await signInWithProvider(this.ui(), this.provider());
2937
+ this.signIn.emit(credential);
1915
2938
  }
1916
2939
  catch (error) {
1917
2940
  if (error instanceof FirebaseUIError) {
@@ -1919,16 +2942,18 @@ class OAuthButtonComponent {
1919
2942
  return;
1920
2943
  }
1921
2944
  console.error(error);
1922
- this.error.set(this.unknownErrorLabel());
2945
+ this.error.set(getTranslation(this.ui(), "errors", "unknownError"));
1923
2946
  }
1924
2947
  }
1925
2948
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OAuthButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1926
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: OAuthButtonComponent, isStandalone: true, selector: "fui-oauth-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2949
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: OAuthButtonComponent, isStandalone: true, selector: "fui-oauth-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: true, transformFunction: null }, themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
1927
2950
  <div>
1928
2951
  <button
1929
2952
  fui-button
1930
2953
  type="button"
1931
2954
  (click)="handleOAuthSignIn()"
2955
+ [variant]="buttonVariant()"
2956
+ [attr.data-themed]="themed()"
1932
2957
  [disabled]="ui().state !== 'idle'"
1933
2958
  [attr.data-provider]="provider().providerId"
1934
2959
  class="fui-provider__button"
@@ -1937,7 +2962,7 @@ class OAuthButtonComponent {
1937
2962
  </button>
1938
2963
 
1939
2964
  @if (error()) {
1940
- <div class="fui-form__error">{{ error() }}</div>
2965
+ <div class="fui-error">{{ error() }}</div>
1941
2966
  }
1942
2967
  </div>
1943
2968
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "button[fui-button]", inputs: ["variant"] }] });
@@ -1948,12 +2973,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1948
2973
  selector: "fui-oauth-button",
1949
2974
  standalone: true,
1950
2975
  imports: [CommonModule, ButtonComponent],
2976
+ host: {
2977
+ style: "display: block;",
2978
+ },
1951
2979
  template: `
1952
2980
  <div>
1953
2981
  <button
1954
2982
  fui-button
1955
2983
  type="button"
1956
2984
  (click)="handleOAuthSignIn()"
2985
+ [variant]="buttonVariant()"
2986
+ [attr.data-themed]="themed()"
1957
2987
  [disabled]="ui().state !== 'idle'"
1958
2988
  [attr.data-provider]="provider().providerId"
1959
2989
  class="fui-provider__button"
@@ -1962,12 +2992,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1962
2992
  </button>
1963
2993
 
1964
2994
  @if (error()) {
1965
- <div class="fui-form__error">{{ error() }}</div>
2995
+ <div class="fui-error">{{ error() }}</div>
1966
2996
  }
1967
2997
  </div>
1968
2998
  `,
1969
2999
  }]
1970
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: true }] }] } });
3000
+ }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: true }] }], themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
1971
3001
 
1972
3002
  /**
1973
3003
  * Copyright 2025 Google LLC
@@ -2054,21 +3084,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2054
3084
  * See the License for the specific language governing permissions and
2055
3085
  * limitations under the License.
2056
3086
  */
3087
+ /**
3088
+ * A button component for signing in with Google.
3089
+ */
2057
3090
  class GoogleSignInButtonComponent {
2058
3091
  ui = injectUI();
2059
3092
  signInWithGoogleLabel = injectTranslation("labels", "signInWithGoogle");
3093
+ /** Whether to use themed styling. */
3094
+ themed = input(false, ...(ngDevMode ? [{ debugName: "themed" }] : []));
3095
+ /** Event emitter for successful sign-in. */
3096
+ signIn = output();
2060
3097
  defaultProvider = new GoogleAuthProvider();
3098
+ /** Optional custom OAuth provider configuration. */
2061
3099
  provider = input(...(ngDevMode ? [undefined, { debugName: "provider" }] : []));
2062
3100
  get googleProvider() {
2063
3101
  return this.provider() || this.defaultProvider;
2064
3102
  }
2065
3103
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: GoogleSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2066
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: GoogleSignInButtonComponent, isStandalone: true, selector: "fui-google-sign-in-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2067
- <fui-oauth-button [provider]="googleProvider">
3104
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: GoogleSignInButtonComponent, isStandalone: true, selector: "fui-google-sign-in-button", inputs: { themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null }, provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3105
+ <fui-oauth-button [provider]="googleProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2068
3106
  <fui-google-logo />
2069
3107
  <span>{{ signInWithGoogleLabel() }}</span>
2070
3108
  </fui-oauth-button>
2071
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider"] }, { kind: "component", type: GoogleLogoComponent, selector: "fui-google-logo", inputs: ["width", "height", "className"] }] });
3109
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider", "themed"], outputs: ["signIn"] }, { kind: "component", type: GoogleLogoComponent, selector: "fui-google-logo", inputs: ["width", "height", "className"] }] });
2072
3110
  }
2073
3111
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: GoogleSignInButtonComponent, decorators: [{
2074
3112
  type: Component,
@@ -2076,14 +3114,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2076
3114
  selector: "fui-google-sign-in-button",
2077
3115
  standalone: true,
2078
3116
  imports: [CommonModule, OAuthButtonComponent, GoogleLogoComponent],
3117
+ host: {
3118
+ style: "display: block;",
3119
+ },
2079
3120
  template: `
2080
- <fui-oauth-button [provider]="googleProvider">
3121
+ <fui-oauth-button [provider]="googleProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2081
3122
  <fui-google-logo />
2082
3123
  <span>{{ signInWithGoogleLabel() }}</span>
2083
3124
  </fui-oauth-button>
2084
3125
  `,
2085
3126
  }]
2086
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
3127
+ }], propDecorators: { themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }], provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
2087
3128
 
2088
3129
  /**
2089
3130
  * Copyright 2025 Google LLC
@@ -2107,7 +3148,7 @@ class FacebookLogoComponent {
2107
3148
  className = input("", ...(ngDevMode ? [{ debugName: "className" }] : []));
2108
3149
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FacebookLogoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2109
3150
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: FacebookLogoComponent, isStandalone: true, selector: "fui-facebook-logo", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2110
- <?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" class="fui-provider__icon">
3151
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" class="fui-provider__icon">
2111
3152
  <path
2112
3153
  fill="currentColor"
2113
3154
  d="M25,3C12.85,3,3,12.85,3,25c0,11.03,8.125,20.137,18.712,21.728V30.831h-5.443v-5.783h5.443v-3.848 c0-6.371,3.104-9.168,8.399-9.168c2.536,0,3.877,0.188,4.512,0.274v5.048h-3.612c-2.248,0-3.033,2.131-3.033,4.533v3.161h6.588 l-0.894,5.783h-5.694v15.944C38.716,45.318,47,36.137,47,25C47,12.85,37.15,3,25,3z"
@@ -2121,7 +3162,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2121
3162
  selector: "fui-facebook-logo",
2122
3163
  standalone: true,
2123
3164
  template: `
2124
- <?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" class="fui-provider__icon">
3165
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" class="fui-provider__icon">
2125
3166
  <path
2126
3167
  fill="currentColor"
2127
3168
  d="M25,3C12.85,3,3,12.85,3,25c0,11.03,8.125,20.137,18.712,21.728V30.831h-5.443v-5.783h5.443v-3.848 c0-6.371,3.104-9.168,8.399-9.168c2.536,0,3.877,0.188,4.512,0.274v5.048h-3.612c-2.248,0-3.033,2.131-3.033,4.533v3.161h6.588 l-0.894,5.783h-5.694v15.944C38.716,45.318,47,36.137,47,25C47,12.85,37.15,3,25,3z"
@@ -2146,21 +3187,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2146
3187
  * See the License for the specific language governing permissions and
2147
3188
  * limitations under the License.
2148
3189
  */
3190
+ /**
3191
+ * A button component for signing in with Facebook.
3192
+ */
2149
3193
  class FacebookSignInButtonComponent {
2150
3194
  ui = injectUI();
2151
3195
  signInWithFacebookLabel = injectTranslation("labels", "signInWithFacebook");
3196
+ /** Whether to use themed styling. */
3197
+ themed = input(false, ...(ngDevMode ? [{ debugName: "themed" }] : []));
3198
+ /** Event emitter for successful sign-in. */
3199
+ signIn = output();
2152
3200
  defaultProvider = new FacebookAuthProvider();
3201
+ /** Optional custom OAuth provider configuration. */
2153
3202
  provider = input(...(ngDevMode ? [undefined, { debugName: "provider" }] : []));
2154
3203
  get facebookProvider() {
2155
3204
  return this.provider() || this.defaultProvider;
2156
3205
  }
2157
3206
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FacebookSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2158
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: FacebookSignInButtonComponent, isStandalone: true, selector: "fui-facebook-sign-in-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2159
- <fui-oauth-button [provider]="facebookProvider">
3207
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: FacebookSignInButtonComponent, isStandalone: true, selector: "fui-facebook-sign-in-button", inputs: { themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null }, provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3208
+ <fui-oauth-button [provider]="facebookProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2160
3209
  <fui-facebook-logo />
2161
3210
  <span>{{ signInWithFacebookLabel() }}</span>
2162
3211
  </fui-oauth-button>
2163
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider"] }, { kind: "component", type: FacebookLogoComponent, selector: "fui-facebook-logo", inputs: ["width", "height", "className"] }] });
3212
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider", "themed"], outputs: ["signIn"] }, { kind: "component", type: FacebookLogoComponent, selector: "fui-facebook-logo", inputs: ["width", "height", "className"] }] });
2164
3213
  }
2165
3214
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FacebookSignInButtonComponent, decorators: [{
2166
3215
  type: Component,
@@ -2168,14 +3217,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2168
3217
  selector: "fui-facebook-sign-in-button",
2169
3218
  standalone: true,
2170
3219
  imports: [CommonModule, OAuthButtonComponent, FacebookLogoComponent],
3220
+ host: {
3221
+ style: "display: block;",
3222
+ },
2171
3223
  template: `
2172
- <fui-oauth-button [provider]="facebookProvider">
3224
+ <fui-oauth-button [provider]="facebookProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2173
3225
  <fui-facebook-logo />
2174
3226
  <span>{{ signInWithFacebookLabel() }}</span>
2175
3227
  </fui-oauth-button>
2176
3228
  `,
2177
3229
  }]
2178
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
3230
+ }], propDecorators: { themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }], provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
2179
3231
 
2180
3232
  /**
2181
3233
  * Copyright 2025 Google LLC
@@ -2238,21 +3290,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2238
3290
  * See the License for the specific language governing permissions and
2239
3291
  * limitations under the License.
2240
3292
  */
3293
+ /**
3294
+ * A button component for signing in with Apple.
3295
+ */
2241
3296
  class AppleSignInButtonComponent {
2242
3297
  ui = injectUI();
2243
3298
  signInWithAppleLabel = injectTranslation("labels", "signInWithApple");
3299
+ /** Whether to use themed styling. */
3300
+ themed = input(false, ...(ngDevMode ? [{ debugName: "themed" }] : []));
3301
+ /** Event emitter for successful sign-in. */
3302
+ signIn = output();
2244
3303
  defaultProvider = new OAuthProvider("apple.com");
3304
+ /** Optional custom OAuth provider configuration. */
2245
3305
  provider = input(...(ngDevMode ? [undefined, { debugName: "provider" }] : []));
2246
3306
  get appleProvider() {
2247
3307
  return this.provider() || this.defaultProvider;
2248
3308
  }
2249
3309
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AppleSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2250
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: AppleSignInButtonComponent, isStandalone: true, selector: "fui-apple-sign-in-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2251
- <fui-oauth-button [provider]="appleProvider">
3310
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: AppleSignInButtonComponent, isStandalone: true, selector: "fui-apple-sign-in-button", inputs: { themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null }, provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3311
+ <fui-oauth-button [provider]="appleProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2252
3312
  <fui-apple-logo />
2253
3313
  <span>{{ signInWithAppleLabel() }}</span>
2254
3314
  </fui-oauth-button>
2255
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider"] }, { kind: "component", type: AppleLogoComponent, selector: "fui-apple-logo", inputs: ["width", "height", "className"] }] });
3315
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider", "themed"], outputs: ["signIn"] }, { kind: "component", type: AppleLogoComponent, selector: "fui-apple-logo", inputs: ["width", "height", "className"] }] });
2256
3316
  }
2257
3317
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AppleSignInButtonComponent, decorators: [{
2258
3318
  type: Component,
@@ -2260,14 +3320,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2260
3320
  selector: "fui-apple-sign-in-button",
2261
3321
  standalone: true,
2262
3322
  imports: [CommonModule, OAuthButtonComponent, AppleLogoComponent],
3323
+ host: {
3324
+ style: "display: block;",
3325
+ },
2263
3326
  template: `
2264
- <fui-oauth-button [provider]="appleProvider">
3327
+ <fui-oauth-button [provider]="appleProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2265
3328
  <fui-apple-logo />
2266
3329
  <span>{{ signInWithAppleLabel() }}</span>
2267
3330
  </fui-oauth-button>
2268
3331
  `,
2269
3332
  }]
2270
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
3333
+ }], propDecorators: { themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }], provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
2271
3334
 
2272
3335
  /**
2273
3336
  * Copyright 2025 Google LLC
@@ -2330,20 +3393,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2330
3393
  * See the License for the specific language governing permissions and
2331
3394
  * limitations under the License.
2332
3395
  */
3396
+ /**
3397
+ * A button component for signing in with Microsoft.
3398
+ */
2333
3399
  class MicrosoftSignInButtonComponent {
2334
3400
  signInWithMicrosoftLabel = injectTranslation("labels", "signInWithMicrosoft");
3401
+ /** Whether to use themed styling. */
3402
+ themed = input(false, ...(ngDevMode ? [{ debugName: "themed" }] : []));
3403
+ /** Event emitter for successful sign-in. */
3404
+ signIn = output();
2335
3405
  defaultProvider = new OAuthProvider("microsoft.com");
3406
+ /** Optional custom OAuth provider configuration. */
2336
3407
  provider = input(...(ngDevMode ? [undefined, { debugName: "provider" }] : []));
2337
3408
  get microsoftProvider() {
2338
3409
  return this.provider() || this.defaultProvider;
2339
3410
  }
2340
3411
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MicrosoftSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2341
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: MicrosoftSignInButtonComponent, isStandalone: true, selector: "fui-microsoft-sign-in-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2342
- <fui-oauth-button [provider]="microsoftProvider">
3412
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: MicrosoftSignInButtonComponent, isStandalone: true, selector: "fui-microsoft-sign-in-button", inputs: { themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null }, provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3413
+ <fui-oauth-button [provider]="microsoftProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2343
3414
  <fui-microsoft-logo />
2344
3415
  <span>{{ signInWithMicrosoftLabel() }}</span>
2345
3416
  </fui-oauth-button>
2346
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider"] }, { kind: "component", type: MicrosoftLogoComponent, selector: "fui-microsoft-logo", inputs: ["width", "height", "className"] }] });
3417
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider", "themed"], outputs: ["signIn"] }, { kind: "component", type: MicrosoftLogoComponent, selector: "fui-microsoft-logo", inputs: ["width", "height", "className"] }] });
2347
3418
  }
2348
3419
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MicrosoftSignInButtonComponent, decorators: [{
2349
3420
  type: Component,
@@ -2351,14 +3422,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2351
3422
  selector: "fui-microsoft-sign-in-button",
2352
3423
  standalone: true,
2353
3424
  imports: [CommonModule, OAuthButtonComponent, MicrosoftLogoComponent],
3425
+ host: {
3426
+ style: "display: block;",
3427
+ },
2354
3428
  template: `
2355
- <fui-oauth-button [provider]="microsoftProvider">
3429
+ <fui-oauth-button [provider]="microsoftProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2356
3430
  <fui-microsoft-logo />
2357
3431
  <span>{{ signInWithMicrosoftLabel() }}</span>
2358
3432
  </fui-oauth-button>
2359
3433
  `,
2360
3434
  }]
2361
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
3435
+ }], propDecorators: { themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }], provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
2362
3436
 
2363
3437
  /**
2364
3438
  * Copyright 2025 Google LLC
@@ -2419,20 +3493,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2419
3493
  * See the License for the specific language governing permissions and
2420
3494
  * limitations under the License.
2421
3495
  */
3496
+ /**
3497
+ * A button component for signing in with Twitter/X.
3498
+ */
2422
3499
  class TwitterSignInButtonComponent {
2423
3500
  signInWithTwitterLabel = injectTranslation("labels", "signInWithTwitter");
3501
+ /** Whether to use themed styling. */
3502
+ themed = input(false, ...(ngDevMode ? [{ debugName: "themed" }] : []));
3503
+ /** Event emitter for successful sign-in. */
3504
+ signIn = output();
2424
3505
  defaultProvider = new TwitterAuthProvider();
3506
+ /** Optional custom OAuth provider configuration. */
2425
3507
  provider = input(...(ngDevMode ? [undefined, { debugName: "provider" }] : []));
2426
3508
  get twitterProvider() {
2427
3509
  return this.provider() || this.defaultProvider;
2428
3510
  }
2429
3511
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TwitterSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2430
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: TwitterSignInButtonComponent, isStandalone: true, selector: "fui-twitter-sign-in-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2431
- <fui-oauth-button [provider]="twitterProvider">
3512
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: TwitterSignInButtonComponent, isStandalone: true, selector: "fui-twitter-sign-in-button", inputs: { themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null }, provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3513
+ <fui-oauth-button [provider]="twitterProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2432
3514
  <fui-twitter-logo />
2433
3515
  <span>{{ signInWithTwitterLabel() }}</span>
2434
3516
  </fui-oauth-button>
2435
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider"] }, { kind: "component", type: TwitterLogoComponent, selector: "fui-twitter-logo", inputs: ["width", "height", "className"] }] });
3517
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider", "themed"], outputs: ["signIn"] }, { kind: "component", type: TwitterLogoComponent, selector: "fui-twitter-logo", inputs: ["width", "height", "className"] }] });
2436
3518
  }
2437
3519
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TwitterSignInButtonComponent, decorators: [{
2438
3520
  type: Component,
@@ -2440,14 +3522,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2440
3522
  selector: "fui-twitter-sign-in-button",
2441
3523
  standalone: true,
2442
3524
  imports: [CommonModule, OAuthButtonComponent, TwitterLogoComponent],
3525
+ host: {
3526
+ style: "display: block;",
3527
+ },
2443
3528
  template: `
2444
- <fui-oauth-button [provider]="twitterProvider">
3529
+ <fui-oauth-button [provider]="twitterProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2445
3530
  <fui-twitter-logo />
2446
3531
  <span>{{ signInWithTwitterLabel() }}</span>
2447
3532
  </fui-oauth-button>
2448
3533
  `,
2449
3534
  }]
2450
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
3535
+ }], propDecorators: { themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }], provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
2451
3536
 
2452
3537
  /**
2453
3538
  * Copyright 2025 Google LLC
@@ -2508,35 +3593,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2508
3593
  * See the License for the specific language governing permissions and
2509
3594
  * limitations under the License.
2510
3595
  */
2511
- class GithubSignInButtonComponent {
2512
- signInWithGithubLabel = injectTranslation("labels", "signInWithGithub");
3596
+ /**
3597
+ * A button component for signing in with GitHub.
3598
+ */
3599
+ class GitHubSignInButtonComponent {
3600
+ signInWithGitHubLabel = injectTranslation("labels", "signInWithGitHub");
3601
+ /** Whether to use themed styling. */
3602
+ themed = input(false, ...(ngDevMode ? [{ debugName: "themed" }] : []));
3603
+ /** Event emitter for successful sign-in. */
3604
+ signIn = output();
2513
3605
  defaultProvider = new GithubAuthProvider();
3606
+ /** Optional custom OAuth provider configuration. */
2514
3607
  provider = input(...(ngDevMode ? [undefined, { debugName: "provider" }] : []));
2515
3608
  get githubProvider() {
2516
3609
  return this.provider() || this.defaultProvider;
2517
3610
  }
2518
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: GithubSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2519
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: GithubSignInButtonComponent, isStandalone: true, selector: "fui-github-sign-in-button", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2520
- <fui-oauth-button [provider]="githubProvider">
3611
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: GitHubSignInButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3612
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: GitHubSignInButtonComponent, isStandalone: true, selector: "fui-github-sign-in-button", inputs: { themed: { classPropertyName: "themed", publicName: "themed", isSignal: true, isRequired: false, transformFunction: null }, provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3613
+ <fui-oauth-button [provider]="githubProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2521
3614
  <fui-github-logo />
2522
- <span>{{ signInWithGithubLabel() }}</span>
3615
+ <span>{{ signInWithGitHubLabel() }}</span>
2523
3616
  </fui-oauth-button>
2524
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider"] }, { kind: "component", type: GithubLogoComponent, selector: "fui-github-logo", inputs: ["width", "height", "className"] }] });
3617
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OAuthButtonComponent, selector: "fui-oauth-button", inputs: ["provider", "themed"], outputs: ["signIn"] }, { kind: "component", type: GithubLogoComponent, selector: "fui-github-logo", inputs: ["width", "height", "className"] }] });
2525
3618
  }
2526
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: GithubSignInButtonComponent, decorators: [{
3619
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: GitHubSignInButtonComponent, decorators: [{
2527
3620
  type: Component,
2528
3621
  args: [{
2529
3622
  selector: "fui-github-sign-in-button",
2530
3623
  standalone: true,
2531
3624
  imports: [CommonModule, OAuthButtonComponent, GithubLogoComponent],
3625
+ host: {
3626
+ style: "display: block;",
3627
+ },
2532
3628
  template: `
2533
- <fui-oauth-button [provider]="githubProvider">
3629
+ <fui-oauth-button [provider]="githubProvider" [themed]="themed()" (signIn)="signIn.emit($event)">
2534
3630
  <fui-github-logo />
2535
- <span>{{ signInWithGithubLabel() }}</span>
3631
+ <span>{{ signInWithGitHubLabel() }}</span>
2536
3632
  </fui-oauth-button>
2537
3633
  `,
2538
3634
  }]
2539
- }], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
3635
+ }], propDecorators: { themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }], provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }] } });
2540
3636
 
2541
3637
  /**
2542
3638
  * Copyright 2025 Google LLC
@@ -2553,13 +3649,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2553
3649
  * See the License for the specific language governing permissions and
2554
3650
  * limitations under the License.
2555
3651
  */
3652
+ /**
3653
+ * A card container component for grouping related content.
3654
+ */
2556
3655
  class CardComponent {
2557
3656
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2558
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardComponent, isStandalone: true, selector: "fui-card", ngImport: i0, template: `
2559
- <div class="fui-card">
2560
- <ng-content select="fui-card-header"></ng-content>
2561
- <ng-content select="fui-card-content"></ng-content>
2562
- </div>
3657
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardComponent, isStandalone: true, selector: "fui-card", host: { styleAttribute: "display: block;", classAttribute: "fui-card" }, ngImport: i0, template: `
3658
+ <ng-content select="fui-card-header"></ng-content>
3659
+ <ng-content select="fui-card-content"></ng-content>
2563
3660
  `, isInline: true });
2564
3661
  }
2565
3662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardComponent, decorators: [{
@@ -2568,21 +3665,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2568
3665
  selector: "fui-card",
2569
3666
  standalone: true,
2570
3667
  imports: [],
3668
+ host: {
3669
+ class: "fui-card",
3670
+ style: "display: block;",
3671
+ },
2571
3672
  template: `
2572
- <div class="fui-card">
2573
- <ng-content select="fui-card-header"></ng-content>
2574
- <ng-content select="fui-card-content"></ng-content>
2575
- </div>
3673
+ <ng-content select="fui-card-header"></ng-content>
3674
+ <ng-content select="fui-card-content"></ng-content>
2576
3675
  `,
2577
3676
  }]
2578
3677
  }] });
3678
+ /**
3679
+ * The header section of a card.
3680
+ */
2579
3681
  class CardHeaderComponent {
2580
3682
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2581
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardHeaderComponent, isStandalone: true, selector: "fui-card-header", ngImport: i0, template: `
2582
- <div class="fui-card__header">
2583
- <ng-content select="fui-card-title"></ng-content>
2584
- <ng-content select="fui-card-subtitle"></ng-content>
2585
- </div>
3683
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardHeaderComponent, isStandalone: true, selector: "fui-card-header", host: { styleAttribute: "display: block;", classAttribute: "fui-card__header" }, ngImport: i0, template: `
3684
+ <ng-content select="fui-card-title"></ng-content>
3685
+ <ng-content select="fui-card-subtitle"></ng-content>
2586
3686
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2587
3687
  }
2588
3688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardHeaderComponent, decorators: [{
@@ -2591,18 +3691,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2591
3691
  selector: "fui-card-header",
2592
3692
  standalone: true,
2593
3693
  imports: [CommonModule],
3694
+ host: {
3695
+ class: "fui-card__header",
3696
+ style: "display: block;",
3697
+ },
2594
3698
  template: `
2595
- <div class="fui-card__header">
2596
- <ng-content select="fui-card-title"></ng-content>
2597
- <ng-content select="fui-card-subtitle"></ng-content>
2598
- </div>
3699
+ <ng-content select="fui-card-title"></ng-content>
3700
+ <ng-content select="fui-card-subtitle"></ng-content>
2599
3701
  `,
2600
3702
  }]
2601
3703
  }] });
3704
+ /**
3705
+ * The title of a card.
3706
+ */
2602
3707
  class CardTitleComponent {
2603
3708
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardTitleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2604
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardTitleComponent, isStandalone: true, selector: "fui-card-title", ngImport: i0, template: `
2605
- <h2 class="fui-card__title">
3709
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardTitleComponent, isStandalone: true, selector: "fui-card-title", host: { styleAttribute: "display: block;", classAttribute: "fui-card__title" }, ngImport: i0, template: `
3710
+ <h2>
2606
3711
  <ng-content></ng-content>
2607
3712
  </h2>
2608
3713
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
@@ -2613,17 +3718,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2613
3718
  selector: "fui-card-title",
2614
3719
  standalone: true,
2615
3720
  imports: [CommonModule],
3721
+ host: {
3722
+ class: "fui-card__title",
3723
+ style: "display: block;",
3724
+ },
2616
3725
  template: `
2617
- <h2 class="fui-card__title">
3726
+ <h2>
2618
3727
  <ng-content></ng-content>
2619
3728
  </h2>
2620
3729
  `,
2621
3730
  }]
2622
3731
  }] });
3732
+ /**
3733
+ * The subtitle of a card.
3734
+ */
2623
3735
  class CardSubtitleComponent {
2624
3736
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardSubtitleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2625
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardSubtitleComponent, isStandalone: true, selector: "fui-card-subtitle", ngImport: i0, template: `
2626
- <p class="fui-card__subtitle">
3737
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardSubtitleComponent, isStandalone: true, selector: "fui-card-subtitle", host: { styleAttribute: "display: block;", classAttribute: "fui-card__subtitle" }, ngImport: i0, template: `
3738
+ <p>
2627
3739
  <ng-content></ng-content>
2628
3740
  </p>
2629
3741
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
@@ -2634,20 +3746,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2634
3746
  selector: "fui-card-subtitle",
2635
3747
  standalone: true,
2636
3748
  imports: [CommonModule],
3749
+ host: {
3750
+ class: "fui-card__subtitle",
3751
+ style: "display: block;",
3752
+ },
2637
3753
  template: `
2638
- <p class="fui-card__subtitle">
3754
+ <p>
2639
3755
  <ng-content></ng-content>
2640
3756
  </p>
2641
3757
  `,
2642
3758
  }]
2643
3759
  }] });
3760
+ /**
3761
+ * The content section of a card.
3762
+ */
2644
3763
  class CardContentComponent {
2645
3764
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardContentComponent, isStandalone: true, selector: "fui-card-content", ngImport: i0, template: `
2647
- <div class="fui-card__content">
2648
- <ng-content></ng-content>
2649
- </div>
2650
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
3765
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CardContentComponent, isStandalone: true, selector: "fui-card-content", host: { styleAttribute: "display: block;", classAttribute: "fui-card__content" }, ngImport: i0, template: ` <ng-content></ng-content> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2651
3766
  }
2652
3767
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CardContentComponent, decorators: [{
2653
3768
  type: Component,
@@ -2655,13 +3770,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2655
3770
  selector: "fui-card-content",
2656
3771
  standalone: true,
2657
3772
  imports: [CommonModule],
3773
+ host: {
3774
+ class: "fui-card__content",
3775
+ style: "display: block;",
3776
+ },
3777
+ template: ` <ng-content></ng-content> `,
3778
+ }]
3779
+ }] });
3780
+
3781
+ /**
3782
+ * Copyright 2025 Google LLC
3783
+ *
3784
+ * Licensed under the Apache License, Version 2.0 (the "License");
3785
+ * you may not use this file except in compliance with the License.
3786
+ * You may obtain a copy of the License at
3787
+ *
3788
+ * http://www.apache.org/licenses/LICENSE-2.0
3789
+ *
3790
+ * Unless required by applicable law or agreed to in writing, software
3791
+ * distributed under the License is distributed on an "AS IS" BASIS,
3792
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3793
+ * See the License for the specific language governing permissions and
3794
+ * limitations under the License.
3795
+ */
3796
+ /**
3797
+ * A screen component for multi-factor authentication assertion.
3798
+ *
3799
+ * Displays the MFA assertion form for completing multi-factor verification.
3800
+ */
3801
+ class MultiFactorAuthAssertionScreenComponent {
3802
+ /** Event emitter for successful MFA assertion. */
3803
+ onSuccess = new EventEmitter();
3804
+ titleText = injectTranslation("labels", "multiFactorAssertion");
3805
+ subtitleText = injectTranslation("prompts", "mfaAssertionPrompt");
3806
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthAssertionScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3807
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: MultiFactorAuthAssertionScreenComponent, isStandalone: true, selector: "fui-multi-factor-auth-assertion-screen", outputs: { onSuccess: "onSuccess" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3808
+ <div class="fui-screen">
3809
+ <fui-card>
3810
+ <fui-card-header>
3811
+ <fui-card-title>{{ titleText() }}</fui-card-title>
3812
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3813
+ </fui-card-header>
3814
+ <fui-card-content>
3815
+ <fui-multi-factor-auth-assertion-form (onSuccess)="onSuccess.emit($event)" />
3816
+ </fui-card-content>
3817
+ </fui-card>
3818
+ </div>
3819
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: MultiFactorAuthAssertionFormComponent, selector: "fui-multi-factor-auth-assertion-form", outputs: ["onSuccess"] }] });
3820
+ }
3821
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthAssertionScreenComponent, decorators: [{
3822
+ type: Component,
3823
+ args: [{
3824
+ selector: "fui-multi-factor-auth-assertion-screen",
3825
+ standalone: true,
3826
+ host: {
3827
+ style: "display: block;",
3828
+ },
3829
+ imports: [
3830
+ CommonModule,
3831
+ CardComponent,
3832
+ CardHeaderComponent,
3833
+ CardTitleComponent,
3834
+ CardSubtitleComponent,
3835
+ CardContentComponent,
3836
+ MultiFactorAuthAssertionFormComponent,
3837
+ ],
2658
3838
  template: `
2659
- <div class="fui-card__content">
2660
- <ng-content></ng-content>
3839
+ <div class="fui-screen">
3840
+ <fui-card>
3841
+ <fui-card-header>
3842
+ <fui-card-title>{{ titleText() }}</fui-card-title>
3843
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3844
+ </fui-card-header>
3845
+ <fui-card-content>
3846
+ <fui-multi-factor-auth-assertion-form (onSuccess)="onSuccess.emit($event)" />
3847
+ </fui-card-content>
3848
+ </fui-card>
2661
3849
  </div>
2662
3850
  `,
2663
3851
  }]
2664
- }] });
3852
+ }], propDecorators: { onSuccess: [{
3853
+ type: Output
3854
+ }] } });
2665
3855
 
2666
3856
  /**
2667
3857
  * Copyright 2025 Google LLC
@@ -2677,12 +3867,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2677
3867
  * See the License for the specific language governing permissions and
2678
3868
  * limitations under the License.
2679
3869
  */
3870
+ /**
3871
+ * A component that displays redirect error messages.
3872
+ *
3873
+ * Shows errors that occurred during OAuth redirect flows.
3874
+ */
2680
3875
  class RedirectErrorComponent {
2681
3876
  error = injectRedirectError();
2682
3877
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: RedirectErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2683
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: RedirectErrorComponent, isStandalone: true, selector: "fui-redirect-error", ngImport: i0, template: `
3878
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: RedirectErrorComponent, isStandalone: true, selector: "fui-redirect-error", host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
2684
3879
  @if (error()) {
2685
- <div class="fui-form__error">{{ error() }}</div>
3880
+ <div class="fui-error">{{ error() }}</div>
2686
3881
  }
2687
3882
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2688
3883
  }
@@ -2692,9 +3887,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2692
3887
  selector: "fui-redirect-error",
2693
3888
  standalone: true,
2694
3889
  imports: [CommonModule],
3890
+ host: {
3891
+ style: "display: block;",
3892
+ },
2695
3893
  template: `
2696
3894
  @if (error()) {
2697
- <div class="fui-form__error">{{ error() }}</div>
3895
+ <div class="fui-error">{{ error() }}</div>
2698
3896
  }
2699
3897
  `,
2700
3898
  }]
@@ -2715,33 +3913,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2715
3913
  * See the License for the specific language governing permissions and
2716
3914
  * limitations under the License.
2717
3915
  */
3916
+ /**
3917
+ * A screen component for email link authentication.
3918
+ *
3919
+ * Automatically displays the MFA assertion screen if a multi-factor resolver is present.
3920
+ */
2718
3921
  class EmailLinkAuthScreenComponent {
3922
+ ui = injectUI();
3923
+ mfaResolver = computed(() => this.ui().multiFactorResolver, ...(ngDevMode ? [{ debugName: "mfaResolver" }] : []));
2719
3924
  titleText = injectTranslation("labels", "signIn");
2720
3925
  subtitleText = injectTranslation("prompts", "signInToAccount");
2721
- emailSent = output();
2722
- signIn = output();
3926
+ constructor() {
3927
+ injectUserAuthenticated((user) => {
3928
+ this.signIn.emit(user);
3929
+ });
3930
+ }
3931
+ /** Event emitter fired when sign-in link email is sent. */
3932
+ emailSent = new EventEmitter();
3933
+ /** Event emitter for successful sign-in. */
3934
+ signIn = new EventEmitter();
2723
3935
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: EmailLinkAuthScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2724
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: EmailLinkAuthScreenComponent, isStandalone: true, selector: "fui-email-link-auth-screen", outputs: { emailSent: "emailSent", signIn: "signIn" }, ngImport: i0, template: `
2725
- <div class="fui-screen">
2726
- <fui-card>
2727
- <fui-card-header>
2728
- <fui-card-title>{{ titleText() }}</fui-card-title>
2729
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
2730
- </fui-card-header>
2731
- <fui-card-content>
2732
- <fui-email-link-auth-form (emailSent)="emailSent.emit()" (signIn)="signIn.emit($event)" />
2733
- <fui-redirect-error />
2734
- <ng-content></ng-content>
2735
- </fui-card-content>
2736
- </fui-card>
2737
- </div>
2738
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: EmailLinkAuthFormComponent, selector: "fui-email-link-auth-form", outputs: ["emailSent", "signIn"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
3936
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: EmailLinkAuthScreenComponent, isStandalone: true, selector: "fui-email-link-auth-screen", outputs: { emailSent: "emailSent", signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
3937
+ @if (mfaResolver()) {
3938
+ <fui-multi-factor-auth-assertion-screen />
3939
+ } @else {
3940
+ <div class="fui-screen">
3941
+ <fui-card>
3942
+ <fui-card-header>
3943
+ <fui-card-title>{{ titleText() }}</fui-card-title>
3944
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3945
+ </fui-card-header>
3946
+ <fui-card-content>
3947
+ <fui-email-link-auth-form (emailSent)="emailSent.emit()" (signIn)="signIn.emit($event.user)" />
3948
+ <ng-content></ng-content>
3949
+ <fui-redirect-error />
3950
+ </fui-card-content>
3951
+ </fui-card>
3952
+ </div>
3953
+ }
3954
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: EmailLinkAuthFormComponent, selector: "fui-email-link-auth-form", outputs: ["emailSent", "signIn"] }, { kind: "component", type: MultiFactorAuthAssertionScreenComponent, selector: "fui-multi-factor-auth-assertion-screen", outputs: ["onSuccess"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
2739
3955
  }
2740
3956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: EmailLinkAuthScreenComponent, decorators: [{
2741
3957
  type: Component,
2742
3958
  args: [{
2743
3959
  selector: "fui-email-link-auth-screen",
2744
3960
  standalone: true,
3961
+ host: {
3962
+ style: "display: block;",
3963
+ },
2745
3964
  imports: [
2746
3965
  CommonModule,
2747
3966
  CardComponent,
@@ -2750,25 +3969,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2750
3969
  CardSubtitleComponent,
2751
3970
  CardContentComponent,
2752
3971
  EmailLinkAuthFormComponent,
3972
+ MultiFactorAuthAssertionScreenComponent,
2753
3973
  RedirectErrorComponent,
2754
3974
  ],
2755
3975
  template: `
2756
- <div class="fui-screen">
2757
- <fui-card>
2758
- <fui-card-header>
2759
- <fui-card-title>{{ titleText() }}</fui-card-title>
2760
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
2761
- </fui-card-header>
2762
- <fui-card-content>
2763
- <fui-email-link-auth-form (emailSent)="emailSent.emit()" (signIn)="signIn.emit($event)" />
2764
- <fui-redirect-error />
2765
- <ng-content></ng-content>
2766
- </fui-card-content>
2767
- </fui-card>
2768
- </div>
3976
+ @if (mfaResolver()) {
3977
+ <fui-multi-factor-auth-assertion-screen />
3978
+ } @else {
3979
+ <div class="fui-screen">
3980
+ <fui-card>
3981
+ <fui-card-header>
3982
+ <fui-card-title>{{ titleText() }}</fui-card-title>
3983
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3984
+ </fui-card-header>
3985
+ <fui-card-content>
3986
+ <fui-email-link-auth-form (emailSent)="emailSent.emit()" (signIn)="signIn.emit($event.user)" />
3987
+ <ng-content></ng-content>
3988
+ <fui-redirect-error />
3989
+ </fui-card-content>
3990
+ </fui-card>
3991
+ </div>
3992
+ }
2769
3993
  `,
2770
3994
  }]
2771
- }], propDecorators: { emailSent: [{ type: i0.Output, args: ["emailSent"] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
3995
+ }], ctorParameters: () => [], propDecorators: { emailSent: [{
3996
+ type: Output
3997
+ }], signIn: [{
3998
+ type: Output
3999
+ }] } });
2772
4000
 
2773
4001
  /**
2774
4002
  * Copyright 2025 Google LLC
@@ -2785,13 +4013,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2785
4013
  * See the License for the specific language governing permissions and
2786
4014
  * limitations under the License.
2787
4015
  */
4016
+ /**
4017
+ * A screen component for requesting a password reset.
4018
+ */
2788
4019
  class ForgotPasswordAuthScreenComponent {
2789
4020
  titleText = injectTranslation("labels", "resetPassword");
2790
4021
  subtitleText = injectTranslation("prompts", "enterEmailToReset");
2791
- passwordSent = output();
2792
- backToSignIn = output();
4022
+ /** Event emitter fired when password reset email is sent. */
4023
+ passwordSent = new EventEmitter();
4024
+ /** Event emitter for back to sign in action. */
4025
+ backToSignIn = new EventEmitter();
2793
4026
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ForgotPasswordAuthScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2794
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: ForgotPasswordAuthScreenComponent, isStandalone: true, selector: "fui-forgot-password-auth-screen", outputs: { passwordSent: "passwordSent", backToSignIn: "backToSignIn" }, ngImport: i0, template: `
4027
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: ForgotPasswordAuthScreenComponent, isStandalone: true, selector: "fui-forgot-password-auth-screen", outputs: { passwordSent: "passwordSent", backToSignIn: "backToSignIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
2795
4028
  <div class="fui-screen">
2796
4029
  <fui-card>
2797
4030
  <fui-card-header>
@@ -2799,18 +4032,20 @@ class ForgotPasswordAuthScreenComponent {
2799
4032
  <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
2800
4033
  </fui-card-header>
2801
4034
  <fui-card-content>
2802
- <fui-forgot-password-auth-form (passwordSent)="passwordSent.emit()" (backToSignIn)="backToSignIn.emit()" />
2803
- <fui-redirect-error />
4035
+ <fui-forgot-password-auth-form [backToSignIn]="backToSignIn" (passwordSent)="passwordSent.emit()" />
2804
4036
  </fui-card-content>
2805
4037
  </fui-card>
2806
4038
  </div>
2807
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: ForgotPasswordAuthFormComponent, selector: "fui-forgot-password-auth-form", outputs: ["passwordSent", "backToSignIn"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
4039
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: ForgotPasswordAuthFormComponent, selector: "fui-forgot-password-auth-form", inputs: ["backToSignIn"], outputs: ["passwordSent"] }] });
2808
4040
  }
2809
4041
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ForgotPasswordAuthScreenComponent, decorators: [{
2810
4042
  type: Component,
2811
4043
  args: [{
2812
4044
  selector: "fui-forgot-password-auth-screen",
2813
4045
  standalone: true,
4046
+ host: {
4047
+ style: "display: block;",
4048
+ },
2814
4049
  imports: [
2815
4050
  CommonModule,
2816
4051
  CardComponent,
@@ -2819,7 +4054,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2819
4054
  CardSubtitleComponent,
2820
4055
  CardContentComponent,
2821
4056
  ForgotPasswordAuthFormComponent,
2822
- RedirectErrorComponent,
2823
4057
  ],
2824
4058
  template: `
2825
4059
  <div class="fui-screen">
@@ -2829,14 +4063,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2829
4063
  <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
2830
4064
  </fui-card-header>
2831
4065
  <fui-card-content>
2832
- <fui-forgot-password-auth-form (passwordSent)="passwordSent.emit()" (backToSignIn)="backToSignIn.emit()" />
2833
- <fui-redirect-error />
4066
+ <fui-forgot-password-auth-form [backToSignIn]="backToSignIn" (passwordSent)="passwordSent.emit()" />
2834
4067
  </fui-card-content>
2835
4068
  </fui-card>
2836
4069
  </div>
2837
4070
  `,
2838
4071
  }]
2839
- }], propDecorators: { passwordSent: [{ type: i0.Output, args: ["passwordSent"] }], backToSignIn: [{ type: i0.Output, args: ["backToSignIn"] }] } });
4072
+ }], propDecorators: { passwordSent: [{
4073
+ type: Output
4074
+ }], backToSignIn: [{
4075
+ type: Output
4076
+ }] } });
2840
4077
 
2841
4078
  /**
2842
4079
  * Copyright 2025 Google LLC
@@ -2853,76 +4090,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2853
4090
  * See the License for the specific language governing permissions and
2854
4091
  * limitations under the License.
2855
4092
  */
2856
- class DividerComponent {
2857
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
2858
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: DividerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2859
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: DividerComponent, isStandalone: true, selector: "fui-divider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2860
- <div class="fui-divider my-6">
2861
- <div class="fui-divider__line"></div>
2862
- @if (label()) {
2863
- <div class="fui-divider__text">{{ label() }}</div>
2864
- <div class="fui-divider__line"></div>
2865
- }
2866
- </div>
2867
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2868
- }
2869
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: DividerComponent, decorators: [{
2870
- type: Component,
2871
- args: [{
2872
- selector: "fui-divider",
2873
- standalone: true,
2874
- imports: [CommonModule],
2875
- template: `
2876
- <div class="fui-divider my-6">
2877
- <div class="fui-divider__line"></div>
2878
- @if (label()) {
2879
- <div class="fui-divider__text">{{ label() }}</div>
2880
- <div class="fui-divider__line"></div>
2881
- }
2882
- </div>
2883
- `,
2884
- }]
2885
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
2886
-
2887
4093
  /**
2888
- * Copyright 2025 Google LLC
2889
- *
2890
- * Licensed under the Apache License, Version 2.0 (the "License");
2891
- * you may not use this file except in compliance with the License.
2892
- * You may obtain a copy of the License at
4094
+ * A screen component for multi-factor authentication enrollment.
2893
4095
  *
2894
- * http://www.apache.org/licenses/LICENSE-2.0
2895
- *
2896
- * Unless required by applicable law or agreed to in writing, software
2897
- * distributed under the License is distributed on an "AS IS" BASIS,
2898
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2899
- * See the License for the specific language governing permissions and
2900
- * limitations under the License.
4096
+ * Displays the MFA enrollment form for setting up multi-factor authentication.
2901
4097
  */
2902
- class ContentComponent {
2903
- dividerOrLabel = injectTranslation("messages", "dividerOr");
2904
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2905
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: ContentComponent, isStandalone: true, selector: "fui-content", ngImport: i0, template: `
2906
- <fui-divider [label]="dividerOrLabel()" />
2907
- <div class="fui-screen__children">
2908
- <ng-content></ng-content>
4098
+ class MultiFactorAuthEnrollmentScreenComponent {
4099
+ /** The available MFA factor types for enrollment. */
4100
+ hints = input([FactorId.TOTP, FactorId.PHONE], ...(ngDevMode ? [{ debugName: "hints" }] : []));
4101
+ /** Event emitter fired when MFA enrollment is completed. */
4102
+ onEnrollment = new EventEmitter();
4103
+ titleText = injectTranslation("labels", "multiFactorEnrollment");
4104
+ subtitleText = injectTranslation("prompts", "mfaEnrollmentPrompt");
4105
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthEnrollmentScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4106
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.7", type: MultiFactorAuthEnrollmentScreenComponent, isStandalone: true, selector: "fui-multi-factor-auth-enrollment-screen", inputs: { hints: { classPropertyName: "hints", publicName: "hints", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onEnrollment: "onEnrollment" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4107
+ <div class="fui-screen">
4108
+ <fui-card>
4109
+ <fui-card-header>
4110
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4111
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4112
+ </fui-card-header>
4113
+ <fui-card-content>
4114
+ <fui-multi-factor-auth-enrollment-form [hints]="hints()" (onEnrollment)="onEnrollment.emit()" />
4115
+ </fui-card-content>
4116
+ </fui-card>
2909
4117
  </div>
2910
- `, isInline: true, dependencies: [{ kind: "component", type: DividerComponent, selector: "fui-divider", inputs: ["label"] }] });
4118
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: MultiFactorAuthEnrollmentFormComponent, selector: "fui-multi-factor-auth-enrollment-form", inputs: ["hints"], outputs: ["onEnrollment"] }] });
2911
4119
  }
2912
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ContentComponent, decorators: [{
4120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MultiFactorAuthEnrollmentScreenComponent, decorators: [{
2913
4121
  type: Component,
2914
4122
  args: [{
2915
- selector: "fui-content",
4123
+ selector: "fui-multi-factor-auth-enrollment-screen",
2916
4124
  standalone: true,
2917
- imports: [DividerComponent],
4125
+ host: {
4126
+ style: "display: block;",
4127
+ },
4128
+ imports: [
4129
+ CommonModule,
4130
+ CardComponent,
4131
+ CardHeaderComponent,
4132
+ CardTitleComponent,
4133
+ CardSubtitleComponent,
4134
+ CardContentComponent,
4135
+ MultiFactorAuthEnrollmentFormComponent,
4136
+ ],
2918
4137
  template: `
2919
- <fui-divider [label]="dividerOrLabel()" />
2920
- <div class="fui-screen__children">
2921
- <ng-content></ng-content>
4138
+ <div class="fui-screen">
4139
+ <fui-card>
4140
+ <fui-card-header>
4141
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4142
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4143
+ </fui-card-header>
4144
+ <fui-card-content>
4145
+ <fui-multi-factor-auth-enrollment-form [hints]="hints()" (onEnrollment)="onEnrollment.emit()" />
4146
+ </fui-card-content>
4147
+ </fui-card>
2922
4148
  </div>
2923
4149
  `,
2924
4150
  }]
2925
- }] });
4151
+ }], propDecorators: { hints: [{ type: i0.Input, args: [{ isSignal: true, alias: "hints", required: false }] }], onEnrollment: [{
4152
+ type: Output
4153
+ }] } });
2926
4154
 
2927
4155
  /**
2928
4156
  * Copyright 2025 Google LLC
@@ -2939,39 +4167,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2939
4167
  * See the License for the specific language governing permissions and
2940
4168
  * limitations under the License.
2941
4169
  */
4170
+ /**
4171
+ * A screen component for OAuth authentication.
4172
+ *
4173
+ * Automatically displays the MFA assertion screen if a multi-factor resolver is present.
4174
+ * Use this screen to display OAuth sign-in buttons.
4175
+ */
2942
4176
  class OAuthScreenComponent {
2943
4177
  ui = injectUI();
2944
4178
  mfaResolver = computed(() => this.ui().multiFactorResolver, ...(ngDevMode ? [{ debugName: "mfaResolver" }] : []));
2945
4179
  titleText = injectTranslation("labels", "signIn");
2946
4180
  subtitleText = injectTranslation("prompts", "signInToAccount");
4181
+ constructor() {
4182
+ injectUserAuthenticated((user) => {
4183
+ this.onSignIn.emit(user);
4184
+ });
4185
+ }
4186
+ /** Event emitter for successful sign-in. */
4187
+ onSignIn = new EventEmitter();
2947
4188
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OAuthScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2948
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: OAuthScreenComponent, isStandalone: true, selector: "fui-oauth-screen", ngImport: i0, template: `
2949
- <div class="fui-screen">
2950
- <fui-card>
2951
- <fui-card-header>
2952
- <fui-card-title>{{ titleText() }}</fui-card-title>
2953
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
2954
- </fui-card-header>
2955
- <fui-card-content>
2956
- @if (mfaResolver()) {
2957
- <fui-multi-factor-auth-assertion-form />
2958
- } @else {
2959
- <fui-content>
4189
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: OAuthScreenComponent, isStandalone: true, selector: "fui-oauth-screen", outputs: { onSignIn: "onSignIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4190
+ @if (mfaResolver()) {
4191
+ <fui-multi-factor-auth-assertion-screen />
4192
+ } @else {
4193
+ <div class="fui-screen">
4194
+ <fui-card>
4195
+ <fui-card-header>
4196
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4197
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4198
+ </fui-card-header>
4199
+ <fui-card-content>
4200
+ <div class="fui-screen__children">
2960
4201
  <ng-content></ng-content>
2961
- </fui-content>
2962
- <fui-redirect-error />
2963
- <fui-policies />
2964
- }
2965
- </fui-card-content>
2966
- </fui-card>
2967
- </div>
2968
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: ContentComponent, selector: "fui-content" }, { kind: "component", type: MultiFactorAuthAssertionFormComponent, selector: "fui-multi-factor-auth-assertion-form" }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
4202
+ <fui-redirect-error />
4203
+ <fui-policies />
4204
+ </div>
4205
+ </fui-card-content>
4206
+ </fui-card>
4207
+ </div>
4208
+ }
4209
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: PoliciesComponent, selector: "fui-policies" }, { kind: "component", type: MultiFactorAuthAssertionScreenComponent, selector: "fui-multi-factor-auth-assertion-screen", outputs: ["onSuccess"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
2969
4210
  }
2970
4211
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OAuthScreenComponent, decorators: [{
2971
4212
  type: Component,
2972
4213
  args: [{
2973
4214
  selector: "fui-oauth-screen",
2974
4215
  standalone: true,
4216
+ host: {
4217
+ style: "display: block;",
4218
+ },
2975
4219
  imports: [
2976
4220
  CommonModule,
2977
4221
  CardComponent,
@@ -2980,33 +4224,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
2980
4224
  CardSubtitleComponent,
2981
4225
  CardContentComponent,
2982
4226
  PoliciesComponent,
2983
- ContentComponent,
2984
- MultiFactorAuthAssertionFormComponent,
4227
+ MultiFactorAuthAssertionScreenComponent,
2985
4228
  RedirectErrorComponent,
2986
4229
  ],
2987
4230
  template: `
2988
- <div class="fui-screen">
2989
- <fui-card>
2990
- <fui-card-header>
2991
- <fui-card-title>{{ titleText() }}</fui-card-title>
2992
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
2993
- </fui-card-header>
2994
- <fui-card-content>
2995
- @if (mfaResolver()) {
2996
- <fui-multi-factor-auth-assertion-form />
2997
- } @else {
2998
- <fui-content>
4231
+ @if (mfaResolver()) {
4232
+ <fui-multi-factor-auth-assertion-screen />
4233
+ } @else {
4234
+ <div class="fui-screen">
4235
+ <fui-card>
4236
+ <fui-card-header>
4237
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4238
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4239
+ </fui-card-header>
4240
+ <fui-card-content>
4241
+ <div class="fui-screen__children">
2999
4242
  <ng-content></ng-content>
3000
- </fui-content>
3001
- <fui-redirect-error />
3002
- <fui-policies />
3003
- }
3004
- </fui-card-content>
3005
- </fui-card>
3006
- </div>
4243
+ <fui-redirect-error />
4244
+ <fui-policies />
4245
+ </div>
4246
+ </fui-card-content>
4247
+ </fui-card>
4248
+ </div>
4249
+ }
3007
4250
  `,
3008
4251
  }]
3009
- }] });
4252
+ }], ctorParameters: () => [], propDecorators: { onSignIn: [{
4253
+ type: Output
4254
+ }] } });
3010
4255
 
3011
4256
  /**
3012
4257
  * Copyright 2025 Google LLC
@@ -3023,39 +4268,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3023
4268
  * See the License for the specific language governing permissions and
3024
4269
  * limitations under the License.
3025
4270
  */
4271
+ /**
4272
+ * A screen component for phone number authentication.
4273
+ *
4274
+ * Automatically displays the MFA assertion screen if a multi-factor resolver is present.
4275
+ */
3026
4276
  class PhoneAuthScreenComponent {
3027
4277
  ui = injectUI();
3028
4278
  mfaResolver = computed(() => this.ui().multiFactorResolver, ...(ngDevMode ? [{ debugName: "mfaResolver" }] : []));
3029
4279
  titleText = injectTranslation("labels", "signIn");
3030
4280
  subtitleText = injectTranslation("prompts", "signInToAccount");
3031
- resendDelay = input(30, ...(ngDevMode ? [{ debugName: "resendDelay" }] : []));
3032
- signIn = output();
4281
+ constructor() {
4282
+ injectUserAuthenticated((user) => {
4283
+ this.signIn.emit(user);
4284
+ });
4285
+ }
4286
+ /** Event emitter for successful sign-in. */
4287
+ signIn = new EventEmitter();
3033
4288
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PhoneAuthScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3034
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PhoneAuthScreenComponent, isStandalone: true, selector: "fui-phone-auth-screen", inputs: { resendDelay: { classPropertyName: "resendDelay", publicName: "resendDelay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { signIn: "signIn" }, ngImport: i0, template: `
3035
- <div class="fui-screen">
3036
- <fui-card>
3037
- <fui-card-header>
3038
- <fui-card-title>{{ titleText() }}</fui-card-title>
3039
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3040
- </fui-card-header>
3041
- <fui-card-content>
3042
- @if (mfaResolver()) {
3043
- <fui-multi-factor-auth-assertion-form />
3044
- } @else {
3045
- <fui-phone-auth-form (signIn)="signIn.emit($event)" />
3046
- <fui-redirect-error />
4289
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PhoneAuthScreenComponent, isStandalone: true, selector: "fui-phone-auth-screen", outputs: { signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4290
+ @if (mfaResolver()) {
4291
+ <fui-multi-factor-auth-assertion-screen />
4292
+ } @else {
4293
+ <div class="fui-screen">
4294
+ <fui-card>
4295
+ <fui-card-header>
4296
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4297
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4298
+ </fui-card-header>
4299
+ <fui-card-content>
4300
+ <fui-phone-auth-form (signIn)="signIn.emit($event.user)" />
3047
4301
  <ng-content />
3048
- }
3049
- </fui-card-content>
3050
- </fui-card>
3051
- </div>
3052
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: PhoneAuthFormComponent, selector: "fui-phone-auth-form", outputs: ["signIn"] }, { kind: "component", type: MultiFactorAuthAssertionFormComponent, selector: "fui-multi-factor-auth-assertion-form" }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
4302
+ <fui-redirect-error />
4303
+ </fui-card-content>
4304
+ </fui-card>
4305
+ </div>
4306
+ }
4307
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: PhoneAuthFormComponent, selector: "fui-phone-auth-form", outputs: ["signIn"] }, { kind: "component", type: MultiFactorAuthAssertionScreenComponent, selector: "fui-multi-factor-auth-assertion-screen", outputs: ["onSuccess"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
3053
4308
  }
3054
4309
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PhoneAuthScreenComponent, decorators: [{
3055
4310
  type: Component,
3056
4311
  args: [{
3057
4312
  selector: "fui-phone-auth-screen",
3058
4313
  standalone: true,
4314
+ host: {
4315
+ style: "display: block;",
4316
+ },
3059
4317
  imports: [
3060
4318
  CommonModule,
3061
4319
  CardComponent,
@@ -3064,30 +4322,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3064
4322
  CardSubtitleComponent,
3065
4323
  CardContentComponent,
3066
4324
  PhoneAuthFormComponent,
3067
- MultiFactorAuthAssertionFormComponent,
4325
+ MultiFactorAuthAssertionScreenComponent,
3068
4326
  RedirectErrorComponent,
3069
4327
  ],
3070
4328
  template: `
3071
- <div class="fui-screen">
3072
- <fui-card>
3073
- <fui-card-header>
3074
- <fui-card-title>{{ titleText() }}</fui-card-title>
3075
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3076
- </fui-card-header>
3077
- <fui-card-content>
3078
- @if (mfaResolver()) {
3079
- <fui-multi-factor-auth-assertion-form />
3080
- } @else {
3081
- <fui-phone-auth-form (signIn)="signIn.emit($event)" />
3082
- <fui-redirect-error />
4329
+ @if (mfaResolver()) {
4330
+ <fui-multi-factor-auth-assertion-screen />
4331
+ } @else {
4332
+ <div class="fui-screen">
4333
+ <fui-card>
4334
+ <fui-card-header>
4335
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4336
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4337
+ </fui-card-header>
4338
+ <fui-card-content>
4339
+ <fui-phone-auth-form (signIn)="signIn.emit($event.user)" />
3083
4340
  <ng-content />
3084
- }
3085
- </fui-card-content>
3086
- </fui-card>
3087
- </div>
4341
+ <fui-redirect-error />
4342
+ </fui-card-content>
4343
+ </fui-card>
4344
+ </div>
4345
+ }
3088
4346
  `,
3089
4347
  }]
3090
- }], propDecorators: { resendDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "resendDelay", required: false }] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
4348
+ }], ctorParameters: () => [], propDecorators: { signIn: [{
4349
+ type: Output
4350
+ }] } });
3091
4351
 
3092
4352
  /**
3093
4353
  * Copyright 2025 Google LLC
@@ -3104,44 +4364,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3104
4364
  * See the License for the specific language governing permissions and
3105
4365
  * limitations under the License.
3106
4366
  */
4367
+ /**
4368
+ * A screen component for email/password sign-in.
4369
+ *
4370
+ * Automatically displays the MFA assertion screen if a multi-factor resolver is present.
4371
+ */
3107
4372
  class SignInAuthScreenComponent {
3108
4373
  ui = injectUI();
3109
4374
  mfaResolver = computed(() => this.ui().multiFactorResolver, ...(ngDevMode ? [{ debugName: "mfaResolver" }] : []));
3110
4375
  titleText = injectTranslation("labels", "signIn");
3111
4376
  subtitleText = injectTranslation("prompts", "signInToAccount");
3112
- forgotPassword = output();
3113
- signUp = output();
3114
- signIn = output();
4377
+ constructor() {
4378
+ injectUserAuthenticated((user) => {
4379
+ this.signIn.emit(user);
4380
+ });
4381
+ }
4382
+ /** Event emitter for forgot password action. */
4383
+ forgotPassword = new EventEmitter();
4384
+ /** Event emitter for sign up action. */
4385
+ signUp = new EventEmitter();
4386
+ /** Event emitter for successful sign-in. */
4387
+ signIn = new EventEmitter();
3115
4388
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignInAuthScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3116
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignInAuthScreenComponent, isStandalone: true, selector: "fui-sign-in-auth-screen", outputs: { forgotPassword: "forgotPassword", signUp: "signUp", signIn: "signIn" }, ngImport: i0, template: `
3117
- <div class="fui-screen">
3118
- <fui-card>
3119
- <fui-card-header>
3120
- <fui-card-title>{{ titleText() }}</fui-card-title>
3121
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3122
- </fui-card-header>
3123
- <fui-card-content>
3124
- @if (mfaResolver()) {
3125
- <fui-multi-factor-auth-assertion-form />
3126
- } @else {
3127
- <fui-sign-in-auth-form
3128
- (forgotPassword)="forgotPassword.emit()"
3129
- (signUp)="signUp.emit()"
3130
- (signIn)="signIn.emit($event)"
3131
- />
3132
- <fui-redirect-error />
4389
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignInAuthScreenComponent, isStandalone: true, selector: "fui-sign-in-auth-screen", outputs: { forgotPassword: "forgotPassword", signUp: "signUp", signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4390
+ @if (mfaResolver()) {
4391
+ <fui-multi-factor-auth-assertion-screen />
4392
+ } @else {
4393
+ <div class="fui-screen">
4394
+ <fui-card>
4395
+ <fui-card-header>
4396
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4397
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4398
+ </fui-card-header>
4399
+ <fui-card-content>
4400
+ <fui-sign-in-auth-form [forgotPassword]="forgotPassword" [signUp]="signUp" />
3133
4401
  <ng-content />
3134
- }
3135
- </fui-card-content>
3136
- </fui-card>
3137
- </div>
3138
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: SignInAuthFormComponent, selector: "fui-sign-in-auth-form", outputs: ["forgotPassword", "signUp", "signIn"] }, { kind: "component", type: MultiFactorAuthAssertionFormComponent, selector: "fui-multi-factor-auth-assertion-form" }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
4402
+ <fui-redirect-error />
4403
+ </fui-card-content>
4404
+ </fui-card>
4405
+ </div>
4406
+ }
4407
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: SignInAuthFormComponent, selector: "fui-sign-in-auth-form", inputs: ["forgotPassword", "signUp"], outputs: ["signIn"] }, { kind: "component", type: MultiFactorAuthAssertionScreenComponent, selector: "fui-multi-factor-auth-assertion-screen", outputs: ["onSuccess"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
3139
4408
  }
3140
4409
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignInAuthScreenComponent, decorators: [{
3141
4410
  type: Component,
3142
4411
  args: [{
3143
4412
  selector: "fui-sign-in-auth-screen",
3144
4413
  standalone: true,
4414
+ host: {
4415
+ style: "display: block;",
4416
+ },
3145
4417
  imports: [
3146
4418
  CommonModule,
3147
4419
  CardComponent,
@@ -3150,34 +4422,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3150
4422
  CardSubtitleComponent,
3151
4423
  CardContentComponent,
3152
4424
  SignInAuthFormComponent,
3153
- MultiFactorAuthAssertionFormComponent,
4425
+ MultiFactorAuthAssertionScreenComponent,
3154
4426
  RedirectErrorComponent,
3155
4427
  ],
3156
4428
  template: `
3157
- <div class="fui-screen">
3158
- <fui-card>
3159
- <fui-card-header>
3160
- <fui-card-title>{{ titleText() }}</fui-card-title>
3161
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3162
- </fui-card-header>
3163
- <fui-card-content>
3164
- @if (mfaResolver()) {
3165
- <fui-multi-factor-auth-assertion-form />
3166
- } @else {
3167
- <fui-sign-in-auth-form
3168
- (forgotPassword)="forgotPassword.emit()"
3169
- (signUp)="signUp.emit()"
3170
- (signIn)="signIn.emit($event)"
3171
- />
3172
- <fui-redirect-error />
4429
+ @if (mfaResolver()) {
4430
+ <fui-multi-factor-auth-assertion-screen />
4431
+ } @else {
4432
+ <div class="fui-screen">
4433
+ <fui-card>
4434
+ <fui-card-header>
4435
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4436
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4437
+ </fui-card-header>
4438
+ <fui-card-content>
4439
+ <fui-sign-in-auth-form [forgotPassword]="forgotPassword" [signUp]="signUp" />
3173
4440
  <ng-content />
3174
- }
3175
- </fui-card-content>
3176
- </fui-card>
3177
- </div>
4441
+ <fui-redirect-error />
4442
+ </fui-card-content>
4443
+ </fui-card>
4444
+ </div>
4445
+ }
3178
4446
  `,
3179
4447
  }]
3180
- }], propDecorators: { forgotPassword: [{ type: i0.Output, args: ["forgotPassword"] }], signUp: [{ type: i0.Output, args: ["signUp"] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
4448
+ }], ctorParameters: () => [], propDecorators: { forgotPassword: [{
4449
+ type: Output
4450
+ }], signUp: [{
4451
+ type: Output
4452
+ }], signIn: [{
4453
+ type: Output
4454
+ }] } });
3181
4455
 
3182
4456
  /**
3183
4457
  * Copyright 2025 Google LLC
@@ -3194,39 +4468,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3194
4468
  * See the License for the specific language governing permissions and
3195
4469
  * limitations under the License.
3196
4470
  */
4471
+ /**
4472
+ * A screen component for email/password sign-up.
4473
+ *
4474
+ * Automatically displays the MFA assertion screen if a multi-factor resolver is present.
4475
+ */
3197
4476
  class SignUpAuthScreenComponent {
3198
4477
  ui = injectUI();
3199
4478
  mfaResolver = computed(() => this.ui().multiFactorResolver, ...(ngDevMode ? [{ debugName: "mfaResolver" }] : []));
3200
4479
  titleText = injectTranslation("labels", "signUp");
3201
4480
  subtitleText = injectTranslation("prompts", "enterDetailsToCreate");
3202
- signUp = output();
3203
- signIn = output();
4481
+ constructor() {
4482
+ injectUserAuthenticated((user) => {
4483
+ this.signUp.emit(user);
4484
+ });
4485
+ }
4486
+ /** Event emitter for successful sign-up. */
4487
+ signUp = new EventEmitter();
4488
+ /** Event emitter for sign in action. */
4489
+ signIn = new EventEmitter();
3204
4490
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignUpAuthScreenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3205
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignUpAuthScreenComponent, isStandalone: true, selector: "fui-sign-up-auth-screen", outputs: { signUp: "signUp", signIn: "signIn" }, ngImport: i0, template: `
3206
- <div class="fui-screen">
3207
- <fui-card>
3208
- <fui-card-header>
3209
- <fui-card-title>{{ titleText() }}</fui-card-title>
3210
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3211
- </fui-card-header>
3212
- <fui-card-content>
3213
- @if (mfaResolver()) {
3214
- <fui-multi-factor-auth-assertion-form />
3215
- } @else {
3216
- <fui-sign-up-auth-form (signIn)="signIn.emit()" (signUp)="signUp.emit($event)" />
3217
- <fui-redirect-error />
4491
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SignUpAuthScreenComponent, isStandalone: true, selector: "fui-sign-up-auth-screen", outputs: { signUp: "signUp", signIn: "signIn" }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4492
+ @if (mfaResolver()) {
4493
+ <fui-multi-factor-auth-assertion-screen />
4494
+ } @else {
4495
+ <div class="fui-screen">
4496
+ <fui-card>
4497
+ <fui-card-header>
4498
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4499
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4500
+ </fui-card-header>
4501
+ <fui-card-content>
4502
+ <fui-sign-up-auth-form [signIn]="signIn" (signUp)="signUp.emit($event.user)" />
3218
4503
  <ng-content />
3219
- }
3220
- </fui-card-content>
3221
- </fui-card>
3222
- </div>
3223
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: SignUpAuthFormComponent, selector: "fui-sign-up-auth-form", outputs: ["signUp", "signIn"] }, { kind: "component", type: MultiFactorAuthAssertionFormComponent, selector: "fui-multi-factor-auth-assertion-form" }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
4504
+ <fui-redirect-error />
4505
+ </fui-card-content>
4506
+ </fui-card>
4507
+ </div>
4508
+ }
4509
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "fui-card" }, { kind: "component", type: CardHeaderComponent, selector: "fui-card-header" }, { kind: "component", type: CardTitleComponent, selector: "fui-card-title" }, { kind: "component", type: CardSubtitleComponent, selector: "fui-card-subtitle" }, { kind: "component", type: CardContentComponent, selector: "fui-card-content" }, { kind: "component", type: SignUpAuthFormComponent, selector: "fui-sign-up-auth-form", inputs: ["signIn"], outputs: ["signUp"] }, { kind: "component", type: MultiFactorAuthAssertionScreenComponent, selector: "fui-multi-factor-auth-assertion-screen", outputs: ["onSuccess"] }, { kind: "component", type: RedirectErrorComponent, selector: "fui-redirect-error" }] });
3224
4510
  }
3225
4511
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SignUpAuthScreenComponent, decorators: [{
3226
4512
  type: Component,
3227
4513
  args: [{
3228
4514
  selector: "fui-sign-up-auth-screen",
3229
4515
  standalone: true,
4516
+ host: {
4517
+ style: "display: block;",
4518
+ },
3230
4519
  imports: [
3231
4520
  CommonModule,
3232
4521
  CardComponent,
@@ -3235,30 +4524,133 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3235
4524
  CardSubtitleComponent,
3236
4525
  CardContentComponent,
3237
4526
  SignUpAuthFormComponent,
3238
- MultiFactorAuthAssertionFormComponent,
4527
+ MultiFactorAuthAssertionScreenComponent,
3239
4528
  RedirectErrorComponent,
3240
4529
  ],
3241
4530
  template: `
3242
- <div class="fui-screen">
3243
- <fui-card>
3244
- <fui-card-header>
3245
- <fui-card-title>{{ titleText() }}</fui-card-title>
3246
- <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
3247
- </fui-card-header>
3248
- <fui-card-content>
3249
- @if (mfaResolver()) {
3250
- <fui-multi-factor-auth-assertion-form />
3251
- } @else {
3252
- <fui-sign-up-auth-form (signIn)="signIn.emit()" (signUp)="signUp.emit($event)" />
3253
- <fui-redirect-error />
4531
+ @if (mfaResolver()) {
4532
+ <fui-multi-factor-auth-assertion-screen />
4533
+ } @else {
4534
+ <div class="fui-screen">
4535
+ <fui-card>
4536
+ <fui-card-header>
4537
+ <fui-card-title>{{ titleText() }}</fui-card-title>
4538
+ <fui-card-subtitle>{{ subtitleText() }}</fui-card-subtitle>
4539
+ </fui-card-header>
4540
+ <fui-card-content>
4541
+ <fui-sign-up-auth-form [signIn]="signIn" (signUp)="signUp.emit($event.user)" />
3254
4542
  <ng-content />
3255
- }
3256
- </fui-card-content>
3257
- </fui-card>
4543
+ <fui-redirect-error />
4544
+ </fui-card-content>
4545
+ </fui-card>
4546
+ </div>
4547
+ }
4548
+ `,
4549
+ }]
4550
+ }], ctorParameters: () => [], propDecorators: { signUp: [{
4551
+ type: Output
4552
+ }], signIn: [{
4553
+ type: Output
4554
+ }] } });
4555
+
4556
+ /**
4557
+ * Copyright 2025 Google LLC
4558
+ *
4559
+ * Licensed under the Apache License, Version 2.0 (the "License");
4560
+ * you may not use this file except in compliance with the License.
4561
+ * You may obtain a copy of the License at
4562
+ *
4563
+ * http://www.apache.org/licenses/LICENSE-2.0
4564
+ *
4565
+ * Unless required by applicable law or agreed to in writing, software
4566
+ * distributed under the License is distributed on an "AS IS" BASIS,
4567
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4568
+ * See the License for the specific language governing permissions and
4569
+ * limitations under the License.
4570
+ */
4571
+ /**
4572
+ * A divider component that can display a line or a line with text in the middle.
4573
+ */
4574
+ class DividerComponent {
4575
+ /** Optional label text to display in the center of the divider. */
4576
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
4577
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: DividerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4578
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: DividerComponent, isStandalone: true, selector: "fui-divider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4579
+ <div class="fui-divider my-6">
4580
+ <div class="fui-divider__line"></div>
4581
+ @if (label()) {
4582
+ <div class="fui-divider__text">{{ label() }}</div>
4583
+ <div class="fui-divider__line"></div>
4584
+ }
4585
+ </div>
4586
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
4587
+ }
4588
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: DividerComponent, decorators: [{
4589
+ type: Component,
4590
+ args: [{
4591
+ selector: "fui-divider",
4592
+ standalone: true,
4593
+ imports: [CommonModule],
4594
+ host: {
4595
+ style: "display: block;",
4596
+ },
4597
+ template: `
4598
+ <div class="fui-divider my-6">
4599
+ <div class="fui-divider__line"></div>
4600
+ @if (label()) {
4601
+ <div class="fui-divider__text">{{ label() }}</div>
4602
+ <div class="fui-divider__line"></div>
4603
+ }
4604
+ </div>
4605
+ `,
4606
+ }]
4607
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
4608
+
4609
+ /**
4610
+ * Copyright 2025 Google LLC
4611
+ *
4612
+ * Licensed under the Apache License, Version 2.0 (the "License");
4613
+ * you may not use this file except in compliance with the License.
4614
+ * You may obtain a copy of the License at
4615
+ *
4616
+ * http://www.apache.org/licenses/LICENSE-2.0
4617
+ *
4618
+ * Unless required by applicable law or agreed to in writing, software
4619
+ * distributed under the License is distributed on an "AS IS" BASIS,
4620
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4621
+ * See the License for the specific language governing permissions and
4622
+ * limitations under the License.
4623
+ */
4624
+ /**
4625
+ * A content wrapper component that displays a divider and children content.
4626
+ */
4627
+ class ContentComponent {
4628
+ dividerOrLabel = injectTranslation("messages", "dividerOr");
4629
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4630
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: ContentComponent, isStandalone: true, selector: "fui-content", host: { styleAttribute: "display: block;" }, ngImport: i0, template: `
4631
+ <fui-divider [label]="dividerOrLabel()" />
4632
+ <div class="fui-screen__children">
4633
+ <ng-content></ng-content>
4634
+ </div>
4635
+ `, isInline: true, dependencies: [{ kind: "component", type: DividerComponent, selector: "fui-divider", inputs: ["label"] }] });
4636
+ }
4637
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ContentComponent, decorators: [{
4638
+ type: Component,
4639
+ args: [{
4640
+ selector: "fui-content",
4641
+ standalone: true,
4642
+ imports: [DividerComponent],
4643
+ host: {
4644
+ style: "display: block;",
4645
+ },
4646
+ template: `
4647
+ <fui-divider [label]="dividerOrLabel()" />
4648
+ <div class="fui-screen__children">
4649
+ <ng-content></ng-content>
3258
4650
  </div>
3259
4651
  `,
3260
4652
  }]
3261
- }], propDecorators: { signUp: [{ type: i0.Output, args: ["signUp"] }], signIn: [{ type: i0.Output, args: ["signIn"] }] } });
4653
+ }] });
3262
4654
 
3263
4655
  /**
3264
4656
  * Copyright 2025 Google LLC
@@ -3284,5 +4676,5 @@ if (!isDevMode()) {
3284
4676
  * Generated bundle index. Do not edit.
3285
4677
  */
3286
4678
 
3287
- export { AppleSignInButtonComponent, ButtonComponent, CardComponent, ContentComponent, CountrySelectorComponent, DividerComponent, EmailLinkAuthFormComponent, EmailLinkAuthScreenComponent, FacebookSignInButtonComponent, ForgotPasswordAuthFormComponent, ForgotPasswordAuthScreenComponent, GithubSignInButtonComponent, GoogleSignInButtonComponent, MicrosoftSignInButtonComponent, MultiFactorAuthAssertionFormComponent, OAuthButtonComponent, OAuthScreenComponent, PhoneAuthFormComponent, PhoneAuthScreenComponent, PoliciesComponent, RedirectErrorComponent, SignInAuthFormComponent, SignInAuthScreenComponent, SignUpAuthFormComponent, SignUpAuthScreenComponent, SmsMultiFactorAssertionFormComponent, SmsMultiFactorAssertionPhoneFormComponent, SmsMultiFactorAssertionVerifyFormComponent, TotpMultiFactorAssertionFormComponent, TwitterSignInButtonComponent, injectCountries, injectDefaultCountry, injectEmailLinkAuthFormSchema, injectForgotPasswordAuthFormSchema, injectMultiFactorPhoneAuthNumberFormSchema, injectMultiFactorPhoneAuthVerifyFormSchema, injectMultiFactorTotpAuthNumberFormSchema, injectMultiFactorTotpAuthVerifyFormSchema, injectPhoneAuthFormSchema, injectPhoneAuthVerifyFormSchema, injectPolicies, injectRecaptchaVerifier, injectRedirectError, injectSignInAuthFormSchema, injectSignUpAuthFormSchema, injectTranslation, injectUI, provideFirebaseUI, provideFirebaseUIPolicies };
4679
+ export { AppleSignInButtonComponent, ButtonComponent, CardComponent, CardContentComponent, CardHeaderComponent, CardSubtitleComponent, CardTitleComponent, ContentComponent, CountrySelectorComponent, DividerComponent, EmailLinkAuthFormComponent, EmailLinkAuthScreenComponent, FacebookSignInButtonComponent, ForgotPasswordAuthFormComponent, ForgotPasswordAuthScreenComponent, GitHubSignInButtonComponent, GoogleSignInButtonComponent, MicrosoftSignInButtonComponent, MultiFactorAuthAssertionFormComponent, MultiFactorAuthAssertionScreenComponent, MultiFactorAuthEnrollmentFormComponent, MultiFactorAuthEnrollmentScreenComponent, OAuthButtonComponent, OAuthScreenComponent, PhoneAuthFormComponent, PhoneAuthScreenComponent, PoliciesComponent, RedirectErrorComponent, SignInAuthFormComponent, SignInAuthScreenComponent, SignUpAuthFormComponent, SignUpAuthScreenComponent, SmsMultiFactorAssertionFormComponent, SmsMultiFactorAssertionPhoneFormComponent, SmsMultiFactorAssertionVerifyFormComponent, SmsMultiFactorEnrollmentFormComponent, TotpMultiFactorAssertionFormComponent, TotpMultiFactorEnrollmentFormComponent, TwitterSignInButtonComponent, injectCountries, injectDefaultCountry, injectEmailLinkAuthFormSchema, injectForgotPasswordAuthFormSchema, injectMultiFactorPhoneAuthAssertionFormSchema, injectMultiFactorPhoneAuthNumberFormSchema, injectMultiFactorPhoneAuthVerifyFormSchema, injectMultiFactorTotpAuthNumberFormSchema, injectMultiFactorTotpAuthVerifyFormSchema, injectPhoneAuthFormSchema, injectPhoneAuthVerifyFormSchema, injectPolicies, injectRecaptchaVerifier, injectRedirectError, injectSignInAuthFormSchema, injectSignUpAuthFormSchema, injectTranslation, injectUI, injectUserAuthenticated, provideFirebaseUI, provideFirebaseUIPolicies };
3288
4680
  //# sourceMappingURL=firebase-oss-ui-angular.mjs.map