diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index 0c62c5cd6..78e7fa8a4 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -57,6 +57,8 @@ type FormErrorMapFromValidator< TOnBlurAsync extends undefined | FormAsyncValidateOrFn, TOnSubmit extends undefined | FormValidateOrFn, TOnSubmitAsync extends undefined | FormAsyncValidateOrFn, + TOnDynamic extends undefined | FormValidateOrFn, + TOnDynamicAsync extends undefined | FormAsyncValidateOrFn, > = Partial< Record< DeepKeys, @@ -67,7 +69,9 @@ type FormErrorMapFromValidator< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > > > @@ -1491,7 +1495,9 @@ export class FormApi< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > } => { const validates = getSyncValidatorArray(cause, { @@ -1511,7 +1517,9 @@ export class FormApi< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > = {} batch(() => { @@ -1654,7 +1662,9 @@ export class FormApi< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > > => { const validates = getAsyncValidatorArray(cause, { @@ -1801,7 +1811,9 @@ export class FormApi< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > = {} if (promises.length) { results = await Promise.all(promises) @@ -1846,7 +1858,9 @@ export class FormApi< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > | Promise< FormErrorMapFromValidator< @@ -1857,7 +1871,9 @@ export class FormApi< TOnBlur, TOnBlurAsync, TOnSubmit, - TOnSubmitAsync + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync > > => { // Attempt to sync validate first diff --git a/packages/form-core/src/formOptions.ts b/packages/form-core/src/formOptions.ts index 7e65bf4c9..e77460e6c 100644 --- a/packages/form-core/src/formOptions.ts +++ b/packages/form-core/src/formOptions.ts @@ -24,35 +24,45 @@ export function formOptions< TOptions extends Partial< FormOptions< TFormData, - undefined | FormValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormAsyncValidateOrFn, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TOnServer, TSubmitMeta > >, - TFormData = TOptions['defaultValues'], - TSubmitMeta = TOptions['onSubmitMeta'], + TFormData, + TOnMount extends undefined | FormValidateOrFn, + TOnChange extends undefined | FormValidateOrFn, + TOnChangeAsync extends undefined | FormAsyncValidateOrFn, + TOnBlur extends undefined | FormValidateOrFn, + TOnBlurAsync extends undefined | FormAsyncValidateOrFn, + TOnSubmit extends undefined | FormValidateOrFn, + TOnSubmitAsync extends undefined | FormAsyncValidateOrFn, + TOnDynamic extends undefined | FormValidateOrFn, + TOnDynamicAsync extends undefined | FormAsyncValidateOrFn, + TOnServer extends undefined | FormAsyncValidateOrFn, + TSubmitMeta = never, >( defaultOpts: Partial< FormOptions< TFormData, - undefined | FormValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormValidateOrFn, - undefined | FormAsyncValidateOrFn, - undefined | FormAsyncValidateOrFn, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TOnServer, TSubmitMeta > > & diff --git a/packages/form-core/tests/formOptions.test-d.ts b/packages/form-core/tests/formOptions.test-d.ts index 3f5f3d5a7..c13ae7d7f 100644 --- a/packages/form-core/tests/formOptions.test-d.ts +++ b/packages/form-core/tests/formOptions.test-d.ts @@ -1,5 +1,6 @@ import { describe, expectTypeOf, it } from 'vitest' import { FormApi, formOptions } from '../src/index' +import type { FormAsyncValidateOrFn, FormValidateOrFn } from '../src/index' describe('formOptions', () => { it('types should be properly inferred', () => { @@ -15,11 +16,13 @@ describe('formOptions', () => { } as Person, }) - const form = new FormApi({ + const form1 = new FormApi(formOpts) + const form2 = new FormApi({ ...formOpts, }) - expectTypeOf(form.state.values).toEqualTypeOf() + expectTypeOf(form1.state.values).toEqualTypeOf() + expectTypeOf(form2.state.values).toEqualTypeOf() }) it('types should be properly inferred when passing args alongside formOptions', () => { @@ -65,6 +68,9 @@ describe('formOptions', () => { } as Person, }) + const formOnly = new FormApi(formOpts) + expectTypeOf(formOnly.state.values).toEqualTypeOf() + const form = new FormApi({ ...formOpts, defaultValues: { @@ -76,4 +82,245 @@ describe('formOptions', () => { expectTypeOf(form.state.values).toExtend() }) + + it('types should infer submitMeta', () => { + type FormData = { + firstName: string + lastName: string + } + + type SubmitMeta = { bool: boolean } + + type ExpectedShape = FormApi< + FormData, + FormValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + SubmitMeta + > + + const formOpts = formOptions({ + defaultValues: { + firstName: '', + lastName: '', + } as FormData, + onSubmitMeta: { bool: false } as SubmitMeta, + onSubmit: ({ meta }) => { + expectTypeOf(meta).toEqualTypeOf() + }, + }) + + const form = new FormApi(formOpts) + expectTypeOf(form).toEqualTypeOf() + + const form2 = new FormApi({ ...formOpts }) + expectTypeOf(form2).toEqualTypeOf() + }) + + it('types should infer validator types', () => { + type FormData = { + firstName: string + lastName: string + } + + type AnyFormApi = FormApi< + FormData, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any + > + + type ExpectedShape = FormApi< + FormData, + FormValidateOrFn | undefined, + ({ value, formApi }: { value: FormData; formApi: AnyFormApi }) => void, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + never + > + + const formOpts = formOptions({ + defaultValues: { + firstName: '', + lastName: '', + } as FormData, + validators: { + onChange: ({ value, formApi }) => { + expectTypeOf(value).toEqualTypeOf() + expectTypeOf(formApi).toEqualTypeOf() + }, + }, + }) + + const form = new FormApi(formOpts) + expectTypeOf(form).toEqualTypeOf() + + const form2 = new FormApi({ ...formOpts }) + expectTypeOf(form2).toEqualTypeOf() + }) + + it('types should infer listeners types', () => { + type FormData = { + firstName: string + lastName: string + } + + type ExpectedShape = FormApi< + FormData, + FormValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined + > + + const formOpts = formOptions({ + defaultValues: { + firstName: '', + lastName: '', + } as FormData, + listeners: { + onSubmit: ({ formApi, meta }) => { + expectTypeOf(formApi).toEqualTypeOf() + expectTypeOf(meta).toEqualTypeOf() + }, + }, + }) + + const form = new FormApi(formOpts) + expectTypeOf(form).toEqualTypeOf() + + const form2 = new FormApi({ ...formOpts }) + expectTypeOf(form2).toEqualTypeOf() + }) + + it('types should infer listeners types with submitMeta', () => { + type FormData = { + firstName: string + lastName: string + } + type SubmitMeta = { bool: boolean } + + type ExpectedShape = FormApi< + FormData, + FormValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + FormAsyncValidateOrFn | undefined, + SubmitMeta + > + + const formOpts = formOptions({ + defaultValues: { + firstName: '', + lastName: '', + } as FormData, + onSubmitMeta: { bool: false } as SubmitMeta, + listeners: { + onSubmit: ({ formApi, meta }) => { + expectTypeOf(formApi).toEqualTypeOf() + expectTypeOf(meta).toEqualTypeOf() + }, + }, + }) + + const form = new FormApi(formOpts) + expectTypeOf(form).toEqualTypeOf() + + const form2 = new FormApi({ ...formOpts }) + expectTypeOf(form2).toEqualTypeOf() + }) + + it('should allow overriding formOptions values', () => { + type FormData = { + firstName: string + lastName: string + } + + const formOpts = formOptions({ + defaultValues: { + firstName: '', + lastName: '', + } as FormData, + validators: { + onSubmit: ({ formApi }) => { + if (formApi.formId === undefined) { + return 'needs formId' + } + return undefined + }, + }, + }) + + const form = new FormApi(formOpts) + + expectTypeOf(form.state.errors).toEqualTypeOf< + ('needs formId' | undefined)[] + >() + + const form2 = new FormApi({ + ...formOpts, + validators: { + onChange: (params) => { + if (params.value.firstName.length === 0) { + return 'Too short!' + } + return undefined + }, + }, + }) + + expectTypeOf(form2.state.errors).toEqualTypeOf< + (undefined | 'Too short!')[] + >() + + const form3 = new FormApi({ + ...formOpts, + validators: { + ...formOpts.validators, + onChange: (params) => { + if (params.value.firstName.length === 0) { + return 'Too short!' + } + return undefined + }, + }, + }) + + expectTypeOf(form3.state.errors).toEqualTypeOf< + (undefined | 'Too short!' | 'needs formId')[] + >() + }) }) diff --git a/packages/form-core/tsconfig.json b/packages/form-core/tsconfig.json index e04bee269..328e2f243 100644 --- a/packages/form-core/tsconfig.json +++ b/packages/form-core/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "moduleResolution": "Bundler" + // "noErrorTruncation": true }, "include": ["src", "tests", "eslint.config.js", "vite.config.ts"] }