remix-validated-form 4.0.1 → 4.1.0-beta.3

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 (66) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/README.md +4 -4
  3. package/browser/ValidatedForm.d.ts +2 -2
  4. package/browser/ValidatedForm.js +142 -149
  5. package/browser/components.d.ts +5 -8
  6. package/browser/components.js +5 -5
  7. package/browser/hooks.d.ts +19 -14
  8. package/browser/hooks.js +41 -39
  9. package/browser/index.d.ts +1 -1
  10. package/browser/index.js +1 -0
  11. package/browser/internal/constants.d.ts +3 -0
  12. package/browser/internal/constants.js +3 -0
  13. package/browser/internal/formContext.d.ts +7 -49
  14. package/browser/internal/formContext.js +1 -1
  15. package/browser/internal/getInputProps.js +4 -3
  16. package/browser/internal/hooks.d.ts +22 -0
  17. package/browser/internal/hooks.js +110 -0
  18. package/browser/internal/state.d.ts +269 -0
  19. package/browser/internal/state.js +82 -0
  20. package/browser/internal/util.d.ts +1 -0
  21. package/browser/internal/util.js +2 -0
  22. package/browser/lowLevelHooks.d.ts +0 -0
  23. package/browser/lowLevelHooks.js +1 -0
  24. package/browser/server.d.ts +5 -0
  25. package/browser/server.js +5 -0
  26. package/browser/userFacingFormContext.d.ts +56 -0
  27. package/browser/userFacingFormContext.js +40 -0
  28. package/browser/validation/createValidator.js +4 -0
  29. package/browser/validation/types.d.ts +3 -0
  30. package/build/ValidatedForm.d.ts +2 -2
  31. package/build/ValidatedForm.js +138 -145
  32. package/build/hooks.d.ts +19 -14
  33. package/build/hooks.js +43 -45
  34. package/build/index.d.ts +1 -1
  35. package/build/index.js +1 -0
  36. package/build/internal/constants.d.ts +3 -0
  37. package/build/internal/constants.js +7 -0
  38. package/build/internal/formContext.d.ts +7 -49
  39. package/build/internal/formContext.js +2 -2
  40. package/build/internal/getInputProps.js +7 -3
  41. package/build/internal/hooks.d.ts +22 -0
  42. package/build/internal/hooks.js +130 -0
  43. package/build/internal/state.d.ts +269 -0
  44. package/build/internal/state.js +92 -0
  45. package/build/internal/util.d.ts +1 -0
  46. package/build/internal/util.js +3 -1
  47. package/build/server.d.ts +5 -0
  48. package/build/server.js +7 -1
  49. package/build/userFacingFormContext.d.ts +56 -0
  50. package/build/userFacingFormContext.js +44 -0
  51. package/build/validation/createValidator.js +4 -0
  52. package/build/validation/types.d.ts +3 -0
  53. package/package.json +3 -1
  54. package/src/ValidatedForm.tsx +205 -200
  55. package/src/hooks.ts +71 -54
  56. package/src/index.ts +1 -1
  57. package/src/internal/constants.ts +4 -0
  58. package/src/internal/formContext.ts +8 -49
  59. package/src/internal/getInputProps.ts +6 -4
  60. package/src/internal/hooks.ts +191 -0
  61. package/src/internal/state.ts +210 -0
  62. package/src/internal/util.ts +4 -0
  63. package/src/server.ts +16 -0
  64. package/src/userFacingFormContext.ts +129 -0
  65. package/src/validation/createValidator.ts +4 -0
  66. package/src/validation/types.ts +3 -1
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.createGetInputProps = void 0;
7
+ const omitBy_1 = __importDefault(require("lodash/omitBy"));
4
8
  const defaultValidationBehavior = {
5
9
  initial: "onBlur",
6
10
  whenTouched: "onChange",
@@ -46,18 +50,18 @@ const createGetInputProps = ({ clearError, validate, defaultValue, touched, setT
46
50
  },
47
51
  name,
48
52
  };
49
- if (inputProps.type === "checkbox") {
53
+ if (props.type === "checkbox") {
50
54
  const value = (_a = props.value) !== null && _a !== void 0 ? _a : "on";
51
55
  inputProps.defaultChecked = getCheckboxDefaultChecked(value, defaultValue);
52
56
  }
53
- else if (inputProps.type === "radio") {
57
+ else if (props.type === "radio") {
54
58
  const value = (_b = props.value) !== null && _b !== void 0 ? _b : "on";
55
59
  inputProps.defaultChecked = defaultValue === value;
56
60
  }
57
61
  else {
58
62
  inputProps.defaultValue = defaultValue;
59
63
  }
60
- return inputProps;
64
+ return (0, omitBy_1.default)(inputProps, (value) => value === undefined);
61
65
  };
62
66
  };
63
67
  exports.createGetInputProps = createGetInputProps;
@@ -0,0 +1,22 @@
1
+ import { Atom } from "jotai";
2
+ import { useUpdateAtom } from "jotai/utils";
3
+ import { ValidationErrorResponseData } from "..";
4
+ import { InternalFormContextValue } from "./formContext";
5
+ import { FormAtom } from "./state";
6
+ declare type FormSelectorAtomCreator<T> = (formState: FormAtom) => Atom<T>;
7
+ declare const USE_HYDRATED_STATE: unique symbol;
8
+ export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
9
+ export declare const useContextSelectAtom: <T>(formId: string | symbol, selectorAtomCreator: FormSelectorAtomCreator<T>) => T extends Promise<infer V> ? V : T;
10
+ export declare const useHydratableSelector: <T, U>({ formId }: InternalFormContextValue, atomCreator: FormSelectorAtomCreator<T>, dataToUse: U | typeof USE_HYDRATED_STATE, selector?: (data: U) => T) => T | (T extends Promise<infer V> ? V : T);
11
+ export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
12
+ export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
13
+ export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
14
+ export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => any;
15
+ export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
16
+ export declare const useFieldTouched: (name: string, { formId }: InternalFormContextValue) => boolean;
17
+ export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
18
+ export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
19
+ export declare const useFormUpdateAtom: typeof useUpdateAtom;
20
+ export declare const useClearError: (context: InternalFormContextValue) => (name: string) => void;
21
+ export declare const useSetTouched: (context: InternalFormContextValue) => (name: string, touched: boolean) => void;
22
+ export {};
@@ -0,0 +1,130 @@
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.useHydratableSelector = exports.useContextSelectAtom = 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 identity_1 = __importDefault(require("lodash/identity"));
11
+ const react_2 = require("react");
12
+ const constants_1 = require("./constants");
13
+ const formContext_1 = require("./formContext");
14
+ const state_1 = require("./state");
15
+ const USE_HYDRATED_STATE = Symbol("USE_HYDRATED_STATE");
16
+ const useInternalFormContext = (formId, hookName) => {
17
+ const formContext = (0, react_2.useContext)(formContext_1.InternalFormContext);
18
+ if (formId)
19
+ return { formId };
20
+ if (formContext)
21
+ return formContext;
22
+ throw new Error(`Unable to determine form for ${hookName}. Please use it inside a form or pass a 'formId'.`);
23
+ };
24
+ exports.useInternalFormContext = useInternalFormContext;
25
+ const useContextSelectAtom = (formId, selectorAtomCreator) => {
26
+ const formAtom = (0, state_1.formRegistry)(formId);
27
+ const selectorAtom = (0, react_2.useMemo)(() => selectorAtomCreator(formAtom), [formAtom, selectorAtomCreator]);
28
+ return (0, utils_1.useAtomValue)(selectorAtom, state_1.ATOM_SCOPE);
29
+ };
30
+ exports.useContextSelectAtom = useContextSelectAtom;
31
+ const useHydratableSelector = ({ formId }, atomCreator, dataToUse, selector = identity_1.default) => {
32
+ const dataFromState = (0, exports.useContextSelectAtom)(formId, atomCreator);
33
+ return dataToUse === USE_HYDRATED_STATE ? dataFromState : selector(dataToUse);
34
+ };
35
+ exports.useHydratableSelector = useHydratableSelector;
36
+ function useErrorResponseForForm({ fetcher, subaction, formId, }) {
37
+ var _a;
38
+ const actionData = (0, react_1.useActionData)();
39
+ if (fetcher) {
40
+ if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
41
+ return fetcher.data;
42
+ return null;
43
+ }
44
+ if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
45
+ return null;
46
+ // If there's an explicit id, we should ignore data that has the wrong id
47
+ if (typeof formId === "string" && actionData.formId)
48
+ return actionData.formId === formId ? actionData : null;
49
+ if ((!subaction && !actionData.subaction) ||
50
+ actionData.subaction === subaction)
51
+ return actionData;
52
+ return null;
53
+ }
54
+ exports.useErrorResponseForForm = useErrorResponseForForm;
55
+ const useFieldErrorsForForm = (context) => {
56
+ const response = useErrorResponseForForm(context);
57
+ const hydrated = (0, exports.useContextSelectAtom)(context.formId, state_1.isHydratedAtom);
58
+ return hydrated ? USE_HYDRATED_STATE : response === null || response === void 0 ? void 0 : response.fieldErrors;
59
+ };
60
+ exports.useFieldErrorsForForm = useFieldErrorsForForm;
61
+ const useDefaultValuesFromLoader = ({ formId, }) => {
62
+ const matches = (0, react_1.useMatches)();
63
+ if (typeof formId === "string") {
64
+ const dataKey = (0, constants_1.formDefaultValuesKey)(formId);
65
+ // If multiple loaders declare the same default values,
66
+ // we should use the data from the deepest route.
67
+ const match = matches
68
+ .reverse()
69
+ .find((match) => match.data && dataKey in match.data);
70
+ return match === null || match === void 0 ? void 0 : match.data[dataKey];
71
+ }
72
+ return null;
73
+ };
74
+ exports.useDefaultValuesFromLoader = useDefaultValuesFromLoader;
75
+ const useDefaultValuesForForm = (context) => {
76
+ const { formId, defaultValuesProp } = context;
77
+ const hydrated = (0, exports.useContextSelectAtom)(formId, state_1.isHydratedAtom);
78
+ const errorResponse = useErrorResponseForForm(context);
79
+ const defaultValuesFromLoader = (0, exports.useDefaultValuesFromLoader)(context);
80
+ // Typical flow is:
81
+ // - Default values only available from props or server
82
+ // - Props have a higher priority than server
83
+ // - State gets hydrated with default values
84
+ // - After submit, we may need to use values from the error
85
+ if (hydrated)
86
+ return USE_HYDRATED_STATE;
87
+ if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields)
88
+ return errorResponse.repopulateFields;
89
+ if (defaultValuesProp)
90
+ return defaultValuesProp;
91
+ return defaultValuesFromLoader;
92
+ };
93
+ exports.useDefaultValuesForForm = useDefaultValuesForForm;
94
+ const useHasActiveFormSubmit = ({ fetcher, }) => {
95
+ const transition = (0, react_1.useTransition)();
96
+ const hasActiveSubmission = fetcher
97
+ ? fetcher.state === "submitting"
98
+ : !!transition.submission;
99
+ return hasActiveSubmission;
100
+ };
101
+ exports.useHasActiveFormSubmit = useHasActiveFormSubmit;
102
+ const useFieldTouched = (name, { formId }) => {
103
+ const atomCreator = (0, react_2.useMemo)(() => (0, state_1.fieldTouchedAtom)(name), [name]);
104
+ return (0, exports.useContextSelectAtom)(formId, atomCreator);
105
+ };
106
+ exports.useFieldTouched = useFieldTouched;
107
+ const useFieldError = (name, context) => {
108
+ return (0, exports.useHydratableSelector)(context, (0, react_2.useMemo)(() => (0, state_1.fieldErrorAtom)(name), [name]), (0, exports.useFieldErrorsForForm)(context), (fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name]);
109
+ };
110
+ exports.useFieldError = useFieldError;
111
+ const useFieldDefaultValue = (name, context) => {
112
+ return (0, exports.useHydratableSelector)(context, (0, react_2.useMemo)(() => (0, state_1.fieldDefaultValueAtom)(name), [name]), (0, exports.useDefaultValuesForForm)(context), (val) => (0, get_1.default)(val, name));
113
+ };
114
+ exports.useFieldDefaultValue = useFieldDefaultValue;
115
+ const useFormUpdateAtom = (atom) => (0, utils_1.useUpdateAtom)(atom, state_1.ATOM_SCOPE);
116
+ exports.useFormUpdateAtom = useFormUpdateAtom;
117
+ const useClearError = (context) => {
118
+ const clearError = (0, exports.useFormUpdateAtom)(state_1.clearErrorAtom);
119
+ return (0, react_2.useCallback)((name) => {
120
+ clearError({ name, formAtom: (0, state_1.formRegistry)(context.formId) });
121
+ }, [clearError, context.formId]);
122
+ };
123
+ exports.useClearError = useClearError;
124
+ const useSetTouched = (context) => {
125
+ const setTouched = (0, exports.useFormUpdateAtom)(state_1.setTouchedAtom);
126
+ return (0, react_2.useCallback)((name, touched) => {
127
+ setTouched({ name, formAtom: (0, state_1.formRegistry)(context.formId), touched });
128
+ }, [setTouched, context.formId]);
129
+ };
130
+ exports.useSetTouched = useSetTouched;
@@ -0,0 +1,269 @@
1
+ import { FieldErrors, TouchedFields } from "../validation/types";
2
+ export declare const ATOM_SCOPE: unique symbol;
3
+ export declare type FormState = {
4
+ hydrated: boolean;
5
+ fieldErrors?: FieldErrors;
6
+ isSubmitting: boolean;
7
+ hasBeenSubmitted: boolean;
8
+ touchedFields: TouchedFields;
9
+ formId?: string;
10
+ action?: string;
11
+ subaction?: string;
12
+ defaultValues?: {
13
+ [fieldName: string]: any;
14
+ };
15
+ validateField: (fieldName: string) => Promise<string | null>;
16
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
17
+ };
18
+ export declare type FormAtom = ReturnType<typeof formRegistry>;
19
+ export declare type FieldState = {
20
+ touched: boolean;
21
+ defaultValue?: any;
22
+ error?: string;
23
+ };
24
+ export declare const formRegistry: {
25
+ (param: string | symbol): import("jotai").WritableAtom<FormState, FormState | ((draft: import("immer/dist/internal").WritableDraft<FormState>) => void), void>;
26
+ remove(param: string | symbol): void;
27
+ setShouldRemove(shouldRemove: ((createdAt: number, param: string | symbol) => boolean) | null): void;
28
+ };
29
+ export declare const fieldErrorAtom: (name: string) => (formAtom: FormAtom) => import("jotai").Atom<string | undefined>;
30
+ export declare const fieldTouchedAtom: (name: string) => (formAtom: FormAtom) => import("jotai").Atom<boolean>;
31
+ export declare const fieldDefaultValueAtom: (name: string) => (formAtom: FormAtom) => import("jotai").Atom<any>;
32
+ export declare const formSelectorAtom: <T>(selector: (state: FormState) => T) => (formAtom: FormAtom) => import("jotai").Atom<T>;
33
+ export declare const fieldErrorsAtom: (formAtom: FormAtom) => import("jotai").Atom<FieldErrors | undefined>;
34
+ export declare const touchedFieldsAtom: (formAtom: FormAtom) => import("jotai").Atom<TouchedFields>;
35
+ export declare const actionAtom: (formAtom: FormAtom) => import("jotai").Atom<string | undefined>;
36
+ export declare const hasBeenSubmittedAtom: (formAtom: FormAtom) => import("jotai").Atom<boolean>;
37
+ export declare const validateFieldAtom: (formAtom: FormAtom) => import("jotai").Atom<(fieldName: string) => Promise<string | null>>;
38
+ export declare const registerReceiveFocusAtom: (formAtom: FormAtom) => import("jotai").Atom<(fieldName: string, handler: () => void) => () => void>;
39
+ export declare const isSubmittingAtom: (formAtom: FormAtom) => import("jotai").Atom<boolean>;
40
+ export declare const defaultValuesAtom: (formAtom: FormAtom) => import("jotai").Atom<{
41
+ [fieldName: string]: any;
42
+ } | undefined>;
43
+ export declare const isValidAtom: (formAtom: FormAtom) => import("jotai").Atom<boolean>;
44
+ export declare const isHydratedAtom: (formAtom: FormAtom) => import("jotai").Atom<boolean>;
45
+ export declare type FieldAtomArgs = {
46
+ name: string;
47
+ formAtom: FormAtom;
48
+ };
49
+ export declare const clearErrorAtom: import("jotai").Atom<null> & {
50
+ write: (get: {
51
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
52
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
53
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
54
+ } & {
55
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
56
+ unstable_promise: true;
57
+ }): Value_3 | Promise<Value_3>;
58
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
59
+ unstable_promise: true;
60
+ }): Value_4 | Promise<Value_4>;
61
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
62
+ unstable_promise: true;
63
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
64
+ }, set: {
65
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
66
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
67
+ }, update: FieldAtomArgs) => void;
68
+ onMount?: (<S extends (update: FieldAtomArgs) => void>(setAtom: S) => void | (() => void)) | undefined;
69
+ } & {
70
+ init: null;
71
+ };
72
+ export declare const addErrorAtom: import("jotai").Atom<null> & {
73
+ write: (get: {
74
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
75
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
76
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
77
+ } & {
78
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
79
+ unstable_promise: true;
80
+ }): Value_3 | Promise<Value_3>;
81
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
82
+ unstable_promise: true;
83
+ }): Value_4 | Promise<Value_4>;
84
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
85
+ unstable_promise: true;
86
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
87
+ }, set: {
88
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
89
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
90
+ }, update: FieldAtomArgs & {
91
+ error: string;
92
+ }) => void;
93
+ onMount?: (<S extends (update: FieldAtomArgs & {
94
+ error: string;
95
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
96
+ } & {
97
+ init: null;
98
+ };
99
+ export declare const setFieldErrorsAtom: import("jotai").Atom<null> & {
100
+ write: (get: {
101
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
102
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
103
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
104
+ } & {
105
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
106
+ unstable_promise: true;
107
+ }): Value_3 | Promise<Value_3>;
108
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
109
+ unstable_promise: true;
110
+ }): Value_4 | Promise<Value_4>;
111
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
112
+ unstable_promise: true;
113
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
114
+ }, set: {
115
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
116
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
117
+ }, update: {
118
+ fieldErrors: FieldErrors;
119
+ formAtom: FormAtom;
120
+ }) => void;
121
+ onMount?: (<S extends (update: {
122
+ fieldErrors: FieldErrors;
123
+ formAtom: FormAtom;
124
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
125
+ } & {
126
+ init: null;
127
+ };
128
+ export declare const setTouchedAtom: import("jotai").Atom<null> & {
129
+ write: (get: {
130
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
131
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
132
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
133
+ } & {
134
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
135
+ unstable_promise: true;
136
+ }): Value_3 | Promise<Value_3>;
137
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
138
+ unstable_promise: true;
139
+ }): Value_4 | Promise<Value_4>;
140
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
141
+ unstable_promise: true;
142
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
143
+ }, set: {
144
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
145
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
146
+ }, update: FieldAtomArgs & {
147
+ touched: boolean;
148
+ }) => void;
149
+ onMount?: (<S extends (update: FieldAtomArgs & {
150
+ touched: boolean;
151
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
152
+ } & {
153
+ init: null;
154
+ };
155
+ export declare const resetAtom: import("jotai").Atom<null> & {
156
+ write: (get: {
157
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
158
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
159
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
160
+ } & {
161
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
162
+ unstable_promise: true;
163
+ }): Value_3 | Promise<Value_3>;
164
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
165
+ unstable_promise: true;
166
+ }): Value_4 | Promise<Value_4>;
167
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
168
+ unstable_promise: true;
169
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
170
+ }, set: {
171
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
172
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
173
+ }, update: {
174
+ formAtom: FormAtom;
175
+ }) => void;
176
+ onMount?: (<S extends (update: {
177
+ formAtom: FormAtom;
178
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
179
+ } & {
180
+ init: null;
181
+ };
182
+ export declare const startSubmitAtom: import("jotai").Atom<null> & {
183
+ write: (get: {
184
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
185
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
186
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
187
+ } & {
188
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
189
+ unstable_promise: true;
190
+ }): Value_3 | Promise<Value_3>;
191
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
192
+ unstable_promise: true;
193
+ }): Value_4 | Promise<Value_4>;
194
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
195
+ unstable_promise: true;
196
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
197
+ }, set: {
198
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
199
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
200
+ }, update: {
201
+ formAtom: FormAtom;
202
+ }) => void;
203
+ onMount?: (<S extends (update: {
204
+ formAtom: FormAtom;
205
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
206
+ } & {
207
+ init: null;
208
+ };
209
+ export declare const endSubmitAtom: import("jotai").Atom<null> & {
210
+ write: (get: {
211
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
212
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
213
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
214
+ } & {
215
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
216
+ unstable_promise: true;
217
+ }): Value_3 | Promise<Value_3>;
218
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
219
+ unstable_promise: true;
220
+ }): Value_4 | Promise<Value_4>;
221
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
222
+ unstable_promise: true;
223
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
224
+ }, set: {
225
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
226
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
227
+ }, update: {
228
+ formAtom: FormAtom;
229
+ }) => void;
230
+ onMount?: (<S extends (update: {
231
+ formAtom: FormAtom;
232
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
233
+ } & {
234
+ init: null;
235
+ };
236
+ declare type SyncFormContextArgs = {
237
+ defaultValues?: {
238
+ [fieldName: string]: any;
239
+ };
240
+ action?: string;
241
+ subaction?: string;
242
+ validateField: FormState["validateField"];
243
+ registerReceiveFocus: FormState["registerReceiveFocus"];
244
+ formAtom: FormAtom;
245
+ };
246
+ export declare const syncFormContextAtom: import("jotai").Atom<null> & {
247
+ write: (get: {
248
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
249
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
250
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
251
+ } & {
252
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
253
+ unstable_promise: true;
254
+ }): Value_3 | Promise<Value_3>;
255
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
256
+ unstable_promise: true;
257
+ }): Value_4 | Promise<Value_4>;
258
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
259
+ unstable_promise: true;
260
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
261
+ }, set: {
262
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
263
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
264
+ }, update: SyncFormContextArgs) => void;
265
+ onMount?: (<S extends (update: SyncFormContextArgs) => void>(setAtom: S) => void | (() => void)) | undefined;
266
+ } & {
267
+ init: null;
268
+ };
269
+ export {};
@@ -0,0 +1,92 @@
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.syncFormContextAtom = exports.endSubmitAtom = exports.startSubmitAtom = exports.resetAtom = exports.setTouchedAtom = exports.setFieldErrorsAtom = exports.addErrorAtom = exports.clearErrorAtom = exports.isHydratedAtom = exports.isValidAtom = exports.defaultValuesAtom = exports.isSubmittingAtom = exports.registerReceiveFocusAtom = exports.validateFieldAtom = exports.hasBeenSubmittedAtom = exports.actionAtom = exports.touchedFieldsAtom = exports.fieldErrorsAtom = exports.formSelectorAtom = exports.fieldDefaultValueAtom = exports.fieldTouchedAtom = exports.fieldErrorAtom = exports.formRegistry = exports.ATOM_SCOPE = void 0;
7
+ const jotai_1 = require("jotai");
8
+ const immer_1 = require("jotai/immer");
9
+ const utils_1 = require("jotai/utils");
10
+ const get_1 = __importDefault(require("lodash/get"));
11
+ exports.ATOM_SCOPE = Symbol("remix-validated-form-scope");
12
+ exports.formRegistry = (0, utils_1.atomFamily)((formId) => (0, immer_1.atomWithImmer)({
13
+ hydrated: false,
14
+ isSubmitting: false,
15
+ hasBeenSubmitted: false,
16
+ touchedFields: {},
17
+ // The symbol version is just to keep things straight with the `atomFamily`
18
+ formId: typeof formId === "string" ? formId : undefined,
19
+ // Will change upon hydration -- these will never actually be used
20
+ validateField: () => Promise.resolve(null),
21
+ registerReceiveFocus: () => () => { },
22
+ }));
23
+ const fieldErrorAtom = (name) => (formAtom) => (0, utils_1.selectAtom)(formAtom, (formState) => { var _a; return (_a = formState.fieldErrors) === null || _a === void 0 ? void 0 : _a[name]; });
24
+ exports.fieldErrorAtom = fieldErrorAtom;
25
+ const fieldTouchedAtom = (name) => (formAtom) => (0, utils_1.selectAtom)(formAtom, (formState) => formState.touchedFields[name]);
26
+ exports.fieldTouchedAtom = fieldTouchedAtom;
27
+ const fieldDefaultValueAtom = (name) => (formAtom) => (0, utils_1.selectAtom)(formAtom, (formState) => formState.defaultValues && (0, get_1.default)(formState.defaultValues, name));
28
+ exports.fieldDefaultValueAtom = fieldDefaultValueAtom;
29
+ // Selector atoms
30
+ const formSelectorAtom = (selector) => (formAtom) => (0, utils_1.selectAtom)(formAtom, selector);
31
+ exports.formSelectorAtom = formSelectorAtom;
32
+ exports.fieldErrorsAtom = (0, exports.formSelectorAtom)((state) => state.fieldErrors);
33
+ exports.touchedFieldsAtom = (0, exports.formSelectorAtom)((state) => state.touchedFields);
34
+ exports.actionAtom = (0, exports.formSelectorAtom)((state) => state.action);
35
+ exports.hasBeenSubmittedAtom = (0, exports.formSelectorAtom)((state) => state.hasBeenSubmitted);
36
+ exports.validateFieldAtom = (0, exports.formSelectorAtom)((state) => state.validateField);
37
+ exports.registerReceiveFocusAtom = (0, exports.formSelectorAtom)((state) => state.registerReceiveFocus);
38
+ exports.isSubmittingAtom = (0, exports.formSelectorAtom)((state) => state.isSubmitting);
39
+ exports.defaultValuesAtom = (0, exports.formSelectorAtom)((state) => state.defaultValues);
40
+ exports.isValidAtom = (0, exports.formSelectorAtom)((state) => { var _a; return Object.keys((_a = state.fieldErrors) !== null && _a !== void 0 ? _a : {}).length === 0; });
41
+ exports.isHydratedAtom = (0, exports.formSelectorAtom)((state) => state.hydrated);
42
+ exports.clearErrorAtom = (0, jotai_1.atom)(null, (get, set, { name, formAtom }) => set(formAtom, (state) => {
43
+ var _a;
44
+ (_a = state.fieldErrors) === null || _a === void 0 ? true : delete _a[name];
45
+ return state;
46
+ }));
47
+ exports.addErrorAtom = (0, jotai_1.atom)(null, (get, set, { name, formAtom, error }) => set(formAtom, (state) => {
48
+ if (!state.fieldErrors)
49
+ state.fieldErrors = {};
50
+ state.fieldErrors[name] = error;
51
+ return state;
52
+ }));
53
+ exports.setFieldErrorsAtom = (0, jotai_1.atom)(null, (get, set, { formAtom, fieldErrors }) => set(formAtom, (state) => {
54
+ state.fieldErrors = fieldErrors;
55
+ return state;
56
+ }));
57
+ exports.setTouchedAtom = (0, jotai_1.atom)(null, (get, set, { name, formAtom, touched }) => set(formAtom, (state) => {
58
+ state.touchedFields[name] = touched;
59
+ return state;
60
+ }));
61
+ exports.resetAtom = (0, jotai_1.atom)(null, (get, set, { formAtom }) => {
62
+ set(formAtom, (state) => {
63
+ state.fieldErrors = {};
64
+ state.touchedFields = {};
65
+ state.hasBeenSubmitted = false;
66
+ return state;
67
+ });
68
+ });
69
+ exports.startSubmitAtom = (0, jotai_1.atom)(null, (get, set, { formAtom }) => {
70
+ set(formAtom, (state) => {
71
+ state.hasBeenSubmitted = true;
72
+ state.isSubmitting = true;
73
+ return state;
74
+ });
75
+ });
76
+ exports.endSubmitAtom = (0, jotai_1.atom)(null, (get, set, { formAtom }) => {
77
+ set(formAtom, (state) => {
78
+ state.isSubmitting = false;
79
+ return state;
80
+ });
81
+ });
82
+ exports.syncFormContextAtom = (0, jotai_1.atom)(null, (get, set, { defaultValues, action, subaction, formAtom, validateField, registerReceiveFocus, }) => {
83
+ set(formAtom, (state) => {
84
+ state.defaultValues = defaultValues;
85
+ state.action = action;
86
+ state.subaction = subaction;
87
+ state.registerReceiveFocus = registerReceiveFocus;
88
+ state.validateField = validateField;
89
+ state.hydrated = true;
90
+ return state;
91
+ });
92
+ });
@@ -1,3 +1,4 @@
1
1
  import type React from "react";
2
2
  export declare const omit: (obj: any, ...keys: string[]) => any;
3
3
  export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
4
+ export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mergeRefs = exports.omit = void 0;
3
+ exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
4
+ const react_1 = require("react");
4
5
  const omit = (obj, ...keys) => {
5
6
  const result = { ...obj };
6
7
  for (const key of keys) {
@@ -22,3 +23,4 @@ const mergeRefs = (refs) => {
22
23
  };
23
24
  };
24
25
  exports.mergeRefs = mergeRefs;
26
+ exports.useIsomorphicLayoutEffect = typeof window !== "undefined" ? react_1.useLayoutEffect : react_1.useEffect;
package/build/server.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { FORM_DEFAULTS_FIELD } from "./internal/constants";
1
2
  import { ValidatorError } from "./validation/types";
2
3
  /**
3
4
  * Takes the errors from a `Validator` and returns a `Response`.
@@ -14,3 +15,7 @@ import { ValidatorError } from "./validation/types";
14
15
  * ```
15
16
  */
16
17
  export declare function validationError(error: ValidatorError, repopulateFields?: unknown): Response;
18
+ export declare type FormDefaults = {
19
+ [formDefaultsKey: `${typeof FORM_DEFAULTS_FIELD}_${string}`]: any;
20
+ };
21
+ export declare const setFormDefaults: <DataType = any>(formId: string, defaultValues: Partial<DataType>) => FormDefaults;
package/build/server.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validationError = void 0;
3
+ exports.setFormDefaults = exports.validationError = void 0;
4
4
  const server_runtime_1 = require("@remix-run/server-runtime");
5
+ const constants_1 = require("./internal/constants");
5
6
  /**
6
7
  * Takes the errors from a `Validator` and returns a `Response`.
7
8
  * When you return this from your action, `ValidatedForm` on the frontend will automatically
@@ -21,6 +22,11 @@ function validationError(error, repopulateFields) {
21
22
  fieldErrors: error.fieldErrors,
22
23
  subaction: error.subaction,
23
24
  repopulateFields,
25
+ formId: error.formId,
24
26
  }, { status: 422 });
25
27
  }
26
28
  exports.validationError = validationError;
29
+ const setFormDefaults = (formId, defaultValues) => ({
30
+ [(0, constants_1.formDefaultValuesKey)(formId)]: defaultValues,
31
+ });
32
+ exports.setFormDefaults = setFormDefaults;
@@ -0,0 +1,56 @@
1
+ import { FieldErrors, TouchedFields } from "./validation/types";
2
+ export declare type FormContextValue = {
3
+ /**
4
+ * All the errors in all the fields in the form.
5
+ */
6
+ fieldErrors: FieldErrors;
7
+ /**
8
+ * Clear the errors of the specified fields.
9
+ */
10
+ clearError: (...names: string[]) => void;
11
+ /**
12
+ * Validate the specified field.
13
+ */
14
+ validateField: (fieldName: string) => Promise<string | null>;
15
+ /**
16
+ * The `action` prop of the form.
17
+ */
18
+ action?: string;
19
+ /**
20
+ * Whether or not the form is submitting.
21
+ */
22
+ isSubmitting: boolean;
23
+ /**
24
+ * Whether or not a submission has been attempted.
25
+ * This is true once the form has been submitted, even if there were validation errors.
26
+ * Resets to false when the form is reset.
27
+ */
28
+ hasBeenSubmitted: boolean;
29
+ /**
30
+ * Whether or not the form is valid.
31
+ */
32
+ isValid: boolean;
33
+ /**
34
+ * The default values of the form.
35
+ */
36
+ defaultValues?: {
37
+ [fieldName: string]: any;
38
+ };
39
+ /**
40
+ * Register a custom focus handler to be used when
41
+ * the field needs to receive focus due to a validation error.
42
+ */
43
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
44
+ /**
45
+ * Any fields that have been touched by the user.
46
+ */
47
+ touchedFields: TouchedFields;
48
+ /**
49
+ * Change the touched state of the specified field.
50
+ */
51
+ setFieldTouched: (fieldName: string, touched: boolean) => void;
52
+ };
53
+ /**
54
+ * Provides access to some of the internal state of the form.
55
+ */
56
+ export declare const useFormContext: (formId?: string | undefined) => FormContextValue;