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.
- package/.turbo/turbo-build.log +2 -2
- package/browser/ValidatedForm.d.ts +1 -1
- package/browser/ValidatedForm.js +12 -6
- package/browser/internal/MultiValueMap.js +3 -3
- package/browser/internal/hooks-valtio.d.ts +18 -0
- package/browser/internal/hooks-valtio.js +110 -0
- package/browser/internal/hooks-zustand.d.ts +16 -0
- package/browser/internal/hooks-zustand.js +100 -0
- package/browser/internal/hydratable.d.ts +14 -0
- package/browser/internal/hydratable.js +14 -0
- package/browser/internal/immerMiddleware.d.ts +6 -0
- package/browser/internal/immerMiddleware.js +7 -0
- package/browser/internal/logic/getCheckboxChecked copy.d.ts +1 -0
- package/browser/internal/logic/getCheckboxChecked copy.js +9 -0
- package/browser/internal/logic/getCheckboxChecked.d.ts +1 -0
- package/browser/internal/logic/getCheckboxChecked.js +9 -0
- package/browser/internal/logic/getRadioChecked.d.ts +1 -0
- package/browser/internal/logic/getRadioChecked.js +5 -0
- package/browser/internal/logic/setFieldValue.d.ts +1 -0
- package/browser/internal/logic/setFieldValue.js +40 -0
- package/browser/internal/logic/setInputValueInForm.d.ts +1 -0
- package/browser/internal/logic/setInputValueInForm.js +120 -0
- package/browser/internal/setFieldValue.d.ts +20 -0
- package/browser/internal/setFieldValue.js +83 -0
- package/browser/internal/setFormValues.d.ts +2 -0
- package/browser/internal/setFormValues.js +26 -0
- package/browser/internal/state/atomUtils.d.ts +38 -0
- package/browser/internal/state/atomUtils.js +5 -0
- package/browser/internal/state/controlledFields.d.ts +66 -0
- package/browser/internal/state/controlledFields.js +93 -0
- package/browser/internal/state/setFieldValue.d.ts +0 -0
- package/browser/internal/state/setFieldValue.js +1 -0
- package/browser/internal/state-valtio.d.ts +62 -0
- package/browser/internal/state-valtio.js +69 -0
- package/browser/internal/state-zustand.d.ts +47 -0
- package/browser/internal/state-zustand.js +85 -0
- package/browser/internal/util.d.ts +1 -0
- package/browser/internal/util.js +12 -1
- package/build/ValidatedForm.d.ts +1 -1
- package/build/ValidatedForm.js +11 -5
- package/build/internal/MultiValueMap.js +2 -2
- package/build/internal/hooks-valtio.d.ts +18 -0
- package/build/internal/hooks-valtio.js +128 -0
- package/build/internal/hooks-zustand.d.ts +16 -0
- package/build/internal/hooks-zustand.js +117 -0
- package/build/internal/hydratable.d.ts +14 -0
- package/build/internal/hydratable.js +17 -0
- package/build/internal/immerMiddleware.d.ts +6 -0
- package/build/internal/immerMiddleware.js +14 -0
- package/build/internal/logic/getCheckboxChecked.d.ts +1 -0
- package/build/internal/logic/getCheckboxChecked.js +13 -0
- package/build/internal/logic/getRadioChecked.d.ts +1 -0
- package/build/internal/logic/getRadioChecked.js +9 -0
- package/build/internal/logic/setFieldValue.d.ts +1 -0
- package/build/internal/logic/setFieldValue.js +47 -0
- package/build/internal/logic/setInputValueInForm.d.ts +1 -0
- package/build/internal/logic/setInputValueInForm.js +127 -0
- package/build/internal/setFormValues.d.ts +2 -0
- package/build/internal/setFormValues.js +33 -0
- package/build/internal/state/atomUtils.d.ts +38 -0
- package/build/internal/state/atomUtils.js +13 -0
- package/build/internal/state/controlledFields.d.ts +66 -0
- package/build/internal/state/controlledFields.js +95 -0
- package/build/internal/state-valtio.d.ts +62 -0
- package/build/internal/state-valtio.js +83 -0
- package/build/internal/state-zustand.d.ts +47 -0
- package/build/internal/state-zustand.js +91 -0
- package/build/internal/util.d.ts +1 -0
- package/build/internal/util.js +16 -1
- package/package.json +5 -5
- package/src/ValidatedForm.tsx +13 -5
- package/src/internal/MultiValueMap.ts +3 -3
- package/src/internal/util.ts +13 -1
@@ -0,0 +1,38 @@
|
|
1
|
+
import { Atom } from "jotai";
|
2
|
+
export declare type InternalFormId = string | symbol;
|
3
|
+
export declare const formAtomFamily: <T>(data: T) => {
|
4
|
+
(param: InternalFormId): Atom<T> & {
|
5
|
+
write: (get: {
|
6
|
+
<Value>(atom: Atom<Value | Promise<Value>>): Value;
|
7
|
+
<Value_1>(atom: Atom<Promise<Value_1>>): Value_1;
|
8
|
+
<Value_2>(atom: Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
9
|
+
} & {
|
10
|
+
<Value_3>(atom: Atom<Value_3 | Promise<Value_3>>, options: {
|
11
|
+
unstable_promise: true;
|
12
|
+
}): Value_3 | Promise<Value_3>;
|
13
|
+
<Value_4>(atom: Atom<Promise<Value_4>>, options: {
|
14
|
+
unstable_promise: true;
|
15
|
+
}): Value_4 | Promise<Value_4>;
|
16
|
+
<Value_5>(atom: Atom<Value_5>, options: {
|
17
|
+
unstable_promise: true;
|
18
|
+
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
19
|
+
}, set: {
|
20
|
+
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
21
|
+
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
22
|
+
}, update: T | ((prev: T) => T)) => void;
|
23
|
+
onMount?: (<S extends import("jotai/core/atom").SetAtom<T | ((prev: T) => T), void>>(setAtom: S) => void | (() => void)) | undefined;
|
24
|
+
} & {
|
25
|
+
init: T;
|
26
|
+
};
|
27
|
+
remove(param: InternalFormId): void;
|
28
|
+
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
29
|
+
};
|
30
|
+
export declare type FieldAtomKey = {
|
31
|
+
formId: InternalFormId;
|
32
|
+
field: string;
|
33
|
+
};
|
34
|
+
export declare const fieldAtomFamily: <T extends Atom<unknown>>(func: (key: FieldAtomKey) => T) => {
|
35
|
+
(param: FieldAtomKey): T;
|
36
|
+
remove(param: FieldAtomKey): void;
|
37
|
+
setShouldRemove(shouldRemove: ((createdAt: number, param: FieldAtomKey) => boolean) | null): void;
|
38
|
+
};
|
@@ -0,0 +1,13 @@
|
|
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.fieldAtomFamily = exports.formAtomFamily = void 0;
|
7
|
+
const jotai_1 = require("jotai");
|
8
|
+
const utils_1 = require("jotai/utils");
|
9
|
+
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
10
|
+
const formAtomFamily = (data) => (0, utils_1.atomFamily)((_) => (0, jotai_1.atom)(data));
|
11
|
+
exports.formAtomFamily = formAtomFamily;
|
12
|
+
const fieldAtomFamily = (func) => (0, utils_1.atomFamily)(func, isEqual_1.default);
|
13
|
+
exports.fieldAtomFamily = fieldAtomFamily;
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { PrimitiveAtom } from "jotai";
|
2
|
+
import { InternalFormId } from "./atomUtils";
|
3
|
+
export declare type ValueSerializer = (val: unknown) => string;
|
4
|
+
export declare const controlledFieldsAtom: {
|
5
|
+
(param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
|
6
|
+
write: (get: {
|
7
|
+
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
8
|
+
<Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
|
9
|
+
<Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
10
|
+
} & {
|
11
|
+
<Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
|
12
|
+
unstable_promise: true;
|
13
|
+
}): Value_3 | Promise<Value_3>;
|
14
|
+
<Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
|
15
|
+
unstable_promise: true;
|
16
|
+
}): Value_4 | Promise<Value_4>;
|
17
|
+
<Value_5>(atom: import("jotai").Atom<Value_5>, options: {
|
18
|
+
unstable_promise: true;
|
19
|
+
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
20
|
+
}, set: {
|
21
|
+
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
22
|
+
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
23
|
+
}, update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void;
|
24
|
+
onMount?: (<S extends (update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void>(setAtom: S) => void | (() => void)) | undefined;
|
25
|
+
} & {
|
26
|
+
init: Record<string, PrimitiveAtom<unknown>>;
|
27
|
+
};
|
28
|
+
remove(param: InternalFormId): void;
|
29
|
+
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
30
|
+
};
|
31
|
+
export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
|
32
|
+
write: (get: {
|
33
|
+
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
34
|
+
<Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
|
35
|
+
<Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
36
|
+
} & {
|
37
|
+
<Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
|
38
|
+
unstable_promise: true;
|
39
|
+
}): Value_3 | Promise<Value_3>;
|
40
|
+
<Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
|
41
|
+
unstable_promise: true;
|
42
|
+
}): Value_4 | Promise<Value_4>;
|
43
|
+
<Value_5>(atom: import("jotai").Atom<Value_5>, options: {
|
44
|
+
unstable_promise: true;
|
45
|
+
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
46
|
+
}, set: {
|
47
|
+
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
48
|
+
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
49
|
+
}, update: {
|
50
|
+
formId: InternalFormId;
|
51
|
+
field: string;
|
52
|
+
value: unknown;
|
53
|
+
}) => Promise<void>;
|
54
|
+
onMount?: (<S extends (update: {
|
55
|
+
formId: InternalFormId;
|
56
|
+
field: string;
|
57
|
+
value: unknown;
|
58
|
+
}) => Promise<void>>(setAtom: S) => void | (() => void)) | undefined;
|
59
|
+
} & {
|
60
|
+
init: null;
|
61
|
+
};
|
62
|
+
export declare const useAllControlledFields: (formId: InternalFormId) => Record<string, PrimitiveAtom<unknown>>;
|
63
|
+
export declare const useControlledFieldValue: (formId: InternalFormId, field: string) => any;
|
64
|
+
export declare const useControllableValue: (formId: InternalFormId, field: string) => readonly [any, (value: unknown) => Promise<void>];
|
65
|
+
export declare const useFieldSerializer: (formId: InternalFormId, field: string) => ValueSerializer;
|
66
|
+
export declare const useRegisterFieldSerializer: (formId: InternalFormId, field: string, serializer?: ValueSerializer | undefined) => void;
|
@@ -0,0 +1,95 @@
|
|
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.useRegisterFieldSerializer = exports.useFieldSerializer = exports.useControllableValue = exports.useControlledFieldValue = exports.useAllControlledFields = exports.setControlledFieldValueAtom = exports.controlledFieldsAtom = void 0;
|
7
|
+
const jotai_1 = require("jotai");
|
8
|
+
const omit_1 = __importDefault(require("lodash/omit"));
|
9
|
+
const react_1 = require("react");
|
10
|
+
const hooks_1 = require("../hooks");
|
11
|
+
const state_1 = require("../state");
|
12
|
+
const atomUtils_1 = require("./atomUtils");
|
13
|
+
exports.controlledFieldsAtom = (0, atomUtils_1.formAtomFamily)({});
|
14
|
+
const refCountAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(0));
|
15
|
+
const fieldValueAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
|
16
|
+
const fieldValueHydratedAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(false));
|
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 }));
|
19
|
+
const registerAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
|
20
|
+
set(refCountAtom({ formId, field }), (prev) => prev + 1);
|
21
|
+
const newRefCount = get(refCountAtom({ formId, field }));
|
22
|
+
if (newRefCount === 1) {
|
23
|
+
set((0, exports.controlledFieldsAtom)(formId), (prev) => ({
|
24
|
+
...prev,
|
25
|
+
[field]: fieldValueAtom({ formId, field }),
|
26
|
+
}));
|
27
|
+
}
|
28
|
+
});
|
29
|
+
const unregisterAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
|
30
|
+
set(refCountAtom({ formId, field }), (prev) => prev - 1);
|
31
|
+
const newRefCount = get(refCountAtom({ formId, field }));
|
32
|
+
if (newRefCount === 0) {
|
33
|
+
set((0, exports.controlledFieldsAtom)(formId), (prev) => (0, omit_1.default)(prev, field));
|
34
|
+
fieldValueAtom.remove({ formId, field });
|
35
|
+
pendingValidateAtom.remove({ formId, field });
|
36
|
+
}
|
37
|
+
});
|
38
|
+
exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, async (_get, set, { formId, field, value, }) => {
|
39
|
+
set(fieldValueAtom({ formId, field }), value);
|
40
|
+
const pending = pendingValidateAtom({ formId, field });
|
41
|
+
await new Promise((resolve) => set(pending, resolve));
|
42
|
+
set(pending, undefined);
|
43
|
+
});
|
44
|
+
const useAllControlledFields = (formId) => (0, hooks_1.useFormAtomValue)((0, exports.controlledFieldsAtom)(formId));
|
45
|
+
exports.useAllControlledFields = useAllControlledFields;
|
46
|
+
const useControlledFieldValue = (formId, field) => {
|
47
|
+
const fieldAtom = fieldValueAtom({ formId, field });
|
48
|
+
const [value, setValue] = (0, hooks_1.useFormAtom)(fieldAtom);
|
49
|
+
const defaultValue = (0, hooks_1.useFieldDefaultValue)(field, { formId });
|
50
|
+
const isHydrated = (0, hooks_1.useFormAtomValue)((0, state_1.isHydratedAtom)(formId));
|
51
|
+
const [isFieldHydrated, setIsFieldHydrated] = (0, hooks_1.useFormAtom)(fieldValueHydratedAtom({ formId, field }));
|
52
|
+
(0, react_1.useEffect)(() => {
|
53
|
+
if (isHydrated && !isFieldHydrated) {
|
54
|
+
setValue({ formId, field, value: defaultValue });
|
55
|
+
setIsFieldHydrated(true);
|
56
|
+
}
|
57
|
+
}, [
|
58
|
+
defaultValue,
|
59
|
+
field,
|
60
|
+
formId,
|
61
|
+
isFieldHydrated,
|
62
|
+
isHydrated,
|
63
|
+
setIsFieldHydrated,
|
64
|
+
setValue,
|
65
|
+
]);
|
66
|
+
return isFieldHydrated ? value : defaultValue;
|
67
|
+
};
|
68
|
+
exports.useControlledFieldValue = useControlledFieldValue;
|
69
|
+
const useControllableValue = (formId, field) => {
|
70
|
+
const pending = (0, hooks_1.useFormAtomValue)(pendingValidateAtom({ formId, field }));
|
71
|
+
(0, react_1.useEffect)(() => {
|
72
|
+
pending === null || pending === void 0 ? void 0 : pending();
|
73
|
+
}, [pending]);
|
74
|
+
const register = (0, hooks_1.useFormUpdateAtom)(registerAtom);
|
75
|
+
const unregister = (0, hooks_1.useFormUpdateAtom)(unregisterAtom);
|
76
|
+
(0, react_1.useEffect)(() => {
|
77
|
+
register({ formId, field });
|
78
|
+
return () => unregister({ formId, field });
|
79
|
+
}, [field, formId, register, unregister]);
|
80
|
+
const setControlledFieldValue = (0, hooks_1.useFormUpdateAtom)(exports.setControlledFieldValueAtom);
|
81
|
+
const setValue = (0, react_1.useCallback)((value) => setControlledFieldValue({ formId, field, value }), [field, formId, setControlledFieldValue]);
|
82
|
+
const value = (0, exports.useControlledFieldValue)(formId, field);
|
83
|
+
return [value, setValue];
|
84
|
+
};
|
85
|
+
exports.useControllableValue = useControllableValue;
|
86
|
+
const useFieldSerializer = (formId, field) => (0, hooks_1.useFormAtomValue)(valueSerializerAtom({ formId, field })).serializer;
|
87
|
+
exports.useFieldSerializer = useFieldSerializer;
|
88
|
+
const useRegisterFieldSerializer = (formId, field, serializer) => {
|
89
|
+
const setSerializer = (0, hooks_1.useFormUpdateAtom)(valueSerializerAtom({ formId, field }));
|
90
|
+
(0, react_1.useEffect)(() => {
|
91
|
+
if (serializer)
|
92
|
+
setSerializer({ serializer });
|
93
|
+
}, [serializer, setSerializer]);
|
94
|
+
};
|
95
|
+
exports.useRegisterFieldSerializer = useRegisterFieldSerializer;
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import { FieldErrors, TouchedFields } from "..";
|
2
|
+
export declare type InternalFormState = {
|
3
|
+
hydrated: boolean;
|
4
|
+
fieldErrors: FieldErrors;
|
5
|
+
isSubmitting: boolean;
|
6
|
+
hasBeenSubmitted: boolean;
|
7
|
+
touchedFields: TouchedFields;
|
8
|
+
action?: string;
|
9
|
+
subaction?: string;
|
10
|
+
defaultValues: {
|
11
|
+
[fieldName: string]: any;
|
12
|
+
};
|
13
|
+
validateField: (fieldName: string) => Promise<string | null>;
|
14
|
+
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
15
|
+
setFieldValue: (fieldName: string, value: unknown) => void;
|
16
|
+
};
|
17
|
+
declare type SyncFormArgs = {
|
18
|
+
defaultValues?: {
|
19
|
+
[fieldName: string]: any;
|
20
|
+
};
|
21
|
+
action?: string;
|
22
|
+
subaction?: string;
|
23
|
+
validateField: InternalFormState["validateField"];
|
24
|
+
registerReceiveFocus: InternalFormState["registerReceiveFocus"];
|
25
|
+
setFieldValueForForm: InternalFormState["setFieldValue"];
|
26
|
+
};
|
27
|
+
declare type StoreState = {
|
28
|
+
forms: {
|
29
|
+
[formId: string | symbol]: InternalFormState;
|
30
|
+
};
|
31
|
+
};
|
32
|
+
export declare const state: StoreState;
|
33
|
+
export declare const registerFormSlice: (formId: string | symbol, { registerReceiveFocus, setFieldValueForForm, validateField, action, defaultValues, subaction, }: SyncFormArgs) => void;
|
34
|
+
export declare const unregisterFormSlice: (formId: string | symbol) => void;
|
35
|
+
export declare const useFormData: (formId: string | symbol) => {
|
36
|
+
readonly hydrated: boolean;
|
37
|
+
readonly fieldErrors: {
|
38
|
+
readonly [x: string]: string;
|
39
|
+
};
|
40
|
+
readonly isSubmitting: boolean;
|
41
|
+
readonly hasBeenSubmitted: boolean;
|
42
|
+
readonly touchedFields: {
|
43
|
+
readonly [x: string]: boolean;
|
44
|
+
};
|
45
|
+
readonly action?: string | undefined;
|
46
|
+
readonly subaction?: string | undefined;
|
47
|
+
readonly defaultValues: {
|
48
|
+
readonly [x: string]: any;
|
49
|
+
};
|
50
|
+
readonly validateField: (fieldName: string) => Promise<string | null>;
|
51
|
+
readonly registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
52
|
+
readonly setFieldValue: (fieldName: string, value: unknown) => void;
|
53
|
+
};
|
54
|
+
export declare const startSubmit: (formId: string | symbol) => void;
|
55
|
+
export declare const endSubmit: (formId: string | symbol) => void;
|
56
|
+
export declare const sync: (formId: string | symbol, { defaultValues, action, subaction, registerReceiveFocus, validateField, setFieldValueForForm, }: SyncFormArgs) => void;
|
57
|
+
export declare const clearError: (formId: string | symbol, fieldName: string) => void;
|
58
|
+
export declare const addError: (formId: string | symbol, fieldName: string, error: string) => void;
|
59
|
+
export declare const setTouched: (formId: string | symbol, fieldName: string, touched: boolean) => void;
|
60
|
+
export declare const reset: (formId: string | symbol) => void;
|
61
|
+
export declare const setFieldErrors: (formId: string | symbol, fieldErrors: FieldErrors) => void;
|
62
|
+
export {};
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.setFieldErrors = exports.reset = exports.setTouched = exports.addError = exports.clearError = exports.sync = exports.endSubmit = exports.startSubmit = exports.useFormData = exports.unregisterFormSlice = exports.registerFormSlice = exports.state = void 0;
|
4
|
+
const valtio_1 = require("valtio");
|
5
|
+
exports.state = (0, valtio_1.proxy)({ forms: {} });
|
6
|
+
const registerFormSlice = (formId, { registerReceiveFocus, setFieldValueForForm, validateField, action, defaultValues, subaction, }) => {
|
7
|
+
exports.state.forms[formId] = {
|
8
|
+
hydrated: true,
|
9
|
+
defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
|
10
|
+
fieldErrors: {},
|
11
|
+
hasBeenSubmitted: false,
|
12
|
+
isSubmitting: false,
|
13
|
+
touchedFields: {},
|
14
|
+
registerReceiveFocus,
|
15
|
+
setFieldValue: setFieldValueForForm,
|
16
|
+
validateField,
|
17
|
+
action,
|
18
|
+
subaction,
|
19
|
+
};
|
20
|
+
};
|
21
|
+
exports.registerFormSlice = registerFormSlice;
|
22
|
+
const unregisterFormSlice = (formId) => {
|
23
|
+
delete exports.state.forms[formId];
|
24
|
+
};
|
25
|
+
exports.unregisterFormSlice = unregisterFormSlice;
|
26
|
+
const unhydratedFormState = {
|
27
|
+
hydrated: false,
|
28
|
+
fieldErrors: {},
|
29
|
+
isSubmitting: false,
|
30
|
+
hasBeenSubmitted: false,
|
31
|
+
touchedFields: {},
|
32
|
+
defaultValues: {},
|
33
|
+
validateField: () => Promise.resolve(null),
|
34
|
+
registerReceiveFocus: () => () => { },
|
35
|
+
setFieldValue: () => { },
|
36
|
+
};
|
37
|
+
const useFormData = (formId) => {
|
38
|
+
var _a;
|
39
|
+
const snapshot = (0, valtio_1.useSnapshot)(exports.state);
|
40
|
+
return (_a = snapshot.forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState;
|
41
|
+
};
|
42
|
+
exports.useFormData = useFormData;
|
43
|
+
const startSubmit = (formId) => {
|
44
|
+
exports.state.forms[formId].isSubmitting = true;
|
45
|
+
exports.state.forms[formId].hasBeenSubmitted = true;
|
46
|
+
};
|
47
|
+
exports.startSubmit = startSubmit;
|
48
|
+
const endSubmit = (formId) => {
|
49
|
+
exports.state.forms[formId].isSubmitting = false;
|
50
|
+
};
|
51
|
+
exports.endSubmit = endSubmit;
|
52
|
+
const sync = (formId, { defaultValues, action, subaction, registerReceiveFocus, validateField, setFieldValueForForm, }) => {
|
53
|
+
exports.state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
|
54
|
+
exports.state.forms[formId].action = action;
|
55
|
+
exports.state.forms[formId].subaction = subaction;
|
56
|
+
exports.state.forms[formId].registerReceiveFocus = registerReceiveFocus;
|
57
|
+
exports.state.forms[formId].validateField = validateField;
|
58
|
+
exports.state.forms[formId].hydrated = true;
|
59
|
+
exports.state.forms[formId].setFieldValue = setFieldValueForForm;
|
60
|
+
};
|
61
|
+
exports.sync = sync;
|
62
|
+
const clearError = (formId, fieldName) => {
|
63
|
+
delete exports.state.forms[formId].fieldErrors[fieldName];
|
64
|
+
};
|
65
|
+
exports.clearError = clearError;
|
66
|
+
const addError = (formId, fieldName, error) => {
|
67
|
+
exports.state.forms[formId].fieldErrors[fieldName] = error;
|
68
|
+
};
|
69
|
+
exports.addError = addError;
|
70
|
+
const setTouched = (formId, fieldName, touched) => {
|
71
|
+
exports.state.forms[formId].touchedFields[fieldName] = touched;
|
72
|
+
};
|
73
|
+
exports.setTouched = setTouched;
|
74
|
+
const reset = (formId) => {
|
75
|
+
exports.state.forms[formId].fieldErrors = {};
|
76
|
+
exports.state.forms[formId].touchedFields = {};
|
77
|
+
exports.state.forms[formId].hasBeenSubmitted = false;
|
78
|
+
};
|
79
|
+
exports.reset = reset;
|
80
|
+
const setFieldErrors = (formId, fieldErrors) => {
|
81
|
+
exports.state.forms[formId].fieldErrors = fieldErrors;
|
82
|
+
};
|
83
|
+
exports.setFieldErrors = setFieldErrors;
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { FieldErrors, TouchedFields } from "..";
|
2
|
+
export declare type InternalFormState = {
|
3
|
+
hydrated: boolean;
|
4
|
+
fieldErrors: FieldErrors;
|
5
|
+
isSubmitting: boolean;
|
6
|
+
hasBeenSubmitted: boolean;
|
7
|
+
touchedFields: TouchedFields;
|
8
|
+
action?: string;
|
9
|
+
subaction?: string;
|
10
|
+
defaultValues: {
|
11
|
+
[fieldName: string]: any;
|
12
|
+
};
|
13
|
+
validateField: (fieldName: string) => Promise<string | null>;
|
14
|
+
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
15
|
+
setFieldValue: (fieldName: string, value: unknown) => void;
|
16
|
+
};
|
17
|
+
declare type Helpers = {
|
18
|
+
startSubmit: () => void;
|
19
|
+
endSubmit: () => void;
|
20
|
+
sync: (args: SyncFormArgs) => void;
|
21
|
+
clearError: (name: string) => void;
|
22
|
+
addError: (name: string, error: string) => void;
|
23
|
+
setTouched: (name: string, touched: boolean) => void;
|
24
|
+
reset: () => void;
|
25
|
+
setFieldErrors: (fieldErrors: FieldErrors) => void;
|
26
|
+
register: (init: SyncFormArgs) => void;
|
27
|
+
unregister: () => void;
|
28
|
+
};
|
29
|
+
declare type SyncFormArgs = {
|
30
|
+
defaultValues?: {
|
31
|
+
[fieldName: string]: any;
|
32
|
+
};
|
33
|
+
action?: string;
|
34
|
+
subaction?: string;
|
35
|
+
validateField: InternalFormState["validateField"];
|
36
|
+
registerReceiveFocus: InternalFormState["registerReceiveFocus"];
|
37
|
+
setFieldValueForForm: InternalFormState["setFieldValue"];
|
38
|
+
};
|
39
|
+
declare type StoreState = {
|
40
|
+
forms: {
|
41
|
+
[formId: string | symbol]: InternalFormState;
|
42
|
+
};
|
43
|
+
form: (formId: string | symbol) => InternalFormState;
|
44
|
+
helpers: (formId: string | symbol) => Helpers;
|
45
|
+
};
|
46
|
+
export declare const useStore: import("zustand").UseBoundStore<StoreState, import("zustand").StoreApi<StoreState>>;
|
47
|
+
export {};
|
@@ -0,0 +1,91 @@
|
|
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.useStore = void 0;
|
7
|
+
const zustand_1 = __importDefault(require("zustand"));
|
8
|
+
const immerMiddleware_1 = require("./immerMiddleware");
|
9
|
+
const unhydratedFormState = {
|
10
|
+
hydrated: false,
|
11
|
+
fieldErrors: {},
|
12
|
+
isSubmitting: false,
|
13
|
+
hasBeenSubmitted: false,
|
14
|
+
touchedFields: {},
|
15
|
+
defaultValues: {},
|
16
|
+
validateField: () => Promise.resolve(null),
|
17
|
+
registerReceiveFocus: () => () => { },
|
18
|
+
setFieldValue: () => { },
|
19
|
+
// clearError: () => {},
|
20
|
+
// addError: () => {},
|
21
|
+
// setTouched: () => {},
|
22
|
+
// reset: () => {},
|
23
|
+
// startSubmit: () => {},
|
24
|
+
// endSubmit: () => {},
|
25
|
+
// sync: () => {},
|
26
|
+
// setFieldErrors: () => {},
|
27
|
+
};
|
28
|
+
exports.useStore = (0, zustand_1.default)((0, immerMiddleware_1.immer)((set, get) => ({
|
29
|
+
forms: {},
|
30
|
+
form: (formId) => { var _a; return (_a = get().forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState; },
|
31
|
+
helpers: (formId) => ({
|
32
|
+
clearError: (name) => set((state) => {
|
33
|
+
delete state.forms[formId].fieldErrors[name];
|
34
|
+
}),
|
35
|
+
addError: (name, error) => set((state) => {
|
36
|
+
state.forms[formId].fieldErrors[name] = error;
|
37
|
+
}),
|
38
|
+
setTouched: (name, touched) => set((state) => {
|
39
|
+
state.forms[formId].touchedFields[name] = touched;
|
40
|
+
}),
|
41
|
+
reset: () => set((state) => {
|
42
|
+
state.forms[formId].fieldErrors = {};
|
43
|
+
state.forms[formId].touchedFields = {};
|
44
|
+
state.forms[formId].hasBeenSubmitted = false;
|
45
|
+
}),
|
46
|
+
startSubmit: () => set((state) => {
|
47
|
+
state.forms[formId].hasBeenSubmitted = true;
|
48
|
+
state.forms[formId].isSubmitting = true;
|
49
|
+
}),
|
50
|
+
endSubmit: () => set((state) => {
|
51
|
+
state.forms[formId].isSubmitting = false;
|
52
|
+
}),
|
53
|
+
setFieldErrors: (fieldErrors) => {
|
54
|
+
set((state) => {
|
55
|
+
state.forms[formId].fieldErrors = fieldErrors;
|
56
|
+
});
|
57
|
+
},
|
58
|
+
sync: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => set((state) => {
|
59
|
+
state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
|
60
|
+
state.forms[formId].action = action;
|
61
|
+
state.forms[formId].subaction = subaction;
|
62
|
+
state.forms[formId].registerReceiveFocus = registerReceiveFocus;
|
63
|
+
state.forms[formId].validateField = validateField;
|
64
|
+
state.forms[formId].hydrated = true;
|
65
|
+
state.forms[formId].setFieldValue = setFieldValueForForm;
|
66
|
+
}),
|
67
|
+
unregister: () => {
|
68
|
+
set((state) => {
|
69
|
+
delete state.forms[formId];
|
70
|
+
});
|
71
|
+
},
|
72
|
+
register: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => {
|
73
|
+
set((state) => {
|
74
|
+
state.forms[formId] = {
|
75
|
+
defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
|
76
|
+
setFieldValue: setFieldValueForForm,
|
77
|
+
registerReceiveFocus,
|
78
|
+
validateField,
|
79
|
+
action,
|
80
|
+
subaction,
|
81
|
+
hydrated: true,
|
82
|
+
fieldErrors: {},
|
83
|
+
isSubmitting: false,
|
84
|
+
hasBeenSubmitted: false,
|
85
|
+
touchedFields: {},
|
86
|
+
// helpers
|
87
|
+
};
|
88
|
+
});
|
89
|
+
},
|
90
|
+
}),
|
91
|
+
})));
|
package/build/internal/util.d.ts
CHANGED
@@ -2,3 +2,4 @@ import type React from "react";
|
|
2
2
|
export declare const omit: (obj: any, ...keys: string[]) => any;
|
3
3
|
export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
|
4
4
|
export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
|
5
|
+
export declare const useDeepEqualsMemo: <T>(item: T) => T;
|
package/build/internal/util.js
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
|
6
|
+
exports.useDeepEqualsMemo = exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
|
7
|
+
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
4
8
|
const react_1 = require("react");
|
5
9
|
const omit = (obj, ...keys) => {
|
6
10
|
const result = { ...obj };
|
@@ -24,3 +28,14 @@ const mergeRefs = (refs) => {
|
|
24
28
|
};
|
25
29
|
exports.mergeRefs = mergeRefs;
|
26
30
|
exports.useIsomorphicLayoutEffect = typeof window !== "undefined" ? react_1.useLayoutEffect : react_1.useEffect;
|
31
|
+
const useDeepEqualsMemo = (item) => {
|
32
|
+
const ref = (0, react_1.useRef)(item);
|
33
|
+
const areEqual = ref.current === item || (0, isEqual_1.default)(ref.current, item);
|
34
|
+
(0, react_1.useEffect)(() => {
|
35
|
+
if (!areEqual) {
|
36
|
+
ref.current = item;
|
37
|
+
}
|
38
|
+
});
|
39
|
+
return areEqual ? ref.current : item;
|
40
|
+
};
|
41
|
+
exports.useDeepEqualsMemo = useDeepEqualsMemo;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "remix-validated-form",
|
3
|
-
"version": "4.1.0",
|
3
|
+
"version": "4.1.4-beta.0",
|
4
4
|
"description": "Form component and utils for easy form validation in remix",
|
5
5
|
"browser": "./browser/index.js",
|
6
6
|
"main": "./build/index.js",
|
@@ -33,13 +33,13 @@
|
|
33
33
|
"validation"
|
34
34
|
],
|
35
35
|
"peerDependencies": {
|
36
|
-
"@remix-run/react": "
|
37
|
-
"@remix-run/server-runtime": "
|
36
|
+
"@remix-run/react": "1.x",
|
37
|
+
"@remix-run/server-runtime": "1.x",
|
38
38
|
"react": "^17.0.2"
|
39
39
|
},
|
40
40
|
"devDependencies": {
|
41
|
-
"@remix-run/react": "^1.
|
42
|
-
"@remix-run/server-runtime": "^1.
|
41
|
+
"@remix-run/react": "^1.2.1",
|
42
|
+
"@remix-run/server-runtime": "^1.2.1",
|
43
43
|
"@types/lodash": "^4.14.178",
|
44
44
|
"@types/react": "^17.0.37",
|
45
45
|
"fetch-blob": "^3.1.3",
|
package/src/ValidatedForm.tsx
CHANGED
@@ -38,6 +38,7 @@ import {
|
|
38
38
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
39
39
|
import {
|
40
40
|
mergeRefs,
|
41
|
+
useDeepEqualsMemo,
|
41
42
|
useIsomorphicLayoutEffect as useLayoutEffect,
|
42
43
|
} from "./internal/util";
|
43
44
|
import { FieldErrors, Validator } from "./validation/types";
|
@@ -206,7 +207,7 @@ export function ValidatedForm<DataType>({
|
|
206
207
|
children,
|
207
208
|
fetcher,
|
208
209
|
action,
|
209
|
-
defaultValues:
|
210
|
+
defaultValues: unMemoizedDefaults,
|
210
211
|
formRef: formRefProp,
|
211
212
|
onReset,
|
212
213
|
subaction,
|
@@ -219,6 +220,7 @@ export function ValidatedForm<DataType>({
|
|
219
220
|
}: FormProps<DataType>) {
|
220
221
|
const formId = useFormId(id);
|
221
222
|
const formAtom = useFormAtom(formId);
|
223
|
+
const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
|
222
224
|
const contextValue = useMemo<InternalFormContextValue>(
|
223
225
|
() => ({
|
224
226
|
formId,
|
@@ -325,9 +327,9 @@ export function ValidatedForm<DataType>({
|
|
325
327
|
}
|
326
328
|
}
|
327
329
|
|
328
|
-
window.addEventListener("click", handleClick);
|
330
|
+
window.addEventListener("click", handleClick, { capture: true });
|
329
331
|
return () => {
|
330
|
-
window.removeEventListener("click", handleClick);
|
332
|
+
window.removeEventListener("click", handleClick, { capture: true });
|
331
333
|
};
|
332
334
|
}, []);
|
333
335
|
|
@@ -352,12 +354,18 @@ export function ValidatedForm<DataType>({
|
|
352
354
|
return;
|
353
355
|
}
|
354
356
|
|
355
|
-
|
357
|
+
// We deviate from the remix code here a bit because of our async submit.
|
358
|
+
// In remix's `FormImpl`, they use `event.currentTarget` to get the form,
|
359
|
+
// but we already have the form in `formRef.current` so we can just use that.
|
360
|
+
// If we use `event.currentTarget` here, it will break because `currentTarget`
|
361
|
+
// will have changed since the start of the submission.
|
362
|
+
if (fetcher) fetcher.submit(clickedButtonRef.current || formRef.current);
|
356
363
|
else
|
357
|
-
submit(clickedButtonRef.current ||
|
364
|
+
submit(clickedButtonRef.current || formRef.current, {
|
358
365
|
method,
|
359
366
|
replace,
|
360
367
|
});
|
368
|
+
|
361
369
|
clickedButtonRef.current = null;
|
362
370
|
}
|
363
371
|
};
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { useRef } from "react";
|
1
|
+
import { useCallback, useRef } from "react";
|
2
2
|
|
3
3
|
export class MultiValueMap<Key, Value> {
|
4
4
|
private dict: Map<Key, Value[]> = new Map();
|
@@ -30,9 +30,9 @@ export class MultiValueMap<Key, Value> {
|
|
30
30
|
|
31
31
|
export const useMultiValueMap = <Key, Value>() => {
|
32
32
|
const ref = useRef<MultiValueMap<Key, Value> | null>(null);
|
33
|
-
return () => {
|
33
|
+
return useCallback(() => {
|
34
34
|
if (ref.current) return ref.current;
|
35
35
|
ref.current = new MultiValueMap();
|
36
36
|
return ref.current;
|
37
|
-
};
|
37
|
+
}, []);
|
38
38
|
};
|
package/src/internal/util.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
import isEqual from "lodash/isEqual";
|
1
2
|
import type React from "react";
|
2
|
-
import { useEffect, useLayoutEffect } from "react";
|
3
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
3
4
|
|
4
5
|
export const omit = (obj: any, ...keys: string[]) => {
|
5
6
|
const result = { ...obj };
|
@@ -25,3 +26,14 @@ export const mergeRefs = <T = any>(
|
|
25
26
|
|
26
27
|
export const useIsomorphicLayoutEffect =
|
27
28
|
typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
29
|
+
|
30
|
+
export const useDeepEqualsMemo = <T>(item: T): T => {
|
31
|
+
const ref = useRef<T>(item);
|
32
|
+
const areEqual = ref.current === item || isEqual(ref.current, item);
|
33
|
+
useEffect(() => {
|
34
|
+
if (!areEqual) {
|
35
|
+
ref.current = item;
|
36
|
+
}
|
37
|
+
});
|
38
|
+
return areEqual ? ref.current : item;
|
39
|
+
};
|