@tanstack/form-core 0.7.1 → 0.7.2

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.
@@ -184,8 +184,10 @@ var FormApi = class {
184
184
  });
185
185
  };
186
186
  this.deleteField = (field) => {
187
- delete this.state.values[field];
188
- delete this.state.fieldMeta[field];
187
+ const newState = { ...this.state };
188
+ delete newState.values[field];
189
+ delete newState.fieldMeta[field];
190
+ this.store.setState((_) => newState);
189
191
  };
190
192
  this.pushFieldValue = (field, value, opts) => {
191
193
  return this.setFieldValue(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n delete this.state.values[field as keyof TFormData]\n delete this.state.fieldMeta[field]\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n const newState = { ...this.state }\n delete newState.values[field as keyof TFormData]\n delete newState.fieldMeta[field]\n\n this.store.setState((_) => newState)\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -160,8 +160,10 @@ var FormApi = class {
160
160
  });
161
161
  };
162
162
  this.deleteField = (field) => {
163
- delete this.state.values[field];
164
- delete this.state.fieldMeta[field];
163
+ const newState = { ...this.state };
164
+ delete newState.values[field];
165
+ delete newState.fieldMeta[field];
166
+ this.store.setState((_) => newState);
165
167
  };
166
168
  this.pushFieldValue = (field, value, opts) => {
167
169
  return this.setFieldValue(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n delete this.state.values[field as keyof TFormData]\n delete this.state.fieldMeta[field]\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n const newState = { ...this.state }\n delete newState.values[field as keyof TFormData]\n delete newState.fieldMeta[field]\n\n this.store.setState((_) => newState)\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -179,8 +179,10 @@ var FormApi = class {
179
179
  });
180
180
  };
181
181
  this.deleteField = (field) => {
182
- delete this.state.values[field];
183
- delete this.state.fieldMeta[field];
182
+ const newState = { ...this.state };
183
+ delete newState.values[field];
184
+ delete newState.fieldMeta[field];
185
+ this.store.setState((_) => newState);
184
186
  };
185
187
  this.pushFieldValue = (field, value, opts) => {
186
188
  return this.setFieldValue(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n delete this.state.values[field as keyof TFormData]\n delete this.state.fieldMeta[field]\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n const newState = { ...this.state }\n delete newState.values[field as keyof TFormData]\n delete newState.fieldMeta[field]\n\n this.store.setState((_) => newState)\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -155,8 +155,10 @@ var FormApi = class {
155
155
  });
156
156
  };
157
157
  this.deleteField = (field) => {
158
- delete this.state.values[field];
159
- delete this.state.fieldMeta[field];
158
+ const newState = { ...this.state };
159
+ delete newState.values[field];
160
+ delete newState.fieldMeta[field];
161
+ this.store.setState((_) => newState);
160
162
  };
161
163
  this.pushFieldValue = (field, value, opts) => {
162
164
  return this.setFieldValue(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n delete this.state.values[field as keyof TFormData]\n delete this.state.fieldMeta[field]\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n const newState = { ...this.state }\n delete newState.values[field as keyof TFormData]\n delete newState.fieldMeta[field]\n\n this.store.setState((_) => newState)\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/form-core",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Powerful, type-safe, framework agnostic forms.",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
package/src/FormApi.ts CHANGED
@@ -360,8 +360,11 @@ export class FormApi<TFormData, ValidatorType> {
360
360
  }
361
361
 
362
362
  deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {
363
- delete this.state.values[field as keyof TFormData]
364
- delete this.state.fieldMeta[field]
363
+ const newState = { ...this.state }
364
+ delete newState.values[field as keyof TFormData]
365
+ delete newState.fieldMeta[field]
366
+
367
+ this.store.setState((_) => newState)
365
368
  }
366
369
 
367
370
  pushFieldValue = <TField extends DeepKeys<TFormData>>(
@@ -1,4 +1,4 @@
1
- import { expect } from 'vitest'
1
+ import { expect, vitest } from 'vitest'
2
2
 
3
3
  import { FormApi } from '../FormApi'
4
4
  import { FieldApi } from '../FieldApi'
@@ -597,11 +597,17 @@ describe('field api', () => {
597
597
  })
598
598
 
599
599
  const unmount = field.mount()
600
+ const callback = vitest.fn()
601
+ const subscription = form.store.subscribe(callback)
600
602
  unmount()
601
603
  const info = form.getFieldInfo(field.name)
604
+ subscription()
602
605
  expect(info.instances[field.uid]).toBeUndefined()
603
606
  expect(Object.keys(info.instances).length).toBe(0)
604
607
 
608
+ // Check that form store has been updated
609
+ expect(callback).toHaveBeenCalledOnce()
610
+
605
611
  // Field should have been removed from the form as well
606
612
  expect(form.state.values.name).toBeUndefined()
607
613
  expect(form.state.fieldMeta.name).toBeUndefined()