react-form-manage 1.0.6-beta.2 → 1.0.6-beta.3
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/CHANGELOG.md +7 -0
- package/dist/components/Form/FormItem.d.ts +2 -1
- package/dist/hooks/useFormItemControl.d.ts +2 -1
- package/dist/hooks/useFormItemControl.js +25 -10
- package/dist/index.d.ts +2 -2
- package/dist/providers/Form.d.ts +3 -2
- package/dist/providers/Form.js +21 -4
- package/dist/stores/formStore.d.ts +12 -9
- package/dist/stores/formStore.js +1 -1
- package/dist/types/public.d.ts +37 -9
- package/package.json +1 -1
- package/src/App.tsx +74 -3
- package/src/components/Form/FormItem.tsx +2 -1
- package/src/hooks/useFormItemControl.ts +39 -10
- package/src/index.ts +2 -0
- package/src/providers/Form.tsx +48 -10
- package/src/stores/formStore.ts +286 -284
- package/src/types/public.ts +37 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.6-beta.3] - 2026-01-22
|
|
6
|
+
|
|
7
|
+
- Add `ValidationRule` typing and export
|
|
8
|
+
- Support custom regex `pattern` in validation
|
|
9
|
+
- Update usage docs with numeric-only example
|
|
10
|
+
- Add demo field `numericCode` using pattern rule
|
|
11
|
+
|
|
5
12
|
## [1.0.6-beta.2] - 2026-01-22
|
|
6
13
|
|
|
7
14
|
- Fix TypeScript prop types for all components
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
|
+
import type { ValidationRule } from "../../types/public";
|
|
2
3
|
export interface FormItemProps {
|
|
3
4
|
children: ReactElement;
|
|
4
5
|
name: string;
|
|
5
6
|
formName?: string;
|
|
6
7
|
initialValue?: any;
|
|
7
8
|
formItemId?: string;
|
|
8
|
-
rules?:
|
|
9
|
+
rules?: ValidationRule[];
|
|
9
10
|
valuePropName?: string;
|
|
10
11
|
getValueFromEvent?: (event: any) => any;
|
|
11
12
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RefObject } from "react";
|
|
2
2
|
import type { FormInstance } from "../stores/formStore";
|
|
3
|
+
import type { ValidationRule } from "../types/public";
|
|
3
4
|
type AnyObject = Record<string, any>;
|
|
4
5
|
interface UseFormItemControlProps {
|
|
5
6
|
formName?: string;
|
|
@@ -7,7 +8,7 @@ interface UseFormItemControlProps {
|
|
|
7
8
|
name?: string;
|
|
8
9
|
initialValue?: any;
|
|
9
10
|
formItemId?: string;
|
|
10
|
-
rules?:
|
|
11
|
+
rules?: ValidationRule[];
|
|
11
12
|
elementRef?: RefObject<any> | null;
|
|
12
13
|
}
|
|
13
14
|
interface UseFormItemControlReturn {
|
|
@@ -8,12 +8,14 @@ import { useFormCleanUp, useFormListeners, useFormStore, } from "../stores/formS
|
|
|
8
8
|
const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
|
|
9
9
|
export default function useFormItemControl({ formName, form, name, initialValue, formItemId, rules, elementRef, }) {
|
|
10
10
|
const contextForm = useFormContext();
|
|
11
|
-
const { value, setData, getCacheData, getFormValues } = useFormStore(useShallow((state) => {
|
|
11
|
+
const { value, setData, getCacheData, getFormValues, getFormState, isStateInitied, } = useFormStore(useShallow((state) => {
|
|
12
12
|
return {
|
|
13
13
|
value: get(state.forms, `${formName || form?.formName || contextForm?.formName}.${name}`),
|
|
14
14
|
setData: state.setData,
|
|
15
15
|
getCacheData: state.getCacheData,
|
|
16
16
|
getFormValues: state.getFormValues,
|
|
17
|
+
getFormState: state.getFormState,
|
|
18
|
+
isStateInitied: state.formStates?.[formName || form?.formName || contextForm?.formName]?.isInitied,
|
|
17
19
|
};
|
|
18
20
|
}));
|
|
19
21
|
const { setCleanUpStack } = useFormCleanUp(useShallow((state) => ({
|
|
@@ -53,6 +55,7 @@ export default function useFormItemControl({ formName, form, name, initialValue,
|
|
|
53
55
|
// value
|
|
54
56
|
// );
|
|
55
57
|
setInitData(formName || form?.formName || contextForm?.formName, name, value);
|
|
58
|
+
onChange(value, { notTriggerDirty: true });
|
|
56
59
|
};
|
|
57
60
|
const onFocus = () => {
|
|
58
61
|
setListener({
|
|
@@ -171,6 +174,14 @@ export default function useFormItemControl({ formName, form, name, initialValue,
|
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
}
|
|
177
|
+
// Custom pattern
|
|
178
|
+
if (r.pattern && !r.pattern.test(fieldValue || "")) {
|
|
179
|
+
listErrors.push({
|
|
180
|
+
ruleName,
|
|
181
|
+
message: r.message,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
174
185
|
// Password
|
|
175
186
|
if (r.isPassword && !IS_PASSWORD_REGEX.test(fieldValue)) {
|
|
176
187
|
// tempMessage.push(IS_PASSWORD_INVALID_MESSAGE);
|
|
@@ -319,18 +330,22 @@ export default function useFormItemControl({ formName, form, name, initialValue,
|
|
|
319
330
|
// console.log({ internalInitValue, value, initialValue });
|
|
320
331
|
// console.log("Init item value: ", name, value, internalInitValue);
|
|
321
332
|
// console.log("internalInitValue: ", internalInitValue);
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
333
|
+
if (isStateInitied) {
|
|
334
|
+
// console.log("Skip set init data when form is resetting");
|
|
335
|
+
if (isNil(value)) {
|
|
336
|
+
if (isNil(internalInitValue)) {
|
|
337
|
+
if (isNil(initialValue)) {
|
|
338
|
+
console.log("On init data", initialValue);
|
|
339
|
+
onInitData(initialValue);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
onChange(internalInitValue, { notTriggerDirty: true });
|
|
327
344
|
}
|
|
328
345
|
}
|
|
329
|
-
|
|
330
|
-
onChange(internalInitValue);
|
|
331
|
-
}
|
|
346
|
+
return;
|
|
332
347
|
}
|
|
333
|
-
}, [
|
|
348
|
+
}, [isStateInitied]);
|
|
334
349
|
useEffect(() => {
|
|
335
350
|
if (!listener) {
|
|
336
351
|
setListener({
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps } from "./providers/Form";
|
|
1
|
+
import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type ValidationRule } from "./providers/Form";
|
|
2
2
|
import FormItem, { type FormItemProps } from "./components/Form/FormItem";
|
|
3
3
|
import FormList, { type FormListProps } from "./components/Form/FormList";
|
|
4
4
|
import Input from "./components/Input";
|
|
5
5
|
import InputWrapper, { type InputWrapperProps } from "./components/Form/InputWrapper";
|
|
6
6
|
import useFormItemControl from "./hooks/useFormItemControl";
|
|
7
7
|
import useFormListControl from "./hooks/useFormListControl";
|
|
8
|
-
export { Form, FormItem, FormList, Input, InputWrapper, useFormItemControl, useFormListControl, useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type FormItemProps, type FormListProps, type InputWrapperProps, };
|
|
8
|
+
export { Form, FormItem, FormList, Input, InputWrapper, useFormItemControl, useFormListControl, useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type FormItemProps, type FormListProps, type InputWrapperProps, type ValidationRule, };
|
|
9
9
|
export default Form;
|
package/dist/providers/Form.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
2
2
|
import type { PublicFormInstance } from "../types/public";
|
|
3
|
+
export type { ValidationRule } from "../types/public";
|
|
3
4
|
export declare const FormContext: import("react").Context<any>;
|
|
4
|
-
export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>,
|
|
5
|
+
export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
|
|
5
6
|
children: ReactNode;
|
|
6
7
|
formName: string;
|
|
7
8
|
initialValues?: T;
|
package/dist/providers/Form.js
CHANGED
|
@@ -9,8 +9,9 @@ import { useFormListeners, useFormStore } from "../stores/formStore";
|
|
|
9
9
|
import { getAllNoneObjStringPath } from "../utils/obj.util";
|
|
10
10
|
export const FormContext = createContext(null);
|
|
11
11
|
export default function Form({ children, formName, initialValues, onFinish, onReject, onFinally, FormElement, ...props }) {
|
|
12
|
-
const { getFormItemValue, setInitData, setData, getFormValues, setFormState, setFormInstance, revokeFormInstance, setSubmitHistory, clearFormValues, clearFormInitialValues, clearFormState, } = useFormStore(useShallow((state) => ({
|
|
13
|
-
|
|
12
|
+
const { appInitValue, getFormItemValue, setInitData, setData, getFormValues, setFormState, setFormInstance, revokeFormInstance, setSubmitHistory, clearFormValues, clearFormInitialValues, clearFormState, } = useFormStore(useShallow((state) => ({
|
|
13
|
+
appInitValue: state.initialValues,
|
|
14
|
+
setInitData: state.setInitData,
|
|
14
15
|
getFormValues: state.getFormValues,
|
|
15
16
|
setFormState: state.setFormState,
|
|
16
17
|
setFormInstance: state.setFormInstance,
|
|
@@ -237,7 +238,19 @@ export default function Form({ children, formName, initialValues, onFinish, onRe
|
|
|
237
238
|
});
|
|
238
239
|
};
|
|
239
240
|
useEffect(() => {
|
|
240
|
-
|
|
241
|
+
// set initial values
|
|
242
|
+
if (initialValues) {
|
|
243
|
+
const allStringPath = getAllNoneObjStringPath(initialValues);
|
|
244
|
+
allStringPath.forEach((p) => {
|
|
245
|
+
// console.log(
|
|
246
|
+
// "Set initial value for form",
|
|
247
|
+
// formName,
|
|
248
|
+
// p,
|
|
249
|
+
// get(initialValues, p),
|
|
250
|
+
// );
|
|
251
|
+
setInitData(formName, p, get(initialValues, p));
|
|
252
|
+
});
|
|
253
|
+
}
|
|
241
254
|
setFormState({
|
|
242
255
|
formName,
|
|
243
256
|
isInitied: true,
|
|
@@ -262,6 +275,9 @@ export default function Form({ children, formName, initialValues, onFinish, onRe
|
|
|
262
275
|
clearFormState(formName);
|
|
263
276
|
};
|
|
264
277
|
}, []);
|
|
278
|
+
// useEffect(() => {
|
|
279
|
+
// // console.log("Form appInitValue changed", appInitValue);
|
|
280
|
+
// }, [appInitValue]);
|
|
265
281
|
return (_jsxs(FormContext.Provider, { value: {
|
|
266
282
|
formName,
|
|
267
283
|
}, children: [FormElement ? (_jsx(FormElement, { onSubmit: (e) => {
|
|
@@ -281,7 +297,8 @@ export function useFormContext() {
|
|
|
281
297
|
return c;
|
|
282
298
|
}
|
|
283
299
|
export function useForm(formNameOrFormInstance) {
|
|
284
|
-
const targetFormName = typeof formNameOrFormInstance === "object" &&
|
|
300
|
+
const targetFormName = typeof formNameOrFormInstance === "object" &&
|
|
301
|
+
formNameOrFormInstance !== null
|
|
285
302
|
? formNameOrFormInstance.formName
|
|
286
303
|
: formNameOrFormInstance;
|
|
287
304
|
const formInstance = useFormStore((state) => {
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export interface FormInstance {
|
|
2
2
|
formName: string;
|
|
3
|
-
resetFields?: any;
|
|
4
|
-
submit?: any;
|
|
5
|
-
submitAsync?: any
|
|
6
|
-
setFieldValue?: any;
|
|
7
|
-
setFieldValues?: any;
|
|
8
|
-
getFieldValue
|
|
9
|
-
getFieldValues?:
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
resetFields: (values?: any) => void;
|
|
4
|
+
submit: (values?: any) => void;
|
|
5
|
+
submitAsync: (values?: any) => Promise<any>;
|
|
6
|
+
setFieldValue: (name: string, value: any, options?: any) => void;
|
|
7
|
+
setFieldValues: (values: Record<string, any>, options?: any) => void;
|
|
8
|
+
getFieldValue: (name: string) => any;
|
|
9
|
+
getFieldValues: (names?: string[]) => Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
value: any;
|
|
12
|
+
}>;
|
|
13
|
+
getFieldErrors: () => Record<string, any>;
|
|
14
|
+
setFieldFocus: (name: string) => void;
|
|
12
15
|
}
|
|
13
16
|
interface FormStoreState {
|
|
14
17
|
forms: Record<string, any>;
|
package/dist/stores/formStore.js
CHANGED
|
@@ -126,7 +126,7 @@ export const useFormStore = create((storeSet, storeGet) => ({
|
|
|
126
126
|
setInitData(formName, name, value) {
|
|
127
127
|
return storeSet(produce((state) => {
|
|
128
128
|
const oldValues = state.initialValues;
|
|
129
|
-
|
|
129
|
+
console.log("Inner init: ", `${formName}.${name}`, value);
|
|
130
130
|
set(oldValues, `${formName}.${name}`, value);
|
|
131
131
|
}));
|
|
132
132
|
},
|
package/dist/types/public.d.ts
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
export type FormValues<T = any> = T;
|
|
2
|
+
export interface ValidationRule<T = any, TValues = any> {
|
|
3
|
+
name?: string;
|
|
4
|
+
message: string;
|
|
5
|
+
handler?: (value: T, formValues: TValues) => boolean | Promise<boolean>;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
minLength?: number;
|
|
10
|
+
maxLength?: number;
|
|
11
|
+
isPassword?: boolean;
|
|
12
|
+
isUsername?: boolean;
|
|
13
|
+
isPhoneNumber?: boolean;
|
|
14
|
+
isStringNumber?: boolean;
|
|
15
|
+
isPositiveStringNumber?: boolean;
|
|
16
|
+
isPositiveIntegerStringNumber?: boolean;
|
|
17
|
+
isEmail?: boolean;
|
|
18
|
+
isVietnameseName?: boolean;
|
|
19
|
+
isNoSpaceStringAndNumber?: boolean;
|
|
20
|
+
isStringAndNumber?: boolean;
|
|
21
|
+
isNoSpaceOnlyAlphabetStringAndNumber?: boolean;
|
|
22
|
+
isOnlyAlphabetStringAndNumber?: boolean;
|
|
23
|
+
isNoSpaceAlphabetString?: boolean;
|
|
24
|
+
pattern?: RegExp;
|
|
25
|
+
}
|
|
2
26
|
export interface PublicFormInstance<T = any> {
|
|
3
27
|
formName: string;
|
|
4
|
-
resetFields
|
|
5
|
-
submit
|
|
6
|
-
submitAsync
|
|
7
|
-
setFieldValue
|
|
8
|
-
setFieldValues
|
|
9
|
-
getFieldValue
|
|
10
|
-
getFieldValues?:
|
|
11
|
-
|
|
28
|
+
resetFields: (values?: Partial<T>) => void;
|
|
29
|
+
submit: (values?: T) => void;
|
|
30
|
+
submitAsync: (values?: T) => Promise<any>;
|
|
31
|
+
setFieldValue: (name: keyof T & string, value: any, options?: any) => void;
|
|
32
|
+
setFieldValues: (values: Partial<T>, options?: any) => void;
|
|
33
|
+
getFieldValue: (name: keyof T & string) => any;
|
|
34
|
+
getFieldValues: (names?: Array<keyof T & string>) => Array<{
|
|
35
|
+
name: string;
|
|
36
|
+
value: any;
|
|
37
|
+
}>;
|
|
38
|
+
getFieldErrors: () => Record<string, any>;
|
|
39
|
+
setFieldFocus: (name: keyof T & string) => void;
|
|
12
40
|
}
|
|
13
41
|
export interface UseFormItemProps<T = any> {
|
|
14
42
|
formName?: string;
|
|
@@ -16,7 +44,7 @@ export interface UseFormItemProps<T = any> {
|
|
|
16
44
|
name?: keyof T & string;
|
|
17
45
|
initialValue?: any;
|
|
18
46
|
formItemId?: string;
|
|
19
|
-
rules?: any[];
|
|
47
|
+
rules?: ValidationRule<any, T>[];
|
|
20
48
|
}
|
|
21
49
|
export interface UseFormItemReturn<T = any> {
|
|
22
50
|
value: T | undefined;
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Button, Input } from "antd";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import FormItem from "./components/Form/FormItem";
|
|
4
|
+
import FormList from "./components/Form/FormList";
|
|
4
5
|
import InputWrapper from "./components/Form/InputWrapper";
|
|
5
6
|
import Form, { useForm } from "./providers/Form";
|
|
6
7
|
|
|
@@ -9,14 +10,19 @@ const App = () => {
|
|
|
9
10
|
|
|
10
11
|
useEffect(() => {
|
|
11
12
|
if (form) {
|
|
12
|
-
setTimeout(() => {
|
|
13
|
-
|
|
14
|
-
}, 500);
|
|
13
|
+
// setTimeout(() => {
|
|
14
|
+
// form.setFieldValue("TestData", "HelloWorld");
|
|
15
|
+
// }, 500);
|
|
15
16
|
}
|
|
16
17
|
}, [form]);
|
|
17
18
|
return (
|
|
18
19
|
<div>
|
|
19
20
|
<Form
|
|
21
|
+
initialValues={{
|
|
22
|
+
TestData: "",
|
|
23
|
+
numericCode: "",
|
|
24
|
+
arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
25
|
+
}}
|
|
20
26
|
onFinish={(values) => {
|
|
21
27
|
console.log(values);
|
|
22
28
|
}}
|
|
@@ -27,7 +33,72 @@ const App = () => {
|
|
|
27
33
|
<Input />
|
|
28
34
|
</InputWrapper>
|
|
29
35
|
</FormItem>
|
|
36
|
+
|
|
37
|
+
<FormItem
|
|
38
|
+
name={"numericCode"}
|
|
39
|
+
rules={[
|
|
40
|
+
{ required: true, message: "Vui lòng nhập mã" },
|
|
41
|
+
{
|
|
42
|
+
pattern: /^\d+$/,
|
|
43
|
+
message: "Chỉ cho phép chuỗi số",
|
|
44
|
+
},
|
|
45
|
+
]}
|
|
46
|
+
>
|
|
47
|
+
<InputWrapper>
|
|
48
|
+
<Input placeholder="Mã chỉ gồm số" style={{ width: 200 }} />
|
|
49
|
+
</InputWrapper>
|
|
50
|
+
</FormItem>
|
|
51
|
+
<FormList
|
|
52
|
+
initialValues={[
|
|
53
|
+
{
|
|
54
|
+
el: "",
|
|
55
|
+
},
|
|
56
|
+
]}
|
|
57
|
+
name="arr"
|
|
58
|
+
>
|
|
59
|
+
{(fields, { add, remove, move }) => (
|
|
60
|
+
<div>
|
|
61
|
+
{fields.map((field, index) => (
|
|
62
|
+
<div key={field.key} style={{ marginBottom: 8 }}>
|
|
63
|
+
<FormItem name={`${field.name}.el`}>
|
|
64
|
+
<InputWrapper>
|
|
65
|
+
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
66
|
+
</InputWrapper>
|
|
67
|
+
</FormItem>
|
|
68
|
+
<Button
|
|
69
|
+
onClick={() => remove({ index })}
|
|
70
|
+
style={{ marginLeft: 8 }}
|
|
71
|
+
>
|
|
72
|
+
Remove
|
|
73
|
+
</Button>
|
|
74
|
+
{index > 0 && (
|
|
75
|
+
<Button
|
|
76
|
+
onClick={() => move({ from: index, to: index - 1 })}
|
|
77
|
+
style={{ marginLeft: 8 }}
|
|
78
|
+
>
|
|
79
|
+
Move Up
|
|
80
|
+
</Button>
|
|
81
|
+
)}
|
|
82
|
+
{index < fields.length - 1 && (
|
|
83
|
+
<Button
|
|
84
|
+
onClick={() => move({ from: index, to: index + 1 })}
|
|
85
|
+
style={{ marginLeft: 8 }}
|
|
86
|
+
>
|
|
87
|
+
Move Down
|
|
88
|
+
</Button>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
))}
|
|
92
|
+
<Button onClick={() => add(fields.length)}>Add Item</Button>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</FormList>
|
|
30
96
|
<Button htmlType="submit">Submit</Button>
|
|
97
|
+
<Button
|
|
98
|
+
onClick={() => {
|
|
99
|
+
form?.resetFields?.();
|
|
100
|
+
}}
|
|
101
|
+
></Button>
|
|
31
102
|
</Form>
|
|
32
103
|
</div>
|
|
33
104
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneElement, useRef, useState } from "react";
|
|
2
2
|
import type { ReactElement } from "react";
|
|
3
|
+
import type { ValidationRule } from "../../types/public";
|
|
3
4
|
import { v4 } from "uuid";
|
|
4
5
|
import useFormItemControl from "../../hooks/useFormItemControl";
|
|
5
6
|
|
|
@@ -9,7 +10,7 @@ export interface FormItemProps {
|
|
|
9
10
|
formName?: string;
|
|
10
11
|
initialValue?: any;
|
|
11
12
|
formItemId?: string;
|
|
12
|
-
rules?:
|
|
13
|
+
rules?: ValidationRule[];
|
|
13
14
|
valuePropName?: string;
|
|
14
15
|
getValueFromEvent?: (event: any) => any;
|
|
15
16
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "../stores/formStore";
|
|
26
26
|
|
|
27
27
|
import type { FormInstance } from "../stores/formStore";
|
|
28
|
+
import type { ValidationRule } from "../types/public";
|
|
28
29
|
|
|
29
30
|
type AnyObject = Record<string, any>;
|
|
30
31
|
|
|
@@ -34,7 +35,7 @@ interface UseFormItemControlProps {
|
|
|
34
35
|
name?: string;
|
|
35
36
|
initialValue?: any;
|
|
36
37
|
formItemId?: string;
|
|
37
|
-
rules?:
|
|
38
|
+
rules?: ValidationRule[];
|
|
38
39
|
elementRef?: RefObject<any> | null;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -59,7 +60,14 @@ export default function useFormItemControl<T = any>({
|
|
|
59
60
|
elementRef,
|
|
60
61
|
}: UseFormItemControlProps): UseFormItemControlReturn {
|
|
61
62
|
const contextForm = useFormContext();
|
|
62
|
-
const {
|
|
63
|
+
const {
|
|
64
|
+
value,
|
|
65
|
+
setData,
|
|
66
|
+
getCacheData,
|
|
67
|
+
getFormValues,
|
|
68
|
+
getFormState,
|
|
69
|
+
isStateInitied,
|
|
70
|
+
} = useFormStore(
|
|
63
71
|
useShallow((state) => {
|
|
64
72
|
return {
|
|
65
73
|
value: get(
|
|
@@ -69,6 +77,11 @@ export default function useFormItemControl<T = any>({
|
|
|
69
77
|
setData: state.setData,
|
|
70
78
|
getCacheData: state.getCacheData,
|
|
71
79
|
getFormValues: state.getFormValues,
|
|
80
|
+
getFormState: state.getFormState,
|
|
81
|
+
isStateInitied:
|
|
82
|
+
state.formStates?.[
|
|
83
|
+
formName || form?.formName || contextForm?.formName
|
|
84
|
+
]?.isInitied,
|
|
72
85
|
};
|
|
73
86
|
}),
|
|
74
87
|
);
|
|
@@ -130,6 +143,7 @@ export default function useFormItemControl<T = any>({
|
|
|
130
143
|
name,
|
|
131
144
|
value,
|
|
132
145
|
);
|
|
146
|
+
onChange(value, { notTriggerDirty: true });
|
|
133
147
|
};
|
|
134
148
|
|
|
135
149
|
const onFocus = () => {
|
|
@@ -283,6 +297,16 @@ export default function useFormItemControl<T = any>({
|
|
|
283
297
|
}
|
|
284
298
|
}
|
|
285
299
|
|
|
300
|
+
// Custom pattern
|
|
301
|
+
if (r.pattern && !r.pattern.test(fieldValue || "")) {
|
|
302
|
+
listErrors.push({
|
|
303
|
+
ruleName,
|
|
304
|
+
message: r.message,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
286
310
|
// Password
|
|
287
311
|
if (r.isPassword && !IS_PASSWORD_REGEX.test(fieldValue)) {
|
|
288
312
|
// tempMessage.push(IS_PASSWORD_INVALID_MESSAGE);
|
|
@@ -478,17 +502,22 @@ export default function useFormItemControl<T = any>({
|
|
|
478
502
|
// console.log("Init item value: ", name, value, internalInitValue);
|
|
479
503
|
// console.log("internalInitValue: ", internalInitValue);
|
|
480
504
|
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
505
|
+
if (isStateInitied) {
|
|
506
|
+
// console.log("Skip set init data when form is resetting");
|
|
507
|
+
|
|
508
|
+
if (isNil(value)) {
|
|
509
|
+
if (isNil(internalInitValue)) {
|
|
510
|
+
if (isNil(initialValue)) {
|
|
511
|
+
console.log("On init data", initialValue);
|
|
512
|
+
onInitData(initialValue);
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
onChange(internalInitValue, { notTriggerDirty: true });
|
|
486
516
|
}
|
|
487
|
-
} else {
|
|
488
|
-
onChange(internalInitValue);
|
|
489
517
|
}
|
|
518
|
+
return;
|
|
490
519
|
}
|
|
491
|
-
}, [
|
|
520
|
+
}, [isStateInitied]);
|
|
492
521
|
|
|
493
522
|
useEffect(() => {
|
|
494
523
|
if (!listener) {
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import Form, {
|
|
|
4
4
|
useSubmitDataWatch,
|
|
5
5
|
useFormStateWatch,
|
|
6
6
|
type FormProps,
|
|
7
|
+
type ValidationRule,
|
|
7
8
|
} from "./providers/Form";
|
|
8
9
|
|
|
9
10
|
import FormItem, { type FormItemProps } from "./components/Form/FormItem";
|
|
@@ -30,6 +31,7 @@ export {
|
|
|
30
31
|
type FormItemProps,
|
|
31
32
|
type FormListProps,
|
|
32
33
|
type InputWrapperProps,
|
|
34
|
+
type ValidationRule,
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
export default Form;
|
package/src/providers/Form.tsx
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import { cloneDeep, get, isEqual, last, set, uniq } from "lodash";
|
|
2
2
|
import { useTask } from "minh-custom-hooks-release";
|
|
3
|
+
import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
3
4
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
4
|
-
import type { ReactNode, ComponentType, FormHTMLAttributes } from "react";
|
|
5
5
|
import { flushSync } from "react-dom";
|
|
6
6
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
7
7
|
import FormCleanUp from "../components/Form/FormCleanUp";
|
|
8
8
|
import { useFormListeners, useFormStore } from "../stores/formStore";
|
|
9
9
|
import type { PublicFormInstance } from "../types/public";
|
|
10
10
|
import { getAllNoneObjStringPath } from "../utils/obj.util";
|
|
11
|
+
export type { ValidationRule } from "../types/public";
|
|
11
12
|
|
|
12
13
|
export const FormContext = createContext(null);
|
|
13
14
|
|
|
14
|
-
export interface FormProps<T = any> extends Omit<
|
|
15
|
+
export interface FormProps<T = any> extends Omit<
|
|
16
|
+
FormHTMLAttributes<HTMLFormElement>,
|
|
17
|
+
"onSubmit"
|
|
18
|
+
> {
|
|
15
19
|
children: ReactNode;
|
|
16
20
|
formName: string;
|
|
17
21
|
initialValues?: T;
|
|
18
22
|
onFinish?: (values: T, allValues?: any) => void | Promise<void>;
|
|
19
23
|
onReject?: (errorFields: any[]) => void | Promise<void>;
|
|
20
|
-
onFinally?: (result: {
|
|
24
|
+
onFinally?: (result: {
|
|
25
|
+
errorsField: any[];
|
|
26
|
+
values: T;
|
|
27
|
+
withUnRegisteredValues: any;
|
|
28
|
+
}) => void | Promise<void>;
|
|
21
29
|
FormElement?: ComponentType<any>;
|
|
22
30
|
}
|
|
23
31
|
|
|
@@ -32,6 +40,7 @@ export default function Form<T = any>({
|
|
|
32
40
|
...props
|
|
33
41
|
}: FormProps<T>) {
|
|
34
42
|
const {
|
|
43
|
+
appInitValue,
|
|
35
44
|
getFormItemValue,
|
|
36
45
|
setInitData,
|
|
37
46
|
setData,
|
|
@@ -45,7 +54,8 @@ export default function Form<T = any>({
|
|
|
45
54
|
clearFormState,
|
|
46
55
|
} = useFormStore(
|
|
47
56
|
useShallow((state) => ({
|
|
48
|
-
|
|
57
|
+
appInitValue: state.initialValues,
|
|
58
|
+
setInitData: state.setInitData,
|
|
49
59
|
getFormValues: state.getFormValues,
|
|
50
60
|
setFormState: state.setFormState,
|
|
51
61
|
setFormInstance: state.setFormInstance,
|
|
@@ -303,7 +313,19 @@ export default function Form<T = any>({
|
|
|
303
313
|
};
|
|
304
314
|
|
|
305
315
|
useEffect(() => {
|
|
306
|
-
|
|
316
|
+
// set initial values
|
|
317
|
+
if (initialValues) {
|
|
318
|
+
const allStringPath = getAllNoneObjStringPath(initialValues);
|
|
319
|
+
allStringPath.forEach((p) => {
|
|
320
|
+
// console.log(
|
|
321
|
+
// "Set initial value for form",
|
|
322
|
+
// formName,
|
|
323
|
+
// p,
|
|
324
|
+
// get(initialValues, p),
|
|
325
|
+
// );
|
|
326
|
+
setInitData(formName, p, get(initialValues, p));
|
|
327
|
+
});
|
|
328
|
+
}
|
|
307
329
|
setFormState({
|
|
308
330
|
formName,
|
|
309
331
|
isInitied: true,
|
|
@@ -330,6 +352,10 @@ export default function Form<T = any>({
|
|
|
330
352
|
};
|
|
331
353
|
}, []);
|
|
332
354
|
|
|
355
|
+
// useEffect(() => {
|
|
356
|
+
// // console.log("Form appInitValue changed", appInitValue);
|
|
357
|
+
// }, [appInitValue]);
|
|
358
|
+
|
|
333
359
|
return (
|
|
334
360
|
<FormContext.Provider
|
|
335
361
|
value={{
|
|
@@ -374,9 +400,12 @@ export function useFormContext() {
|
|
|
374
400
|
return c;
|
|
375
401
|
}
|
|
376
402
|
|
|
377
|
-
export function useForm<T = any>(
|
|
403
|
+
export function useForm<T = any>(
|
|
404
|
+
formNameOrFormInstance?: string | PublicFormInstance<T>,
|
|
405
|
+
) {
|
|
378
406
|
const targetFormName =
|
|
379
|
-
typeof formNameOrFormInstance === "object" &&
|
|
407
|
+
typeof formNameOrFormInstance === "object" &&
|
|
408
|
+
formNameOrFormInstance !== null
|
|
380
409
|
? (formNameOrFormInstance as PublicFormInstance<T>).formName
|
|
381
410
|
: (formNameOrFormInstance as string | undefined);
|
|
382
411
|
|
|
@@ -387,7 +416,10 @@ export function useForm<T = any>(formNameOrFormInstance?: string | PublicFormIns
|
|
|
387
416
|
return [formInstance];
|
|
388
417
|
}
|
|
389
418
|
|
|
390
|
-
export function useWatch<T = any>(
|
|
419
|
+
export function useWatch<T = any>(
|
|
420
|
+
name: keyof T & string,
|
|
421
|
+
formNameOrFormInstance?: string | PublicFormInstance<T>,
|
|
422
|
+
) {
|
|
391
423
|
const [formInstance] = useForm<T>(formNameOrFormInstance as any);
|
|
392
424
|
|
|
393
425
|
const value = useFormStore((state) => {
|
|
@@ -404,7 +436,11 @@ export function useSubmitDataWatch<T = any>({
|
|
|
404
436
|
formNameOrFormInstance,
|
|
405
437
|
triggerWhenChange = false,
|
|
406
438
|
mapFn,
|
|
407
|
-
}: {
|
|
439
|
+
}: {
|
|
440
|
+
formNameOrFormInstance?: string | PublicFormInstance<T>;
|
|
441
|
+
triggerWhenChange?: boolean;
|
|
442
|
+
mapFn?: (v: any, prev: any) => any;
|
|
443
|
+
}) {
|
|
408
444
|
const [formInstance] = useForm<T>(formNameOrFormInstance as any);
|
|
409
445
|
|
|
410
446
|
const value = useFormStore((state) => {
|
|
@@ -424,7 +460,9 @@ export function useSubmitDataWatch<T = any>({
|
|
|
424
460
|
return submitData as T | undefined;
|
|
425
461
|
}
|
|
426
462
|
|
|
427
|
-
export const useFormStateWatch = <T = any
|
|
463
|
+
export const useFormStateWatch = <T = any,>(
|
|
464
|
+
formNameOrFormInstance?: string | PublicFormInstance<T>,
|
|
465
|
+
) => {
|
|
428
466
|
const [formInstance] = useForm<T>(formNameOrFormInstance as any);
|
|
429
467
|
|
|
430
468
|
const formState = useFormStore((state) => {
|