@tanstack/form-core 1.20.0 → 1.21.0

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.
@@ -0,0 +1,13 @@
1
+ import { EventClient } from "@tanstack/devtools-event-client";
2
+ class FormEventClient extends EventClient {
3
+ constructor() {
4
+ super({
5
+ pluginId: "form-devtools"
6
+ });
7
+ }
8
+ }
9
+ const formEventClient = new FormEventClient();
10
+ export {
11
+ formEventClient
12
+ };
13
+ //# sourceMappingURL=EventClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventClient.js","sources":["../../src/EventClient.ts"],"sourcesContent":["import { EventClient } from '@tanstack/devtools-event-client'\n\nimport type { AnyFormOptions, AnyFormState } from './FormApi'\n\ntype ExtractEventNames<T> = T extends `${string}:${infer EventName}`\n ? EventName\n : never\n\nexport type BroadcastFormState = {\n id: string\n state: AnyFormState\n options: AnyFormOptions\n}\n\nexport type BroadcastFormSubmissionState =\n | {\n id: string\n submissionAttempt: number\n successful: false\n stage: 'validateAllFields' | 'validate'\n errors: any[]\n }\n | {\n id: string\n submissionAttempt: number\n successful: false\n stage: 'inflight'\n onError: unknown\n }\n | {\n id: string\n submissionAttempt: number\n successful: true\n }\n\nexport type BroadcastFormUnmounted = {\n id: string\n}\n\nexport type RequestFormState = {\n id: string\n}\n\nexport type RequestFormReset = {\n id: string\n}\n\nexport type RequestFormForceReset = {\n id: string\n}\n\ntype EventMap = {\n 'form-devtools:form-state-change': BroadcastFormState\n 'form-devtools:form-submission-state-change': BroadcastFormSubmissionState\n 'form-devtools:form-unmounted': BroadcastFormUnmounted\n 'form-devtools:request-form-state': RequestFormState\n 'form-devtools:request-form-reset': RequestFormReset\n 'form-devtools:request-form-force-submit': RequestFormForceReset\n}\n\nexport type EventClientEventMap = keyof EventMap\n\nexport type EventClientEventNames = ExtractEventNames<EventClientEventMap>\n\nclass FormEventClient extends EventClient<EventMap> {\n constructor() {\n super({\n pluginId: 'form-devtools',\n })\n }\n}\n\nexport const formEventClient = new FormEventClient()\n"],"names":[],"mappings":";AAgEA,MAAM,wBAAwB,YAAsB;AAAA,EAClD,cAAc;AACZ,UAAM;AAAA,MACJ,UAAU;AAAA,IAAA,CACX;AAAA,EACH;AACF;AAEO,MAAM,kBAAkB,IAAI,gBAAA;"}
@@ -164,6 +164,7 @@ export interface FormOptions<in out TFormData, in out TOnMount extends undefined
164
164
  }) => void;
165
165
  transform?: FormTransform<NoInfer<TFormData>, NoInfer<TOnMount>, NoInfer<TOnChange>, NoInfer<TOnChangeAsync>, NoInfer<TOnBlur>, NoInfer<TOnBlurAsync>, NoInfer<TOnSubmit>, NoInfer<TOnSubmitAsync>, NoInfer<TOnDynamic>, NoInfer<TOnDynamicAsync>, NoInfer<TOnServer>, NoInfer<TSubmitMeta>>;
166
166
  }
167
+ export type AnyFormOptions = FormOptions<any, any, any, any, any, any, any, any, any, any, any, any>;
167
168
  /**
168
169
  * An object representing the validation metadata for a field. Not intended for public usage.
169
170
  */
@@ -332,18 +333,26 @@ export declare class FormApi<in out TFormData, in out TOnMount extends undefined
332
333
  */
333
334
  prevTransformArray: unknown[];
334
335
  /**
335
- *
336
+ * @private
336
337
  */
337
338
  timeoutIds: {
338
339
  validations: Record<ValidationCause, ReturnType<typeof setTimeout> | null>;
339
340
  listeners: Record<ListenerCause, ReturnType<typeof setTimeout> | null>;
340
341
  formListeners: Record<ListenerCause, ReturnType<typeof setTimeout> | null>;
341
342
  };
343
+ /**
344
+ * @private
345
+ */
346
+ private _formId;
347
+ /**
348
+ * @private
349
+ */
350
+ private _devtoolsSubmissionOverride;
342
351
  /**
343
352
  * Constructs a new `FormApi` instance with the given form options.
344
353
  */
345
354
  constructor(opts?: FormOptions<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync, TOnServer, TSubmitMeta>);
346
- get formId(): string | undefined;
355
+ formId(): string | undefined;
347
356
  /**
348
357
  * @private
349
358
  */
@@ -3,6 +3,7 @@ import { evaluate, getSyncValidatorArray, determineFormLevelErrorSourceAndValue,
3
3
  import { defaultValidationLogic } from "./ValidationLogic.js";
4
4
  import { standardSchemaValidators, isStandardSchemaValidator } from "./standardSchemaValidator.js";
5
5
  import { defaultFieldMeta, metaHelper } from "./metaHelper.js";
6
+ import { formEventClient } from "./EventClient.js";
6
7
  function getDefaultFormState(defaultState) {
7
8
  return {
8
9
  values: defaultState.values ?? {},
@@ -37,9 +38,17 @@ class FormApi {
37
38
  const cleanup = () => {
38
39
  cleanupFieldMetaDerived();
39
40
  cleanupStoreDerived();
41
+ formEventClient.emit("form-unmounted", {
42
+ id: this._formId
43
+ });
40
44
  };
41
45
  this.options.listeners?.onMount?.({ formApi: this });
42
46
  const { onMount } = this.options.validators || {};
47
+ formEventClient.emit("form-state-change", {
48
+ id: this._formId,
49
+ state: this.store.state,
50
+ options: this.options
51
+ });
43
52
  if (!onMount) return cleanup;
44
53
  this.validateSync("mount");
45
54
  return cleanup;
@@ -621,6 +630,8 @@ class FormApi {
621
630
  listeners: {},
622
631
  formListeners: {}
623
632
  };
633
+ this._formId = opts?.formId ?? crypto.randomUUID();
634
+ this._devtoolsSubmissionOverride = false;
624
635
  this.baseStore = new Store(
625
636
  getDefaultFormState({
626
637
  ...opts?.defaultState,
@@ -770,12 +781,40 @@ class FormApi {
770
781
  });
771
782
  this.handleSubmit = this.handleSubmit.bind(this);
772
783
  this.update(opts || {});
784
+ this.store.subscribe(() => {
785
+ formEventClient.emit("form-state-change", {
786
+ id: this._formId,
787
+ state: this.store.state,
788
+ options: this.options
789
+ });
790
+ });
791
+ formEventClient.on("request-form-state", (e) => {
792
+ if (e.payload.id === this._formId) {
793
+ formEventClient.emit("form-state-change", {
794
+ id: this._formId,
795
+ state: this.store.state,
796
+ options: this.options
797
+ });
798
+ }
799
+ });
800
+ formEventClient.on("request-form-reset", (e) => {
801
+ if (e.payload.id === this._formId) {
802
+ this.reset();
803
+ }
804
+ });
805
+ formEventClient.on("request-form-force-submit", (e) => {
806
+ if (e.payload.id === this._formId) {
807
+ this._devtoolsSubmissionOverride = true;
808
+ this.handleSubmit();
809
+ this._devtoolsSubmissionOverride = false;
810
+ }
811
+ });
773
812
  }
774
813
  get state() {
775
814
  return this.store.state;
776
815
  }
777
- get formId() {
778
- return this.options.formId;
816
+ formId() {
817
+ return this._formId;
779
818
  }
780
819
  /**
781
820
  * @private
@@ -809,7 +848,7 @@ class FormApi {
809
848
  }
810
849
  );
811
850
  });
812
- if (!this.state.canSubmit) return;
851
+ if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) return;
813
852
  const submitMetaArg = submitMeta ?? this.options.onSubmitMeta;
814
853
  this.baseStore.setState((d) => ({ ...d, isSubmitting: true }));
815
854
  const done = () => {
@@ -823,6 +862,13 @@ class FormApi {
823
862
  formApi: this,
824
863
  meta: submitMetaArg
825
864
  });
865
+ formEventClient.emit("form-submission-state-change", {
866
+ id: this._formId,
867
+ submissionAttempt: this.state.submissionAttempts,
868
+ successful: false,
869
+ stage: "validateAllFields",
870
+ errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat()
871
+ });
826
872
  return;
827
873
  }
828
874
  await this.validate("submit");
@@ -833,6 +879,13 @@ class FormApi {
833
879
  formApi: this,
834
880
  meta: submitMetaArg
835
881
  });
882
+ formEventClient.emit("form-submission-state-change", {
883
+ id: this._formId,
884
+ submissionAttempt: this.state.submissionAttempts,
885
+ successful: false,
886
+ stage: "validate",
887
+ errors: this.state.errors
888
+ });
836
889
  return;
837
890
  }
838
891
  batch(() => {
@@ -859,6 +912,11 @@ class FormApi {
859
912
  isSubmitSuccessful: true
860
913
  // Set isSubmitSuccessful to true on successful submission
861
914
  }));
915
+ formEventClient.emit("form-submission-state-change", {
916
+ id: this._formId,
917
+ submissionAttempt: this.state.submissionAttempts,
918
+ successful: true
919
+ });
862
920
  done();
863
921
  });
864
922
  } catch (err) {
@@ -867,6 +925,13 @@ class FormApi {
867
925
  isSubmitSuccessful: false
868
926
  // Ensure isSubmitSuccessful is false if an error occurs
869
927
  }));
928
+ formEventClient.emit("form-submission-state-change", {
929
+ id: this._formId,
930
+ submissionAttempt: this.state.submissionAttempts,
931
+ successful: false,
932
+ stage: "inflight",
933
+ onError: err
934
+ });
870
935
  done();
871
936
  throw err;
872
937
  }