remix-validated-form 4.4.3 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/.turbo/turbo-dev.log +0 -95
  3. package/browser/ValidatedForm.js +24 -39
  4. package/browser/hooks.d.ts +1 -1
  5. package/browser/internal/hooks.d.ts +6 -3
  6. package/browser/internal/hooks.js +1 -0
  7. package/browser/internal/logic/nestedObjectToPathObject.d.ts +1 -0
  8. package/browser/internal/logic/nestedObjectToPathObject.js +47 -0
  9. package/browser/internal/state/arrayUtil.d.ts +6 -0
  10. package/browser/internal/state/arrayUtil.js +108 -0
  11. package/browser/internal/state/controlledFieldStore.d.ts +23 -21
  12. package/browser/internal/state/controlledFieldStore.js +32 -19
  13. package/browser/internal/state/controlledFields.d.ts +3 -3
  14. package/browser/internal/state/controlledFields.js +19 -21
  15. package/browser/internal/state/createFormStore.d.ts +16 -8
  16. package/browser/internal/state/createFormStore.js +62 -8
  17. package/browser/internal/state/fieldArray.d.ts +21 -0
  18. package/browser/internal/state/fieldArray.js +50 -0
  19. package/browser/internal/state/storeHooks.d.ts +1 -3
  20. package/browser/internal/state/storeHooks.js +2 -8
  21. package/browser/internal/state/types.d.ts +1 -0
  22. package/browser/internal/state/types.js +1 -0
  23. package/browser/unreleased/formStateHooks.d.ts +8 -2
  24. package/browser/unreleased/formStateHooks.js +12 -2
  25. package/browser/userFacingFormContext.d.ts +8 -2
  26. package/browser/userFacingFormContext.js +3 -1
  27. package/dist/remix-validated-form.cjs.js +3 -3
  28. package/dist/remix-validated-form.cjs.js.map +1 -1
  29. package/dist/remix-validated-form.es.js +158 -111
  30. package/dist/remix-validated-form.es.js.map +1 -1
  31. package/dist/remix-validated-form.umd.js +3 -3
  32. package/dist/remix-validated-form.umd.js.map +1 -1
  33. package/dist/types/hooks.d.ts +1 -1
  34. package/dist/types/internal/hooks.d.ts +3 -2
  35. package/dist/types/internal/state/controlledFieldStore.d.ts +23 -21
  36. package/dist/types/internal/state/controlledFields.d.ts +3 -3
  37. package/dist/types/internal/state/createFormStore.d.ts +16 -8
  38. package/dist/types/internal/state/storeHooks.d.ts +1 -3
  39. package/dist/types/internal/state/types.d.ts +1 -0
  40. package/dist/types/unreleased/formStateHooks.d.ts +8 -2
  41. package/dist/types/userFacingFormContext.d.ts +8 -2
  42. package/package.json +4 -4
  43. package/src/ValidatedForm.tsx +41 -56
  44. package/src/internal/hooks.ts +4 -1
  45. package/src/internal/state/controlledFieldStore.ts +95 -74
  46. package/src/internal/state/controlledFields.ts +38 -26
  47. package/src/internal/state/createFormStore.ts +199 -115
  48. package/src/internal/state/storeHooks.ts +3 -16
  49. package/src/internal/state/types.ts +1 -0
  50. package/src/unreleased/formStateHooks.ts +24 -3
  51. package/src/userFacingFormContext.ts +15 -2
  52. package/dist/types/internal/state/cleanup.d.ts +0 -2
  53. package/dist/types/internal/state/storeFamily.d.ts +0 -9
  54. package/src/internal/state/cleanup.ts +0 -8
  55. package/src/internal/state/storeFamily.ts +0 -24
@@ -1,15 +1,18 @@
1
- $ vite build
2
- vite v2.9.5 building for production...
1
+ $ vite build
2
+ vite v2.9.5 building for production...
3
3
  transforming...
4
- 321 modules transformed.
4
+ ✓ 320 modules transformed.
5
5
  rendering chunks...
6
- dist/remix-validated-form.cjs.js 43.88 KiB / gzip: 16.70 KiB
7
- dist/remix-validated-form.es.js 98.54 KiB / gzip: 23.35 KiB
8
- dist/remix-validated-form.umd.js 44.13 KiB / gzip: 16.84 KiB
9
-
10
- [vite:dts] Start generate declaration files...
11
- [vite:dts] Declaration files built in 1804ms.
12
-
6
+ dist/remix-validated-form.cjs.js  47.23 KiB / gzip: 17.41 KiB
7
+ dist/remix-validated-form.cjs.js.map 265.34 KiB
8
+ dist/remix-validated-form.es.js  104.23 KiB / gzip: 24.22 KiB
9
+ dist/remix-validated-form.es.js.map 273.50 KiB
10
+ dist/remix-validated-form.umd.js  47.48 KiB / gzip: 17.53 KiB
11
+ dist/remix-validated-form.umd.js.map 265.31 KiB
12
+ 
13
+ [vite:dts] Start generate declaration files...
14
+ [vite:dts] Declaration files built in 1961ms.
15
+ 
13
16
  No name was provided for external module 'react' in output.globals – guessing 'React'
14
17
  No name was provided for external module '@remix-run/react' in output.globals – guessing 'react'
15
18
  No name was provided for external module '@remix-run/server-runtime' in output.globals – guessing 'serverRuntime'
@@ -1,95 +0,0 @@
1
- $ tsc --module ESNext --outDir ./browser --watch
2
- c10:51:22 PM - Starting compilation in watch mode...
3
-
4
-
5
- 10:51:23 PM - Found 0 errors. Watching for file changes.
6
- c10:58:01 PM - File change detected. Starting incremental compilation...
7
-
8
- src/internal/hooks.ts(149,23): error TS2304: Cannot find name 'formId'.
9
- src/internal/hooks.ts(151,41): error TS2304: Cannot find name 'defaultDefaultValues'.
10
- src/unreleased/formStateHooks.ts(18,3): error TS2724: '"../internal/hooks"' has no exported member named 'useSyncedDefaultValues'. Did you mean 'useFieldDefaultValue'?
11
-
12
- 10:58:02 PM - Found 3 errors. Watching for file changes.
13
- c10:58:40 PM - File change detected. Starting incremental compilation...
14
-
15
- src/internal/hooks.ts(156,23): error TS2304: Cannot find name 'formId'.
16
-
17
- 10:58:40 PM - Found 1 error. Watching for file changes.
18
- c10:59:28 PM - File change detected. Starting incremental compilation...
19
-
20
-
21
- 10:59:28 PM - Found 0 errors. Watching for file changes.
22
- c11:00:16 PM - File change detected. Starting incremental compilation...
23
-
24
- src/internal/hooks.ts(160,4): error TS1005: ',' expected.
25
- src/internal/hooks.ts(161,1): error TS1128: Declaration or statement expected.
26
-
27
- 11:00:16 PM - Found 2 errors. Watching for file changes.
28
- c11:01:07 PM - File change detected. Starting incremental compilation...
29
-
30
-
31
- 11:01:07 PM - Found 0 errors. Watching for file changes.
32
- c11:01:30 PM - File change detected. Starting incremental compilation...
33
-
34
-
35
- 11:01:30 PM - Found 0 errors. Watching for file changes.
36
- c11:01:50 PM - File change detected. Starting incremental compilation...
37
-
38
-
39
- 11:01:50 PM - Found 0 errors. Watching for file changes.
40
- c11:02:06 PM - File change detected. Starting incremental compilation...
41
-
42
-
43
- 11:02:06 PM - Found 0 errors. Watching for file changes.
44
- c11:04:31 PM - File change detected. Starting incremental compilation...
45
-
46
-
47
- 11:04:31 PM - Found 0 errors. Watching for file changes.
48
- c11:05:28 PM - File change detected. Starting incremental compilation...
49
-
50
-
51
- 11:05:28 PM - Found 0 errors. Watching for file changes.
52
- c11:05:52 PM - File change detected. Starting incremental compilation...
53
-
54
-
55
- 11:05:52 PM - Found 0 errors. Watching for file changes.
56
- c11:06:11 PM - File change detected. Starting incremental compilation...
57
-
58
-
59
- 11:06:11 PM - Found 0 errors. Watching for file changes.
60
- c11:06:33 PM - File change detected. Starting incremental compilation...
61
-
62
-
63
- 11:06:33 PM - Found 0 errors. Watching for file changes.
64
- c11:06:48 PM - File change detected. Starting incremental compilation...
65
-
66
-
67
- 11:06:48 PM - Found 0 errors. Watching for file changes.
68
- c11:08:57 PM - File change detected. Starting incremental compilation...
69
-
70
- src/internal/hooks.ts(114,25): error TS2304: Cannot find name 'useCorrectDefaultValues'.
71
- src/unreleased/formStateHooks.ts(19,3): error TS2305: Module '"../internal/hooks"' has no exported member 'useCorrectDefaultValues'.
72
-
73
- 11:08:57 PM - Found 2 errors. Watching for file changes.
74
- c11:09:06 PM - File change detected. Starting incremental compilation...
75
-
76
- src/unreleased/formStateHooks.ts(19,3): error TS2305: Module '"../internal/hooks"' has no exported member 'useCorrectDefaultValues'.
77
-
78
- 11:09:06 PM - Found 1 error. Watching for file changes.
79
- c11:09:19 PM - File change detected. Starting incremental compilation...
80
-
81
- src/unreleased/formStateHooks.ts(48,49): error TS2345: Argument of type 'string | symbol' is not assignable to parameter of type 'InternalFormContextValue'.
82
- Type 'string' is not assignable to type 'InternalFormContextValue'.
83
-
84
- 11:09:19 PM - Found 1 error. Watching for file changes.
85
- c11:09:34 PM - File change detected. Starting incremental compilation...
86
-
87
- src/unreleased/formStateHooks.ts(48,49): error TS2345: Argument of type 'string | symbol' is not assignable to parameter of type 'InternalFormContextValue'.
88
- Type 'string' is not assignable to type 'InternalFormContextValue'.
89
-
90
- 11:09:34 PM - Found 1 error. Watching for file changes.
91
- c11:09:38 PM - File change detected. Starting incremental compilation...
92
-
93
-
94
- 11:09:38 PM - Found 0 errors. Watching for file changes.
95
- c11:11:35 PM - File change detected. Starting incremental compilat
@@ -1,14 +1,15 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Form as RemixForm, useSubmit } from "@remix-run/react";
3
3
  import uniq from "lodash/uniq";
4
- import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
4
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
5
  import { useIsSubmitting, useIsValid } from "./hooks";
6
6
  import { FORM_ID_FIELD } from "./internal/constants";
7
7
  import { InternalFormContext, } from "./internal/formContext";
8
8
  import { useDefaultValuesFromLoader, useErrorResponseForForm, useHasActiveFormSubmit, useSetFieldErrors, } from "./internal/hooks";
9
9
  import { useMultiValueMap } from "./internal/MultiValueMap";
10
- import { cleanupFormState } from "./internal/state/cleanup";
11
- import { useControlledFieldStore, useFormStore, } from "./internal/state/storeHooks";
10
+ import { useControlledFieldStore } from "./internal/state/controlledFieldStore";
11
+ import { useRootFormStore, } from "./internal/state/createFormStore";
12
+ import { useFormStore } from "./internal/state/storeHooks";
12
13
  import { useSubmitComplete } from "./internal/submissionCallbacks";
13
14
  import { mergeRefs, useDeepEqualsMemo, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
14
15
  const getDataFromForm = (el) => new FormData(el);
@@ -114,16 +115,13 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
114
115
  const setFieldErrors = useSetFieldErrors(formId);
115
116
  const setFieldError = useFormStore(formId, (state) => state.setFieldError);
116
117
  const reset = useFormStore(formId, (state) => state.reset);
117
- const resetControlledFields = useControlledFieldStore(formId, (state) => state.reset);
118
+ const resetControlledFields = useControlledFieldStore((state) => state.reset);
118
119
  const startSubmit = useFormStore(formId, (state) => state.startSubmit);
119
120
  const endSubmit = useFormStore(formId, (state) => state.endSubmit);
120
121
  const syncFormProps = useFormStore(formId, (state) => state.syncFormProps);
121
- const setHydrated = useFormStore(formId, (state) => state.setHydrated);
122
122
  const setFormElementInState = useFormStore(formId, (state) => state.setFormElement);
123
- useEffect(() => {
124
- setHydrated();
125
- return () => cleanupFormState(formId);
126
- }, [formId, setHydrated]);
123
+ const cleanupForm = useRootFormStore((state) => state.cleanupForm);
124
+ const registerForm = useRootFormStore((state) => state.registerForm);
127
125
  const customFocusHandlers = useMultiValueMap();
128
126
  const registerReceiveFocus = useCallback((fieldName, handler) => {
129
127
  customFocusHandlers().add(fieldName, handler);
@@ -131,6 +129,12 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
131
129
  customFocusHandlers().remove(fieldName, handler);
132
130
  };
133
131
  }, [customFocusHandlers]);
132
+ // TODO: all these hooks running at startup cause extra, unnecessary renders
133
+ // There must be a nice way to avoid this.
134
+ useLayoutEffect(() => {
135
+ registerForm(formId);
136
+ return () => cleanupForm(formId);
137
+ }, [cleanupForm, formId, registerForm]);
134
138
  useLayoutEffect(() => {
135
139
  var _a;
136
140
  syncFormProps({
@@ -149,6 +153,9 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
149
153
  backendDefaultValues,
150
154
  validator,
151
155
  ]);
156
+ useLayoutEffect(() => {
157
+ setFormElementInState(formRef.current);
158
+ }, [setFormElementInState]);
152
159
  useEffect(() => {
153
160
  var _a;
154
161
  setFieldErrors((_a = backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors) !== null && _a !== void 0 ? _a : {});
@@ -156,27 +163,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
156
163
  useSubmitComplete(hasActiveSubmission, () => {
157
164
  endSubmit();
158
165
  });
159
- let clickedButtonRef = React.useRef();
160
- useEffect(() => {
161
- let form = formRef.current;
162
- if (!form)
163
- return;
164
- function handleClick(event) {
165
- if (!(event.target instanceof HTMLElement))
166
- return;
167
- let submitButton = event.target.closest("button,input[type=submit]");
168
- if (submitButton &&
169
- submitButton.form === form &&
170
- submitButton.type === "submit") {
171
- clickedButtonRef.current = submitButton;
172
- }
173
- }
174
- window.addEventListener("click", handleClick, { capture: true });
175
- return () => {
176
- window.removeEventListener("click", handleClick, { capture: true });
177
- };
178
- }, []);
179
- const handleSubmit = async (e) => {
166
+ const handleSubmit = async (e, target, nativeEvent) => {
180
167
  startSubmit();
181
168
  const result = await validator.validate(getDataFromForm(e.currentTarget));
182
169
  if (result.error) {
@@ -193,8 +180,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
193
180
  endSubmit();
194
181
  return;
195
182
  }
196
- const submitter = e.nativeEvent
197
- .submitter;
183
+ const submitter = nativeEvent.submitter;
198
184
  // We deviate from the remix code here a bit because of our async submit.
199
185
  // In remix's `FormImpl`, they use `event.currentTarget` to get the form,
200
186
  // but we already have the form in `formRef.current` so we can just use that.
@@ -203,18 +189,17 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
203
189
  if (fetcher)
204
190
  fetcher.submit(submitter || e.currentTarget);
205
191
  else
206
- submit(submitter || e.currentTarget, { method, replace });
207
- clickedButtonRef.current = null;
192
+ submit(submitter || target, { method, replace });
208
193
  }
209
194
  };
210
- return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp, setFormElementInState]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
195
+ return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
211
196
  e.preventDefault();
212
- handleSubmit(e);
197
+ handleSubmit(e, e.currentTarget, e.nativeEvent);
213
198
  }, onReset: (event) => {
214
199
  onReset === null || onReset === void 0 ? void 0 : onReset(event);
215
200
  if (event.defaultPrevented)
216
201
  return;
217
202
  reset();
218
- resetControlledFields();
219
- }, children: _jsxs(InternalFormContext.Provider, { value: contextValue, children: [_jsx(FormResetter, { formRef: formRef, resetAfterSubmit: resetAfterSubmit }, void 0), subaction && (_jsx("input", { type: "hidden", value: subaction, name: "subaction" }, void 0)), id && _jsx("input", { type: "hidden", value: id, name: FORM_ID_FIELD }, void 0), children] }, void 0) }, void 0));
203
+ resetControlledFields(formId);
204
+ }, children: _jsx(InternalFormContext.Provider, { value: contextValue, children: _jsxs(_Fragment, { children: [_jsx(FormResetter, { formRef: formRef, resetAfterSubmit: resetAfterSubmit }, void 0), subaction && (_jsx("input", { type: "hidden", value: subaction, name: "subaction" }, void 0)), id && _jsx("input", { type: "hidden", value: id, name: FORM_ID_FIELD }, void 0), children] }, void 0) }, void 0) }, void 0));
220
205
  }
@@ -64,4 +64,4 @@ export declare const useField: (name: string, options?: {
64
64
  formId?: string | undefined;
65
65
  } | undefined) => FieldProps;
66
66
  export declare const useControlField: <T>(name: string, formId?: string | undefined) => readonly [T, (value: T) => void];
67
- export declare const useUpdateControlledField: (formId?: string | undefined) => (fieldName: string, value: unknown) => void;
67
+ export declare const useUpdateControlledField: (formId?: string | undefined) => (field: string, value: unknown) => void;
@@ -1,7 +1,7 @@
1
1
  import { FieldErrors, ValidationErrorResponseData } from "..";
2
2
  import { InternalFormContextValue } from "./formContext";
3
3
  import { Hydratable } from "./hydratable";
4
- import { InternalFormId } from "./state/storeFamily";
4
+ import { InternalFormId } from "./state/types";
5
5
  export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
6
6
  export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
7
7
  export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
@@ -18,13 +18,16 @@ export declare const useInternalIsSubmitting: (formId: InternalFormId) => boolea
18
18
  export declare const useInternalIsValid: (formId: InternalFormId) => boolean;
19
19
  export declare const useInternalHasBeenSubmitted: (formId: InternalFormId) => boolean;
20
20
  export declare const useValidateField: (formId: InternalFormId) => (fieldName: string) => Promise<string | null>;
21
- export declare const useValidate: (formId: InternalFormId) => () => Promise<void>;
21
+ export declare const useValidate: (formId: InternalFormId) => () => Promise<import("..").ValidationResult<unknown>>;
22
22
  export declare const useRegisterReceiveFocus: (formId: InternalFormId) => (fieldName: string, handler: () => void) => () => void;
23
- export declare const useSyncedDefaultValues: (formId: InternalFormId) => {};
23
+ export declare const useSyncedDefaultValues: (formId: InternalFormId) => {
24
+ [fieldName: string]: any;
25
+ };
24
26
  export declare const useSetTouched: ({ formId }: InternalFormContextValue) => (field: string, touched: boolean) => void;
25
27
  export declare const useTouchedFields: (formId: InternalFormId) => import("..").TouchedFields;
26
28
  export declare const useFieldErrors: (formId: InternalFormId) => FieldErrors;
27
29
  export declare const useSetFieldErrors: (formId: InternalFormId) => (errors: FieldErrors) => void;
28
30
  export declare const useResetFormElement: (formId: InternalFormId) => () => void;
31
+ export declare const useSubmitForm: (formId: InternalFormId) => () => void;
29
32
  export declare const useFormActionProp: (formId: InternalFormId) => string | undefined;
30
33
  export declare const useFormSubactionProp: (formId: InternalFormId) => string | undefined;
@@ -113,5 +113,6 @@ export const useTouchedFields = (formId) => useFormStore(formId, (state) => stat
113
113
  export const useFieldErrors = (formId) => useFormStore(formId, (state) => state.fieldErrors);
114
114
  export const useSetFieldErrors = (formId) => useFormStore(formId, (state) => state.setFieldErrors);
115
115
  export const useResetFormElement = (formId) => useFormStore(formId, (state) => state.resetFormElement);
116
+ export const useSubmitForm = (formId) => useFormStore(formId, (state) => state.submit);
116
117
  export const useFormActionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.action; });
117
118
  export const useFormSubactionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.subaction; });
@@ -0,0 +1 @@
1
+ export declare const nestedObjectToPathObject: (val: any, acc: Record<string, any>, path: string) => any;
@@ -0,0 +1,47 @@
1
+ export const nestedObjectToPathObject = (val, acc, path) => {
2
+ if (Array.isArray(val)) {
3
+ val.forEach((v, index) => nestedObjectToPathObject(v, acc, `${path}[${index}]`));
4
+ return acc;
5
+ }
6
+ if (typeof val === "object") {
7
+ Object.entries(val).forEach(([key, value]) => {
8
+ const nextPath = path ? `${path}.${key}` : key;
9
+ nestedObjectToPathObject(value, acc, nextPath);
10
+ });
11
+ return acc;
12
+ }
13
+ if (val !== undefined) {
14
+ acc[path] = val;
15
+ }
16
+ return acc;
17
+ };
18
+ if (import.meta.vitest) {
19
+ const { describe, expect, it } = import.meta.vitest;
20
+ describe("nestedObjectToPathObject", () => {
21
+ it("should return an object with the correct path", () => {
22
+ const result = nestedObjectToPathObject({
23
+ a: 1,
24
+ b: 2,
25
+ c: { foo: "bar", baz: [true, false] },
26
+ d: [
27
+ { foo: "bar", baz: [true, false] },
28
+ { e: true, f: "hi" },
29
+ ],
30
+ g: undefined,
31
+ }, {}, "");
32
+ expect(result).toEqual({
33
+ a: 1,
34
+ b: 2,
35
+ "c.foo": "bar",
36
+ "c.baz[0]": true,
37
+ "c.baz[1]": false,
38
+ "d[0].foo": "bar",
39
+ "d[0].baz[0]": true,
40
+ "d[0].baz[1]": false,
41
+ "d[1].e": true,
42
+ "d[1].f": "hi",
43
+ });
44
+ expect(Object.keys(result)).toHaveLength(10);
45
+ });
46
+ });
47
+ }
@@ -0,0 +1,6 @@
1
+ export declare const getArray: (values: any, field: string) => unknown[];
2
+ export declare const swap: (array: unknown[], indexA: number, indexB: number) => void;
3
+ export declare const move: (array: unknown[], from: number, to: number) => void;
4
+ export declare const insert: (array: unknown[], index: number, value: unknown) => void;
5
+ export declare const remove: (array: unknown[], index: number) => void;
6
+ export declare const replace: (array: unknown[], index: number, value: unknown) => void;
@@ -0,0 +1,108 @@
1
+ import lodashGet from "lodash/get";
2
+ import lodashSet from "lodash/set";
3
+ import invariant from "tiny-invariant";
4
+ ////
5
+ // All of these array helpers are written in a way that mutates the original array.
6
+ // This is because we're working with immer.
7
+ ////
8
+ export const getArray = (values, field) => {
9
+ const value = lodashGet(values, field);
10
+ if (value === undefined || value === null) {
11
+ const newValue = [];
12
+ lodashSet(values, field, newValue);
13
+ return newValue;
14
+ }
15
+ invariant(Array.isArray(value), `FieldArray: defaultValue value for ${field} must be an array, null, or undefined`);
16
+ return value;
17
+ };
18
+ export const swap = (array, indexA, indexB) => {
19
+ const itemA = array[indexA];
20
+ const itemB = array[indexB];
21
+ array[indexA] = itemB;
22
+ array[indexB] = itemA;
23
+ };
24
+ export const move = (array, from, to) => {
25
+ const [item] = array.splice(from, 1);
26
+ array.splice(to, 0, item);
27
+ };
28
+ export const insert = (array, index, value) => {
29
+ array.splice(index, 0, value);
30
+ };
31
+ export const remove = (array, index) => {
32
+ array.splice(index, 1);
33
+ };
34
+ export const replace = (array, index, value) => {
35
+ array.splice(index, 1, value);
36
+ };
37
+ if (import.meta.vitest) {
38
+ const { describe, expect, it } = import.meta.vitest;
39
+ describe("getArray", () => {
40
+ it("shoud get a deeply nested array that can be mutated to update the nested value", () => {
41
+ const values = {
42
+ d: [
43
+ { foo: "bar", baz: [true, false] },
44
+ { e: true, f: "hi" },
45
+ ],
46
+ };
47
+ const result = getArray(values, "d[0].baz");
48
+ const finalValues = {
49
+ d: [
50
+ { foo: "bar", baz: [true, false, true] },
51
+ { e: true, f: "hi" },
52
+ ],
53
+ };
54
+ expect(result).toEqual([true, false]);
55
+ result.push(true);
56
+ expect(values).toEqual(finalValues);
57
+ });
58
+ it("should return an empty array that can be mutated if result is null or undefined", () => {
59
+ const values = {};
60
+ const result = getArray(values, "a.foo[0].bar");
61
+ const finalValues = {
62
+ a: { foo: [{ bar: ["Bob ross"] }] },
63
+ };
64
+ expect(result).toEqual([]);
65
+ result.push("Bob ross");
66
+ expect(values).toEqual(finalValues);
67
+ });
68
+ it("should throw if the value is defined and not an array", () => {
69
+ const values = { foo: "foo" };
70
+ expect(() => getArray(values, "foo")).toThrow();
71
+ });
72
+ });
73
+ describe("swap", () => {
74
+ it("should swap two items", () => {
75
+ const array = [1, 2, 3];
76
+ swap(array, 0, 1);
77
+ expect(array).toEqual([2, 1, 3]);
78
+ });
79
+ });
80
+ describe("move", () => {
81
+ it("should move an item to a new index", () => {
82
+ const array = [1, 2, 3];
83
+ move(array, 0, 1);
84
+ expect(array).toEqual([2, 1, 3]);
85
+ });
86
+ });
87
+ describe("insert", () => {
88
+ it("should insert an item at a new index", () => {
89
+ const array = [1, 2, 3];
90
+ insert(array, 1, 4);
91
+ expect(array).toEqual([1, 4, 2, 3]);
92
+ });
93
+ });
94
+ describe("remove", () => {
95
+ it("should remove an item at a given index", () => {
96
+ const array = [1, 2, 3];
97
+ remove(array, 1);
98
+ expect(array).toEqual([1, 3]);
99
+ });
100
+ });
101
+ describe("replace", () => {
102
+ it("should replace an item at a given index", () => {
103
+ const array = [1, 2, 3];
104
+ replace(array, 1, 4);
105
+ expect(array).toEqual([1, 4, 3]);
106
+ });
107
+ });
108
+ }
@@ -1,24 +1,26 @@
1
+ import { InternalFormId } from "./types";
2
+ export declare type FieldState = {
3
+ refCount: number;
4
+ value: unknown;
5
+ defaultValue?: unknown;
6
+ hydrated: boolean;
7
+ valueUpdatePromise: Promise<void> | undefined;
8
+ resolveValueUpdate: (() => void) | undefined;
9
+ };
1
10
  export declare type ControlledFieldState = {
2
- fields: {
3
- [fieldName: string]: {
4
- refCount: number;
5
- value: unknown;
6
- defaultValue?: unknown;
7
- hydrated: boolean;
8
- valueUpdatePromise: Promise<void> | undefined;
9
- resolveValueUpdate: (() => void) | undefined;
10
- } | undefined;
11
+ forms: {
12
+ [formId: InternalFormId]: {
13
+ [fieldName: string]: FieldState | undefined;
14
+ };
11
15
  };
12
- register: (fieldName: string) => void;
13
- unregister: (fieldName: string) => void;
14
- setValue: (fieldName: string, value: unknown) => void;
15
- hydrateWithDefault: (fieldName: string, defaultValue: unknown) => void;
16
- awaitValueUpdate: (fieldName: string) => Promise<void>;
17
- reset: () => void;
18
- };
19
- export declare const controlledFieldStore: {
20
- (formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
21
- setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
22
- }>;
23
- remove(formId: import("./storeFamily").InternalFormId): void;
16
+ register: (formId: InternalFormId, fieldName: string) => void;
17
+ unregister: (formId: InternalFormId, fieldName: string) => void;
18
+ getField: (formId: InternalFormId, fieldName: string) => FieldState | undefined;
19
+ setValue: (formId: InternalFormId, fieldName: string, value: unknown) => void;
20
+ hydrateWithDefault: (formId: InternalFormId, fieldName: string, defaultValue: unknown) => void;
21
+ awaitValueUpdate: (formId: InternalFormId, fieldName: string) => Promise<void>;
22
+ reset: (formId: InternalFormId) => void;
24
23
  };
24
+ export declare const useControlledFieldStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
25
+ setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
26
+ }>;
@@ -1,14 +1,16 @@
1
1
  import create from "zustand";
2
2
  import { immer } from "zustand/middleware/immer";
3
- import { storeFamily } from "./storeFamily";
4
- export const controlledFieldStore = storeFamily(() => create()(immer((set, get, api) => ({
5
- fields: {},
6
- register: (field) => set((state) => {
7
- if (state.fields[field]) {
8
- state.fields[field].refCount++;
3
+ export const useControlledFieldStore = create()(immer((set, get) => ({
4
+ forms: {},
5
+ register: (formId, field) => set((state) => {
6
+ if (!state.forms[formId]) {
7
+ state.forms[formId] = {};
8
+ }
9
+ if (state.forms[formId][field]) {
10
+ state.forms[formId][field].refCount++;
9
11
  }
10
12
  else {
11
- state.fields[field] = {
13
+ state.forms[formId][field] = {
12
14
  refCount: 1,
13
15
  value: undefined,
14
16
  hydrated: false,
@@ -17,16 +19,23 @@ export const controlledFieldStore = storeFamily(() => create()(immer((set, get,
17
19
  };
18
20
  }
19
21
  }),
20
- unregister: (field) => set((state) => {
21
- const fieldState = state.fields[field];
22
+ unregister: (formId, field) => set((state) => {
23
+ var _a;
24
+ const formState = (_a = state.forms) === null || _a === void 0 ? void 0 : _a[formId];
25
+ const fieldState = formState === null || formState === void 0 ? void 0 : formState[field];
22
26
  if (!fieldState)
23
27
  return;
24
28
  fieldState.refCount--;
25
29
  if (fieldState.refCount === 0)
26
- delete state.fields[field];
30
+ delete formState[field];
27
31
  }),
28
- setValue: (field, value) => set((state) => {
29
- const fieldState = state.fields[field];
32
+ getField: (formId, field) => {
33
+ var _a, _b;
34
+ return (_b = (_a = get().forms) === null || _a === void 0 ? void 0 : _a[formId]) === null || _b === void 0 ? void 0 : _b[field];
35
+ },
36
+ setValue: (formId, field, value) => set((state) => {
37
+ var _a, _b;
38
+ const fieldState = (_b = (_a = state.forms) === null || _a === void 0 ? void 0 : _a[formId]) === null || _b === void 0 ? void 0 : _b[field];
30
39
  if (!fieldState)
31
40
  return;
32
41
  fieldState.value = value;
@@ -35,23 +44,27 @@ export const controlledFieldStore = storeFamily(() => create()(immer((set, get,
35
44
  });
36
45
  fieldState.valueUpdatePromise = promise;
37
46
  }),
38
- hydrateWithDefault: (field, defaultValue) => set((state) => {
39
- const fieldState = state.fields[field];
47
+ hydrateWithDefault: (formId, field, defaultValue) => set((state) => {
48
+ var _a;
49
+ const fieldState = (_a = state.forms[formId]) === null || _a === void 0 ? void 0 : _a[field];
40
50
  if (!fieldState)
41
51
  return;
42
52
  fieldState.value = defaultValue;
43
53
  fieldState.defaultValue = defaultValue;
44
54
  fieldState.hydrated = true;
45
55
  }),
46
- awaitValueUpdate: async (field) => {
56
+ awaitValueUpdate: async (formId, field) => {
47
57
  var _a;
48
- await ((_a = get().fields[field]) === null || _a === void 0 ? void 0 : _a.valueUpdatePromise);
58
+ await ((_a = get().getField(formId, field)) === null || _a === void 0 ? void 0 : _a.valueUpdatePromise);
49
59
  },
50
- reset: () => set((state) => {
51
- Object.values(state.fields).forEach((field) => {
60
+ reset: (formId) => set((state) => {
61
+ const formState = state.forms[formId];
62
+ if (!formState)
63
+ return;
64
+ Object.values(formState).forEach((field) => {
52
65
  if (!field)
53
66
  return;
54
67
  field.value = field.defaultValue;
55
68
  });
56
69
  }),
57
- }))));
70
+ })));
@@ -1,6 +1,6 @@
1
1
  import { InternalFormContextValue } from "../formContext";
2
- import { InternalFormId } from "./storeFamily";
2
+ import { InternalFormId } from "./types";
3
3
  export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
4
4
  export declare const useControllableValue: (context: InternalFormContextValue, field: string) => readonly [any, (value: unknown) => void];
5
- export declare const useUpdateControllableValue: (formId: InternalFormId) => (fieldName: string, value: unknown) => void;
6
- export declare const useAwaitValue: (formId: InternalFormId) => (fieldName: string) => Promise<void>;
5
+ export declare const useUpdateControllableValue: (formId: InternalFormId) => (field: string, value: unknown) => void;
6
+ export declare const useAwaitValue: (formId: InternalFormId) => (field: string) => Promise<void>;