remix-validated-form 4.1.1 → 4.1.4

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.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/browser/ValidatedForm.js +9 -4
  3. package/browser/internal/hooks-valtio.d.ts +18 -0
  4. package/browser/internal/hooks-valtio.js +110 -0
  5. package/browser/internal/hooks-zustand.d.ts +16 -0
  6. package/browser/internal/hooks-zustand.js +100 -0
  7. package/browser/internal/hydratable.d.ts +14 -0
  8. package/browser/internal/hydratable.js +14 -0
  9. package/browser/internal/immerMiddleware.d.ts +6 -0
  10. package/browser/internal/immerMiddleware.js +7 -0
  11. package/browser/internal/logic/getCheckboxChecked.js +1 -1
  12. package/browser/internal/logic/setInputValueInForm.d.ts +1 -0
  13. package/browser/internal/logic/setInputValueInForm.js +120 -0
  14. package/browser/internal/setFieldValue.d.ts +20 -0
  15. package/browser/internal/setFieldValue.js +83 -0
  16. package/browser/internal/setFormValues.d.ts +2 -0
  17. package/browser/internal/setFormValues.js +26 -0
  18. package/browser/internal/state/atomUtils.d.ts +38 -0
  19. package/browser/internal/state/atomUtils.js +5 -0
  20. package/browser/internal/state/controlledFields.d.ts +66 -0
  21. package/browser/internal/state/controlledFields.js +93 -0
  22. package/browser/internal/state/setFieldValue.d.ts +0 -0
  23. package/browser/internal/state/setFieldValue.js +1 -0
  24. package/browser/internal/state-valtio.d.ts +62 -0
  25. package/browser/internal/state-valtio.js +69 -0
  26. package/browser/internal/state-zustand.d.ts +47 -0
  27. package/browser/internal/state-zustand.js +85 -0
  28. package/browser/internal/util.js +1 -1
  29. package/build/ValidatedForm.js +9 -4
  30. package/build/internal/hooks-valtio.d.ts +18 -0
  31. package/build/internal/hooks-valtio.js +128 -0
  32. package/build/internal/hooks-zustand.d.ts +16 -0
  33. package/build/internal/hooks-zustand.js +117 -0
  34. package/build/internal/hydratable.d.ts +14 -0
  35. package/build/internal/hydratable.js +17 -0
  36. package/build/internal/immerMiddleware.d.ts +6 -0
  37. package/build/internal/immerMiddleware.js +14 -0
  38. package/build/internal/logic/getCheckboxChecked.js +1 -1
  39. package/build/internal/logic/setFieldValue.js +3 -3
  40. package/build/internal/logic/setInputValueInForm.d.ts +1 -0
  41. package/build/internal/logic/setInputValueInForm.js +127 -0
  42. package/build/internal/setFormValues.d.ts +2 -0
  43. package/build/internal/setFormValues.js +33 -0
  44. package/build/internal/state/atomUtils.d.ts +38 -0
  45. package/build/internal/state/atomUtils.js +13 -0
  46. package/build/internal/state/controlledFields.d.ts +66 -0
  47. package/build/internal/state/controlledFields.js +95 -0
  48. package/build/internal/state-valtio.d.ts +62 -0
  49. package/build/internal/state-valtio.js +83 -0
  50. package/build/internal/state-zustand.d.ts +47 -0
  51. package/build/internal/state-zustand.js +91 -0
  52. package/build/internal/util.js +5 -2
  53. package/package.json +5 -5
  54. package/src/ValidatedForm.tsx +10 -4
  55. package/src/internal/util.ts +1 -1
@@ -0,0 +1,66 @@
1
+ import { PrimitiveAtom } from "jotai";
2
+ import { InternalFormId } from "./atomUtils";
3
+ export declare type ValueSerializer = (val: unknown) => string;
4
+ export declare const controlledFieldsAtom: {
5
+ (param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
6
+ write: (get: {
7
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
8
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
9
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
10
+ } & {
11
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
12
+ unstable_promise: true;
13
+ }): Value_3 | Promise<Value_3>;
14
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
15
+ unstable_promise: true;
16
+ }): Value_4 | Promise<Value_4>;
17
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
18
+ unstable_promise: true;
19
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
20
+ }, set: {
21
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
22
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
23
+ }, update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void;
24
+ onMount?: (<S extends (update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void>(setAtom: S) => void | (() => void)) | undefined;
25
+ } & {
26
+ init: Record<string, PrimitiveAtom<unknown>>;
27
+ };
28
+ remove(param: InternalFormId): void;
29
+ setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
30
+ };
31
+ export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
32
+ write: (get: {
33
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
34
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
35
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
36
+ } & {
37
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
38
+ unstable_promise: true;
39
+ }): Value_3 | Promise<Value_3>;
40
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
41
+ unstable_promise: true;
42
+ }): Value_4 | Promise<Value_4>;
43
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
44
+ unstable_promise: true;
45
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
46
+ }, set: {
47
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
48
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
49
+ }, update: {
50
+ formId: InternalFormId;
51
+ field: string;
52
+ value: unknown;
53
+ }) => Promise<void>;
54
+ onMount?: (<S extends (update: {
55
+ formId: InternalFormId;
56
+ field: string;
57
+ value: unknown;
58
+ }) => Promise<void>>(setAtom: S) => void | (() => void)) | undefined;
59
+ } & {
60
+ init: null;
61
+ };
62
+ export declare const useAllControlledFields: (formId: InternalFormId) => Record<string, PrimitiveAtom<unknown>>;
63
+ export declare const useControlledFieldValue: (formId: InternalFormId, field: string) => any;
64
+ export declare const useControllableValue: (formId: InternalFormId, field: string) => readonly [any, (value: unknown) => Promise<void>];
65
+ export declare const useFieldSerializer: (formId: InternalFormId, field: string) => ValueSerializer;
66
+ export declare const useRegisterFieldSerializer: (formId: InternalFormId, field: string, serializer?: ValueSerializer | undefined) => void;
@@ -0,0 +1,93 @@
1
+ import { atom } from "jotai";
2
+ import omit from "lodash/omit";
3
+ import { useCallback, useEffect } from "react";
4
+ import { useFieldDefaultValue, useFormAtomValue, useFormAtom, useFormUpdateAtom, } from "../hooks";
5
+ import { isHydratedAtom } from "../state";
6
+ import { fieldAtomFamily, formAtomFamily, } from "./atomUtils";
7
+ export const controlledFieldsAtom = formAtomFamily({});
8
+ const refCountAtom = fieldAtomFamily(() => atom(0));
9
+ const fieldValueAtom = fieldAtomFamily(() => atom(undefined));
10
+ const fieldValueHydratedAtom = fieldAtomFamily(() => atom(false));
11
+ const pendingValidateAtom = fieldAtomFamily(() => atom(undefined));
12
+ const valueSerializerAtom = fieldAtomFamily(() => atom({
13
+ serializer: (value) => {
14
+ if (value === undefined)
15
+ return "";
16
+ return JSON.stringify(value);
17
+ },
18
+ }));
19
+ const registerAtom = atom(null, (get, set, { formId, field }) => {
20
+ set(refCountAtom({ formId, field }), (prev) => prev + 1);
21
+ const newRefCount = get(refCountAtom({ formId, field }));
22
+ // We don't set hydrated here because it gets set when we know
23
+ // we have the right default values
24
+ if (newRefCount === 1) {
25
+ set(controlledFieldsAtom(formId), (prev) => ({
26
+ ...prev,
27
+ [field]: fieldValueAtom({ formId, field }),
28
+ }));
29
+ }
30
+ });
31
+ const unregisterAtom = atom(null, (get, set, { formId, field }) => {
32
+ set(refCountAtom({ formId, field }), (prev) => prev - 1);
33
+ const newRefCount = get(refCountAtom({ formId, field }));
34
+ if (newRefCount === 0) {
35
+ set(controlledFieldsAtom(formId), (prev) => omit(prev, field));
36
+ fieldValueAtom.remove({ formId, field });
37
+ pendingValidateAtom.remove({ formId, field });
38
+ fieldValueHydratedAtom.remove({ formId, field });
39
+ }
40
+ });
41
+ export const setControlledFieldValueAtom = atom(null, async (_get, set, { formId, field, value, }) => {
42
+ set(fieldValueAtom({ formId, field }), value);
43
+ const pending = pendingValidateAtom({ formId, field });
44
+ await new Promise((resolve) => set(pending, resolve));
45
+ set(pending, undefined);
46
+ });
47
+ export const useAllControlledFields = (formId) => useFormAtomValue(controlledFieldsAtom(formId));
48
+ export const useControlledFieldValue = (formId, field) => {
49
+ const fieldAtom = fieldValueAtom({ formId, field });
50
+ const [value, setValue] = useFormAtom(fieldAtom);
51
+ const defaultValue = useFieldDefaultValue(field, { formId });
52
+ const isHydrated = useFormAtomValue(isHydratedAtom(formId));
53
+ const [isFieldHydrated, setIsFieldHydrated] = useFormAtom(fieldValueHydratedAtom({ formId, field }));
54
+ useEffect(() => {
55
+ if (isHydrated && !isFieldHydrated) {
56
+ setValue(defaultValue);
57
+ setIsFieldHydrated(true);
58
+ }
59
+ }, [
60
+ defaultValue,
61
+ field,
62
+ formId,
63
+ isFieldHydrated,
64
+ isHydrated,
65
+ setIsFieldHydrated,
66
+ setValue,
67
+ ]);
68
+ return isFieldHydrated ? value : defaultValue;
69
+ };
70
+ export const useControllableValue = (formId, field) => {
71
+ const pending = useFormAtomValue(pendingValidateAtom({ formId, field }));
72
+ useEffect(() => {
73
+ pending === null || pending === void 0 ? void 0 : pending();
74
+ }, [pending]);
75
+ const register = useFormUpdateAtom(registerAtom);
76
+ const unregister = useFormUpdateAtom(unregisterAtom);
77
+ useEffect(() => {
78
+ register({ formId, field });
79
+ return () => unregister({ formId, field });
80
+ }, [field, formId, register, unregister]);
81
+ const setControlledFieldValue = useFormUpdateAtom(setControlledFieldValueAtom);
82
+ const setValue = useCallback((value) => setControlledFieldValue({ formId, field, value }), [field, formId, setControlledFieldValue]);
83
+ const value = useControlledFieldValue(formId, field);
84
+ return [value, setValue];
85
+ };
86
+ export const useFieldSerializer = (formId, field) => useFormAtomValue(valueSerializerAtom({ formId, field })).serializer;
87
+ export const useRegisterFieldSerializer = (formId, field, serializer) => {
88
+ const setSerializer = useFormUpdateAtom(valueSerializerAtom({ formId, field }));
89
+ useEffect(() => {
90
+ if (serializer)
91
+ setSerializer({ serializer });
92
+ }, [serializer, setSerializer]);
93
+ };
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,62 @@
1
+ import { FieldErrors, TouchedFields } from "..";
2
+ export declare type InternalFormState = {
3
+ hydrated: boolean;
4
+ fieldErrors: FieldErrors;
5
+ isSubmitting: boolean;
6
+ hasBeenSubmitted: boolean;
7
+ touchedFields: TouchedFields;
8
+ action?: string;
9
+ subaction?: string;
10
+ defaultValues: {
11
+ [fieldName: string]: any;
12
+ };
13
+ validateField: (fieldName: string) => Promise<string | null>;
14
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
15
+ setFieldValue: (fieldName: string, value: unknown) => void;
16
+ };
17
+ declare type SyncFormArgs = {
18
+ defaultValues?: {
19
+ [fieldName: string]: any;
20
+ };
21
+ action?: string;
22
+ subaction?: string;
23
+ validateField: InternalFormState["validateField"];
24
+ registerReceiveFocus: InternalFormState["registerReceiveFocus"];
25
+ setFieldValueForForm: InternalFormState["setFieldValue"];
26
+ };
27
+ declare type StoreState = {
28
+ forms: {
29
+ [formId: string | symbol]: InternalFormState;
30
+ };
31
+ };
32
+ export declare const state: StoreState;
33
+ export declare const registerFormSlice: (formId: string | symbol, { registerReceiveFocus, setFieldValueForForm, validateField, action, defaultValues, subaction, }: SyncFormArgs) => void;
34
+ export declare const unregisterFormSlice: (formId: string | symbol) => void;
35
+ export declare const useFormData: (formId: string | symbol) => {
36
+ readonly hydrated: boolean;
37
+ readonly fieldErrors: {
38
+ readonly [x: string]: string;
39
+ };
40
+ readonly isSubmitting: boolean;
41
+ readonly hasBeenSubmitted: boolean;
42
+ readonly touchedFields: {
43
+ readonly [x: string]: boolean;
44
+ };
45
+ readonly action?: string | undefined;
46
+ readonly subaction?: string | undefined;
47
+ readonly defaultValues: {
48
+ readonly [x: string]: any;
49
+ };
50
+ readonly validateField: (fieldName: string) => Promise<string | null>;
51
+ readonly registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
52
+ readonly setFieldValue: (fieldName: string, value: unknown) => void;
53
+ };
54
+ export declare const startSubmit: (formId: string | symbol) => void;
55
+ export declare const endSubmit: (formId: string | symbol) => void;
56
+ export declare const sync: (formId: string | symbol, { defaultValues, action, subaction, registerReceiveFocus, validateField, setFieldValueForForm, }: SyncFormArgs) => void;
57
+ export declare const clearError: (formId: string | symbol, fieldName: string) => void;
58
+ export declare const addError: (formId: string | symbol, fieldName: string, error: string) => void;
59
+ export declare const setTouched: (formId: string | symbol, fieldName: string, touched: boolean) => void;
60
+ export declare const reset: (formId: string | symbol) => void;
61
+ export declare const setFieldErrors: (formId: string | symbol, fieldErrors: FieldErrors) => void;
62
+ export {};
@@ -0,0 +1,69 @@
1
+ import { proxy, useSnapshot } from "valtio";
2
+ export const state = proxy({ forms: {} });
3
+ export const registerFormSlice = (formId, { registerReceiveFocus, setFieldValueForForm, validateField, action, defaultValues, subaction, }) => {
4
+ state.forms[formId] = {
5
+ hydrated: true,
6
+ defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
7
+ fieldErrors: {},
8
+ hasBeenSubmitted: false,
9
+ isSubmitting: false,
10
+ touchedFields: {},
11
+ registerReceiveFocus,
12
+ setFieldValue: setFieldValueForForm,
13
+ validateField,
14
+ action,
15
+ subaction,
16
+ };
17
+ };
18
+ export const unregisterFormSlice = (formId) => {
19
+ delete state.forms[formId];
20
+ };
21
+ const unhydratedFormState = {
22
+ hydrated: false,
23
+ fieldErrors: {},
24
+ isSubmitting: false,
25
+ hasBeenSubmitted: false,
26
+ touchedFields: {},
27
+ defaultValues: {},
28
+ validateField: () => Promise.resolve(null),
29
+ registerReceiveFocus: () => () => { },
30
+ setFieldValue: () => { },
31
+ };
32
+ export const useFormData = (formId) => {
33
+ var _a;
34
+ const snapshot = useSnapshot(state);
35
+ return (_a = snapshot.forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState;
36
+ };
37
+ export const startSubmit = (formId) => {
38
+ state.forms[formId].isSubmitting = true;
39
+ state.forms[formId].hasBeenSubmitted = true;
40
+ };
41
+ export const endSubmit = (formId) => {
42
+ state.forms[formId].isSubmitting = false;
43
+ };
44
+ export const sync = (formId, { defaultValues, action, subaction, registerReceiveFocus, validateField, setFieldValueForForm, }) => {
45
+ state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
46
+ state.forms[formId].action = action;
47
+ state.forms[formId].subaction = subaction;
48
+ state.forms[formId].registerReceiveFocus = registerReceiveFocus;
49
+ state.forms[formId].validateField = validateField;
50
+ state.forms[formId].hydrated = true;
51
+ state.forms[formId].setFieldValue = setFieldValueForForm;
52
+ };
53
+ export const clearError = (formId, fieldName) => {
54
+ delete state.forms[formId].fieldErrors[fieldName];
55
+ };
56
+ export const addError = (formId, fieldName, error) => {
57
+ state.forms[formId].fieldErrors[fieldName] = error;
58
+ };
59
+ export const setTouched = (formId, fieldName, touched) => {
60
+ state.forms[formId].touchedFields[fieldName] = touched;
61
+ };
62
+ export const reset = (formId) => {
63
+ state.forms[formId].fieldErrors = {};
64
+ state.forms[formId].touchedFields = {};
65
+ state.forms[formId].hasBeenSubmitted = false;
66
+ };
67
+ export const setFieldErrors = (formId, fieldErrors) => {
68
+ state.forms[formId].fieldErrors = fieldErrors;
69
+ };
@@ -0,0 +1,47 @@
1
+ import { FieldErrors, TouchedFields } from "..";
2
+ export declare type InternalFormState = {
3
+ hydrated: boolean;
4
+ fieldErrors: FieldErrors;
5
+ isSubmitting: boolean;
6
+ hasBeenSubmitted: boolean;
7
+ touchedFields: TouchedFields;
8
+ action?: string;
9
+ subaction?: string;
10
+ defaultValues: {
11
+ [fieldName: string]: any;
12
+ };
13
+ validateField: (fieldName: string) => Promise<string | null>;
14
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
15
+ setFieldValue: (fieldName: string, value: unknown) => void;
16
+ };
17
+ declare type Helpers = {
18
+ startSubmit: () => void;
19
+ endSubmit: () => void;
20
+ sync: (args: SyncFormArgs) => void;
21
+ clearError: (name: string) => void;
22
+ addError: (name: string, error: string) => void;
23
+ setTouched: (name: string, touched: boolean) => void;
24
+ reset: () => void;
25
+ setFieldErrors: (fieldErrors: FieldErrors) => void;
26
+ register: (init: SyncFormArgs) => void;
27
+ unregister: () => void;
28
+ };
29
+ declare type SyncFormArgs = {
30
+ defaultValues?: {
31
+ [fieldName: string]: any;
32
+ };
33
+ action?: string;
34
+ subaction?: string;
35
+ validateField: InternalFormState["validateField"];
36
+ registerReceiveFocus: InternalFormState["registerReceiveFocus"];
37
+ setFieldValueForForm: InternalFormState["setFieldValue"];
38
+ };
39
+ declare type StoreState = {
40
+ forms: {
41
+ [formId: string | symbol]: InternalFormState;
42
+ };
43
+ form: (formId: string | symbol) => InternalFormState;
44
+ helpers: (formId: string | symbol) => Helpers;
45
+ };
46
+ export declare const useStore: import("zustand").UseBoundStore<StoreState, import("zustand").StoreApi<StoreState>>;
47
+ export {};
@@ -0,0 +1,85 @@
1
+ import create from "zustand";
2
+ import { immer } from "./immerMiddleware";
3
+ const unhydratedFormState = {
4
+ hydrated: false,
5
+ fieldErrors: {},
6
+ isSubmitting: false,
7
+ hasBeenSubmitted: false,
8
+ touchedFields: {},
9
+ defaultValues: {},
10
+ validateField: () => Promise.resolve(null),
11
+ registerReceiveFocus: () => () => { },
12
+ setFieldValue: () => { },
13
+ // clearError: () => {},
14
+ // addError: () => {},
15
+ // setTouched: () => {},
16
+ // reset: () => {},
17
+ // startSubmit: () => {},
18
+ // endSubmit: () => {},
19
+ // sync: () => {},
20
+ // setFieldErrors: () => {},
21
+ };
22
+ export const useStore = create(immer((set, get) => ({
23
+ forms: {},
24
+ form: (formId) => { var _a; return (_a = get().forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState; },
25
+ helpers: (formId) => ({
26
+ clearError: (name) => set((state) => {
27
+ delete state.forms[formId].fieldErrors[name];
28
+ }),
29
+ addError: (name, error) => set((state) => {
30
+ state.forms[formId].fieldErrors[name] = error;
31
+ }),
32
+ setTouched: (name, touched) => set((state) => {
33
+ state.forms[formId].touchedFields[name] = touched;
34
+ }),
35
+ reset: () => set((state) => {
36
+ state.forms[formId].fieldErrors = {};
37
+ state.forms[formId].touchedFields = {};
38
+ state.forms[formId].hasBeenSubmitted = false;
39
+ }),
40
+ startSubmit: () => set((state) => {
41
+ state.forms[formId].hasBeenSubmitted = true;
42
+ state.forms[formId].isSubmitting = true;
43
+ }),
44
+ endSubmit: () => set((state) => {
45
+ state.forms[formId].isSubmitting = false;
46
+ }),
47
+ setFieldErrors: (fieldErrors) => {
48
+ set((state) => {
49
+ state.forms[formId].fieldErrors = fieldErrors;
50
+ });
51
+ },
52
+ sync: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => set((state) => {
53
+ state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
54
+ state.forms[formId].action = action;
55
+ state.forms[formId].subaction = subaction;
56
+ state.forms[formId].registerReceiveFocus = registerReceiveFocus;
57
+ state.forms[formId].validateField = validateField;
58
+ state.forms[formId].hydrated = true;
59
+ state.forms[formId].setFieldValue = setFieldValueForForm;
60
+ }),
61
+ unregister: () => {
62
+ set((state) => {
63
+ delete state.forms[formId];
64
+ });
65
+ },
66
+ register: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => {
67
+ set((state) => {
68
+ state.forms[formId] = {
69
+ defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
70
+ setFieldValue: setFieldValueForForm,
71
+ registerReceiveFocus,
72
+ validateField,
73
+ action,
74
+ subaction,
75
+ hydrated: true,
76
+ fieldErrors: {},
77
+ isSubmitting: false,
78
+ hasBeenSubmitted: false,
79
+ touchedFields: {},
80
+ // helpers
81
+ };
82
+ });
83
+ },
84
+ }),
85
+ })));
@@ -1,4 +1,4 @@
1
- import { isEqual } from "lodash";
1
+ import isEqual from "lodash/isEqual";
2
2
  import { useEffect, useLayoutEffect, useRef } from "react";
3
3
  export const omit = (obj, ...keys) => {
4
4
  const result = { ...obj };
@@ -212,9 +212,9 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
212
212
  clickedButtonRef.current = submitButton;
213
213
  }
214
214
  }
215
- window.addEventListener("click", handleClick);
215
+ window.addEventListener("click", handleClick, { capture: true });
216
216
  return () => {
217
- window.removeEventListener("click", handleClick);
217
+ window.removeEventListener("click", handleClick, { capture: true });
218
218
  };
219
219
  }, []);
220
220
  const handleSubmit = async (e) => {
@@ -234,10 +234,15 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
234
234
  endSubmit({ formAtom });
235
235
  return;
236
236
  }
237
+ // We deviate from the remix code here a bit because of our async submit.
238
+ // In remix's `FormImpl`, they use `event.currentTarget` to get the form,
239
+ // but we already have the form in `formRef.current` so we can just use that.
240
+ // If we use `event.currentTarget` here, it will break because `currentTarget`
241
+ // will have changed since the start of the submission.
237
242
  if (fetcher)
238
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
243
+ fetcher.submit(clickedButtonRef.current || formRef.current);
239
244
  else
240
- submit(clickedButtonRef.current || e.currentTarget, {
245
+ submit(clickedButtonRef.current || formRef.current, {
241
246
  method,
242
247
  replace,
243
248
  });
@@ -0,0 +1,18 @@
1
+ import { useUpdateAtom } from "jotai/utils";
2
+ import { FieldErrors, ValidationErrorResponseData } from "..";
3
+ import { InternalFormContextValue } from "./formContext";
4
+ import { Hydratable } from "./hydratable";
5
+ export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
6
+ export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
7
+ export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
8
+ export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
9
+ export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => Hydratable<{
10
+ [fieldName: string]: any;
11
+ }>;
12
+ export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
13
+ export declare const useFieldTouched: (name: string, { formId }: InternalFormContextValue) => boolean;
14
+ export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
15
+ export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
16
+ export declare const useFormUpdateAtom: typeof useUpdateAtom;
17
+ export declare const useClearError: (context: InternalFormContextValue) => (name: string) => void;
18
+ export declare const useSetTouched: (context: InternalFormContextValue) => (name: string, touched: boolean) => void;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.useSetTouched = exports.useClearError = exports.useFormUpdateAtom = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useInternalFormContext = void 0;
7
+ const react_1 = require("@remix-run/react");
8
+ const utils_1 = require("jotai/utils");
9
+ const get_1 = __importDefault(require("lodash/get"));
10
+ const react_2 = require("react");
11
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
12
+ const constants_1 = require("./constants");
13
+ const formContext_1 = require("./formContext");
14
+ const hydratable_1 = require("./hydratable");
15
+ const state_1 = require("./state");
16
+ const state_valtio_1 = require("./state-valtio");
17
+ const useInternalFormContext = (formId, hookName) => {
18
+ const formContext = (0, react_2.useContext)(formContext_1.InternalFormContext);
19
+ if (formId)
20
+ return { formId };
21
+ if (formContext)
22
+ return formContext;
23
+ throw new Error(`Unable to determine form for ${hookName}. Please use it inside a form or pass a 'formId'.`);
24
+ };
25
+ exports.useInternalFormContext = useInternalFormContext;
26
+ function useErrorResponseForForm({ fetcher, subaction, formId, }) {
27
+ var _a;
28
+ const actionData = (0, react_1.useActionData)();
29
+ if (fetcher) {
30
+ if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
31
+ return fetcher.data;
32
+ return null;
33
+ }
34
+ if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
35
+ return null;
36
+ // If there's an explicit id, we should ignore data that has the wrong id
37
+ if (typeof formId === "string" && actionData.formId)
38
+ return actionData.formId === formId ? actionData : null;
39
+ if ((!subaction && !actionData.subaction) ||
40
+ actionData.subaction === subaction)
41
+ return actionData;
42
+ return null;
43
+ }
44
+ exports.useErrorResponseForForm = useErrorResponseForForm;
45
+ const useFieldErrorsForForm = (context) => {
46
+ const response = useErrorResponseForForm(context);
47
+ const form = (0, state_valtio_1.useFormData)(context.formId);
48
+ return hydratable_1.hydratable.from(response === null || response === void 0 ? void 0 : response.fieldErrors, form.hydrated);
49
+ };
50
+ exports.useFieldErrorsForForm = useFieldErrorsForForm;
51
+ const useDefaultValuesFromLoader = ({ formId, }) => {
52
+ const matches = (0, react_1.useMatches)();
53
+ if (typeof formId === "string") {
54
+ const dataKey = (0, constants_1.formDefaultValuesKey)(formId);
55
+ // If multiple loaders declare the same default values,
56
+ // we should use the data from the deepest route.
57
+ const match = matches
58
+ .reverse()
59
+ .find((match) => match.data && dataKey in match.data);
60
+ return match === null || match === void 0 ? void 0 : match.data[dataKey];
61
+ }
62
+ return null;
63
+ };
64
+ exports.useDefaultValuesFromLoader = useDefaultValuesFromLoader;
65
+ const useDefaultValuesForForm = (context) => {
66
+ const { formId, defaultValuesProp } = context;
67
+ const form = (0, state_valtio_1.useFormData)(formId);
68
+ const errorResponse = useErrorResponseForForm(context);
69
+ const defaultValuesFromLoader = (0, exports.useDefaultValuesFromLoader)(context);
70
+ // Typical flow is:
71
+ // - Default values only available from props or server
72
+ // - Props have a higher priority than server
73
+ // - State gets hydrated with default values
74
+ // - After submit, we may need to use values from the error
75
+ if (form.hydrated)
76
+ return hydratable_1.hydratable.hydratedData();
77
+ if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields) {
78
+ (0, tiny_invariant_1.default)(typeof errorResponse.repopulateFields === "object", "repopulateFields returned something other than an object");
79
+ return hydratable_1.hydratable.serverData(errorResponse.repopulateFields);
80
+ }
81
+ if (defaultValuesProp)
82
+ return hydratable_1.hydratable.serverData(defaultValuesProp);
83
+ return hydratable_1.hydratable.serverData(defaultValuesFromLoader);
84
+ };
85
+ exports.useDefaultValuesForForm = useDefaultValuesForForm;
86
+ const useHasActiveFormSubmit = ({ fetcher, }) => {
87
+ const transition = (0, react_1.useTransition)();
88
+ const hasActiveSubmission = fetcher
89
+ ? fetcher.state === "submitting"
90
+ : !!transition.submission;
91
+ return hasActiveSubmission;
92
+ };
93
+ exports.useHasActiveFormSubmit = useHasActiveFormSubmit;
94
+ const useFieldTouched = (name, { formId }) => {
95
+ const form = (0, state_valtio_1.useFormData)(formId);
96
+ return form.touchedFields[name];
97
+ };
98
+ exports.useFieldTouched = useFieldTouched;
99
+ const useFieldError = (name, context) => {
100
+ const fieldErrors = (0, exports.useFieldErrorsForForm)(context);
101
+ const form = (0, state_valtio_1.useFormData)(context.formId);
102
+ return fieldErrors
103
+ .map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name])
104
+ .hydrateTo(form.fieldErrors[name]);
105
+ };
106
+ exports.useFieldError = useFieldError;
107
+ const useFieldDefaultValue = (name, context) => {
108
+ const defaultValues = (0, exports.useDefaultValuesForForm)(context);
109
+ const state = (0, state_valtio_1.useFormData)(context.formId);
110
+ return defaultValues.map((val) => (0, get_1.default)(val, name)).hydrateTo(state);
111
+ };
112
+ exports.useFieldDefaultValue = useFieldDefaultValue;
113
+ const useFormUpdateAtom = (atom) => (0, utils_1.useUpdateAtom)(atom, state_1.ATOM_SCOPE);
114
+ exports.useFormUpdateAtom = useFormUpdateAtom;
115
+ const useClearError = (context) => {
116
+ const clearError = (0, exports.useFormUpdateAtom)(state_1.clearErrorAtom);
117
+ return (0, react_2.useCallback)((name) => {
118
+ clearError({ name, formAtom: (0, state_1.formRegistry)(context.formId) });
119
+ }, [clearError, context.formId]);
120
+ };
121
+ exports.useClearError = useClearError;
122
+ const useSetTouched = (context) => {
123
+ const setTouched = (0, exports.useFormUpdateAtom)(state_1.setTouchedAtom);
124
+ return (0, react_2.useCallback)((name, touched) => {
125
+ setTouched({ name, formAtom: (0, state_1.formRegistry)(context.formId), touched });
126
+ }, [setTouched, context.formId]);
127
+ };
128
+ exports.useSetTouched = useSetTouched;
@@ -0,0 +1,16 @@
1
+ import { FieldErrors, ValidationErrorResponseData } from "..";
2
+ import { InternalFormContextValue } from "./formContext";
3
+ import { Hydratable } from "./hydratable";
4
+ export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
5
+ export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
6
+ export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
7
+ export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
8
+ export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => Hydratable<{
9
+ [fieldName: string]: any;
10
+ }>;
11
+ export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
12
+ export declare const useFieldTouched: (name: string, { formId }: InternalFormContextValue) => boolean;
13
+ export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
14
+ export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
15
+ export declare const useClearError: (context: InternalFormContextValue) => (name: string) => void;
16
+ export declare const useSetTouched: (context: InternalFormContextValue) => (name: string, touched: boolean) => void;