remix-validated-form 4.1.0 → 4.1.4-beta.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.
Files changed (73) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/browser/ValidatedForm.d.ts +1 -1
  3. package/browser/ValidatedForm.js +12 -6
  4. package/browser/internal/MultiValueMap.js +3 -3
  5. package/browser/internal/hooks-valtio.d.ts +18 -0
  6. package/browser/internal/hooks-valtio.js +110 -0
  7. package/browser/internal/hooks-zustand.d.ts +16 -0
  8. package/browser/internal/hooks-zustand.js +100 -0
  9. package/browser/internal/hydratable.d.ts +14 -0
  10. package/browser/internal/hydratable.js +14 -0
  11. package/browser/internal/immerMiddleware.d.ts +6 -0
  12. package/browser/internal/immerMiddleware.js +7 -0
  13. package/browser/internal/logic/getCheckboxChecked copy.d.ts +1 -0
  14. package/browser/internal/logic/getCheckboxChecked copy.js +9 -0
  15. package/browser/internal/logic/getCheckboxChecked.d.ts +1 -0
  16. package/browser/internal/logic/getCheckboxChecked.js +9 -0
  17. package/browser/internal/logic/getRadioChecked.d.ts +1 -0
  18. package/browser/internal/logic/getRadioChecked.js +5 -0
  19. package/browser/internal/logic/setFieldValue.d.ts +1 -0
  20. package/browser/internal/logic/setFieldValue.js +40 -0
  21. package/browser/internal/logic/setInputValueInForm.d.ts +1 -0
  22. package/browser/internal/logic/setInputValueInForm.js +120 -0
  23. package/browser/internal/setFieldValue.d.ts +20 -0
  24. package/browser/internal/setFieldValue.js +83 -0
  25. package/browser/internal/setFormValues.d.ts +2 -0
  26. package/browser/internal/setFormValues.js +26 -0
  27. package/browser/internal/state/atomUtils.d.ts +38 -0
  28. package/browser/internal/state/atomUtils.js +5 -0
  29. package/browser/internal/state/controlledFields.d.ts +66 -0
  30. package/browser/internal/state/controlledFields.js +93 -0
  31. package/browser/internal/state/setFieldValue.d.ts +0 -0
  32. package/browser/internal/state/setFieldValue.js +1 -0
  33. package/browser/internal/state-valtio.d.ts +62 -0
  34. package/browser/internal/state-valtio.js +69 -0
  35. package/browser/internal/state-zustand.d.ts +47 -0
  36. package/browser/internal/state-zustand.js +85 -0
  37. package/browser/internal/util.d.ts +1 -0
  38. package/browser/internal/util.js +12 -1
  39. package/build/ValidatedForm.d.ts +1 -1
  40. package/build/ValidatedForm.js +11 -5
  41. package/build/internal/MultiValueMap.js +2 -2
  42. package/build/internal/hooks-valtio.d.ts +18 -0
  43. package/build/internal/hooks-valtio.js +128 -0
  44. package/build/internal/hooks-zustand.d.ts +16 -0
  45. package/build/internal/hooks-zustand.js +117 -0
  46. package/build/internal/hydratable.d.ts +14 -0
  47. package/build/internal/hydratable.js +17 -0
  48. package/build/internal/immerMiddleware.d.ts +6 -0
  49. package/build/internal/immerMiddleware.js +14 -0
  50. package/build/internal/logic/getCheckboxChecked.d.ts +1 -0
  51. package/build/internal/logic/getCheckboxChecked.js +13 -0
  52. package/build/internal/logic/getRadioChecked.d.ts +1 -0
  53. package/build/internal/logic/getRadioChecked.js +9 -0
  54. package/build/internal/logic/setFieldValue.d.ts +1 -0
  55. package/build/internal/logic/setFieldValue.js +47 -0
  56. package/build/internal/logic/setInputValueInForm.d.ts +1 -0
  57. package/build/internal/logic/setInputValueInForm.js +127 -0
  58. package/build/internal/setFormValues.d.ts +2 -0
  59. package/build/internal/setFormValues.js +33 -0
  60. package/build/internal/state/atomUtils.d.ts +38 -0
  61. package/build/internal/state/atomUtils.js +13 -0
  62. package/build/internal/state/controlledFields.d.ts +66 -0
  63. package/build/internal/state/controlledFields.js +95 -0
  64. package/build/internal/state-valtio.d.ts +62 -0
  65. package/build/internal/state-valtio.js +83 -0
  66. package/build/internal/state-zustand.d.ts +47 -0
  67. package/build/internal/state-zustand.js +91 -0
  68. package/build/internal/util.d.ts +1 -0
  69. package/build/internal/util.js +16 -1
  70. package/package.json +5 -5
  71. package/src/ValidatedForm.tsx +13 -5
  72. package/src/internal/MultiValueMap.ts +3 -3
  73. package/src/internal/util.ts +13 -1
@@ -124,10 +124,11 @@ const useFormAtom = (formId) => {
124
124
  /**
125
125
  * The primary form component of `remix-validated-form`.
126
126
  */
127
- function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
127
+ function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
128
128
  var _a;
129
129
  const formId = useFormId(id);
130
130
  const formAtom = useFormAtom(formId);
131
+ const providedDefaultValues = (0, util_1.useDeepEqualsMemo)(unMemoizedDefaults);
131
132
  const contextValue = (0, react_2.useMemo)(() => ({
132
133
  formId,
133
134
  action,
@@ -211,9 +212,9 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
211
212
  clickedButtonRef.current = submitButton;
212
213
  }
213
214
  }
214
- window.addEventListener("click", handleClick);
215
+ window.addEventListener("click", handleClick, { capture: true });
215
216
  return () => {
216
- window.removeEventListener("click", handleClick);
217
+ window.removeEventListener("click", handleClick, { capture: true });
217
218
  };
218
219
  }, []);
219
220
  const handleSubmit = async (e) => {
@@ -233,10 +234,15 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
233
234
  endSubmit({ formAtom });
234
235
  return;
235
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.
236
242
  if (fetcher)
237
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
243
+ fetcher.submit(clickedButtonRef.current || formRef.current);
238
244
  else
239
- submit(clickedButtonRef.current || e.currentTarget, {
245
+ submit(clickedButtonRef.current || formRef.current, {
240
246
  method,
241
247
  replace,
242
248
  });
@@ -34,11 +34,11 @@ class MultiValueMap {
34
34
  exports.MultiValueMap = MultiValueMap;
35
35
  const useMultiValueMap = () => {
36
36
  const ref = (0, react_1.useRef)(null);
37
- return () => {
37
+ return (0, react_1.useCallback)(() => {
38
38
  if (ref.current)
39
39
  return ref.current;
40
40
  ref.current = new MultiValueMap();
41
41
  return ref.current;
42
- };
42
+ }, []);
43
43
  };
44
44
  exports.useMultiValueMap = useMultiValueMap;
@@ -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;
@@ -0,0 +1,117 @@
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.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 get_1 = __importDefault(require("lodash/get"));
9
+ const react_2 = require("react");
10
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
11
+ const constants_1 = require("./constants");
12
+ const formContext_1 = require("./formContext");
13
+ const hydratable_1 = require("./hydratable");
14
+ const state_zustand_1 = require("./state-zustand");
15
+ const useInternalFormContext = (formId, hookName) => {
16
+ const formContext = (0, react_2.useContext)(formContext_1.InternalFormContext);
17
+ if (formId)
18
+ return { formId };
19
+ if (formContext)
20
+ return formContext;
21
+ throw new Error(`Unable to determine form for ${hookName}. Please use it inside a form or pass a 'formId'.`);
22
+ };
23
+ exports.useInternalFormContext = useInternalFormContext;
24
+ function useErrorResponseForForm({ fetcher, subaction, formId, }) {
25
+ var _a;
26
+ const actionData = (0, react_1.useActionData)();
27
+ if (fetcher) {
28
+ if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
29
+ return fetcher.data;
30
+ return null;
31
+ }
32
+ if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
33
+ return null;
34
+ // If there's an explicit id, we should ignore data that has the wrong id
35
+ if (typeof formId === "string" && actionData.formId)
36
+ return actionData.formId === formId ? actionData : null;
37
+ if ((!subaction && !actionData.subaction) ||
38
+ actionData.subaction === subaction)
39
+ return actionData;
40
+ return null;
41
+ }
42
+ exports.useErrorResponseForForm = useErrorResponseForForm;
43
+ const useFieldErrorsForForm = (context) => {
44
+ const response = useErrorResponseForForm(context);
45
+ const hydrated = (0, state_zustand_1.useStore)((state) => state.form(context.formId).hydrated);
46
+ return hydratable_1.hydratable.from(response === null || response === void 0 ? void 0 : response.fieldErrors, hydrated);
47
+ };
48
+ exports.useFieldErrorsForForm = useFieldErrorsForForm;
49
+ const useDefaultValuesFromLoader = ({ formId, }) => {
50
+ const matches = (0, react_1.useMatches)();
51
+ if (typeof formId === "string") {
52
+ const dataKey = (0, constants_1.formDefaultValuesKey)(formId);
53
+ // If multiple loaders declare the same default values,
54
+ // we should use the data from the deepest route.
55
+ const match = matches
56
+ .reverse()
57
+ .find((match) => match.data && dataKey in match.data);
58
+ return match === null || match === void 0 ? void 0 : match.data[dataKey];
59
+ }
60
+ return null;
61
+ };
62
+ exports.useDefaultValuesFromLoader = useDefaultValuesFromLoader;
63
+ const useDefaultValuesForForm = (context) => {
64
+ const { formId, defaultValuesProp } = context;
65
+ const hydrated = (0, state_zustand_1.useStore)((state) => state.form(formId).hydrated);
66
+ const errorResponse = useErrorResponseForForm(context);
67
+ const defaultValuesFromLoader = (0, exports.useDefaultValuesFromLoader)(context);
68
+ // Typical flow is:
69
+ // - Default values only available from props or server
70
+ // - Props have a higher priority than server
71
+ // - State gets hydrated with default values
72
+ // - After submit, we may need to use values from the error
73
+ if (hydrated)
74
+ return hydratable_1.hydratable.hydratedData();
75
+ if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields) {
76
+ (0, tiny_invariant_1.default)(typeof errorResponse.repopulateFields === "object", "repopulateFields returned something other than an object");
77
+ return hydratable_1.hydratable.serverData(errorResponse.repopulateFields);
78
+ }
79
+ if (defaultValuesProp)
80
+ return hydratable_1.hydratable.serverData(defaultValuesProp);
81
+ return hydratable_1.hydratable.serverData(defaultValuesFromLoader);
82
+ };
83
+ exports.useDefaultValuesForForm = useDefaultValuesForForm;
84
+ const useHasActiveFormSubmit = ({ fetcher, }) => {
85
+ const transition = (0, react_1.useTransition)();
86
+ const hasActiveSubmission = fetcher
87
+ ? fetcher.state === "submitting"
88
+ : !!transition.submission;
89
+ return hasActiveSubmission;
90
+ };
91
+ exports.useHasActiveFormSubmit = useHasActiveFormSubmit;
92
+ const useFieldTouched = (name, { formId }) => {
93
+ return (0, state_zustand_1.useStore)((state) => state.form(formId).touchedFields[name]);
94
+ };
95
+ exports.useFieldTouched = useFieldTouched;
96
+ const useFieldError = (name, context) => {
97
+ const state = (0, state_zustand_1.useStore)((state) => state.form(context.formId).fieldErrors[name]);
98
+ return (0, exports.useFieldErrorsForForm)(context)
99
+ .map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name])
100
+ .hydrateTo(state);
101
+ };
102
+ exports.useFieldError = useFieldError;
103
+ const useFieldDefaultValue = (name, context) => {
104
+ const state = (0, state_zustand_1.useStore)((state) => state.form(context.formId).defaultValues[name]);
105
+ return (0, exports.useDefaultValuesForForm)(context)
106
+ .map((val) => (0, get_1.default)(val, name))
107
+ .hydrateTo(state);
108
+ };
109
+ exports.useFieldDefaultValue = useFieldDefaultValue;
110
+ const useClearError = (context) => {
111
+ return (0, state_zustand_1.useStore)((state) => state.helpers(context.formId).clearError);
112
+ };
113
+ exports.useClearError = useClearError;
114
+ const useSetTouched = (context) => {
115
+ return (0, state_zustand_1.useStore)((state) => state.helpers(context.formId).setTouched);
116
+ };
117
+ exports.useSetTouched = useSetTouched;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The purpose of this type is to simplify the logic
3
+ * around data that needs to come from the server initially,
4
+ * but from the internal state after hydration.
5
+ */
6
+ export declare type Hydratable<T> = {
7
+ hydrateTo: (data: T) => T;
8
+ map: <U>(fn: (data: T) => U) => Hydratable<U>;
9
+ };
10
+ export declare const hydratable: {
11
+ serverData: <T>(data: T) => Hydratable<T>;
12
+ hydratedData: <T_1>() => Hydratable<T_1>;
13
+ from: <T_2>(data: T_2, hydrated: boolean) => Hydratable<T_2>;
14
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hydratable = void 0;
4
+ const serverData = (data) => ({
5
+ hydrateTo: () => data,
6
+ map: (fn) => serverData(fn(data)),
7
+ });
8
+ const hydratedData = () => ({
9
+ hydrateTo: (hydratedData) => hydratedData,
10
+ map: () => hydratedData(),
11
+ });
12
+ const from = (data, hydrated) => hydrated ? hydratedData() : serverData(data);
13
+ exports.hydratable = {
14
+ serverData,
15
+ hydratedData,
16
+ from,
17
+ };
@@ -0,0 +1,6 @@
1
+ import { Draft } from "immer";
2
+ import { State, StateCreator } from "zustand";
3
+ declare type TImmerConfigFn<T extends State> = (partial: ((draft: Draft<T>) => void) | T, replace?: boolean) => void;
4
+ declare type TImmerConfig<T extends State> = StateCreator<T, TImmerConfigFn<T>>;
5
+ export declare const immer: <T extends object>(config: TImmerConfig<T>) => StateCreator<T, import("zustand").SetState<T>, import("zustand").GetState<T>, import("zustand").StoreApi<T>>;
6
+ export {};
@@ -0,0 +1,14 @@
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.immer = void 0;
7
+ const immer_1 = __importDefault(require("immer"));
8
+ const immer = (config) => (set, get, api) => config((partial, replace) => {
9
+ const nextState = typeof partial === "function"
10
+ ? (0, immer_1.default)(partial)
11
+ : partial;
12
+ return set(nextState, replace);
13
+ }, get, api);
14
+ exports.immer = immer;
@@ -0,0 +1 @@
1
+ export declare const getCheckboxChecked: (checkboxValue: string | undefined, newValue: unknown) => boolean | undefined;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCheckboxChecked = void 0;
4
+ const getCheckboxChecked = (checkboxValue = "on", newValue) => {
5
+ if (Array.isArray(newValue))
6
+ return newValue.some((val) => val === true || val === checkboxValue);
7
+ if (typeof newValue === "boolean")
8
+ return newValue;
9
+ if (typeof newValue === "string")
10
+ return newValue === checkboxValue;
11
+ return undefined;
12
+ };
13
+ exports.getCheckboxChecked = getCheckboxChecked;
@@ -0,0 +1 @@
1
+ export declare const getRadioChecked: (radioValue: string | undefined, newValue: unknown) => boolean | undefined;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRadioChecked = void 0;
4
+ const getRadioChecked = (radioValue = "on", newValue) => {
5
+ if (typeof newValue === "string")
6
+ return newValue === radioValue;
7
+ return undefined;
8
+ };
9
+ exports.getRadioChecked = getRadioChecked;
@@ -0,0 +1 @@
1
+ export declare const setFieldValue: (formElement: HTMLFormElement, name: string, value: unknown) => void;
@@ -0,0 +1,47 @@
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.setFieldValue = void 0;
7
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
8
+ const getCheckboxChecked_1 = require("./getCheckboxChecked");
9
+ const getRadioChecked_1 = require("./getRadioChecked");
10
+ const setElementValue = (element, value, name) => {
11
+ if (element instanceof HTMLSelectElement && element.multiple) {
12
+ (0, tiny_invariant_1.default)(Array.isArray(value), "Must specify an array to set the value for a multi-select");
13
+ for (const option of element.options) {
14
+ option.selected = value.includes(option.value);
15
+ }
16
+ return;
17
+ }
18
+ if (element instanceof HTMLInputElement && element.type === "checkbox") {
19
+ const newChecked = (0, getCheckboxChecked_1.getCheckboxChecked)(element.value, value);
20
+ (0, tiny_invariant_1.default)(newChecked !== undefined, `Unable to determine if checkbox should be checked. Provided value was ${value} for checkbox ${name}.`);
21
+ element.checked = newChecked;
22
+ return;
23
+ }
24
+ if (element instanceof HTMLInputElement && element.type === "radio") {
25
+ const newChecked = (0, getRadioChecked_1.getRadioChecked)(element.value, value);
26
+ (0, tiny_invariant_1.default)(newChecked !== undefined, `Unable to determine if radio should be checked. Provided value was ${value} for radio ${name}.`);
27
+ element.checked = newChecked;
28
+ return;
29
+ }
30
+ (0, tiny_invariant_1.default)(typeof value === "string", `Invalid value for field "${name}" which is an ${element.constructor.name}. Expected string but received ${typeof value}`);
31
+ const input = element;
32
+ input.value = value;
33
+ };
34
+ const setFieldValue = (formElement, name, value) => {
35
+ const controlElement = formElement.elements.namedItem(name);
36
+ if (!controlElement)
37
+ return;
38
+ if (controlElement instanceof RadioNodeList) {
39
+ for (const element of controlElement) {
40
+ setElementValue(element, value, name);
41
+ }
42
+ }
43
+ else {
44
+ setElementValue(controlElement, value, name);
45
+ }
46
+ };
47
+ exports.setFieldValue = setFieldValue;
@@ -0,0 +1 @@
1
+ export declare const setInputValueInForm: (formElement: HTMLFormElement, name: string, value: unknown[]) => void;
@@ -0,0 +1,127 @@
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.setInputValueInForm = void 0;
7
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
8
+ const getCheckboxChecked_1 = require("./getCheckboxChecked");
9
+ /**
10
+ * Helper class to track the values being set on uncontrolled fields.
11
+ * HTML is super permissive with inputs that all share the same `name`.
12
+ *
13
+ * This class is strict in the sense that, if the user provides an array value,
14
+ * the values inside the array must be in the same order as the elements in the DOM.
15
+ * Doing this allows us to be flexible with what types of form controls the user is using.
16
+ *
17
+ * This is how HTML tracks inputs of the same name as well.
18
+ * `new FormData(formElement).getAll('myField')` will return values in DOM order.
19
+ */
20
+ class Values {
21
+ constructor(values) {
22
+ this.hasSetRadioValue = false;
23
+ this.bool = (value) => {
24
+ if ((0, getCheckboxChecked_1.getCheckboxChecked)(value, this.values[0])) {
25
+ this.values.shift();
26
+ return true;
27
+ }
28
+ return false;
29
+ };
30
+ this.radio = (value) => {
31
+ if (this.hasSetRadioValue)
32
+ return false;
33
+ const result = this.bool(value);
34
+ if (result)
35
+ this.hasSetRadioValue = true;
36
+ return result;
37
+ };
38
+ this.str = () => { var _a; return String((_a = this.values.pop()) !== null && _a !== void 0 ? _a : ""); };
39
+ this.allValues = () => this.values;
40
+ this.warnIfLeftovers = (field) => {
41
+ if (this.values.length > 0) {
42
+ console.warn(`Could not determine how to use the value for the field ${field}. ` +
43
+ `Leftover values were: ${this.values.join(", ")}.`);
44
+ }
45
+ };
46
+ const unknownValues = Array.isArray(values) ? values : [values];
47
+ this.values = unknownValues;
48
+ }
49
+ }
50
+ /**
51
+ * This subclass is order-permissive, meaning the user doesn't have to worry about
52
+ * the order in which the inputs occur in the DOM.
53
+ * This is useful for multiselects and checkbox groups and provides a better DX than
54
+ * the order-strict version.
55
+ */
56
+ class PermissiveValues extends Values {
57
+ constructor() {
58
+ super(...arguments);
59
+ this.remove = (value) => {
60
+ const index = this.values.indexOf(value);
61
+ const deleted = this.values.splice(index, 1);
62
+ return deleted.length > 0;
63
+ };
64
+ this.bool = (value) => {
65
+ if ((0, getCheckboxChecked_1.getCheckboxChecked)(value, this.values)) {
66
+ this.remove(value) || this.remove(true);
67
+ return true;
68
+ }
69
+ return false;
70
+ };
71
+ }
72
+ }
73
+ const isMultiselect = (node) => node instanceof HTMLSelectElement && node.multiple;
74
+ const isCheckbox = (node) => node instanceof HTMLInputElement && node.type === "checkbox";
75
+ const isRadio = (node) => node instanceof HTMLInputElement && node.type === "radio";
76
+ const setElementValue = (element, values, field) => {
77
+ if (isMultiselect(element)) {
78
+ for (const option of element.options) {
79
+ option.selected = values.bool(option.value);
80
+ }
81
+ return;
82
+ }
83
+ if (isCheckbox(element)) {
84
+ element.checked = values.bool(element.value);
85
+ return;
86
+ }
87
+ if (isRadio(element)) {
88
+ element.checked = values.radio(element.value);
89
+ return;
90
+ }
91
+ const input = element;
92
+ (0, tiny_invariant_1.default)(input.type !== "hidden", `Cannot set value on hidden input if it is not a controlled field. Field being updated was ${field}.`);
93
+ input.value = values.str();
94
+ };
95
+ const areElementsTheSameType = (nodes) => {
96
+ const getType = (node) => {
97
+ if (node instanceof HTMLInputElement)
98
+ return node.type;
99
+ if (node instanceof HTMLSelectElement)
100
+ return node.multiple ? "select" : "multiselect";
101
+ return null;
102
+ };
103
+ const firstElementInstance = nodes[0].constructor;
104
+ const firstElementType = getType(nodes[0]);
105
+ return nodes.every((element) => element.constructor === firstElementInstance &&
106
+ getType(element) === firstElementType);
107
+ };
108
+ const setInputValueInForm = (formElement, name, value) => {
109
+ const controlElement = formElement.elements.namedItem(name);
110
+ if (!controlElement)
111
+ return;
112
+ if (controlElement instanceof RadioNodeList) {
113
+ const values = areElementsTheSameType([...controlElement])
114
+ ? new PermissiveValues(value)
115
+ : new Values(value);
116
+ for (const element of controlElement) {
117
+ setElementValue(element, values, name);
118
+ }
119
+ values.warnIfLeftovers(name);
120
+ }
121
+ else {
122
+ const values = new PermissiveValues(value);
123
+ setElementValue(controlElement, values, name);
124
+ values.warnIfLeftovers(name);
125
+ }
126
+ };
127
+ exports.setInputValueInForm = setInputValueInForm;
@@ -0,0 +1,2 @@
1
+ import { InternalFormId } from "./state/atomUtils";
2
+ export declare const useSetFormValues: (formId: InternalFormId) => (arg: Record<string, unknown>) => Promise<void>;
@@ -0,0 +1,33 @@
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.useSetFormValues = void 0;
7
+ const utils_1 = require("jotai/utils");
8
+ const react_1 = require("react");
9
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
10
+ const setInputValueInForm_1 = require("./logic/setInputValueInForm");
11
+ const state_1 = require("./state");
12
+ const controlledFields_1 = require("./state/controlledFields");
13
+ const useSetFormValues = (formId) => (0, utils_1.useAtomCallback)((0, react_1.useCallback)(async (get, set, updatedValues) => {
14
+ const form = get((0, state_1.formElementAtom)(formId));
15
+ (0, tiny_invariant_1.default)(form, "Unable to access form element when setting field value. This is likely a bug in remix-validated-form.");
16
+ const controlledFields = get((0, controlledFields_1.controlledFieldsAtom)(formId));
17
+ const updatePromises = [];
18
+ for (const [field, value] of Object.entries(updatedValues)) {
19
+ const isControlled = !!controlledFields[field];
20
+ if (isControlled) {
21
+ updatePromises.push(set(controlledFields_1.setControlledFieldValueAtom, {
22
+ field,
23
+ formId,
24
+ value,
25
+ }));
26
+ }
27
+ else {
28
+ (0, setInputValueInForm_1.setInputValueInForm)(form, field, Array.isArray(value) ? value : [value]);
29
+ }
30
+ }
31
+ await Promise.all(updatePromises);
32
+ }, [formId]), state_1.ATOM_SCOPE);
33
+ exports.useSetFormValues = useSetFormValues;