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.
- package/.turbo/turbo-build.log +13 -10
- package/.turbo/turbo-dev.log +0 -95
- package/browser/ValidatedForm.js +24 -39
- package/browser/hooks.d.ts +1 -1
- package/browser/internal/hooks.d.ts +6 -3
- package/browser/internal/hooks.js +1 -0
- package/browser/internal/logic/nestedObjectToPathObject.d.ts +1 -0
- package/browser/internal/logic/nestedObjectToPathObject.js +47 -0
- package/browser/internal/state/arrayUtil.d.ts +6 -0
- package/browser/internal/state/arrayUtil.js +108 -0
- package/browser/internal/state/controlledFieldStore.d.ts +23 -21
- package/browser/internal/state/controlledFieldStore.js +32 -19
- package/browser/internal/state/controlledFields.d.ts +3 -3
- package/browser/internal/state/controlledFields.js +19 -21
- package/browser/internal/state/createFormStore.d.ts +16 -8
- package/browser/internal/state/createFormStore.js +62 -8
- package/browser/internal/state/fieldArray.d.ts +21 -0
- package/browser/internal/state/fieldArray.js +50 -0
- package/browser/internal/state/storeHooks.d.ts +1 -3
- package/browser/internal/state/storeHooks.js +2 -8
- package/browser/internal/state/types.d.ts +1 -0
- package/browser/internal/state/types.js +1 -0
- package/browser/unreleased/formStateHooks.d.ts +8 -2
- package/browser/unreleased/formStateHooks.js +12 -2
- package/browser/userFacingFormContext.d.ts +8 -2
- package/browser/userFacingFormContext.js +3 -1
- package/dist/remix-validated-form.cjs.js +3 -3
- package/dist/remix-validated-form.cjs.js.map +1 -1
- package/dist/remix-validated-form.es.js +158 -111
- package/dist/remix-validated-form.es.js.map +1 -1
- package/dist/remix-validated-form.umd.js +3 -3
- package/dist/remix-validated-form.umd.js.map +1 -1
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/internal/hooks.d.ts +3 -2
- package/dist/types/internal/state/controlledFieldStore.d.ts +23 -21
- package/dist/types/internal/state/controlledFields.d.ts +3 -3
- package/dist/types/internal/state/createFormStore.d.ts +16 -8
- package/dist/types/internal/state/storeHooks.d.ts +1 -3
- package/dist/types/internal/state/types.d.ts +1 -0
- package/dist/types/unreleased/formStateHooks.d.ts +8 -2
- package/dist/types/userFacingFormContext.d.ts +8 -2
- package/package.json +4 -4
- package/src/ValidatedForm.tsx +41 -56
- package/src/internal/hooks.ts +4 -1
- package/src/internal/state/controlledFieldStore.ts +95 -74
- package/src/internal/state/controlledFields.ts +38 -26
- package/src/internal/state/createFormStore.ts +199 -115
- package/src/internal/state/storeHooks.ts +3 -16
- package/src/internal/state/types.ts +1 -0
- package/src/unreleased/formStateHooks.ts +24 -3
- package/src/userFacingFormContext.ts +15 -2
- package/dist/types/internal/state/cleanup.d.ts +0 -2
- package/dist/types/internal/state/storeFamily.d.ts +0 -9
- package/src/internal/state/cleanup.ts +0 -8
- package/src/internal/state/storeFamily.ts +0 -24
package/.turbo/turbo-build.log
CHANGED
@@ -1,15 +1,18 @@
|
|
1
|
-
$ vite build
|
2
|
-
|
1
|
+
[2K[1G[2m$ vite build[22m
|
2
|
+
[36mvite v2.9.5 [32mbuilding for production...[36m[39m
|
3
3
|
transforming...
|
4
|
-
|
4
|
+
[32m✓[39m 320 modules transformed.
|
5
5
|
rendering chunks...
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
[
|
11
|
-
[
|
12
|
-
|
6
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.cjs.js [39m [2m47.23 KiB / gzip: 17.41 KiB[22m
|
7
|
+
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.cjs.js.map[39m [2m265.34 KiB[22m
|
8
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.es.js [39m [2m104.23 KiB / gzip: 24.22 KiB[22m
|
9
|
+
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.es.js.map[39m [2m273.50 KiB[22m
|
10
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.umd.js [39m [2m47.48 KiB / gzip: 17.53 KiB[22m
|
11
|
+
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.umd.js.map[39m [2m265.31 KiB[22m
|
12
|
+
[32m[39m
|
13
|
+
[32m[36m[vite:dts][39m[32m Start generate declaration files...[39m
|
14
|
+
[32m[36m[vite:dts][39m[32m Declaration files built in 1961ms.[39m
|
15
|
+
[32m[39m
|
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'
|
package/.turbo/turbo-dev.log
CHANGED
@@ -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
|
package/browser/ValidatedForm.js
CHANGED
@@ -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
|
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 {
|
11
|
-
import {
|
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(
|
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
|
-
|
124
|
-
|
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
|
-
|
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 =
|
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 ||
|
207
|
-
clickedButtonRef.current = null;
|
192
|
+
submit(submitter || target, { method, replace });
|
208
193
|
}
|
209
194
|
};
|
210
|
-
return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp
|
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:
|
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
|
}
|
package/browser/hooks.d.ts
CHANGED
@@ -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) => (
|
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/
|
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<
|
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
|
-
|
3
|
-
[
|
4
|
-
|
5
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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.
|
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
|
-
|
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
|
30
|
+
delete formState[field];
|
27
31
|
}),
|
28
|
-
|
29
|
-
|
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
|
-
|
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().
|
58
|
+
await ((_a = get().getField(formId, field)) === null || _a === void 0 ? void 0 : _a.valueUpdatePromise);
|
49
59
|
},
|
50
|
-
reset: () => set((state) => {
|
51
|
-
|
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 "./
|
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) => (
|
6
|
-
export declare const useAwaitValue: (formId: InternalFormId) => (
|
5
|
+
export declare const useUpdateControllableValue: (formId: InternalFormId) => (field: string, value: unknown) => void;
|
6
|
+
export declare const useAwaitValue: (formId: InternalFormId) => (field: string) => Promise<void>;
|