remix-validated-form 4.1.4-beta.0 → 4.1.6

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 (52) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/browser/ValidatedForm.js +31 -36
  3. package/browser/hooks.js +13 -16
  4. package/browser/internal/customState.d.ts +105 -0
  5. package/browser/internal/customState.js +46 -0
  6. package/browser/internal/getInputProps.js +4 -14
  7. package/browser/internal/hooks.d.ts +14 -15
  8. package/browser/internal/hooks.js +37 -39
  9. package/browser/internal/logic/elementUtils.d.ts +3 -0
  10. package/browser/internal/logic/elementUtils.js +3 -0
  11. package/browser/internal/logic/setInputValueInForm.js +9 -52
  12. package/browser/internal/setFieldValue.d.ts +0 -0
  13. package/browser/internal/setFieldValue.js +0 -0
  14. package/browser/internal/setFormValues.d.ts +0 -0
  15. package/browser/internal/setFormValues.js +0 -0
  16. package/browser/internal/state.d.ts +339 -238
  17. package/browser/internal/state.js +59 -72
  18. package/browser/internal/watch.d.ts +18 -0
  19. package/browser/internal/watch.js +122 -0
  20. package/browser/unreleased/formStateHooks.d.ts +39 -0
  21. package/browser/unreleased/formStateHooks.js +54 -0
  22. package/browser/userFacingFormContext.js +9 -23
  23. package/build/ValidatedForm.js +30 -35
  24. package/build/hooks.js +11 -14
  25. package/build/internal/getInputProps.js +4 -14
  26. package/build/internal/hooks.d.ts +14 -15
  27. package/build/internal/hooks.js +39 -41
  28. package/build/internal/logic/elementUtils.d.ts +3 -0
  29. package/build/internal/logic/elementUtils.js +9 -0
  30. package/build/internal/logic/setInputValueInForm.js +12 -55
  31. package/build/internal/setFormValues.d.ts +0 -0
  32. package/build/internal/setFormValues.js +0 -0
  33. package/build/internal/state/controlledFields.js +11 -2
  34. package/build/internal/state.d.ts +339 -238
  35. package/build/internal/state.js +61 -77
  36. package/build/internal/watch.d.ts +20 -0
  37. package/build/internal/watch.js +126 -0
  38. package/build/unreleased/formStateHooks.d.ts +39 -0
  39. package/build/unreleased/formStateHooks.js +59 -0
  40. package/build/userFacingFormContext.js +9 -23
  41. package/package.json +1 -2
  42. package/src/ValidatedForm.tsx +48 -52
  43. package/src/hooks.ts +15 -26
  44. package/src/internal/getInputProps.ts +4 -14
  45. package/src/internal/hooks.ts +60 -72
  46. package/src/internal/hydratable.ts +28 -0
  47. package/src/internal/logic/getCheckboxChecked.ts +10 -0
  48. package/src/internal/logic/getRadioChecked.ts +7 -0
  49. package/src/internal/state/atomUtils.ts +13 -0
  50. package/src/internal/state.ts +99 -177
  51. package/src/unreleased/formStateHooks.ts +113 -0
  52. package/src/userFacingFormContext.ts +14 -53
@@ -5,27 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createGetInputProps = void 0;
7
7
  const omitBy_1 = __importDefault(require("lodash/omitBy"));
8
+ const getCheckboxChecked_1 = require("./logic/getCheckboxChecked");
9
+ const getRadioChecked_1 = require("./logic/getRadioChecked");
8
10
  const defaultValidationBehavior = {
9
11
  initial: "onBlur",
10
12
  whenTouched: "onChange",
11
13
  whenSubmitted: "onChange",
12
14
  };
13
- const getCheckboxDefaultChecked = (value, defaultValue) => {
14
- if (Array.isArray(defaultValue))
15
- return defaultValue.includes(value);
16
- if (typeof defaultValue === "boolean")
17
- return defaultValue;
18
- if (typeof defaultValue === "string")
19
- return defaultValue === value;
20
- return undefined;
21
- };
22
15
  const createGetInputProps = ({ clearError, validate, defaultValue, touched, setTouched, hasBeenSubmitted, validationBehavior, name, }) => {
23
16
  const validationBehaviors = {
24
17
  ...defaultValidationBehavior,
25
18
  ...validationBehavior,
26
19
  };
27
20
  return (props = {}) => {
28
- var _a, _b;
29
21
  const behavior = hasBeenSubmitted
30
22
  ? validationBehaviors.whenSubmitted
31
23
  : touched
@@ -51,12 +43,10 @@ const createGetInputProps = ({ clearError, validate, defaultValue, touched, setT
51
43
  name,
52
44
  };
53
45
  if (props.type === "checkbox") {
54
- const value = (_a = props.value) !== null && _a !== void 0 ? _a : "on";
55
- inputProps.defaultChecked = getCheckboxDefaultChecked(value, defaultValue);
46
+ inputProps.defaultChecked = (0, getCheckboxChecked_1.getCheckboxChecked)(props.value, defaultValue);
56
47
  }
57
48
  else if (props.type === "radio") {
58
- const value = (_b = props.value) !== null && _b !== void 0 ? _b : "on";
59
- inputProps.defaultChecked = defaultValue === value;
49
+ inputProps.defaultChecked = (0, getRadioChecked_1.getRadioChecked)(props.value, defaultValue);
60
50
  }
61
51
  else {
62
52
  inputProps.defaultValue = defaultValue;
@@ -1,22 +1,21 @@
1
- import { Atom } from "jotai";
1
+ import { Atom, WritableAtom } from "jotai";
2
2
  import { useUpdateAtom } from "jotai/utils";
3
- import { ValidationErrorResponseData } from "..";
3
+ import { FieldErrors, ValidationErrorResponseData } from "..";
4
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;
5
+ import { Hydratable } from "./hydratable";
6
+ export declare const useFormUpdateAtom: typeof useUpdateAtom;
7
+ export declare const useFormAtom: <Value, Update, Result extends void | Promise<void>>(anAtom: WritableAtom<Value, Update, Result>) => [Value extends Promise<infer V> ? V : Value, import("jotai/core/atom").SetAtom<Update, Result>];
8
+ export declare const useFormAtomValue: <Value>(anAtom: Atom<Value>) => Value extends Promise<infer V> ? V : Value;
8
9
  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
10
  export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
12
- export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
11
+ export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
13
12
  export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
14
- export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => any;
13
+ export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => Hydratable<{
14
+ [fieldName: string]: any;
15
+ }>;
15
16
  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;
17
+ export declare const useFieldTouched: (field: string, { formId }: InternalFormContextValue) => [boolean, (update: boolean) => void];
18
+ export declare const useFieldError: (name: string, context: InternalFormContextValue) => readonly [string | undefined, (update?: string | undefined) => void];
18
19
  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 {};
20
+ export declare const useClearError: ({ formId }: InternalFormContextValue) => (name: string) => void;
21
+ export declare const useSetTouched: ({ formId }: InternalFormContextValue) => (name: string, touched: boolean) => void;
@@ -3,36 +3,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
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;
6
+ exports.useSetTouched = exports.useClearError = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useInternalFormContext = exports.useFormAtomValue = exports.useFormAtom = exports.useFormUpdateAtom = void 0;
7
7
  const react_1 = require("@remix-run/react");
8
+ const jotai_1 = require("jotai");
8
9
  const utils_1 = require("jotai/utils");
9
10
  const get_1 = __importDefault(require("lodash/get"));
10
- const identity_1 = __importDefault(require("lodash/identity"));
11
11
  const react_2 = require("react");
12
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
12
13
  const constants_1 = require("./constants");
13
14
  const formContext_1 = require("./formContext");
15
+ const hydratable_1 = require("./hydratable");
14
16
  const state_1 = require("./state");
15
- const USE_HYDRATED_STATE = Symbol("USE_HYDRATED_STATE");
17
+ const useFormUpdateAtom = (atom) => (0, utils_1.useUpdateAtom)(atom, state_1.ATOM_SCOPE);
18
+ exports.useFormUpdateAtom = useFormUpdateAtom;
19
+ const useFormAtom = (anAtom) => (0, jotai_1.useAtom)(anAtom, state_1.ATOM_SCOPE);
20
+ exports.useFormAtom = useFormAtom;
21
+ const useFormAtomValue = (anAtom) => (0, utils_1.useAtomValue)(anAtom, state_1.ATOM_SCOPE);
22
+ exports.useFormAtomValue = useFormAtomValue;
16
23
  const useInternalFormContext = (formId, hookName) => {
17
24
  const formContext = (0, react_2.useContext)(formContext_1.InternalFormContext);
18
25
  if (formId)
19
26
  return { formId };
20
27
  if (formContext)
21
28
  return formContext;
22
- throw new Error(`Unable to determine form for ${hookName}. Please use it inside a form or pass a 'formId'.`);
29
+ throw new Error(`Unable to determine form for ${hookName}. Please use it inside a ValidatedForm or pass a 'formId'.`);
23
30
  };
24
31
  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
32
  function useErrorResponseForForm({ fetcher, subaction, formId, }) {
37
33
  var _a;
38
34
  const actionData = (0, react_1.useActionData)();
@@ -54,8 +50,8 @@ function useErrorResponseForForm({ fetcher, subaction, formId, }) {
54
50
  exports.useErrorResponseForForm = useErrorResponseForForm;
55
51
  const useFieldErrorsForForm = (context) => {
56
52
  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;
53
+ const hydrated = (0, exports.useFormAtomValue)((0, state_1.isHydratedAtom)(context.formId));
54
+ return hydratable_1.hydratable.from(response === null || response === void 0 ? void 0 : response.fieldErrors, hydrated);
59
55
  };
60
56
  exports.useFieldErrorsForForm = useFieldErrorsForForm;
61
57
  const useDefaultValuesFromLoader = ({ formId, }) => {
@@ -74,7 +70,7 @@ const useDefaultValuesFromLoader = ({ formId, }) => {
74
70
  exports.useDefaultValuesFromLoader = useDefaultValuesFromLoader;
75
71
  const useDefaultValuesForForm = (context) => {
76
72
  const { formId, defaultValuesProp } = context;
77
- const hydrated = (0, exports.useContextSelectAtom)(formId, state_1.isHydratedAtom);
73
+ const hydrated = (0, exports.useFormAtomValue)((0, state_1.isHydratedAtom)(formId));
78
74
  const errorResponse = useErrorResponseForForm(context);
79
75
  const defaultValuesFromLoader = (0, exports.useDefaultValuesFromLoader)(context);
80
76
  // Typical flow is:
@@ -83,12 +79,14 @@ const useDefaultValuesForForm = (context) => {
83
79
  // - State gets hydrated with default values
84
80
  // - After submit, we may need to use values from the error
85
81
  if (hydrated)
86
- return USE_HYDRATED_STATE;
87
- if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields)
88
- return errorResponse.repopulateFields;
82
+ return hydratable_1.hydratable.hydratedData();
83
+ if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields) {
84
+ (0, tiny_invariant_1.default)(typeof errorResponse.repopulateFields === "object", "repopulateFields returned something other than an object");
85
+ return hydratable_1.hydratable.serverData(errorResponse.repopulateFields);
86
+ }
89
87
  if (defaultValuesProp)
90
- return defaultValuesProp;
91
- return defaultValuesFromLoader;
88
+ return hydratable_1.hydratable.serverData(defaultValuesProp);
89
+ return hydratable_1.hydratable.serverData(defaultValuesFromLoader);
92
90
  };
93
91
  exports.useDefaultValuesForForm = useDefaultValuesForForm;
94
92
  const useHasActiveFormSubmit = ({ fetcher, }) => {
@@ -99,32 +97,32 @@ const useHasActiveFormSubmit = ({ fetcher, }) => {
99
97
  return hasActiveSubmission;
100
98
  };
101
99
  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
- };
100
+ const useFieldTouched = (field, { formId }) => (0, exports.useFormAtom)((0, state_1.fieldTouchedAtom)({ formId, field }));
106
101
  exports.useFieldTouched = useFieldTouched;
107
102
  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]);
103
+ const fieldErrors = (0, exports.useFieldErrorsForForm)(context);
104
+ const [state, set] = (0, exports.useFormAtom)((0, state_1.fieldErrorAtom)({ formId: context.formId, field: name }));
105
+ return [
106
+ fieldErrors.map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name]).hydrateTo(state),
107
+ set,
108
+ ];
109
109
  };
110
110
  exports.useFieldError = useFieldError;
111
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));
112
+ const defaultValues = (0, exports.useDefaultValuesForForm)(context);
113
+ const { defaultValues: state } = (0, exports.useFormAtomValue)((0, state_1.formPropsAtom)(context.formId));
114
+ return defaultValues
115
+ .map((val) => (0, get_1.default)(val, name))
116
+ .hydrateTo(state[name]);
113
117
  };
114
118
  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]);
119
+ const useClearError = ({ formId }) => {
120
+ const updateError = (0, exports.useFormUpdateAtom)((0, state_1.setFieldErrorAtom)(formId));
121
+ return (0, react_2.useCallback)((name) => updateError({ field: name, error: undefined }), [updateError]);
122
122
  };
123
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]);
124
+ const useSetTouched = ({ formId }) => {
125
+ const setTouched = (0, exports.useFormUpdateAtom)((0, state_1.setTouchedAtom)(formId));
126
+ return (0, react_2.useCallback)((name, touched) => setTouched({ field: name, touched }), [setTouched]);
129
127
  };
130
128
  exports.useSetTouched = useSetTouched;
@@ -0,0 +1,3 @@
1
+ export declare const isMultiselect: (node: Node) => node is HTMLSelectElement;
2
+ export declare const isCheckbox: (node: Node | RadioNodeList) => node is HTMLInputElement;
3
+ export declare const isRadio: (node: Node) => node is HTMLInputElement;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRadio = exports.isCheckbox = exports.isMultiselect = void 0;
4
+ const isMultiselect = (node) => node instanceof HTMLSelectElement && node.multiple;
5
+ exports.isMultiselect = isMultiselect;
6
+ const isCheckbox = (node) => node instanceof HTMLInputElement && node.type === "checkbox";
7
+ exports.isCheckbox = isCheckbox;
8
+ const isRadio = (node) => node instanceof HTMLInputElement && node.type === "radio";
9
+ exports.isRadio = isRadio;
@@ -5,24 +5,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.setInputValueInForm = void 0;
7
7
  const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
8
+ const elementUtils_1 = require("./elementUtils");
8
9
  const getCheckboxChecked_1 = require("./getCheckboxChecked");
9
10
  /**
10
11
  * Helper class to track the values being set on uncontrolled fields.
11
12
  * 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
13
  */
20
14
  class Values {
21
15
  constructor(values) {
22
16
  this.hasSetRadioValue = false;
17
+ this.remove = (value) => {
18
+ const index = this.values.indexOf(value);
19
+ const deleted = this.values.splice(index, 1);
20
+ return deleted.length > 0;
21
+ };
23
22
  this.bool = (value) => {
24
- if ((0, getCheckboxChecked_1.getCheckboxChecked)(value, this.values[0])) {
25
- this.values.shift();
23
+ if ((0, getCheckboxChecked_1.getCheckboxChecked)(value, this.values)) {
24
+ this.remove(value) || this.remove(true);
26
25
  return true;
27
26
  }
28
27
  return false;
@@ -47,44 +46,18 @@ class Values {
47
46
  this.values = unknownValues;
48
47
  }
49
48
  }
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
49
  const setElementValue = (element, values, field) => {
77
- if (isMultiselect(element)) {
50
+ if ((0, elementUtils_1.isMultiselect)(element)) {
78
51
  for (const option of element.options) {
79
52
  option.selected = values.bool(option.value);
80
53
  }
81
54
  return;
82
55
  }
83
- if (isCheckbox(element)) {
56
+ if ((0, elementUtils_1.isCheckbox)(element)) {
84
57
  element.checked = values.bool(element.value);
85
58
  return;
86
59
  }
87
- if (isRadio(element)) {
60
+ if ((0, elementUtils_1.isRadio)(element)) {
88
61
  element.checked = values.radio(element.value);
89
62
  return;
90
63
  }
@@ -92,34 +65,18 @@ const setElementValue = (element, values, field) => {
92
65
  (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
66
  input.value = values.str();
94
67
  };
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
68
  const setInputValueInForm = (formElement, name, value) => {
109
69
  const controlElement = formElement.elements.namedItem(name);
110
70
  if (!controlElement)
111
71
  return;
72
+ const values = new Values(value);
112
73
  if (controlElement instanceof RadioNodeList) {
113
- const values = areElementsTheSameType([...controlElement])
114
- ? new PermissiveValues(value)
115
- : new Values(value);
116
74
  for (const element of controlElement) {
117
75
  setElementValue(element, values, name);
118
76
  }
119
77
  values.warnIfLeftovers(name);
120
78
  }
121
79
  else {
122
- const values = new PermissiveValues(value);
123
80
  setElementValue(controlElement, values, name);
124
81
  values.warnIfLeftovers(name);
125
82
  }
File without changes
File without changes
@@ -15,10 +15,18 @@ const refCountAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(0)
15
15
  const fieldValueAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
16
16
  const fieldValueHydratedAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(false));
17
17
  const pendingValidateAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
18
- const valueSerializerAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)({ serializer: JSON.stringify }));
18
+ const valueSerializerAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)({
19
+ serializer: (value) => {
20
+ if (value === undefined)
21
+ return "";
22
+ return JSON.stringify(value);
23
+ },
24
+ }));
19
25
  const registerAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
20
26
  set(refCountAtom({ formId, field }), (prev) => prev + 1);
21
27
  const newRefCount = get(refCountAtom({ formId, field }));
28
+ // We don't set hydrated here because it gets set when we know
29
+ // we have the right default values
22
30
  if (newRefCount === 1) {
23
31
  set((0, exports.controlledFieldsAtom)(formId), (prev) => ({
24
32
  ...prev,
@@ -33,6 +41,7 @@ const unregisterAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) =>
33
41
  set((0, exports.controlledFieldsAtom)(formId), (prev) => (0, omit_1.default)(prev, field));
34
42
  fieldValueAtom.remove({ formId, field });
35
43
  pendingValidateAtom.remove({ formId, field });
44
+ fieldValueHydratedAtom.remove({ formId, field });
36
45
  }
37
46
  });
38
47
  exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, async (_get, set, { formId, field, value, }) => {
@@ -51,7 +60,7 @@ const useControlledFieldValue = (formId, field) => {
51
60
  const [isFieldHydrated, setIsFieldHydrated] = (0, hooks_1.useFormAtom)(fieldValueHydratedAtom({ formId, field }));
52
61
  (0, react_1.useEffect)(() => {
53
62
  if (isHydrated && !isFieldHydrated) {
54
- setValue({ formId, field, value: defaultValue });
63
+ setValue(defaultValue);
55
64
  setIsFieldHydrated(true);
56
65
  }
57
66
  }, [