remix-validated-form 4.1.0-beta.2 → 4.1.2
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 +9 -3
- 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 +40 -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 +8 -2
- 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 +47 -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 +1 -1
- package/src/ValidatedForm.tsx +10 -2
- package/src/internal/MultiValueMap.ts +3 -3
- package/src/internal/util.ts +13 -1
@@ -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,85 @@
|
|
1
|
+
import create from "zustand";
|
2
|
+
import { immer } from "./immerMiddleware";
|
3
|
+
const unhydratedFormState = {
|
4
|
+
hydrated: false,
|
5
|
+
fieldErrors: {},
|
6
|
+
isSubmitting: false,
|
7
|
+
hasBeenSubmitted: false,
|
8
|
+
touchedFields: {},
|
9
|
+
defaultValues: {},
|
10
|
+
validateField: () => Promise.resolve(null),
|
11
|
+
registerReceiveFocus: () => () => { },
|
12
|
+
setFieldValue: () => { },
|
13
|
+
// clearError: () => {},
|
14
|
+
// addError: () => {},
|
15
|
+
// setTouched: () => {},
|
16
|
+
// reset: () => {},
|
17
|
+
// startSubmit: () => {},
|
18
|
+
// endSubmit: () => {},
|
19
|
+
// sync: () => {},
|
20
|
+
// setFieldErrors: () => {},
|
21
|
+
};
|
22
|
+
export const useStore = create(immer((set, get) => ({
|
23
|
+
forms: {},
|
24
|
+
form: (formId) => { var _a; return (_a = get().forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState; },
|
25
|
+
helpers: (formId) => ({
|
26
|
+
clearError: (name) => set((state) => {
|
27
|
+
delete state.forms[formId].fieldErrors[name];
|
28
|
+
}),
|
29
|
+
addError: (name, error) => set((state) => {
|
30
|
+
state.forms[formId].fieldErrors[name] = error;
|
31
|
+
}),
|
32
|
+
setTouched: (name, touched) => set((state) => {
|
33
|
+
state.forms[formId].touchedFields[name] = touched;
|
34
|
+
}),
|
35
|
+
reset: () => set((state) => {
|
36
|
+
state.forms[formId].fieldErrors = {};
|
37
|
+
state.forms[formId].touchedFields = {};
|
38
|
+
state.forms[formId].hasBeenSubmitted = false;
|
39
|
+
}),
|
40
|
+
startSubmit: () => set((state) => {
|
41
|
+
state.forms[formId].hasBeenSubmitted = true;
|
42
|
+
state.forms[formId].isSubmitting = true;
|
43
|
+
}),
|
44
|
+
endSubmit: () => set((state) => {
|
45
|
+
state.forms[formId].isSubmitting = false;
|
46
|
+
}),
|
47
|
+
setFieldErrors: (fieldErrors) => {
|
48
|
+
set((state) => {
|
49
|
+
state.forms[formId].fieldErrors = fieldErrors;
|
50
|
+
});
|
51
|
+
},
|
52
|
+
sync: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => set((state) => {
|
53
|
+
state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
|
54
|
+
state.forms[formId].action = action;
|
55
|
+
state.forms[formId].subaction = subaction;
|
56
|
+
state.forms[formId].registerReceiveFocus = registerReceiveFocus;
|
57
|
+
state.forms[formId].validateField = validateField;
|
58
|
+
state.forms[formId].hydrated = true;
|
59
|
+
state.forms[formId].setFieldValue = setFieldValueForForm;
|
60
|
+
}),
|
61
|
+
unregister: () => {
|
62
|
+
set((state) => {
|
63
|
+
delete state.forms[formId];
|
64
|
+
});
|
65
|
+
},
|
66
|
+
register: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => {
|
67
|
+
set((state) => {
|
68
|
+
state.forms[formId] = {
|
69
|
+
defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
|
70
|
+
setFieldValue: setFieldValueForForm,
|
71
|
+
registerReceiveFocus,
|
72
|
+
validateField,
|
73
|
+
action,
|
74
|
+
subaction,
|
75
|
+
hydrated: true,
|
76
|
+
fieldErrors: {},
|
77
|
+
isSubmitting: false,
|
78
|
+
hasBeenSubmitted: false,
|
79
|
+
touchedFields: {},
|
80
|
+
// helpers
|
81
|
+
};
|
82
|
+
});
|
83
|
+
},
|
84
|
+
}),
|
85
|
+
})));
|
@@ -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/browser/internal/util.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import
|
1
|
+
import isEqual from "lodash/isEqual";
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
2
3
|
export const omit = (obj, ...keys) => {
|
3
4
|
const result = { ...obj };
|
4
5
|
for (const key of keys) {
|
@@ -19,3 +20,13 @@ export const mergeRefs = (refs) => {
|
|
19
20
|
};
|
20
21
|
};
|
21
22
|
export const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
23
|
+
export const useDeepEqualsMemo = (item) => {
|
24
|
+
const ref = useRef(item);
|
25
|
+
const areEqual = ref.current === item || isEqual(ref.current, item);
|
26
|
+
useEffect(() => {
|
27
|
+
if (!areEqual) {
|
28
|
+
ref.current = item;
|
29
|
+
}
|
30
|
+
});
|
31
|
+
return areEqual ? ref.current : item;
|
32
|
+
};
|
package/build/ValidatedForm.d.ts
CHANGED
@@ -47,4 +47,4 @@ export declare type FormProps<DataType> = {
|
|
47
47
|
/**
|
48
48
|
* The primary form component of `remix-validated-form`.
|
49
49
|
*/
|
50
|
-
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues:
|
50
|
+
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, ...rest }: FormProps<DataType>): JSX.Element;
|
package/build/ValidatedForm.js
CHANGED
@@ -116,13 +116,19 @@ function formEventProxy(event) {
|
|
116
116
|
},
|
117
117
|
});
|
118
118
|
}
|
119
|
+
const useFormAtom = (formId) => {
|
120
|
+
const formAtom = (0, state_1.formRegistry)(formId);
|
121
|
+
(0, react_2.useEffect)(() => () => state_1.formRegistry.remove(formId), [formId]);
|
122
|
+
return formAtom;
|
123
|
+
};
|
119
124
|
/**
|
120
125
|
* The primary form component of `remix-validated-form`.
|
121
126
|
*/
|
122
|
-
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues:
|
127
|
+
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
|
123
128
|
var _a;
|
124
129
|
const formId = useFormId(id);
|
125
|
-
const formAtom = (
|
130
|
+
const formAtom = useFormAtom(formId);
|
131
|
+
const providedDefaultValues = (0, util_1.useDeepEqualsMemo)(unMemoizedDefaults);
|
126
132
|
const contextValue = (0, react_2.useMemo)(() => ({
|
127
133
|
formId,
|
128
134
|
action,
|
@@ -34,11 +34,11 @@ class MultiValueMap {
|
|
34
34
|
exports.MultiValueMap = MultiValueMap;
|
35
35
|
const useMultiValueMap = () => {
|
36
36
|
const ref = (0, react_1.useRef)(null);
|
37
|
-
return () => {
|
37
|
+
return (0, react_1.useCallback)(() => {
|
38
38
|
if (ref.current)
|
39
39
|
return ref.current;
|
40
40
|
ref.current = new MultiValueMap();
|
41
41
|
return ref.current;
|
42
|
-
};
|
42
|
+
}, []);
|
43
43
|
};
|
44
44
|
exports.useMultiValueMap = useMultiValueMap;
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { useUpdateAtom } from "jotai/utils";
|
2
|
+
import { FieldErrors, ValidationErrorResponseData } from "..";
|
3
|
+
import { InternalFormContextValue } from "./formContext";
|
4
|
+
import { Hydratable } from "./hydratable";
|
5
|
+
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
6
|
+
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
7
|
+
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
|
8
|
+
export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
|
9
|
+
export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => Hydratable<{
|
10
|
+
[fieldName: string]: any;
|
11
|
+
}>;
|
12
|
+
export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
|
13
|
+
export declare const useFieldTouched: (name: string, { formId }: InternalFormContextValue) => boolean;
|
14
|
+
export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
|
15
|
+
export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
|
16
|
+
export declare const useFormUpdateAtom: typeof useUpdateAtom;
|
17
|
+
export declare const useClearError: (context: InternalFormContextValue) => (name: string) => void;
|
18
|
+
export declare const useSetTouched: (context: InternalFormContextValue) => (name: string, touched: boolean) => void;
|
@@ -0,0 +1,128 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.useSetTouched = exports.useClearError = exports.useFormUpdateAtom = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useInternalFormContext = void 0;
|
7
|
+
const react_1 = require("@remix-run/react");
|
8
|
+
const utils_1 = require("jotai/utils");
|
9
|
+
const get_1 = __importDefault(require("lodash/get"));
|
10
|
+
const react_2 = require("react");
|
11
|
+
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
12
|
+
const constants_1 = require("./constants");
|
13
|
+
const formContext_1 = require("./formContext");
|
14
|
+
const hydratable_1 = require("./hydratable");
|
15
|
+
const state_1 = require("./state");
|
16
|
+
const state_valtio_1 = require("./state-valtio");
|
17
|
+
const useInternalFormContext = (formId, hookName) => {
|
18
|
+
const formContext = (0, react_2.useContext)(formContext_1.InternalFormContext);
|
19
|
+
if (formId)
|
20
|
+
return { formId };
|
21
|
+
if (formContext)
|
22
|
+
return formContext;
|
23
|
+
throw new Error(`Unable to determine form for ${hookName}. Please use it inside a form or pass a 'formId'.`);
|
24
|
+
};
|
25
|
+
exports.useInternalFormContext = useInternalFormContext;
|
26
|
+
function useErrorResponseForForm({ fetcher, subaction, formId, }) {
|
27
|
+
var _a;
|
28
|
+
const actionData = (0, react_1.useActionData)();
|
29
|
+
if (fetcher) {
|
30
|
+
if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
|
31
|
+
return fetcher.data;
|
32
|
+
return null;
|
33
|
+
}
|
34
|
+
if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
|
35
|
+
return null;
|
36
|
+
// If there's an explicit id, we should ignore data that has the wrong id
|
37
|
+
if (typeof formId === "string" && actionData.formId)
|
38
|
+
return actionData.formId === formId ? actionData : null;
|
39
|
+
if ((!subaction && !actionData.subaction) ||
|
40
|
+
actionData.subaction === subaction)
|
41
|
+
return actionData;
|
42
|
+
return null;
|
43
|
+
}
|
44
|
+
exports.useErrorResponseForForm = useErrorResponseForForm;
|
45
|
+
const useFieldErrorsForForm = (context) => {
|
46
|
+
const response = useErrorResponseForForm(context);
|
47
|
+
const form = (0, state_valtio_1.useFormData)(context.formId);
|
48
|
+
return hydratable_1.hydratable.from(response === null || response === void 0 ? void 0 : response.fieldErrors, form.hydrated);
|
49
|
+
};
|
50
|
+
exports.useFieldErrorsForForm = useFieldErrorsForForm;
|
51
|
+
const useDefaultValuesFromLoader = ({ formId, }) => {
|
52
|
+
const matches = (0, react_1.useMatches)();
|
53
|
+
if (typeof formId === "string") {
|
54
|
+
const dataKey = (0, constants_1.formDefaultValuesKey)(formId);
|
55
|
+
// If multiple loaders declare the same default values,
|
56
|
+
// we should use the data from the deepest route.
|
57
|
+
const match = matches
|
58
|
+
.reverse()
|
59
|
+
.find((match) => match.data && dataKey in match.data);
|
60
|
+
return match === null || match === void 0 ? void 0 : match.data[dataKey];
|
61
|
+
}
|
62
|
+
return null;
|
63
|
+
};
|
64
|
+
exports.useDefaultValuesFromLoader = useDefaultValuesFromLoader;
|
65
|
+
const useDefaultValuesForForm = (context) => {
|
66
|
+
const { formId, defaultValuesProp } = context;
|
67
|
+
const form = (0, state_valtio_1.useFormData)(formId);
|
68
|
+
const errorResponse = useErrorResponseForForm(context);
|
69
|
+
const defaultValuesFromLoader = (0, exports.useDefaultValuesFromLoader)(context);
|
70
|
+
// Typical flow is:
|
71
|
+
// - Default values only available from props or server
|
72
|
+
// - Props have a higher priority than server
|
73
|
+
// - State gets hydrated with default values
|
74
|
+
// - After submit, we may need to use values from the error
|
75
|
+
if (form.hydrated)
|
76
|
+
return hydratable_1.hydratable.hydratedData();
|
77
|
+
if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields) {
|
78
|
+
(0, tiny_invariant_1.default)(typeof errorResponse.repopulateFields === "object", "repopulateFields returned something other than an object");
|
79
|
+
return hydratable_1.hydratable.serverData(errorResponse.repopulateFields);
|
80
|
+
}
|
81
|
+
if (defaultValuesProp)
|
82
|
+
return hydratable_1.hydratable.serverData(defaultValuesProp);
|
83
|
+
return hydratable_1.hydratable.serverData(defaultValuesFromLoader);
|
84
|
+
};
|
85
|
+
exports.useDefaultValuesForForm = useDefaultValuesForForm;
|
86
|
+
const useHasActiveFormSubmit = ({ fetcher, }) => {
|
87
|
+
const transition = (0, react_1.useTransition)();
|
88
|
+
const hasActiveSubmission = fetcher
|
89
|
+
? fetcher.state === "submitting"
|
90
|
+
: !!transition.submission;
|
91
|
+
return hasActiveSubmission;
|
92
|
+
};
|
93
|
+
exports.useHasActiveFormSubmit = useHasActiveFormSubmit;
|
94
|
+
const useFieldTouched = (name, { formId }) => {
|
95
|
+
const form = (0, state_valtio_1.useFormData)(formId);
|
96
|
+
return form.touchedFields[name];
|
97
|
+
};
|
98
|
+
exports.useFieldTouched = useFieldTouched;
|
99
|
+
const useFieldError = (name, context) => {
|
100
|
+
const fieldErrors = (0, exports.useFieldErrorsForForm)(context);
|
101
|
+
const form = (0, state_valtio_1.useFormData)(context.formId);
|
102
|
+
return fieldErrors
|
103
|
+
.map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name])
|
104
|
+
.hydrateTo(form.fieldErrors[name]);
|
105
|
+
};
|
106
|
+
exports.useFieldError = useFieldError;
|
107
|
+
const useFieldDefaultValue = (name, context) => {
|
108
|
+
const defaultValues = (0, exports.useDefaultValuesForForm)(context);
|
109
|
+
const state = (0, state_valtio_1.useFormData)(context.formId);
|
110
|
+
return defaultValues.map((val) => (0, get_1.default)(val, name)).hydrateTo(state);
|
111
|
+
};
|
112
|
+
exports.useFieldDefaultValue = useFieldDefaultValue;
|
113
|
+
const useFormUpdateAtom = (atom) => (0, utils_1.useUpdateAtom)(atom, state_1.ATOM_SCOPE);
|
114
|
+
exports.useFormUpdateAtom = useFormUpdateAtom;
|
115
|
+
const useClearError = (context) => {
|
116
|
+
const clearError = (0, exports.useFormUpdateAtom)(state_1.clearErrorAtom);
|
117
|
+
return (0, react_2.useCallback)((name) => {
|
118
|
+
clearError({ name, formAtom: (0, state_1.formRegistry)(context.formId) });
|
119
|
+
}, [clearError, context.formId]);
|
120
|
+
};
|
121
|
+
exports.useClearError = useClearError;
|
122
|
+
const useSetTouched = (context) => {
|
123
|
+
const setTouched = (0, exports.useFormUpdateAtom)(state_1.setTouchedAtom);
|
124
|
+
return (0, react_2.useCallback)((name, touched) => {
|
125
|
+
setTouched({ name, formAtom: (0, state_1.formRegistry)(context.formId), touched });
|
126
|
+
}, [setTouched, context.formId]);
|
127
|
+
};
|
128
|
+
exports.useSetTouched = useSetTouched;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { FieldErrors, ValidationErrorResponseData } from "..";
|
2
|
+
import { InternalFormContextValue } from "./formContext";
|
3
|
+
import { Hydratable } from "./hydratable";
|
4
|
+
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
5
|
+
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
6
|
+
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
|
7
|
+
export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
|
8
|
+
export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => Hydratable<{
|
9
|
+
[fieldName: string]: any;
|
10
|
+
}>;
|
11
|
+
export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
|
12
|
+
export declare const useFieldTouched: (name: string, { formId }: InternalFormContextValue) => boolean;
|
13
|
+
export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
|
14
|
+
export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
|
15
|
+
export declare const useClearError: (context: InternalFormContextValue) => (name: string) => void;
|
16
|
+
export declare const useSetTouched: (context: InternalFormContextValue) => (name: string, touched: boolean) => void;
|
@@ -0,0 +1,117 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.useSetTouched = exports.useClearError = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useInternalFormContext = void 0;
|
7
|
+
const react_1 = require("@remix-run/react");
|
8
|
+
const get_1 = __importDefault(require("lodash/get"));
|
9
|
+
const react_2 = require("react");
|
10
|
+
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
11
|
+
const constants_1 = require("./constants");
|
12
|
+
const formContext_1 = require("./formContext");
|
13
|
+
const hydratable_1 = require("./hydratable");
|
14
|
+
const state_zustand_1 = require("./state-zustand");
|
15
|
+
const useInternalFormContext = (formId, hookName) => {
|
16
|
+
const formContext = (0, react_2.useContext)(formContext_1.InternalFormContext);
|
17
|
+
if (formId)
|
18
|
+
return { formId };
|
19
|
+
if (formContext)
|
20
|
+
return formContext;
|
21
|
+
throw new Error(`Unable to determine form for ${hookName}. Please use it inside a form or pass a 'formId'.`);
|
22
|
+
};
|
23
|
+
exports.useInternalFormContext = useInternalFormContext;
|
24
|
+
function useErrorResponseForForm({ fetcher, subaction, formId, }) {
|
25
|
+
var _a;
|
26
|
+
const actionData = (0, react_1.useActionData)();
|
27
|
+
if (fetcher) {
|
28
|
+
if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
|
29
|
+
return fetcher.data;
|
30
|
+
return null;
|
31
|
+
}
|
32
|
+
if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
|
33
|
+
return null;
|
34
|
+
// If there's an explicit id, we should ignore data that has the wrong id
|
35
|
+
if (typeof formId === "string" && actionData.formId)
|
36
|
+
return actionData.formId === formId ? actionData : null;
|
37
|
+
if ((!subaction && !actionData.subaction) ||
|
38
|
+
actionData.subaction === subaction)
|
39
|
+
return actionData;
|
40
|
+
return null;
|
41
|
+
}
|
42
|
+
exports.useErrorResponseForForm = useErrorResponseForForm;
|
43
|
+
const useFieldErrorsForForm = (context) => {
|
44
|
+
const response = useErrorResponseForForm(context);
|
45
|
+
const hydrated = (0, state_zustand_1.useStore)((state) => state.form(context.formId).hydrated);
|
46
|
+
return hydratable_1.hydratable.from(response === null || response === void 0 ? void 0 : response.fieldErrors, hydrated);
|
47
|
+
};
|
48
|
+
exports.useFieldErrorsForForm = useFieldErrorsForForm;
|
49
|
+
const useDefaultValuesFromLoader = ({ formId, }) => {
|
50
|
+
const matches = (0, react_1.useMatches)();
|
51
|
+
if (typeof formId === "string") {
|
52
|
+
const dataKey = (0, constants_1.formDefaultValuesKey)(formId);
|
53
|
+
// If multiple loaders declare the same default values,
|
54
|
+
// we should use the data from the deepest route.
|
55
|
+
const match = matches
|
56
|
+
.reverse()
|
57
|
+
.find((match) => match.data && dataKey in match.data);
|
58
|
+
return match === null || match === void 0 ? void 0 : match.data[dataKey];
|
59
|
+
}
|
60
|
+
return null;
|
61
|
+
};
|
62
|
+
exports.useDefaultValuesFromLoader = useDefaultValuesFromLoader;
|
63
|
+
const useDefaultValuesForForm = (context) => {
|
64
|
+
const { formId, defaultValuesProp } = context;
|
65
|
+
const hydrated = (0, state_zustand_1.useStore)((state) => state.form(formId).hydrated);
|
66
|
+
const errorResponse = useErrorResponseForForm(context);
|
67
|
+
const defaultValuesFromLoader = (0, exports.useDefaultValuesFromLoader)(context);
|
68
|
+
// Typical flow is:
|
69
|
+
// - Default values only available from props or server
|
70
|
+
// - Props have a higher priority than server
|
71
|
+
// - State gets hydrated with default values
|
72
|
+
// - After submit, we may need to use values from the error
|
73
|
+
if (hydrated)
|
74
|
+
return hydratable_1.hydratable.hydratedData();
|
75
|
+
if (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.repopulateFields) {
|
76
|
+
(0, tiny_invariant_1.default)(typeof errorResponse.repopulateFields === "object", "repopulateFields returned something other than an object");
|
77
|
+
return hydratable_1.hydratable.serverData(errorResponse.repopulateFields);
|
78
|
+
}
|
79
|
+
if (defaultValuesProp)
|
80
|
+
return hydratable_1.hydratable.serverData(defaultValuesProp);
|
81
|
+
return hydratable_1.hydratable.serverData(defaultValuesFromLoader);
|
82
|
+
};
|
83
|
+
exports.useDefaultValuesForForm = useDefaultValuesForForm;
|
84
|
+
const useHasActiveFormSubmit = ({ fetcher, }) => {
|
85
|
+
const transition = (0, react_1.useTransition)();
|
86
|
+
const hasActiveSubmission = fetcher
|
87
|
+
? fetcher.state === "submitting"
|
88
|
+
: !!transition.submission;
|
89
|
+
return hasActiveSubmission;
|
90
|
+
};
|
91
|
+
exports.useHasActiveFormSubmit = useHasActiveFormSubmit;
|
92
|
+
const useFieldTouched = (name, { formId }) => {
|
93
|
+
return (0, state_zustand_1.useStore)((state) => state.form(formId).touchedFields[name]);
|
94
|
+
};
|
95
|
+
exports.useFieldTouched = useFieldTouched;
|
96
|
+
const useFieldError = (name, context) => {
|
97
|
+
const state = (0, state_zustand_1.useStore)((state) => state.form(context.formId).fieldErrors[name]);
|
98
|
+
return (0, exports.useFieldErrorsForForm)(context)
|
99
|
+
.map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name])
|
100
|
+
.hydrateTo(state);
|
101
|
+
};
|
102
|
+
exports.useFieldError = useFieldError;
|
103
|
+
const useFieldDefaultValue = (name, context) => {
|
104
|
+
const state = (0, state_zustand_1.useStore)((state) => state.form(context.formId).defaultValues[name]);
|
105
|
+
return (0, exports.useDefaultValuesForForm)(context)
|
106
|
+
.map((val) => (0, get_1.default)(val, name))
|
107
|
+
.hydrateTo(state);
|
108
|
+
};
|
109
|
+
exports.useFieldDefaultValue = useFieldDefaultValue;
|
110
|
+
const useClearError = (context) => {
|
111
|
+
return (0, state_zustand_1.useStore)((state) => state.helpers(context.formId).clearError);
|
112
|
+
};
|
113
|
+
exports.useClearError = useClearError;
|
114
|
+
const useSetTouched = (context) => {
|
115
|
+
return (0, state_zustand_1.useStore)((state) => state.helpers(context.formId).setTouched);
|
116
|
+
};
|
117
|
+
exports.useSetTouched = useSetTouched;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/**
|
2
|
+
* The purpose of this type is to simplify the logic
|
3
|
+
* around data that needs to come from the server initially,
|
4
|
+
* but from the internal state after hydration.
|
5
|
+
*/
|
6
|
+
export declare type Hydratable<T> = {
|
7
|
+
hydrateTo: (data: T) => T;
|
8
|
+
map: <U>(fn: (data: T) => U) => Hydratable<U>;
|
9
|
+
};
|
10
|
+
export declare const hydratable: {
|
11
|
+
serverData: <T>(data: T) => Hydratable<T>;
|
12
|
+
hydratedData: <T_1>() => Hydratable<T_1>;
|
13
|
+
from: <T_2>(data: T_2, hydrated: boolean) => Hydratable<T_2>;
|
14
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.hydratable = void 0;
|
4
|
+
const serverData = (data) => ({
|
5
|
+
hydrateTo: () => data,
|
6
|
+
map: (fn) => serverData(fn(data)),
|
7
|
+
});
|
8
|
+
const hydratedData = () => ({
|
9
|
+
hydrateTo: (hydratedData) => hydratedData,
|
10
|
+
map: () => hydratedData(),
|
11
|
+
});
|
12
|
+
const from = (data, hydrated) => hydrated ? hydratedData() : serverData(data);
|
13
|
+
exports.hydratable = {
|
14
|
+
serverData,
|
15
|
+
hydratedData,
|
16
|
+
from,
|
17
|
+
};
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { Draft } from "immer";
|
2
|
+
import { State, StateCreator } from "zustand";
|
3
|
+
declare type TImmerConfigFn<T extends State> = (partial: ((draft: Draft<T>) => void) | T, replace?: boolean) => void;
|
4
|
+
declare type TImmerConfig<T extends State> = StateCreator<T, TImmerConfigFn<T>>;
|
5
|
+
export declare const immer: <T extends object>(config: TImmerConfig<T>) => StateCreator<T, import("zustand").SetState<T>, import("zustand").GetState<T>, import("zustand").StoreApi<T>>;
|
6
|
+
export {};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.immer = void 0;
|
7
|
+
const immer_1 = __importDefault(require("immer"));
|
8
|
+
const immer = (config) => (set, get, api) => config((partial, replace) => {
|
9
|
+
const nextState = typeof partial === "function"
|
10
|
+
? (0, immer_1.default)(partial)
|
11
|
+
: partial;
|
12
|
+
return set(nextState, replace);
|
13
|
+
}, get, api);
|
14
|
+
exports.immer = immer;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getCheckboxChecked: (checkboxValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getCheckboxChecked = void 0;
|
4
|
+
const getCheckboxChecked = (checkboxValue = "on", newValue) => {
|
5
|
+
if (Array.isArray(newValue))
|
6
|
+
return newValue.includes(checkboxValue);
|
7
|
+
if (typeof newValue === "boolean")
|
8
|
+
return newValue;
|
9
|
+
if (typeof newValue === "string")
|
10
|
+
return newValue === checkboxValue;
|
11
|
+
return undefined;
|
12
|
+
};
|
13
|
+
exports.getCheckboxChecked = getCheckboxChecked;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getRadioChecked: (radioValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getRadioChecked = void 0;
|
4
|
+
const getRadioChecked = (radioValue = "on", newValue) => {
|
5
|
+
if (typeof newValue === "string")
|
6
|
+
return newValue === radioValue;
|
7
|
+
return undefined;
|
8
|
+
};
|
9
|
+
exports.getRadioChecked = getRadioChecked;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const setFieldValue: (formElement: HTMLFormElement, name: string, value: unknown) => void;
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.setFieldValue = void 0;
|
7
|
+
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
8
|
+
const getCheckboxChecked_1 = require("./getCheckboxChecked");
|
9
|
+
const getRadioChecked_1 = require("./getRadioChecked");
|
10
|
+
const setElementValue = (element, value, name) => {
|
11
|
+
if (element instanceof HTMLSelectElement && element.multiple) {
|
12
|
+
(0, tiny_invariant_1.default)(Array.isArray(value), "Must specify an array to set the value for a multi-select");
|
13
|
+
for (const option of element.options) {
|
14
|
+
option.selected = value.includes(option.value);
|
15
|
+
}
|
16
|
+
return;
|
17
|
+
}
|
18
|
+
if (element instanceof HTMLInputElement && element.type === "checkbox") {
|
19
|
+
const newChecked = (0, getCheckboxChecked_1.getCheckboxChecked)(element.value, value);
|
20
|
+
(0, tiny_invariant_1.default)(newChecked !== undefined, `Unable to determine if checkbox should be checked. Provided value was ${value} for checkbox ${name}.`);
|
21
|
+
element.checked = newChecked;
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
if (element instanceof HTMLInputElement && element.type === "radio") {
|
25
|
+
const newChecked = (0, getRadioChecked_1.getRadioChecked)(element.value, value);
|
26
|
+
(0, tiny_invariant_1.default)(newChecked !== undefined, `Unable to determine if radio should be checked. Provided value was ${value} for radio ${name}.`);
|
27
|
+
element.checked = newChecked;
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
(0, tiny_invariant_1.default)(typeof value === "string", `Invalid value for field "${name}" which is an ${element.constructor.name}. Expected string but received ${typeof value}`);
|
31
|
+
const input = element;
|
32
|
+
input.value = value;
|
33
|
+
};
|
34
|
+
const setFieldValue = (formElement, name, value) => {
|
35
|
+
const controlElement = formElement.elements.namedItem(name);
|
36
|
+
if (!controlElement)
|
37
|
+
return;
|
38
|
+
if (controlElement instanceof RadioNodeList) {
|
39
|
+
for (const element of controlElement) {
|
40
|
+
setElementValue(element, value, name);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
setElementValue(controlElement, value, name);
|
45
|
+
}
|
46
|
+
};
|
47
|
+
exports.setFieldValue = setFieldValue;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const setInputValueInForm: (formElement: HTMLFormElement, name: string, value: unknown) => void;
|