diff --git a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts index 0e7f0709..2d1d984f 100644 --- a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts @@ -106,7 +106,7 @@ export class EmailLinkAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { await sendSignInLinkToEmail(this.ui(), value.email); diff --git a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts index e45e6509..da0d44ec 100644 --- a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts @@ -114,7 +114,7 @@ export class ForgotPasswordAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { await sendPasswordResetEmail(this.ui(), value.email); diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts index 143ef207..49b32bea 100644 --- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts @@ -203,7 +203,7 @@ export class SmsMultiFactorAssertionVerifyFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts index 680244e5..af8d72f5 100644 --- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts @@ -164,7 +164,7 @@ export class SmsMultiFactorEnrollmentFormComponent { effect(() => { this.phoneForm.update({ validators: { - onBlur: this.phoneFormSchema(), + onChange: this.phoneFormSchema(), onSubmit: this.phoneFormSchema(), onSubmitAsync: async ({ value }) => { try { @@ -192,7 +192,7 @@ export class SmsMultiFactorEnrollmentFormComponent { effect(() => { this.verificationForm.update({ validators: { - onBlur: this.verificationFormSchema(), + onChange: this.verificationFormSchema(), onSubmit: this.verificationFormSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts index d9b08e92..c6d6c479 100644 --- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts @@ -88,7 +88,7 @@ export class TotpMultiFactorAssertionFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const assertion = TotpMultiFactorGenerator.assertionForSignIn(this.hint().uid, value.verificationCode); diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts index 936690eb..a9b2caa6 100644 --- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts @@ -91,7 +91,7 @@ export class TotpMultiFactorSecretGenerationFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { try { @@ -191,7 +191,7 @@ export class TotpMultiFactorVerificationFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/phone-auth-form.ts b/packages/angular/src/lib/auth/forms/phone-auth-form.ts index b87d10fe..88e57422 100644 --- a/packages/angular/src/lib/auth/forms/phone-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/phone-auth-form.ts @@ -110,7 +110,7 @@ export class PhoneNumberFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { const selectedCountry = countryData.find((c) => c.code === this.country()); const formattedNumber = formatPhoneNumber(value.phoneNumber, selectedCountry!); @@ -226,7 +226,7 @@ export class VerificationFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const credential = await confirmPhoneNumber(this.ui(), this.verificationId(), value.verificationCode); diff --git a/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts index a1fa5c12..b6547923 100644 --- a/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts @@ -129,7 +129,7 @@ export class SignInAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const credential = await signInWithEmailAndPassword(this.ui(), value.email, value.password); diff --git a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts index 498d985c..5d455dce 100644 --- a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts @@ -125,7 +125,7 @@ export class SignUpAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const credential = await createUserWithEmailAndPassword( diff --git a/packages/angular/src/lib/components/form.spec.ts b/packages/angular/src/lib/components/form.spec.ts index 34372808..b0d50bdf 100644 --- a/packages/angular/src/lib/components/form.spec.ts +++ b/packages/angular/src/lib/components/form.spec.ts @@ -28,19 +28,13 @@ import { import { ButtonComponent } from "./button"; @Component({ - template: ``, + template: ``, standalone: true, imports: [FormMetadataComponent], }) class TestFormMetadataHostComponent { - field = signal({ - state: { - meta: { - isTouched: true, - errors: [{ message: "Test error" }], - }, - }, - } as any); + isTouched = signal(true); + errors = signal([{ message: "Test error" }]); } @Component({ @@ -90,14 +84,7 @@ describe("Form Components", () => { it("does not render error message when field has no errors", async () => { const component = await render(TestFormMetadataHostComponent); - component.fixture.componentInstance.field.set({ - state: { - meta: { - isTouched: true, - errors: [], - }, - }, - } as any); + component.fixture.componentInstance.errors.set([]); component.fixture.detectChanges(); const errorElement = screen.queryByRole("alert"); @@ -107,14 +94,7 @@ describe("Form Components", () => { it("does not render error message when field is not touched", async () => { const component = await render(TestFormMetadataHostComponent); - component.fixture.componentInstance.field.set({ - state: { - meta: { - isTouched: false, - errors: [{ message: "Test error" }], - }, - }, - } as any); + component.fixture.componentInstance.isTouched.set(false); component.fixture.detectChanges(); const errorElement = screen.queryByRole("alert"); diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts index 856a96fa..d9db7406 100644 --- a/packages/angular/src/lib/components/form.ts +++ b/packages/angular/src/lib/components/form.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Component, computed, input } from "@angular/core"; -import { AnyFieldApi, AnyFormState, injectField } from "@tanstack/angular-form"; +import { ChangeDetectorRef, Component, computed, inject, input, OnChanges, SimpleChanges } from "@angular/core"; +import { AnyFormState, injectField } from "@tanstack/angular-form"; import { ButtonComponent } from "./button"; @Component({ @@ -25,10 +25,10 @@ import { ButtonComponent } from "./button"; style: "display: block;", }, template: ` - @if (field().state.meta.isTouched && errors().length > 0) { + @if (isTouched() && errors().length > 0) {
} @@ -38,13 +38,14 @@ import { ButtonComponent } from "./button"; * A component that displays form field metadata, such as validation errors. */ export class FormMetadataComponent { - /** The form field API instance. */ - field = input.required(); - errors = computed(() => - this.field() - .state.meta.errors.map((error) => error.message) - .join(", ") - ); + isTouched = input.required(); + errors = input.required>(); + + errorMessage(): string { + return this.errors() + .map((error) => error.message) + .join(", "); + } } @Component({ @@ -70,27 +71,35 @@ export class FormMetadataComponent { [id]="field.api.name" [name]="field.api.name" [value]="field.api.state.value" - (blur)="field.api.handleBlur()" (input)="field.api.handleChange($any($event).target.value)" [type]="type()" /> - + `, }) /** * A form input component with label, description, and validation support. */ -export class FormInputComponent { +export class FormInputComponent implements OnChanges { field = injectField(); + private cdr = inject(ChangeDetectorRef); /** The label text for the input field. */ label = input.required(); /** The input type (e.g., "text", "email", "password"). */ type = input("text"); /** Optional description text displayed below the label. */ description = input(); + + ngOnChanges(_changes: SimpleChanges): void { + // Trigger change detection when any input changes + this.cdr.markForCheck(); + } } @Component({ diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index 2e03b42c..255b8ab6 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -29,7 +29,7 @@ import { hasBehavior } from "./behaviors"; */ export function createSignInAuthFormSchema(ui: FirebaseUI) { return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")), }); } @@ -48,7 +48,7 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) { const displayNameRequiredMessage = getTranslation(ui, "errors", "displayNameRequired"); return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")), displayName: requireDisplayName ? z.string().min(1, displayNameRequiredMessage) @@ -66,7 +66,7 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) { */ export function createForgotPasswordAuthFormSchema(ui: FirebaseUI) { return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), }); } @@ -80,7 +80,7 @@ export function createForgotPasswordAuthFormSchema(ui: FirebaseUI) { */ export function createEmailLinkAuthFormSchema(ui: FirebaseUI) { return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), }); } diff --git a/packages/react/src/auth/forms/email-link-auth-form.test.tsx b/packages/react/src/auth/forms/email-link-auth-form.test.tsx index 4e358cc6..6181be94 100644 --- a/packages/react/src/auth/forms/email-link-auth-form.test.tsx +++ b/packages/react/src/auth/forms/email-link-auth-form.test.tsx @@ -292,7 +292,7 @@ describe("", () => { expect(onSignInMock).not.toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", async () => { const mockUI = createMockUI(); const { container } = render( @@ -307,9 +307,9 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); - expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); + expect(await screen.findByText("Please enter a valid email address")).toBeInTheDocument(); }); }); diff --git a/packages/react/src/auth/forms/email-link-auth-form.tsx b/packages/react/src/auth/forms/email-link-auth-form.tsx index 028f25df..064a2e97 100644 --- a/packages/react/src/auth/forms/email-link-auth-form.tsx +++ b/packages/react/src/auth/forms/email-link-auth-form.tsx @@ -71,7 +71,7 @@ export function useEmailLinkAuthForm(onSuccess?: EmailLinkAuthFormProps["onEmail email: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action(value); diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx index 3fe18a8f..ca49549e 100644 --- a/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx +++ b/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx @@ -198,7 +198,7 @@ describe("", () => { expect(onBackToSignInClickMock).toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -213,7 +213,7 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.tsx index ff6937ed..f0e7ce0e 100644 --- a/packages/react/src/auth/forms/forgot-password-auth-form.tsx +++ b/packages/react/src/auth/forms/forgot-password-auth-form.tsx @@ -70,7 +70,7 @@ export function useForgotPasswordAuthForm(onSuccess?: ForgotPasswordAuthFormProp email: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action(value); diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx index 761e9514..055c0ca4 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx @@ -180,7 +180,7 @@ export function useSmsMultiFactorAssertionVerifyForm({ verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx index 3b383955..5612663a 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx @@ -79,7 +79,7 @@ export function useSmsMultiFactorEnrollmentPhoneNumberForm({ phoneNumber: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const formatted = formatPhoneNumber ? formatPhoneNumber(value.phoneNumber) : value.phoneNumber; @@ -201,7 +201,7 @@ export function useMultiFactorEnrollmentVerifyPhoneNumberForm({ verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action({ ...value, displayName }); diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx index acdb607b..efc9aabd 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx @@ -60,7 +60,7 @@ export function useTotpMultiFactorAssertionForm({ hint, onSuccess }: UseTotpMult verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action({ verificationCode: value.verificationCode, hint }); diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx index 6581875a..a1ddf6aa 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx @@ -60,7 +60,7 @@ export function useTotpMultiFactorSecretGenerationForm({ onSuccess }: UseTotpMul displayName: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const secret = await action(); @@ -160,7 +160,7 @@ export function useMultiFactorEnrollmentVerifyTotpForm({ verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action({ secret, verificationCode: value.verificationCode, displayName }); diff --git a/packages/react/src/auth/forms/phone-auth-form.test.tsx b/packages/react/src/auth/forms/phone-auth-form.test.tsx index 7bd9d6fb..ea8580bf 100644 --- a/packages/react/src/auth/forms/phone-auth-form.test.tsx +++ b/packages/react/src/auth/forms/phone-auth-form.test.tsx @@ -479,7 +479,7 @@ describe("", () => { expect(sendCodeButton).toHaveAttribute("type", "submit"); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -494,7 +494,8 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /phone number/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "1" } }); + fireEvent.change(input, { target: { value: "" } }); }); expect(screen.getByText("Please provide a phone number")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/phone-auth-form.tsx b/packages/react/src/auth/forms/phone-auth-form.tsx index 0553c457..09262197 100644 --- a/packages/react/src/auth/forms/phone-auth-form.tsx +++ b/packages/react/src/auth/forms/phone-auth-form.tsx @@ -71,7 +71,7 @@ export function usePhoneNumberForm({ recaptchaVerifier, onSuccess, formatPhoneNu phoneNumber: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const formatted = formatPhoneNumber ? formatPhoneNumber(value.phoneNumber) : value.phoneNumber; @@ -183,7 +183,7 @@ export function useVerifyPhoneNumberForm({ verificationId, onSuccess }: UseVerif verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/auth/forms/sign-in-auth-form.test.tsx b/packages/react/src/auth/forms/sign-in-auth-form.test.tsx index fff23639..57fe6bd4 100644 --- a/packages/react/src/auth/forms/sign-in-auth-form.test.tsx +++ b/packages/react/src/auth/forms/sign-in-auth-form.test.tsx @@ -262,7 +262,7 @@ describe("", () => { expect(onSignUpClick).toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -277,7 +277,7 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/sign-in-auth-form.tsx b/packages/react/src/auth/forms/sign-in-auth-form.tsx index 488a0462..97cef95d 100644 --- a/packages/react/src/auth/forms/sign-in-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-in-auth-form.tsx @@ -74,7 +74,7 @@ export function useSignInAuthForm(onSuccess?: SignInAuthFormProps["onSignIn"]) { password: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/auth/forms/sign-up-auth-form.test.tsx b/packages/react/src/auth/forms/sign-up-auth-form.test.tsx index 0d3cdf53..049a6dcd 100644 --- a/packages/react/src/auth/forms/sign-up-auth-form.test.tsx +++ b/packages/react/src/auth/forms/sign-up-auth-form.test.tsx @@ -315,7 +315,7 @@ describe("", () => { expect(onSignInClickMock).toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -330,7 +330,7 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); @@ -401,7 +401,7 @@ describe("", () => { expect(screen.queryByRole("textbox", { name: /displayName/ })).not.toBeInTheDocument(); }); - it("should trigger displayName validation errors when the form is blurred and requireDisplayName is enabled", () => { + it("should trigger displayName validation errors when the form changes and requireDisplayName is enabled", () => { const mockUI = createMockUI({ locale: registerLocale("test", { errors: { @@ -431,7 +431,8 @@ describe("", () => { expect(displayNameInput).toBeInTheDocument(); act(() => { - fireEvent.blur(displayNameInput); + fireEvent.change(displayNameInput, { target: { value: "a" } }); + fireEvent.change(displayNameInput, { target: { value: "" } }); }); expect(screen.getByText("Please provide a display name")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/sign-up-auth-form.tsx b/packages/react/src/auth/forms/sign-up-auth-form.tsx index b2eab420..74f5e240 100644 --- a/packages/react/src/auth/forms/sign-up-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-up-auth-form.tsx @@ -85,7 +85,7 @@ export function useSignUpAuthForm(onSuccess?: SignUpAuthFormProps["onSignUp"]) { displayName: requireDisplayName ? "" : undefined, } as z.infer, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/components/form.tsx b/packages/react/src/components/form.tsx index 897cf01e..603c8371 100644 --- a/packages/react/src/components/form.tsx +++ b/packages/react/src/components/form.tsx @@ -22,15 +22,13 @@ import { cn } from "~/utils/cn"; const { fieldContext, useFieldContext, formContext, useFormContext } = createFormHookContexts(); function FieldMetadata({ className, ...props }: ComponentProps<"div"> & { field: AnyFieldApi }) { - if (!props.field.state.meta.isTouched || !props.field.state.meta.errors.length) { + if (!props.field.state.meta.errors.length) { return null; } return ( -
-
- {props.field.state.meta.errors.map((error) => error.message).join(", ")} -
+
+ {props.field.state.meta.errors.map((error) => error.message).join(", ")}
); } @@ -46,6 +44,7 @@ function Input({ ComponentProps<"input"> & { label: string; before?: ReactNode; action?: ReactNode; description?: ReactNode } >) { const field = useFieldContext(); + const form = useFormContext(); return (
diff --git a/packages/react/tests/register.integration.test.tsx b/packages/react/tests/register.integration.test.tsx index f0cd04ad..60f9a9db 100644 --- a/packages/react/tests/register.integration.test.tsx +++ b/packages/react/tests/register.integration.test.tsx @@ -43,7 +43,7 @@ import { describe } from "vitest"; describe.skip("TODO"); // describe("Register Integration", () => { -// // Ensure password is at least 8 characters to pass validation +// // Ensure password is at least 6 characters to pass validation // const testPassword = "Test123456!"; // let testEmail: string; diff --git a/packages/shadcn/src/components/email-link-auth-form.tsx b/packages/shadcn/src/components/email-link-auth-form.tsx index 748f93eb..dfb5525d 100644 --- a/packages/shadcn/src/components/email-link-auth-form.tsx +++ b/packages/shadcn/src/components/email-link-auth-form.tsx @@ -46,6 +46,7 @@ export function EmailLinkAuthForm(props: EmailLinkAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", }, diff --git a/packages/shadcn/src/components/forgot-password-auth-form.tsx b/packages/shadcn/src/components/forgot-password-auth-form.tsx index 60546ce1..88219e79 100644 --- a/packages/shadcn/src/components/forgot-password-auth-form.tsx +++ b/packages/shadcn/src/components/forgot-password-auth-form.tsx @@ -43,6 +43,7 @@ export function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", }, diff --git a/packages/shadcn/src/components/phone-auth-form.tsx b/packages/shadcn/src/components/phone-auth-form.tsx index 0708e7be..6f2bb29d 100644 --- a/packages/shadcn/src/components/phone-auth-form.tsx +++ b/packages/shadcn/src/components/phone-auth-form.tsx @@ -57,6 +57,7 @@ function VerifyPhoneNumberForm(props: VerifyPhoneNumberFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { verificationId: props.verificationId, verificationCode: "", @@ -122,6 +123,7 @@ function PhoneNumberForm(props: PhoneNumberFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { phoneNumber: "", }, diff --git a/packages/shadcn/src/components/sign-in-auth-form.tsx b/packages/shadcn/src/components/sign-in-auth-form.tsx index 76c510eb..34438ae6 100644 --- a/packages/shadcn/src/components/sign-in-auth-form.tsx +++ b/packages/shadcn/src/components/sign-in-auth-form.tsx @@ -41,6 +41,7 @@ export function SignInAuthForm(props: SignInAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", password: "", diff --git a/packages/shadcn/src/components/sign-up-auth-form.tsx b/packages/shadcn/src/components/sign-up-auth-form.tsx index 43107394..25d8ee56 100644 --- a/packages/shadcn/src/components/sign-up-auth-form.tsx +++ b/packages/shadcn/src/components/sign-up-auth-form.tsx @@ -43,6 +43,7 @@ export function SignUpAuthForm(props: SignUpAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", password: "", diff --git a/packages/translations/src/locales/en-us.ts b/packages/translations/src/locales/en-us.ts index 2cb38358..4b49b5d9 100644 --- a/packages/translations/src/locales/en-us.ts +++ b/packages/translations/src/locales/en-us.ts @@ -28,7 +28,7 @@ export const enUS = { missingVerificationCode: "Please enter the verification code", emailAlreadyInUse: "An account already exists with this email", invalidCredential: "The provided credentials are invalid.", - weakPassword: "Password should be at least 8 characters", + weakPassword: "Password should be at least 6 characters", unverifiedEmail: "Please verify your email address to continue.", operationNotAllowed: "This operation is not allowed. Please contact support.", invalidPhoneNumber: "The phone number is invalid",