@tanstack/form-core
Advanced tools
+45
-51
@@ -413,2 +413,11 @@ "use strict"; | ||
| }; | ||
| this.setErrorMap = (errorMap) => { | ||
| this.setMeta((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| ...errorMap | ||
| } | ||
| })); | ||
| }; | ||
| this.parseValueWithSchema = (schema) => { | ||
@@ -426,2 +435,38 @@ return standardSchemaValidator.standardSchemaValidators.validate( | ||
| }; | ||
| this.triggerOnChangeListener = () => { | ||
| const formDebounceMs = this.form.options.listeners?.onChangeDebounceMs; | ||
| if (formDebounceMs && formDebounceMs > 0) { | ||
| if (this.timeoutIds.formListeners.change) { | ||
| clearTimeout(this.timeoutIds.formListeners.change); | ||
| } | ||
| this.timeoutIds.formListeners.change = setTimeout(() => { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| }, formDebounceMs); | ||
| } else { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| const fieldDebounceMs = this.options.listeners?.onChangeDebounceMs; | ||
| if (fieldDebounceMs && fieldDebounceMs > 0) { | ||
| if (this.timeoutIds.listeners.change) { | ||
| clearTimeout(this.timeoutIds.listeners.change); | ||
| } | ||
| this.timeoutIds.listeners.change = setTimeout(() => { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| }, fieldDebounceMs); | ||
| } else { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| }; | ||
| this.form = opts.form; | ||
@@ -471,14 +516,2 @@ this.name = opts.name; | ||
| } | ||
| /** | ||
| * Updates the field's errorMap | ||
| */ | ||
| setErrorMap(errorMap) { | ||
| this.setMeta((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| ...errorMap | ||
| } | ||
| })); | ||
| } | ||
| triggerOnBlurListener() { | ||
@@ -520,41 +553,2 @@ const formDebounceMs = this.form.options.listeners?.onBlurDebounceMs; | ||
| } | ||
| /** | ||
| * @private | ||
| */ | ||
| triggerOnChangeListener() { | ||
| const formDebounceMs = this.form.options.listeners?.onChangeDebounceMs; | ||
| if (formDebounceMs && formDebounceMs > 0) { | ||
| if (this.timeoutIds.formListeners.change) { | ||
| clearTimeout(this.timeoutIds.formListeners.change); | ||
| } | ||
| this.timeoutIds.formListeners.change = setTimeout(() => { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| }, formDebounceMs); | ||
| } else { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| const fieldDebounceMs = this.options.listeners?.onChangeDebounceMs; | ||
| if (fieldDebounceMs && fieldDebounceMs > 0) { | ||
| if (this.timeoutIds.listeners.change) { | ||
| clearTimeout(this.timeoutIds.listeners.change); | ||
| } | ||
| this.timeoutIds.listeners.change = setTimeout(() => { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| }, fieldDebounceMs); | ||
| } else { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| } | ||
| } | ||
@@ -561,0 +555,0 @@ function normalizeError(rawError) { |
@@ -243,2 +243,6 @@ import { Derived } from '@tanstack/store'; | ||
| /** | ||
| * We cannot use methods and must use arrow functions. Otherwise, our React adapters | ||
| * will break due to loss of the method when using spread. | ||
| */ | ||
| /** | ||
| * A class representing the API for managing a form field. | ||
@@ -377,3 +381,3 @@ * | ||
| */ | ||
| setErrorMap(errorMap: ValidationErrorMap<UnwrapFieldValidateOrFn<TName, TOnMount, TFormOnMount>, UnwrapFieldValidateOrFn<TName, TOnChange, TFormOnChange>, UnwrapFieldAsyncValidateOrFn<TName, TOnChangeAsync, TFormOnChangeAsync>, UnwrapFieldValidateOrFn<TName, TOnBlur, TFormOnBlur>, UnwrapFieldAsyncValidateOrFn<TName, TOnBlurAsync, TFormOnBlurAsync>, UnwrapFieldValidateOrFn<TName, TOnSubmit, TFormOnSubmit>, UnwrapFieldAsyncValidateOrFn<TName, TOnSubmitAsync, TFormOnSubmitAsync>, UnwrapFieldValidateOrFn<TName, TOnDynamic, TFormOnDynamic>, UnwrapFieldAsyncValidateOrFn<TName, TOnDynamicAsync, TFormOnDynamicAsync>>): void; | ||
| setErrorMap: (errorMap: ValidationErrorMap<UnwrapFieldValidateOrFn<TName, TOnMount, TFormOnMount>, UnwrapFieldValidateOrFn<TName, TOnChange, TFormOnChange>, UnwrapFieldAsyncValidateOrFn<TName, TOnChangeAsync, TFormOnChangeAsync>, UnwrapFieldValidateOrFn<TName, TOnBlur, TFormOnBlur>, UnwrapFieldAsyncValidateOrFn<TName, TOnBlurAsync, TFormOnBlurAsync>, UnwrapFieldValidateOrFn<TName, TOnSubmit, TFormOnSubmit>, UnwrapFieldAsyncValidateOrFn<TName, TOnSubmitAsync, TFormOnSubmitAsync>, UnwrapFieldValidateOrFn<TName, TOnDynamic, TFormOnDynamic>, UnwrapFieldAsyncValidateOrFn<TName, TOnDynamicAsync, TFormOnDynamicAsync>>) => void; | ||
| /** | ||
@@ -395,4 +399,4 @@ * Parses the field's value with the given schema and returns | ||
| */ | ||
| triggerOnChangeListener(): void; | ||
| triggerOnChangeListener: () => void; | ||
| } | ||
| export {}; |
+159
-159
@@ -408,2 +408,117 @@ "use strict"; | ||
| }; | ||
| this._handleSubmit = async (submitMeta) => { | ||
| this.baseStore.setState((old) => ({ | ||
| ...old, | ||
| // Submission attempts mark the form as not submitted | ||
| isSubmitted: false, | ||
| // Count submission attempts | ||
| submissionAttempts: old.submissionAttempts + 1, | ||
| isSubmitSuccessful: false | ||
| // Reset isSubmitSuccessful at the start of submission | ||
| })); | ||
| store.batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| if (!field.instance) return; | ||
| if (!field.instance.state.meta.isTouched) { | ||
| field.instance.setMeta((prev) => ({ ...prev, isTouched: true })); | ||
| } | ||
| } | ||
| ); | ||
| }); | ||
| const submitMetaArg = submitMeta ?? this.options.onSubmitMeta; | ||
| if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) { | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| return; | ||
| } | ||
| this.baseStore.setState((d) => ({ ...d, isSubmitting: true })); | ||
| const done = () => { | ||
| this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false })); | ||
| }; | ||
| await this.validateAllFields("submit"); | ||
| if (!this.state.isFieldsValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validateAllFields", | ||
| errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat() | ||
| }); | ||
| return; | ||
| } | ||
| await this.validate("submit"); | ||
| if (!this.state.isValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validate", | ||
| errors: this.state.errors | ||
| }); | ||
| return; | ||
| } | ||
| store.batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| field.instance?.options.listeners?.onSubmit?.({ | ||
| value: field.instance.state.value, | ||
| fieldApi: field.instance | ||
| }); | ||
| } | ||
| ); | ||
| }); | ||
| this.options.listeners?.onSubmit?.({ formApi: this, meta: submitMetaArg }); | ||
| try { | ||
| await this.options.onSubmit?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| store.batch(() => { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitted: true, | ||
| isSubmitSuccessful: true | ||
| // Set isSubmitSuccessful to true on successful submission | ||
| })); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: true | ||
| }); | ||
| done(); | ||
| }); | ||
| } catch (err) { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitSuccessful: false | ||
| // Ensure isSubmitSuccessful is false if an error occurs | ||
| })); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "inflight", | ||
| onError: err | ||
| }); | ||
| done(); | ||
| throw err; | ||
| } | ||
| }; | ||
| this.getFieldValue = (field) => utils.getBy(this.state.values, field); | ||
@@ -631,2 +746,44 @@ this.getFieldMeta = (field) => { | ||
| }; | ||
| this.setErrorMap = (errorMap) => { | ||
| store.batch(() => { | ||
| Object.entries(errorMap).forEach(([key, value]) => { | ||
| const errorMapKey = key; | ||
| if (utils.isGlobalFormValidationError(value)) { | ||
| const { formError, fieldErrors } = normalizeError(value); | ||
| for (const fieldName of Object.keys( | ||
| this.fieldInfo | ||
| )) { | ||
| const fieldMeta = this.getFieldMeta(fieldName); | ||
| if (!fieldMeta) continue; | ||
| this.setFieldMeta(fieldName, (prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: fieldErrors?.[fieldName] | ||
| }, | ||
| errorSourceMap: { | ||
| ...prev.errorSourceMap, | ||
| [errorMapKey]: "form" | ||
| } | ||
| })); | ||
| } | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: formError | ||
| } | ||
| })); | ||
| } else { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: value | ||
| } | ||
| })); | ||
| } | ||
| }); | ||
| }); | ||
| }; | ||
| this.getAllErrors = () => { | ||
@@ -870,162 +1027,5 @@ return { | ||
| } | ||
| async handleSubmit(submitMeta) { | ||
| this.baseStore.setState((old) => ({ | ||
| ...old, | ||
| // Submission attempts mark the form as not submitted | ||
| isSubmitted: false, | ||
| // Count submission attempts | ||
| submissionAttempts: old.submissionAttempts + 1, | ||
| isSubmitSuccessful: false | ||
| // Reset isSubmitSuccessful at the start of submission | ||
| })); | ||
| store.batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| if (!field.instance) return; | ||
| if (!field.instance.state.meta.isTouched) { | ||
| field.instance.setMeta((prev) => ({ ...prev, isTouched: true })); | ||
| } | ||
| } | ||
| ); | ||
| }); | ||
| const submitMetaArg = submitMeta ?? this.options.onSubmitMeta; | ||
| if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) { | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| return; | ||
| } | ||
| this.baseStore.setState((d) => ({ ...d, isSubmitting: true })); | ||
| const done = () => { | ||
| this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false })); | ||
| }; | ||
| await this.validateAllFields("submit"); | ||
| if (!this.state.isFieldsValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validateAllFields", | ||
| errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat() | ||
| }); | ||
| return; | ||
| } | ||
| await this.validate("submit"); | ||
| if (!this.state.isValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validate", | ||
| errors: this.state.errors | ||
| }); | ||
| return; | ||
| } | ||
| store.batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| field.instance?.options.listeners?.onSubmit?.({ | ||
| value: field.instance.state.value, | ||
| fieldApi: field.instance | ||
| }); | ||
| } | ||
| ); | ||
| }); | ||
| this.options.listeners?.onSubmit?.({ formApi: this, meta: submitMetaArg }); | ||
| try { | ||
| await this.options.onSubmit?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| store.batch(() => { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitted: true, | ||
| isSubmitSuccessful: true | ||
| // Set isSubmitSuccessful to true on successful submission | ||
| })); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: true | ||
| }); | ||
| done(); | ||
| }); | ||
| } catch (err) { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitSuccessful: false | ||
| // Ensure isSubmitSuccessful is false if an error occurs | ||
| })); | ||
| EventClient.formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "inflight", | ||
| onError: err | ||
| }); | ||
| done(); | ||
| throw err; | ||
| } | ||
| handleSubmit(submitMeta) { | ||
| return this._handleSubmit(submitMeta); | ||
| } | ||
| /** | ||
| * Updates the form's errorMap | ||
| */ | ||
| setErrorMap(errorMap) { | ||
| store.batch(() => { | ||
| Object.entries(errorMap).forEach(([key, value]) => { | ||
| const errorMapKey = key; | ||
| if (utils.isGlobalFormValidationError(value)) { | ||
| const { formError, fieldErrors } = normalizeError(value); | ||
| for (const fieldName of Object.keys( | ||
| this.fieldInfo | ||
| )) { | ||
| const fieldMeta = this.getFieldMeta(fieldName); | ||
| if (!fieldMeta) continue; | ||
| this.setFieldMeta(fieldName, (prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: fieldErrors?.[fieldName] | ||
| }, | ||
| errorSourceMap: { | ||
| ...prev.errorSourceMap, | ||
| [errorMapKey]: "form" | ||
| } | ||
| })); | ||
| } | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: formError | ||
| } | ||
| })); | ||
| } else { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: value | ||
| } | ||
| })); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
@@ -1032,0 +1032,0 @@ function normalizeError(rawError) { |
@@ -312,2 +312,6 @@ import { Derived, Store } from '@tanstack/store'; | ||
| /** | ||
| * We cannot use methods and must use arrow functions. Otherwise, our React adapters | ||
| * will break due to loss of the method when using spread. | ||
| */ | ||
| /** | ||
| * A class representing the Form API. It handles the logic and interactions with the form state. | ||
@@ -410,7 +414,8 @@ * | ||
| validate: (cause: ValidationCause) => FormErrorMapFromValidator<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync> | Promise<FormErrorMapFromValidator<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync>>; | ||
| handleSubmit(): Promise<void>; | ||
| handleSubmit(submitMeta: TSubmitMeta): Promise<void>; | ||
| /** | ||
| * Handles the form submission, performs validation, and calls the appropriate onSubmit or onSubmitInvalid callbacks. | ||
| */ | ||
| handleSubmit(): Promise<void>; | ||
| handleSubmit(submitMeta: TSubmitMeta): Promise<void>; | ||
| _handleSubmit: (submitMeta?: TSubmitMeta) => Promise<void>; | ||
| /** | ||
@@ -473,3 +478,3 @@ * Gets the value of the specified field. | ||
| */ | ||
| setErrorMap(errorMap: FormValidationErrorMap<TFormData, UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer>>): void; | ||
| setErrorMap: (errorMap: FormValidationErrorMap<TFormData, UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer>>) => void; | ||
| /** | ||
@@ -476,0 +481,0 @@ * Returns form and field level errors |
@@ -243,2 +243,6 @@ import { Derived } from '@tanstack/store'; | ||
| /** | ||
| * We cannot use methods and must use arrow functions. Otherwise, our React adapters | ||
| * will break due to loss of the method when using spread. | ||
| */ | ||
| /** | ||
| * A class representing the API for managing a form field. | ||
@@ -377,3 +381,3 @@ * | ||
| */ | ||
| setErrorMap(errorMap: ValidationErrorMap<UnwrapFieldValidateOrFn<TName, TOnMount, TFormOnMount>, UnwrapFieldValidateOrFn<TName, TOnChange, TFormOnChange>, UnwrapFieldAsyncValidateOrFn<TName, TOnChangeAsync, TFormOnChangeAsync>, UnwrapFieldValidateOrFn<TName, TOnBlur, TFormOnBlur>, UnwrapFieldAsyncValidateOrFn<TName, TOnBlurAsync, TFormOnBlurAsync>, UnwrapFieldValidateOrFn<TName, TOnSubmit, TFormOnSubmit>, UnwrapFieldAsyncValidateOrFn<TName, TOnSubmitAsync, TFormOnSubmitAsync>, UnwrapFieldValidateOrFn<TName, TOnDynamic, TFormOnDynamic>, UnwrapFieldAsyncValidateOrFn<TName, TOnDynamicAsync, TFormOnDynamicAsync>>): void; | ||
| setErrorMap: (errorMap: ValidationErrorMap<UnwrapFieldValidateOrFn<TName, TOnMount, TFormOnMount>, UnwrapFieldValidateOrFn<TName, TOnChange, TFormOnChange>, UnwrapFieldAsyncValidateOrFn<TName, TOnChangeAsync, TFormOnChangeAsync>, UnwrapFieldValidateOrFn<TName, TOnBlur, TFormOnBlur>, UnwrapFieldAsyncValidateOrFn<TName, TOnBlurAsync, TFormOnBlurAsync>, UnwrapFieldValidateOrFn<TName, TOnSubmit, TFormOnSubmit>, UnwrapFieldAsyncValidateOrFn<TName, TOnSubmitAsync, TFormOnSubmitAsync>, UnwrapFieldValidateOrFn<TName, TOnDynamic, TFormOnDynamic>, UnwrapFieldAsyncValidateOrFn<TName, TOnDynamicAsync, TFormOnDynamicAsync>>) => void; | ||
| /** | ||
@@ -395,4 +399,4 @@ * Parses the field's value with the given schema and returns | ||
| */ | ||
| triggerOnChangeListener(): void; | ||
| triggerOnChangeListener: () => void; | ||
| } | ||
| export {}; |
+45
-51
@@ -411,2 +411,11 @@ import { batch, Derived } from "@tanstack/store"; | ||
| }; | ||
| this.setErrorMap = (errorMap) => { | ||
| this.setMeta((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| ...errorMap | ||
| } | ||
| })); | ||
| }; | ||
| this.parseValueWithSchema = (schema) => { | ||
@@ -424,2 +433,38 @@ return standardSchemaValidators.validate( | ||
| }; | ||
| this.triggerOnChangeListener = () => { | ||
| const formDebounceMs = this.form.options.listeners?.onChangeDebounceMs; | ||
| if (formDebounceMs && formDebounceMs > 0) { | ||
| if (this.timeoutIds.formListeners.change) { | ||
| clearTimeout(this.timeoutIds.formListeners.change); | ||
| } | ||
| this.timeoutIds.formListeners.change = setTimeout(() => { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| }, formDebounceMs); | ||
| } else { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| const fieldDebounceMs = this.options.listeners?.onChangeDebounceMs; | ||
| if (fieldDebounceMs && fieldDebounceMs > 0) { | ||
| if (this.timeoutIds.listeners.change) { | ||
| clearTimeout(this.timeoutIds.listeners.change); | ||
| } | ||
| this.timeoutIds.listeners.change = setTimeout(() => { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| }, fieldDebounceMs); | ||
| } else { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| }; | ||
| this.form = opts.form; | ||
@@ -469,14 +514,2 @@ this.name = opts.name; | ||
| } | ||
| /** | ||
| * Updates the field's errorMap | ||
| */ | ||
| setErrorMap(errorMap) { | ||
| this.setMeta((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| ...errorMap | ||
| } | ||
| })); | ||
| } | ||
| triggerOnBlurListener() { | ||
@@ -518,41 +551,2 @@ const formDebounceMs = this.form.options.listeners?.onBlurDebounceMs; | ||
| } | ||
| /** | ||
| * @private | ||
| */ | ||
| triggerOnChangeListener() { | ||
| const formDebounceMs = this.form.options.listeners?.onChangeDebounceMs; | ||
| if (formDebounceMs && formDebounceMs > 0) { | ||
| if (this.timeoutIds.formListeners.change) { | ||
| clearTimeout(this.timeoutIds.formListeners.change); | ||
| } | ||
| this.timeoutIds.formListeners.change = setTimeout(() => { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| }, formDebounceMs); | ||
| } else { | ||
| this.form.options.listeners?.onChange?.({ | ||
| formApi: this.form, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| const fieldDebounceMs = this.options.listeners?.onChangeDebounceMs; | ||
| if (fieldDebounceMs && fieldDebounceMs > 0) { | ||
| if (this.timeoutIds.listeners.change) { | ||
| clearTimeout(this.timeoutIds.listeners.change); | ||
| } | ||
| this.timeoutIds.listeners.change = setTimeout(() => { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| }, fieldDebounceMs); | ||
| } else { | ||
| this.options.listeners?.onChange?.({ | ||
| value: this.state.value, | ||
| fieldApi: this | ||
| }); | ||
| } | ||
| } | ||
| } | ||
@@ -559,0 +553,0 @@ function normalizeError(rawError) { |
@@ -312,2 +312,6 @@ import { Derived, Store } from '@tanstack/store'; | ||
| /** | ||
| * We cannot use methods and must use arrow functions. Otherwise, our React adapters | ||
| * will break due to loss of the method when using spread. | ||
| */ | ||
| /** | ||
| * A class representing the Form API. It handles the logic and interactions with the form state. | ||
@@ -410,7 +414,8 @@ * | ||
| validate: (cause: ValidationCause) => FormErrorMapFromValidator<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync> | Promise<FormErrorMapFromValidator<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync>>; | ||
| handleSubmit(): Promise<void>; | ||
| handleSubmit(submitMeta: TSubmitMeta): Promise<void>; | ||
| /** | ||
| * Handles the form submission, performs validation, and calls the appropriate onSubmit or onSubmitInvalid callbacks. | ||
| */ | ||
| handleSubmit(): Promise<void>; | ||
| handleSubmit(submitMeta: TSubmitMeta): Promise<void>; | ||
| _handleSubmit: (submitMeta?: TSubmitMeta) => Promise<void>; | ||
| /** | ||
@@ -473,3 +478,3 @@ * Gets the value of the specified field. | ||
| */ | ||
| setErrorMap(errorMap: FormValidationErrorMap<TFormData, UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer>>): void; | ||
| setErrorMap: (errorMap: FormValidationErrorMap<TFormData, UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer>>) => void; | ||
| /** | ||
@@ -476,0 +481,0 @@ * Returns form and field level errors |
+160
-160
| import { batch, Store, Derived } from "@tanstack/store"; | ||
| import { throttle } from "@tanstack/pacer"; | ||
| import { evaluate, getSyncValidatorArray, determineFormLevelErrorSourceAndValue, getAsyncValidatorArray, getBy, functionalUpdate, setBy, deleteBy, mergeOpts, uuid, isNonEmptyArray, isGlobalFormValidationError } from "./utils.js"; | ||
| import { evaluate, getSyncValidatorArray, determineFormLevelErrorSourceAndValue, getAsyncValidatorArray, getBy, functionalUpdate, setBy, deleteBy, mergeOpts, isGlobalFormValidationError, uuid, isNonEmptyArray } from "./utils.js"; | ||
| import { defaultValidationLogic } from "./ValidationLogic.js"; | ||
@@ -406,2 +406,117 @@ import { standardSchemaValidators, isStandardSchemaValidator } from "./standardSchemaValidator.js"; | ||
| }; | ||
| this._handleSubmit = async (submitMeta) => { | ||
| this.baseStore.setState((old) => ({ | ||
| ...old, | ||
| // Submission attempts mark the form as not submitted | ||
| isSubmitted: false, | ||
| // Count submission attempts | ||
| submissionAttempts: old.submissionAttempts + 1, | ||
| isSubmitSuccessful: false | ||
| // Reset isSubmitSuccessful at the start of submission | ||
| })); | ||
| batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| if (!field.instance) return; | ||
| if (!field.instance.state.meta.isTouched) { | ||
| field.instance.setMeta((prev) => ({ ...prev, isTouched: true })); | ||
| } | ||
| } | ||
| ); | ||
| }); | ||
| const submitMetaArg = submitMeta ?? this.options.onSubmitMeta; | ||
| if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) { | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| return; | ||
| } | ||
| this.baseStore.setState((d) => ({ ...d, isSubmitting: true })); | ||
| const done = () => { | ||
| this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false })); | ||
| }; | ||
| await this.validateAllFields("submit"); | ||
| if (!this.state.isFieldsValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validateAllFields", | ||
| errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat() | ||
| }); | ||
| return; | ||
| } | ||
| await this.validate("submit"); | ||
| if (!this.state.isValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validate", | ||
| errors: this.state.errors | ||
| }); | ||
| return; | ||
| } | ||
| batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| field.instance?.options.listeners?.onSubmit?.({ | ||
| value: field.instance.state.value, | ||
| fieldApi: field.instance | ||
| }); | ||
| } | ||
| ); | ||
| }); | ||
| this.options.listeners?.onSubmit?.({ formApi: this, meta: submitMetaArg }); | ||
| try { | ||
| await this.options.onSubmit?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| batch(() => { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitted: true, | ||
| isSubmitSuccessful: true | ||
| // Set isSubmitSuccessful to true on successful submission | ||
| })); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: true | ||
| }); | ||
| done(); | ||
| }); | ||
| } catch (err) { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitSuccessful: false | ||
| // Ensure isSubmitSuccessful is false if an error occurs | ||
| })); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "inflight", | ||
| onError: err | ||
| }); | ||
| done(); | ||
| throw err; | ||
| } | ||
| }; | ||
| this.getFieldValue = (field) => getBy(this.state.values, field); | ||
@@ -629,2 +744,44 @@ this.getFieldMeta = (field) => { | ||
| }; | ||
| this.setErrorMap = (errorMap) => { | ||
| batch(() => { | ||
| Object.entries(errorMap).forEach(([key, value]) => { | ||
| const errorMapKey = key; | ||
| if (isGlobalFormValidationError(value)) { | ||
| const { formError, fieldErrors } = normalizeError(value); | ||
| for (const fieldName of Object.keys( | ||
| this.fieldInfo | ||
| )) { | ||
| const fieldMeta = this.getFieldMeta(fieldName); | ||
| if (!fieldMeta) continue; | ||
| this.setFieldMeta(fieldName, (prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: fieldErrors?.[fieldName] | ||
| }, | ||
| errorSourceMap: { | ||
| ...prev.errorSourceMap, | ||
| [errorMapKey]: "form" | ||
| } | ||
| })); | ||
| } | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: formError | ||
| } | ||
| })); | ||
| } else { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: value | ||
| } | ||
| })); | ||
| } | ||
| }); | ||
| }); | ||
| }; | ||
| this.getAllErrors = () => { | ||
@@ -868,162 +1025,5 @@ return { | ||
| } | ||
| async handleSubmit(submitMeta) { | ||
| this.baseStore.setState((old) => ({ | ||
| ...old, | ||
| // Submission attempts mark the form as not submitted | ||
| isSubmitted: false, | ||
| // Count submission attempts | ||
| submissionAttempts: old.submissionAttempts + 1, | ||
| isSubmitSuccessful: false | ||
| // Reset isSubmitSuccessful at the start of submission | ||
| })); | ||
| batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| if (!field.instance) return; | ||
| if (!field.instance.state.meta.isTouched) { | ||
| field.instance.setMeta((prev) => ({ ...prev, isTouched: true })); | ||
| } | ||
| } | ||
| ); | ||
| }); | ||
| const submitMetaArg = submitMeta ?? this.options.onSubmitMeta; | ||
| if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) { | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| return; | ||
| } | ||
| this.baseStore.setState((d) => ({ ...d, isSubmitting: true })); | ||
| const done = () => { | ||
| this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false })); | ||
| }; | ||
| await this.validateAllFields("submit"); | ||
| if (!this.state.isFieldsValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validateAllFields", | ||
| errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat() | ||
| }); | ||
| return; | ||
| } | ||
| await this.validate("submit"); | ||
| if (!this.state.isValid) { | ||
| done(); | ||
| this.options.onSubmitInvalid?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "validate", | ||
| errors: this.state.errors | ||
| }); | ||
| return; | ||
| } | ||
| batch(() => { | ||
| void Object.values(this.fieldInfo).forEach( | ||
| (field) => { | ||
| field.instance?.options.listeners?.onSubmit?.({ | ||
| value: field.instance.state.value, | ||
| fieldApi: field.instance | ||
| }); | ||
| } | ||
| ); | ||
| }); | ||
| this.options.listeners?.onSubmit?.({ formApi: this, meta: submitMetaArg }); | ||
| try { | ||
| await this.options.onSubmit?.({ | ||
| value: this.state.values, | ||
| formApi: this, | ||
| meta: submitMetaArg | ||
| }); | ||
| batch(() => { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitted: true, | ||
| isSubmitSuccessful: true | ||
| // Set isSubmitSuccessful to true on successful submission | ||
| })); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: true | ||
| }); | ||
| done(); | ||
| }); | ||
| } catch (err) { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| isSubmitSuccessful: false | ||
| // Ensure isSubmitSuccessful is false if an error occurs | ||
| })); | ||
| formEventClient.emit("form-submission", { | ||
| id: this._formId, | ||
| submissionAttempt: this.state.submissionAttempts, | ||
| successful: false, | ||
| stage: "inflight", | ||
| onError: err | ||
| }); | ||
| done(); | ||
| throw err; | ||
| } | ||
| handleSubmit(submitMeta) { | ||
| return this._handleSubmit(submitMeta); | ||
| } | ||
| /** | ||
| * Updates the form's errorMap | ||
| */ | ||
| setErrorMap(errorMap) { | ||
| batch(() => { | ||
| Object.entries(errorMap).forEach(([key, value]) => { | ||
| const errorMapKey = key; | ||
| if (isGlobalFormValidationError(value)) { | ||
| const { formError, fieldErrors } = normalizeError(value); | ||
| for (const fieldName of Object.keys( | ||
| this.fieldInfo | ||
| )) { | ||
| const fieldMeta = this.getFieldMeta(fieldName); | ||
| if (!fieldMeta) continue; | ||
| this.setFieldMeta(fieldName, (prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: fieldErrors?.[fieldName] | ||
| }, | ||
| errorSourceMap: { | ||
| ...prev.errorSourceMap, | ||
| [errorMapKey]: "form" | ||
| } | ||
| })); | ||
| } | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: formError | ||
| } | ||
| })); | ||
| } else { | ||
| this.baseStore.setState((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| [errorMapKey]: value | ||
| } | ||
| })); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
@@ -1030,0 +1030,0 @@ function normalizeError(rawError) { |
+1
-1
| { | ||
| "name": "@tanstack/form-core", | ||
| "version": "1.27.0", | ||
| "version": "1.27.1", | ||
| "description": "Powerful, type-safe, framework agnostic forms.", | ||
@@ -5,0 +5,0 @@ "author": "tannerlinsley", |
+8
-3
@@ -966,2 +966,7 @@ import { Derived, batch } from '@tanstack/store' | ||
| /** | ||
| * We cannot use methods and must use arrow functions. Otherwise, our React adapters | ||
| * will break due to loss of the method when using spread. | ||
| */ | ||
| /** | ||
| * A class representing the API for managing a form field. | ||
@@ -1912,3 +1917,3 @@ * | ||
| */ | ||
| setErrorMap( | ||
| setErrorMap = ( | ||
| errorMap: ValidationErrorMap< | ||
@@ -1925,3 +1930,3 @@ UnwrapFieldValidateOrFn<TName, TOnMount, TFormOnMount>, | ||
| >, | ||
| ) { | ||
| ) => { | ||
| this.setMeta((prev) => ({ | ||
@@ -2003,3 +2008,3 @@ ...prev, | ||
| */ | ||
| triggerOnChangeListener() { | ||
| triggerOnChangeListener = () => { | ||
| const formDebounceMs = this.form.options.listeners?.onChangeDebounceMs | ||
@@ -2006,0 +2011,0 @@ if (formDebounceMs && formDebounceMs > 0) { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
1075782
0.3%13506
0.07%