react-form-manage 1.0.8-beta.23 → 1.0.8-beta.26
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 +23 -0
- package/dist/components/Form/FormItem.d.ts +9 -4
- package/dist/components/Form/FormItem.js +52 -16
- package/dist/hooks/useFormItemControl.js +24 -10
- package/dist/hooks/useFormListControl.js +10 -12
- package/dist/providers/Form.js +4 -5
- package/dist/stores/formStore.js +13 -0
- package/dist/test/CommonTest.d.ts +3 -0
- package/dist/test/CommonTest.js +49 -0
- package/dist/test/TestWrapperFormItem.d.ts +3 -0
- package/dist/test/TestWrapperFormItem.js +13 -0
- package/package.json +1 -1
- package/src/App.tsx +9 -178
- package/src/components/Form/FormCleanUp.tsx +1 -1
- package/src/components/Form/FormItem.tsx +170 -56
- package/src/hooks/useFormItemControl.ts +24 -17
- package/src/hooks/useFormListControl.ts +28 -31
- package/src/providers/Form.tsx +0 -2
- package/src/stores/formStore.ts +24 -0
- package/src/test/CommonTest.tsx +177 -0
- package/src/test/TestWrapperFormItem.tsx +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8-beta.25] - 2026-02-04
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
8
|
+
|
|
9
|
+
- Remove cleanup stack execution for clearing form items and array values
|
|
10
|
+
|
|
11
|
+
## [1.0.8-beta.24] - 2026-02-04
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- Fix cleanup item execution for proper FormItem unmounting
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- Add `Component` prop to FormItem for wrapping input with custom elements
|
|
20
|
+
- Support custom wrapper components in FormItem
|
|
21
|
+
|
|
5
22
|
## [1.0.8-beta.23] - 2026-02-04
|
|
6
23
|
|
|
7
24
|
### Features
|
|
25
|
+
|
|
8
26
|
- Add `hidden` prop to FormItem for conditional visibility control
|
|
9
27
|
- Add `collectHiddenValue` option to control whether hidden form items contribute to form data collection
|
|
10
28
|
- FormItem can now be retrieved from Form instance
|
|
@@ -13,26 +31,31 @@ All notable changes to this project will be documented in this file.
|
|
|
13
31
|
## [1.0.8-beta.22] - 2026-02-02
|
|
14
32
|
|
|
15
33
|
### Changes
|
|
34
|
+
|
|
16
35
|
- Refactor: Adjust useFormItemControl and main.tsx for better performance
|
|
17
36
|
|
|
18
37
|
## [1.0.8-beta.21] - 2026-02-02
|
|
19
38
|
|
|
20
39
|
### Fixes
|
|
40
|
+
|
|
21
41
|
- Patch: Minor adjustments in useFormItemControl
|
|
22
42
|
|
|
23
43
|
## [1.0.8-beta.20] - 2026-02-02
|
|
24
44
|
|
|
25
45
|
### Features
|
|
46
|
+
|
|
26
47
|
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
27
48
|
|
|
28
49
|
## [1.0.8-beta.19] - 2026-02-02
|
|
29
50
|
|
|
30
51
|
### Features
|
|
52
|
+
|
|
31
53
|
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
32
54
|
|
|
33
55
|
## [1.0.8-beta.18] - 2026-02-01
|
|
34
56
|
|
|
35
57
|
### Features
|
|
58
|
+
|
|
36
59
|
- Avoid deep traversal into class instances/functions in path collection
|
|
37
60
|
- `getAllNoneObjStringPath` now skips non-plain objects and functions, only traverses arrays and plain objects
|
|
38
61
|
- Improves performance and prevents accidental property access on class instances
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
1
|
+
import type { ComponentProps, FC, ReactElement } from "react";
|
|
2
2
|
import type { ValidationRule } from "../../types/public";
|
|
3
|
-
export
|
|
3
|
+
export type BaseFormItemProps = {
|
|
4
4
|
children: ReactElement<any>;
|
|
5
5
|
name: string;
|
|
6
6
|
formName?: string;
|
|
@@ -12,5 +12,10 @@ export interface FormItemProps {
|
|
|
12
12
|
controlAfterInit?: boolean;
|
|
13
13
|
hidden?: boolean;
|
|
14
14
|
collectOnHidden?: boolean;
|
|
15
|
-
}
|
|
16
|
-
export
|
|
15
|
+
};
|
|
16
|
+
export type FormItemProps<TCustomWrapper extends FC<any> | undefined> = BaseFormItemProps & ({
|
|
17
|
+
Component?: undefined;
|
|
18
|
+
} | ({
|
|
19
|
+
Component: TCustomWrapper;
|
|
20
|
+
} & Omit<ComponentProps<TCustomWrapper>, keyof BaseFormItemProps>));
|
|
21
|
+
export default function FormItem<TCustomWrapper extends FC<any> | undefined>({ children, Component, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, controlAfterInit, hidden, collectOnHidden, ...props }: FormItemProps<TCustomWrapper>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { isNil } from "lodash";
|
|
3
|
+
import { cloneElement, Fragment, useEffect, useRef, useState } from "react";
|
|
3
4
|
import { v4 } from "uuid";
|
|
4
5
|
import useFormItemControl from "../../hooks/useFormItemControl";
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
formName,
|
|
6
|
+
function BaseFormItem({ children, name, elementRef: elRef, valuePropName, getValueFromEvent, onChange, value, isDirty, errors, onFocus, state, submitState, isTouched }) {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
}, [value]);
|
|
9
|
+
return cloneElement(children, {
|
|
10
10
|
name,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
ref: elRef,
|
|
12
|
+
[valuePropName]: value,
|
|
13
|
+
onChange: (...args) => {
|
|
14
|
+
let val = args[0];
|
|
15
|
+
if (getValueFromEvent && typeof getValueFromEvent === "function") {
|
|
16
|
+
val = getValueFromEvent(...args);
|
|
17
|
+
} else {
|
|
18
|
+
const e = args[0];
|
|
19
|
+
if (e && e.target) {
|
|
20
|
+
val = e.target.value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
onChange(val);
|
|
24
|
+
},
|
|
25
|
+
// onFocus: () => {
|
|
26
|
+
// setIsTouched(true);
|
|
27
|
+
// },
|
|
28
|
+
// isTouched: isTouched,
|
|
29
|
+
isDirty,
|
|
30
|
+
// errors: errors,
|
|
31
|
+
// formState,
|
|
32
|
+
errors,
|
|
33
|
+
onFocus,
|
|
34
|
+
validateState: state,
|
|
35
|
+
submitState,
|
|
36
|
+
isTouched
|
|
17
37
|
});
|
|
18
|
-
|
|
38
|
+
}
|
|
39
|
+
function FormItemWithWrapper({ children, name, elementRef: elRef, valuePropName, getValueFromEvent, onChange, value, isDirty, errors, onFocus, state, submitState, isTouched, Component, ...props }) {
|
|
40
|
+
return _jsx(Component, { ...props, children: cloneElement(children, {
|
|
19
41
|
name,
|
|
20
|
-
|
|
42
|
+
ref: elRef,
|
|
21
43
|
[valuePropName]: value,
|
|
22
44
|
onChange: (...args) => {
|
|
23
45
|
let val = args[0];
|
|
@@ -41,10 +63,24 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
|
|
|
41
63
|
errors,
|
|
42
64
|
onFocus,
|
|
43
65
|
validateState: state,
|
|
44
|
-
ref: elRef,
|
|
45
66
|
submitState,
|
|
46
67
|
isTouched
|
|
47
|
-
})
|
|
68
|
+
}) });
|
|
69
|
+
}
|
|
70
|
+
function FormItem({ children, Component, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent, controlAfterInit = false, hidden, collectOnHidden, ...props }) {
|
|
71
|
+
const [formItemId] = useState(externalFormItemId != null ? externalFormItemId : v4());
|
|
72
|
+
const elRef = useRef(null);
|
|
73
|
+
const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, isInitied } = useFormItemControl({
|
|
74
|
+
formName,
|
|
75
|
+
name,
|
|
76
|
+
initialValue,
|
|
77
|
+
formItemId,
|
|
78
|
+
rules,
|
|
79
|
+
elementRef: elRef,
|
|
80
|
+
hidden,
|
|
81
|
+
collectOnHidden
|
|
82
|
+
});
|
|
83
|
+
return _jsx(Fragment, { children: !hidden && children ? isNil(Component) ? _jsx(BaseFormItem, { elementRef: elRef, name, valuePropName, getValueFromEvent, value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, formItemId, children }) : _jsx(FormItemWithWrapper, { elementRef: elRef, name, valuePropName, getValueFromEvent, value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, formItemId, Component, ...props, children }) : null }, `control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${externalFormItemId}`);
|
|
48
84
|
}
|
|
49
85
|
export {
|
|
50
86
|
FormItem as default
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { get, has, isNil } from "lodash";
|
|
2
2
|
import { useTaskEffect } from "minh-custom-hooks-release";
|
|
3
|
-
import { useEffect, useMemo } from "react";
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo } from "react";
|
|
4
4
|
import { useShallow } from "zustand/react/shallow";
|
|
5
5
|
import { IS_ALPHABET_STRING_AND_NUMBER_REGEX, IS_EMAIL_REGEX, IS_NAME_REGEX, IS_NO_SPACE_ALPHABET_STRING_AND_NUMBER_REGEX, IS_NO_SPACE_ALPHABET_STRING_REGEX, IS_NOSPACE_STRING_AND_NUMBER_REGEX, IS_PASSWORD_REGEX, IS_POSITIVE_INTEGER_STRING_NUMBER_REGEX, IS_POSITIVE_STRING_NUMBER_REGEX, IS_STRING_AND_NUMBER_REGEX, IS_STRING_NUMBER_REGEX, IS_USERNAME_REGEX, IS_VIETNAMESE_PHONE_NUMBER_REGEX } from "../constants/validation";
|
|
6
6
|
import { useFormContext } from "../providers/Form";
|
|
@@ -15,7 +15,10 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
15
15
|
getFormValues,
|
|
16
16
|
// getFormState,
|
|
17
17
|
isStateInitied,
|
|
18
|
-
submitState
|
|
18
|
+
submitState,
|
|
19
|
+
getListener,
|
|
20
|
+
revokeListener,
|
|
21
|
+
clearObjKeyItem
|
|
19
22
|
} = useFormStore(useShallow((state2) => {
|
|
20
23
|
var _a, _b, _c, _d;
|
|
21
24
|
return {
|
|
@@ -25,7 +28,10 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
25
28
|
getFormValues: state2.getFormValues,
|
|
26
29
|
// getFormState: state.getFormState,
|
|
27
30
|
isStateInitied: (_b = (_a = state2.formStates) == null ? void 0 : _a[formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]) == null ? void 0 : _b.isInitied,
|
|
28
|
-
submitState: (_d = (_c = state2.formStates) == null ? void 0 : _c[formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]) == null ? void 0 : _d.submitState
|
|
31
|
+
submitState: (_d = (_c = state2.formStates) == null ? void 0 : _c[formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]) == null ? void 0 : _d.submitState,
|
|
32
|
+
getListener: state2.getListener,
|
|
33
|
+
revokeListener: state2.revokeListener,
|
|
34
|
+
clearObjKeyItem: state2.clearObjKeyItem
|
|
29
35
|
};
|
|
30
36
|
}));
|
|
31
37
|
const { setCleanUpStack } = useFormStore(useShallow((state2) => ({
|
|
@@ -295,7 +301,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
295
301
|
return;
|
|
296
302
|
}
|
|
297
303
|
}, [isStateInitied]);
|
|
298
|
-
|
|
304
|
+
useLayoutEffect(() => {
|
|
299
305
|
if (!listener) {
|
|
300
306
|
setListener({
|
|
301
307
|
onChange,
|
|
@@ -312,14 +318,22 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
312
318
|
});
|
|
313
319
|
}
|
|
314
320
|
return () => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
+
revokeListener(formItemId, (listener2, same) => {
|
|
322
|
+
if (!same.length) {
|
|
323
|
+
clearObjKeyItem(listener2.formName, listener2.name);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
321
326
|
};
|
|
322
327
|
}, []);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (listener) {
|
|
330
|
+
setListener({
|
|
331
|
+
formItemId,
|
|
332
|
+
hidden,
|
|
333
|
+
collectOnHidden
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}, [hidden, collectOnHidden]);
|
|
323
337
|
useEffect(() => {
|
|
324
338
|
if (listener) {
|
|
325
339
|
setListener({
|
|
@@ -9,17 +9,16 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
9
9
|
const contextForm = useFormContext();
|
|
10
10
|
const getFormValues = useFormStore((state) => state.getFormValues);
|
|
11
11
|
const [listFormInitValues, setListFormInitValues] = useState(void 0);
|
|
12
|
-
const { clearCacheData, setCacheData, setListener, getListener } = useFormStore(useShallow((state) => ({
|
|
12
|
+
const { clearCacheData, setCacheData, setListener, getListener, clearArrItems } = useFormStore(useShallow((state) => ({
|
|
13
13
|
// Cache
|
|
14
14
|
cacheData: state.cacheData,
|
|
15
15
|
clearCacheData: state.clearCacheData,
|
|
16
16
|
setCacheData: state.setCacheData,
|
|
17
17
|
// Listener
|
|
18
18
|
setListener: state.setListener,
|
|
19
|
-
getListener: state.getListener
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
setCleanUpStack: state.setCleanUpStack
|
|
19
|
+
getListener: state.getListener,
|
|
20
|
+
// Clear Arr Items
|
|
21
|
+
clearArrItems: state.clearArrItems
|
|
23
22
|
})));
|
|
24
23
|
const { initValue: internalInitValue, formState } = useFormStore(useShallow((state) => {
|
|
25
24
|
return {
|
|
@@ -55,15 +54,14 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
55
54
|
const getNewValueCache = mapCurWithKey.filter(Boolean).map((c) => c.value);
|
|
56
55
|
const startRemoveIndex = formDataBeforeChange.length - getNewValueCache.length;
|
|
57
56
|
if (startRemoveIndex > 0) {
|
|
58
|
-
Array.from(Array(startRemoveIndex)).map((_, index) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
setCleanUpStack({
|
|
57
|
+
const clearItems = Array.from(Array(startRemoveIndex)).map((_, index) => {
|
|
58
|
+
const clearIndex = getNewValueCache.length + index;
|
|
59
|
+
return {
|
|
62
60
|
formName: formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName),
|
|
63
|
-
name: `${name}.${
|
|
64
|
-
|
|
65
|
-
});
|
|
61
|
+
name: `${name}.${clearIndex}`
|
|
62
|
+
};
|
|
66
63
|
});
|
|
64
|
+
clearArrItems(clearItems);
|
|
67
65
|
}
|
|
68
66
|
setCacheData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, getNewValueCache);
|
|
69
67
|
};
|
package/dist/providers/Form.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cloneDeep, filter, get, isEqual, isNil, isPlainObject, last, set, uniqBy } from "lodash";
|
|
3
3
|
import { useTask } from "minh-custom-hooks-release";
|
|
4
4
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
5
5
|
import { flushSync } from "react-dom";
|
|
6
6
|
import { useShallow } from "zustand/react/shallow";
|
|
7
|
-
import FormCleanUp from "../components/Form/FormCleanUp";
|
|
8
7
|
import FormItem from "../components/Form/FormItem";
|
|
9
8
|
import { SUBMIT_STATE } from "../constants/form";
|
|
10
9
|
import { useFormStore } from "../stores/formStore";
|
|
@@ -300,9 +299,9 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
300
299
|
clearFormState(formName);
|
|
301
300
|
};
|
|
302
301
|
}, []);
|
|
303
|
-
return
|
|
302
|
+
return _jsx(FormContext.Provider, { value: {
|
|
304
303
|
formName
|
|
305
|
-
}, children:
|
|
304
|
+
}, children: FormElement ? _jsx(FormElement, { onSubmit: (e) => {
|
|
306
305
|
e.preventDefault();
|
|
307
306
|
e.stopPropagation();
|
|
308
307
|
runSubmit(void 0);
|
|
@@ -310,7 +309,7 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
310
309
|
e.preventDefault();
|
|
311
310
|
e.stopPropagation();
|
|
312
311
|
runSubmit(void 0);
|
|
313
|
-
}, ...props, children })
|
|
312
|
+
}, ...props, children }) });
|
|
314
313
|
}
|
|
315
314
|
function useFormContext() {
|
|
316
315
|
const c = useContext(FormContext);
|
package/dist/stores/formStore.js
CHANGED
|
@@ -72,6 +72,19 @@ const createFormStoreSlice = (storeSet, storeGet, api) => ({
|
|
|
72
72
|
}
|
|
73
73
|
}));
|
|
74
74
|
},
|
|
75
|
+
clearArrItems(clearItems) {
|
|
76
|
+
return storeSet(produce((state) => {
|
|
77
|
+
const oldValues = state.forms;
|
|
78
|
+
clearItems.forEach(({ formName, name }) => {
|
|
79
|
+
const arrPath = name.split(".").slice(0, -1).join(".");
|
|
80
|
+
const elPath = Number(name.split(".").slice(-1).join(""));
|
|
81
|
+
const getArrItem = get(oldValues, `${formName}.${arrPath}`);
|
|
82
|
+
if (isNumber(elPath)) {
|
|
83
|
+
getArrItem.splice(elPath, 1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}));
|
|
87
|
+
},
|
|
75
88
|
setFormState({ formName, isInitied, submitState }) {
|
|
76
89
|
return storeSet(produce((state) => {
|
|
77
90
|
const oldValues = state.formStates;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Checkbox, Input } from "antd";
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
import FormItem from "../components/Form/FormItem";
|
|
5
|
+
import FormList from "../components/Form/FormList";
|
|
6
|
+
import InputWrapper from "../components/Form/InputWrapper";
|
|
7
|
+
import Form from "../providers/Form";
|
|
8
|
+
function CommonTest({}) {
|
|
9
|
+
const [form] = Form.useForm("form1");
|
|
10
|
+
const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
|
|
11
|
+
return _jsxs(Form, { initialValues: {
|
|
12
|
+
TestData: "",
|
|
13
|
+
numericCode: ""
|
|
14
|
+
// arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
15
|
+
}, onFinish: (values) => {
|
|
16
|
+
}, formName: "form1", children: [_jsx(FormItem, { name: "username", rules: [
|
|
17
|
+
{
|
|
18
|
+
required: true,
|
|
19
|
+
message: "Test"
|
|
20
|
+
}
|
|
21
|
+
], initialValue: "283746", children: _jsx(InputWrapper, { children: _jsx(Input, {}) }) }), _jsx(FormItem, { name: "numericCode", rules: [
|
|
22
|
+
{ required: true, message: "Vui l\xF2ng nh\u1EADp m\xE3" },
|
|
23
|
+
{
|
|
24
|
+
pattern: /^\d+$/,
|
|
25
|
+
message: "Ch\u1EC9 cho ph\xE9p chu\u1ED7i s\u1ED1"
|
|
26
|
+
}
|
|
27
|
+
], children: _jsx(InputWrapper, { children: _jsx(Input, { placeholder: "M\xE3 ch\u1EC9 g\u1ED3m s\u1ED1", style: { width: 200 } }) }) }), _jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 1 }, children: _jsx(FormItem, { name: "motionTest", controlAfterInit: true, initialValue: "1234134", children: _jsx(InputWrapper, { children: _jsx(Input, { placeholder: "Motion Test", style: { width: 200 } }) }) }) }), _jsx(FormList, { initialValues: [
|
|
28
|
+
{
|
|
29
|
+
el: "sdfsdf",
|
|
30
|
+
d: { child: "Test Child" }
|
|
31
|
+
}
|
|
32
|
+
], name: "arr", children: (fields, { add, remove, move }) => _jsxs("div", { children: [fields.map((field, index) => _jsxs("div", { style: { marginBottom: 8 }, children: [_jsx(FormItem, { name: `${field.name}.el`, initialValue: "Ch\xE9m gi\xF3", children: _jsx(InputWrapper, { children: _jsx(Input, { placeholder: "Item value", style: { width: 200 } }) }) }), _jsx(FormItem, { name: `${field.name}.d.child`, initialValue: "Con c\u1EE7a item", children: _jsx(InputWrapper, { children: _jsx(Input, { placeholder: "Item value", style: { width: 200 } }) }) }), _jsx(Button, { onClick: () => remove({ index }), style: { marginLeft: 8 }, children: "Remove" }), index > 0 && _jsx(Button, { onClick: () => move({ from: index, to: index - 1 }), style: { marginLeft: 8 }, children: "Move Up" }), index < fields.length - 1 && _jsx(Button, { onClick: () => move({ from: index, to: index + 1 }), style: { marginLeft: 8 }, children: "Move Down" })] }, field.key)), _jsx(Button, { onClick: () => add(fields.length), children: "Add Item" })] }) }), _jsx(Button, { onClick: () => {
|
|
33
|
+
form == null ? void 0 : form.setFieldValue("arr", [
|
|
34
|
+
{ el: "Set Item 1" },
|
|
35
|
+
{ el: "Set Item 2" },
|
|
36
|
+
{ el: "Set Item 3" }
|
|
37
|
+
]);
|
|
38
|
+
}, children: "Test set array list value" }), _jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 1.5 }, children: _jsx(Form.Item, { valuePropName: "checked", getValueFromEvent: (_, checked) => checked, name: "checkControlledAfterInit", controlAfterInit: true, initialValue: true, hidden: true, children: _jsx(Checkbox, {}) }) }), _jsx(Button, { onClick: () => {
|
|
39
|
+
const current = form == null ? void 0 : form.getFieldValue("checkControlledAfterInit");
|
|
40
|
+
form == null ? void 0 : form.setFieldValue("checkControlledAfterInit", !current);
|
|
41
|
+
}, children: "Toggle" }), _jsx(Button, { htmlType: "submit", children: "Submit" }), _jsx(Button, { onClick: () => {
|
|
42
|
+
var _a;
|
|
43
|
+
(_a = form == null ? void 0 : form.resetFields) == null ? void 0 : _a.call(form);
|
|
44
|
+
}, children: "Reset" })] });
|
|
45
|
+
}
|
|
46
|
+
var stdin_default = CommonTest;
|
|
47
|
+
export {
|
|
48
|
+
stdin_default as default
|
|
49
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Col, Input, Row } from "antd";
|
|
3
|
+
import { useToggle } from "minh-custom-hooks-release";
|
|
4
|
+
import Form from "../providers/Form";
|
|
5
|
+
function TestWrapperFormItem({}) {
|
|
6
|
+
const { state, toggle } = useToggle(false);
|
|
7
|
+
return _jsxs(Form, { onFinish: (values, all) => {
|
|
8
|
+
}, formName: "testWrapper", children: [_jsx(Row, { children: _jsx(Form.Item, { hidden: !state, name: "test", collectOnHidden: false, initialValue: "test", Component: Col, xs: 12, children: _jsx(Input, {}) }) }), _jsx(Button, { htmlType: "submit", children: "Submit" }), _jsx(Button, { onClick: toggle, children: state ? "Off" : "On" })] });
|
|
9
|
+
}
|
|
10
|
+
var stdin_default = TestWrapperFormItem;
|
|
11
|
+
export {
|
|
12
|
+
stdin_default as default
|
|
13
|
+
};
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Button, Input } from "antd";
|
|
1
|
+
import { Input } from "antd";
|
|
3
2
|
import { motion } from "framer-motion";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import FormList from "./components/Form/FormList";
|
|
7
|
-
import InputWrapper from "./components/Form/InputWrapper";
|
|
8
|
-
import Form, { useForm } from "./providers/Form";
|
|
9
|
-
import TestDialog from "./test/TestDialog";
|
|
3
|
+
import Form from "./providers/Form";
|
|
4
|
+
import CommonTest from "./test/CommonTest";
|
|
10
5
|
import TestListener from "./test/TestListener";
|
|
6
|
+
import TestWrapperFormItem from "./test/TestWrapperFormItem";
|
|
11
7
|
|
|
12
8
|
import { Form as AntdForm } from "antd";
|
|
13
9
|
|
|
@@ -18,17 +14,6 @@ function TestFormWatch() {
|
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
const App = () => {
|
|
21
|
-
const [form] = useForm("form1");
|
|
22
|
-
|
|
23
|
-
const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (form) {
|
|
27
|
-
// setTimeout(() => {
|
|
28
|
-
// form.setFieldValue("TestData", "HelloWorld");
|
|
29
|
-
// }, 500);
|
|
30
|
-
}
|
|
31
|
-
}, [form]);
|
|
32
17
|
return (
|
|
33
18
|
<div>
|
|
34
19
|
<AntdForm>
|
|
@@ -45,167 +30,13 @@ const App = () => {
|
|
|
45
30
|
</AntdForm>
|
|
46
31
|
|
|
47
32
|
{/* Hidden Test */}
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
53
|
-
}}
|
|
54
|
-
onFinish={(values) => {
|
|
55
|
-
console.log(values);
|
|
56
|
-
}}
|
|
57
|
-
formName={"form1"}
|
|
58
|
-
// hidden
|
|
59
|
-
>
|
|
60
|
-
<FormItem
|
|
61
|
-
name={"username"}
|
|
62
|
-
rules={[
|
|
63
|
-
{
|
|
64
|
-
required: true,
|
|
65
|
-
message: "Test",
|
|
66
|
-
},
|
|
67
|
-
]}
|
|
68
|
-
initialValue={"283746"}
|
|
69
|
-
// hidden
|
|
70
|
-
>
|
|
71
|
-
<InputWrapper>
|
|
72
|
-
<Input />
|
|
73
|
-
</InputWrapper>
|
|
74
|
-
</FormItem>
|
|
75
|
-
|
|
76
|
-
{/* Numberic test */}
|
|
77
|
-
<FormItem
|
|
78
|
-
name={"numericCode"}
|
|
79
|
-
rules={[
|
|
80
|
-
{ required: true, message: "Vui lòng nhập mã" },
|
|
81
|
-
{
|
|
82
|
-
pattern: /^\d+$/,
|
|
83
|
-
message: "Chỉ cho phép chuỗi số",
|
|
84
|
-
},
|
|
85
|
-
]}
|
|
86
|
-
>
|
|
87
|
-
<InputWrapper>
|
|
88
|
-
<Input placeholder="Mã chỉ gồm số" style={{ width: 200 }} />
|
|
89
|
-
</InputWrapper>
|
|
90
|
-
</FormItem>
|
|
91
|
-
|
|
92
|
-
{/* Motion Test */}
|
|
93
|
-
<motion.div
|
|
94
|
-
initial={{ opacity: 0 }}
|
|
95
|
-
animate={{ opacity: 1 }}
|
|
96
|
-
exit={{ opacity: 0 }}
|
|
97
|
-
transition={{ duration: 1 }}
|
|
98
|
-
>
|
|
99
|
-
<FormItem name="motionTest" controlAfterInit initialValue={"1234134"}>
|
|
100
|
-
<InputWrapper>
|
|
101
|
-
<Input placeholder="Motion Test" style={{ width: 200 }} />
|
|
102
|
-
</InputWrapper>
|
|
103
|
-
</FormItem>
|
|
104
|
-
</motion.div>
|
|
105
|
-
<FormList
|
|
106
|
-
initialValues={[
|
|
107
|
-
{
|
|
108
|
-
el: "sdfsdf",
|
|
109
|
-
d: { child: "Test Child" },
|
|
110
|
-
},
|
|
111
|
-
]}
|
|
112
|
-
name="arr"
|
|
113
|
-
>
|
|
114
|
-
{(fields, { add, remove, move }) => (
|
|
115
|
-
<div>
|
|
116
|
-
{fields.map((field, index) => (
|
|
117
|
-
<div key={field.key} style={{ marginBottom: 8 }}>
|
|
118
|
-
<FormItem name={`${field.name}.el`} initialValue={"Chém gió"}>
|
|
119
|
-
<InputWrapper>
|
|
120
|
-
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
121
|
-
</InputWrapper>
|
|
122
|
-
</FormItem>
|
|
123
|
-
<FormItem
|
|
124
|
-
name={`${field.name}.d.child`}
|
|
125
|
-
initialValue={"Con của item"}
|
|
126
|
-
>
|
|
127
|
-
<InputWrapper>
|
|
128
|
-
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
129
|
-
</InputWrapper>
|
|
130
|
-
</FormItem>
|
|
131
|
-
<Button
|
|
132
|
-
onClick={() => remove({ index })}
|
|
133
|
-
style={{ marginLeft: 8 }}
|
|
134
|
-
>
|
|
135
|
-
Remove
|
|
136
|
-
</Button>
|
|
137
|
-
{index > 0 && (
|
|
138
|
-
<Button
|
|
139
|
-
onClick={() => move({ from: index, to: index - 1 })}
|
|
140
|
-
style={{ marginLeft: 8 }}
|
|
141
|
-
>
|
|
142
|
-
Move Up
|
|
143
|
-
</Button>
|
|
144
|
-
)}
|
|
145
|
-
{index < fields.length - 1 && (
|
|
146
|
-
<Button
|
|
147
|
-
onClick={() => move({ from: index, to: index + 1 })}
|
|
148
|
-
style={{ marginLeft: 8 }}
|
|
149
|
-
>
|
|
150
|
-
Move Down
|
|
151
|
-
</Button>
|
|
152
|
-
)}
|
|
153
|
-
</div>
|
|
154
|
-
))}
|
|
155
|
-
<Button onClick={() => add(fields.length)}>Add Item</Button>
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
</FormList>
|
|
159
|
-
<TestDialog />
|
|
160
|
-
<Button
|
|
161
|
-
onClick={() => {
|
|
162
|
-
form?.setFieldValue("arr", [
|
|
163
|
-
{ el: "Set Item 1" },
|
|
164
|
-
{ el: "Set Item 2" },
|
|
165
|
-
{ el: "Set Item 3" },
|
|
166
|
-
]);
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
Test set array list value
|
|
170
|
-
</Button>
|
|
171
|
-
<motion.div
|
|
172
|
-
initial={{ opacity: 0 }}
|
|
173
|
-
animate={{ opacity: 1 }}
|
|
174
|
-
exit={{ opacity: 0 }}
|
|
175
|
-
transition={{ duration: 1.5 }}
|
|
176
|
-
>
|
|
177
|
-
<Form.Item
|
|
178
|
-
valuePropName="checked"
|
|
179
|
-
getValueFromEvent={(_, checked) => checked}
|
|
180
|
-
name="checkControlledAfterInit"
|
|
181
|
-
controlAfterInit={true}
|
|
182
|
-
initialValue={true}
|
|
183
|
-
hidden
|
|
184
|
-
>
|
|
185
|
-
<Checkbox />
|
|
186
|
-
</Form.Item>
|
|
187
|
-
</motion.div>
|
|
188
|
-
<TestFormWatch />
|
|
189
|
-
<Button
|
|
190
|
-
onClick={() => {
|
|
191
|
-
const current = form?.getFieldValue("checkControlledAfterInit");
|
|
192
|
-
console.log("Toggle controlled after init: ", current);
|
|
193
|
-
form?.setFieldValue("checkControlledAfterInit", !current);
|
|
194
|
-
}}
|
|
195
|
-
>
|
|
196
|
-
Toggle
|
|
197
|
-
</Button>
|
|
198
|
-
<Button htmlType="submit">Submit</Button>
|
|
199
|
-
<Button
|
|
200
|
-
onClick={() => {
|
|
201
|
-
form?.resetFields?.();
|
|
202
|
-
}}
|
|
203
|
-
>
|
|
204
|
-
Reset
|
|
205
|
-
</Button>
|
|
206
|
-
</Form>
|
|
33
|
+
<CommonTest />
|
|
34
|
+
{/* <Form formName="TestDialog" >
|
|
35
|
+
<TestDialog />
|
|
36
|
+
</Form> */}
|
|
207
37
|
|
|
208
38
|
<TestListener />
|
|
39
|
+
<TestWrapperFormItem />
|
|
209
40
|
</div>
|
|
210
41
|
);
|
|
211
42
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { isNil } from "lodash";
|
|
2
|
+
import type { ComponentProps, FC, ReactElement } from "react";
|
|
3
|
+
import { cloneElement, Fragment, useEffect, useRef, useState } from "react";
|
|
3
4
|
import { v4 } from "uuid";
|
|
4
5
|
import useFormItemControl from "../../hooks/useFormItemControl";
|
|
5
6
|
import type { ValidationRule } from "../../types/public";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
+
export type BaseFormItemProps = {
|
|
8
9
|
children: ReactElement<any>;
|
|
9
10
|
name: string;
|
|
10
11
|
formName?: string;
|
|
@@ -16,10 +17,129 @@ export interface FormItemProps {
|
|
|
16
17
|
controlAfterInit?: boolean;
|
|
17
18
|
hidden?: boolean;
|
|
18
19
|
collectOnHidden?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type FormItemProps<TCustomWrapper extends FC<any> | undefined> =
|
|
23
|
+
BaseFormItemProps &
|
|
24
|
+
(
|
|
25
|
+
| {
|
|
26
|
+
Component?: undefined;
|
|
27
|
+
}
|
|
28
|
+
| ({
|
|
29
|
+
Component: TCustomWrapper;
|
|
30
|
+
} & Omit<ComponentProps<TCustomWrapper>, keyof BaseFormItemProps>)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function BaseFormItem({
|
|
34
|
+
children,
|
|
35
|
+
|
|
36
|
+
name,
|
|
37
|
+
elementRef: elRef,
|
|
38
|
+
|
|
39
|
+
valuePropName,
|
|
40
|
+
getValueFromEvent,
|
|
41
|
+
onChange,
|
|
42
|
+
value,
|
|
43
|
+
isDirty,
|
|
44
|
+
errors,
|
|
45
|
+
onFocus,
|
|
46
|
+
state,
|
|
47
|
+
submitState,
|
|
48
|
+
isTouched,
|
|
49
|
+
}: any) {
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
console.log("Base Form Item: ", { name, value });
|
|
52
|
+
}, [value]);
|
|
53
|
+
return cloneElement(children, {
|
|
54
|
+
name,
|
|
55
|
+
ref: elRef,
|
|
56
|
+
[valuePropName]: value,
|
|
57
|
+
onChange: (...args: any[]) => {
|
|
58
|
+
let val = args[0];
|
|
59
|
+
if (getValueFromEvent && typeof getValueFromEvent === "function") {
|
|
60
|
+
val = getValueFromEvent(...args);
|
|
61
|
+
} else {
|
|
62
|
+
const e = args[0];
|
|
63
|
+
if (e && e.target) {
|
|
64
|
+
val = e.target.value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
onChange(val);
|
|
68
|
+
},
|
|
69
|
+
// onFocus: () => {
|
|
70
|
+
// setIsTouched(true);
|
|
71
|
+
// },
|
|
72
|
+
// isTouched: isTouched,
|
|
73
|
+
isDirty: isDirty,
|
|
74
|
+
// errors: errors,
|
|
75
|
+
// formState,
|
|
76
|
+
|
|
77
|
+
errors,
|
|
78
|
+
onFocus,
|
|
79
|
+
validateState: state,
|
|
80
|
+
submitState,
|
|
81
|
+
isTouched: isTouched,
|
|
82
|
+
} as any);
|
|
19
83
|
}
|
|
20
84
|
|
|
21
|
-
|
|
85
|
+
function FormItemWithWrapper<TComponent extends FC<any>>({
|
|
22
86
|
children,
|
|
87
|
+
|
|
88
|
+
name,
|
|
89
|
+
elementRef: elRef,
|
|
90
|
+
|
|
91
|
+
valuePropName,
|
|
92
|
+
getValueFromEvent,
|
|
93
|
+
onChange,
|
|
94
|
+
value,
|
|
95
|
+
isDirty,
|
|
96
|
+
errors,
|
|
97
|
+
onFocus,
|
|
98
|
+
state,
|
|
99
|
+
submitState,
|
|
100
|
+
isTouched,
|
|
101
|
+
Component,
|
|
102
|
+
...props
|
|
103
|
+
}: any) {
|
|
104
|
+
return (
|
|
105
|
+
<Component {...props}>
|
|
106
|
+
{cloneElement(children, {
|
|
107
|
+
name,
|
|
108
|
+
ref: elRef,
|
|
109
|
+
[valuePropName]: value,
|
|
110
|
+
onChange: (...args: any[]) => {
|
|
111
|
+
let val = args[0];
|
|
112
|
+
if (getValueFromEvent && typeof getValueFromEvent === "function") {
|
|
113
|
+
val = getValueFromEvent(...args);
|
|
114
|
+
} else {
|
|
115
|
+
const e = args[0];
|
|
116
|
+
if (e && e.target) {
|
|
117
|
+
val = e.target.value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
onChange(val);
|
|
121
|
+
},
|
|
122
|
+
// onFocus: () => {
|
|
123
|
+
// setIsTouched(true);
|
|
124
|
+
// },
|
|
125
|
+
// isTouched: isTouched,
|
|
126
|
+
isDirty: isDirty,
|
|
127
|
+
// errors: errors,
|
|
128
|
+
// formState,
|
|
129
|
+
|
|
130
|
+
errors,
|
|
131
|
+
onFocus,
|
|
132
|
+
validateState: state,
|
|
133
|
+
submitState,
|
|
134
|
+
isTouched: isTouched,
|
|
135
|
+
} as any)}
|
|
136
|
+
</Component>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default function FormItem<TCustomWrapper extends FC<any> | undefined>({
|
|
141
|
+
children,
|
|
142
|
+
Component,
|
|
23
143
|
name,
|
|
24
144
|
formName,
|
|
25
145
|
initialValue,
|
|
@@ -30,10 +150,12 @@ export default function FormItem({
|
|
|
30
150
|
controlAfterInit = false,
|
|
31
151
|
hidden,
|
|
32
152
|
collectOnHidden,
|
|
33
|
-
|
|
153
|
+
...props
|
|
154
|
+
}: FormItemProps<TCustomWrapper>) {
|
|
155
|
+
const [formItemId] = useState(externalFormItemId ?? v4());
|
|
156
|
+
|
|
34
157
|
const elRef = useRef<any>(null);
|
|
35
158
|
|
|
36
|
-
const [formItemId] = useState(externalFormItemId ?? v4());
|
|
37
159
|
const {
|
|
38
160
|
value,
|
|
39
161
|
onChange,
|
|
@@ -54,60 +176,52 @@ export default function FormItem({
|
|
|
54
176
|
hidden,
|
|
55
177
|
collectOnHidden,
|
|
56
178
|
});
|
|
57
|
-
// console.log("re-render", formName, name);
|
|
58
|
-
|
|
59
|
-
// useEffect(() => {
|
|
60
|
-
// console.log({ value });
|
|
61
|
-
// }, [value]);
|
|
62
179
|
|
|
63
|
-
// useEffect(() => {
|
|
64
|
-
// console.log("isInitied changed: ", {
|
|
65
|
-
// isInitied,
|
|
66
|
-
// name,
|
|
67
|
-
// value,
|
|
68
|
-
// controlAfterInit,
|
|
69
|
-
// });
|
|
70
|
-
// }, [isInitied]);
|
|
71
180
|
return (
|
|
72
181
|
<Fragment
|
|
73
|
-
key={`control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${
|
|
182
|
+
key={`control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${externalFormItemId}`}
|
|
74
183
|
>
|
|
75
|
-
{!hidden && children
|
|
76
|
-
?
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
onFocus
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
184
|
+
{!hidden && children ? (
|
|
185
|
+
isNil(Component) ? (
|
|
186
|
+
<BaseFormItem
|
|
187
|
+
elementRef={elRef}
|
|
188
|
+
name={name}
|
|
189
|
+
valuePropName={valuePropName}
|
|
190
|
+
getValueFromEvent={getValueFromEvent}
|
|
191
|
+
value={value}
|
|
192
|
+
onChange={onChange}
|
|
193
|
+
errors={errors}
|
|
194
|
+
state={state}
|
|
195
|
+
onFocus={onFocus}
|
|
196
|
+
isDirty={isDirty}
|
|
197
|
+
submitState={submitState}
|
|
198
|
+
isTouched={isTouched}
|
|
199
|
+
formItemId={formItemId}
|
|
200
|
+
>
|
|
201
|
+
{children}
|
|
202
|
+
</BaseFormItem>
|
|
203
|
+
) : (
|
|
204
|
+
<FormItemWithWrapper
|
|
205
|
+
elementRef={elRef}
|
|
206
|
+
name={name}
|
|
207
|
+
valuePropName={valuePropName}
|
|
208
|
+
getValueFromEvent={getValueFromEvent}
|
|
209
|
+
value={value}
|
|
210
|
+
onChange={onChange}
|
|
211
|
+
errors={errors}
|
|
212
|
+
state={state}
|
|
213
|
+
onFocus={onFocus}
|
|
214
|
+
isDirty={isDirty}
|
|
215
|
+
submitState={submitState}
|
|
216
|
+
isTouched={isTouched}
|
|
217
|
+
formItemId={formItemId}
|
|
218
|
+
Component={Component}
|
|
219
|
+
{...props}
|
|
220
|
+
>
|
|
221
|
+
{children}
|
|
222
|
+
</FormItemWithWrapper>
|
|
223
|
+
)
|
|
224
|
+
) : null}
|
|
111
225
|
</Fragment>
|
|
112
226
|
);
|
|
113
227
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { get, has, isNil } from "lodash";
|
|
2
2
|
import { useTaskEffect } from "minh-custom-hooks-release";
|
|
3
|
-
import { useEffect, useMemo, type RefObject } from "react";
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, type RefObject } from "react";
|
|
4
4
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
5
5
|
import {
|
|
6
6
|
IS_ALPHABET_STRING_AND_NUMBER_REGEX,
|
|
@@ -75,6 +75,9 @@ export default function useFormItemControl<T = any>({
|
|
|
75
75
|
// getFormState,
|
|
76
76
|
isStateInitied,
|
|
77
77
|
submitState,
|
|
78
|
+
getListener,
|
|
79
|
+
revokeListener,
|
|
80
|
+
clearObjKeyItem,
|
|
78
81
|
} = useFormStore(
|
|
79
82
|
useShallow((state) => {
|
|
80
83
|
return {
|
|
@@ -94,6 +97,9 @@ export default function useFormItemControl<T = any>({
|
|
|
94
97
|
state.formStates?.[
|
|
95
98
|
formName || form?.formName || contextForm?.formName
|
|
96
99
|
]?.submitState,
|
|
100
|
+
getListener: state.getListener,
|
|
101
|
+
revokeListener: state.revokeListener,
|
|
102
|
+
clearObjKeyItem: state.clearObjKeyItem,
|
|
97
103
|
};
|
|
98
104
|
}),
|
|
99
105
|
);
|
|
@@ -566,7 +572,8 @@ export default function useFormItemControl<T = any>({
|
|
|
566
572
|
}
|
|
567
573
|
}, [isStateInitied]);
|
|
568
574
|
|
|
569
|
-
|
|
575
|
+
useLayoutEffect(() => {
|
|
576
|
+
console.log("Component mount: ", name, formItemId, listener);
|
|
570
577
|
if (!listener) {
|
|
571
578
|
setListener({
|
|
572
579
|
onChange,
|
|
@@ -584,12 +591,12 @@ export default function useFormItemControl<T = any>({
|
|
|
584
591
|
}
|
|
585
592
|
|
|
586
593
|
return () => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
}
|
|
594
|
+
revokeListener(formItemId, (listener, same) => {
|
|
595
|
+
if (!same.length) {
|
|
596
|
+
console.log("Trigger after revoke: ", listener, same);
|
|
597
|
+
clearObjKeyItem(listener.formName, listener.name);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
593
600
|
};
|
|
594
601
|
|
|
595
602
|
// return () => {
|
|
@@ -598,15 +605,15 @@ export default function useFormItemControl<T = any>({
|
|
|
598
605
|
// };
|
|
599
606
|
}, []);
|
|
600
607
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
if (listener) {
|
|
610
|
+
setListener({
|
|
611
|
+
formItemId,
|
|
612
|
+
hidden,
|
|
613
|
+
collectOnHidden,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}, [hidden, collectOnHidden]);
|
|
610
617
|
|
|
611
618
|
useEffect(() => {
|
|
612
619
|
if (listener) {
|
|
@@ -35,24 +35,28 @@ export default function useFormListControl<T = any>({
|
|
|
35
35
|
const [listFormInitValues, setListFormInitValues] = useState<
|
|
36
36
|
any[] | undefined
|
|
37
37
|
>(undefined);
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Listener
|
|
47
|
-
setListener: state.setListener,
|
|
48
|
-
getListener: state.getListener,
|
|
49
|
-
})),
|
|
50
|
-
);
|
|
51
|
-
const { setCleanUpStack } = useFormStore(
|
|
38
|
+
const {
|
|
39
|
+
clearCacheData,
|
|
40
|
+
setCacheData,
|
|
41
|
+
setListener,
|
|
42
|
+
getListener,
|
|
43
|
+
clearArrItems,
|
|
44
|
+
} = useFormStore(
|
|
52
45
|
useShallow((state) => ({
|
|
53
|
-
|
|
46
|
+
// Cache
|
|
47
|
+
cacheData: state.cacheData,
|
|
48
|
+
clearCacheData: state.clearCacheData,
|
|
49
|
+
setCacheData: state.setCacheData,
|
|
50
|
+
|
|
51
|
+
// Listener
|
|
52
|
+
setListener: state.setListener,
|
|
53
|
+
getListener: state.getListener,
|
|
54
|
+
|
|
55
|
+
// Clear Arr Items
|
|
56
|
+
clearArrItems: state.clearArrItems,
|
|
54
57
|
})),
|
|
55
58
|
);
|
|
59
|
+
|
|
56
60
|
const { initValue: internalInitValue, formState } = useFormStore(
|
|
57
61
|
useShallow((state) => {
|
|
58
62
|
// console.log(
|
|
@@ -129,22 +133,15 @@ export default function useFormListControl<T = any>({
|
|
|
129
133
|
|
|
130
134
|
// Nếu số phần tử trước khi thay đổi mảng lớn hơn thì đẩy các phần tử còn lại vào clean up stack để clear
|
|
131
135
|
if (startRemoveIndex > 0) {
|
|
132
|
-
Array.from(Array(startRemoveIndex))
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// });
|
|
142
|
-
setCleanUpStack({
|
|
143
|
-
formName: formName || form?.formName || contextForm?.formName,
|
|
144
|
-
name: `${name}.${index}`,
|
|
145
|
-
type: "array",
|
|
146
|
-
});
|
|
147
|
-
});
|
|
136
|
+
const clearItems = Array.from(Array(startRemoveIndex)).map((_, index) => {
|
|
137
|
+
const clearIndex = getNewValueCache.length + index;
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
141
|
+
name: `${name}.${clearIndex}`,
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
clearArrItems(clearItems);
|
|
148
145
|
}
|
|
149
146
|
|
|
150
147
|
console.log("Set cache data for form list: ", {
|
package/src/providers/Form.tsx
CHANGED
|
@@ -14,7 +14,6 @@ import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
|
14
14
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
15
15
|
import { flushSync } from "react-dom";
|
|
16
16
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
17
|
-
import FormCleanUp from "../components/Form/FormCleanUp";
|
|
18
17
|
import FormItem from "../components/Form/FormItem";
|
|
19
18
|
import { SUBMIT_STATE } from "../constants/form";
|
|
20
19
|
import { ListenerItem, SubmitProps, useFormStore } from "../stores/formStore";
|
|
@@ -492,7 +491,6 @@ export default function Form<T = any>({
|
|
|
492
491
|
{children}
|
|
493
492
|
</form>
|
|
494
493
|
)}
|
|
495
|
-
<FormCleanUp />
|
|
496
494
|
</FormContext.Provider>
|
|
497
495
|
);
|
|
498
496
|
}
|
package/src/stores/formStore.ts
CHANGED
|
@@ -185,6 +185,30 @@ const createFormStoreSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
185
185
|
);
|
|
186
186
|
},
|
|
187
187
|
|
|
188
|
+
clearArrItems(clearItems: { formName: string; name: string }[]) {
|
|
189
|
+
return storeSet(
|
|
190
|
+
produce<any>((state: any) => {
|
|
191
|
+
const oldValues = state.forms;
|
|
192
|
+
|
|
193
|
+
console.log("Clear item array values: ", clearItems);
|
|
194
|
+
|
|
195
|
+
clearItems.forEach(({ formName, name }) => {
|
|
196
|
+
const arrPath = name.split(".").slice(0, -1).join(".");
|
|
197
|
+
|
|
198
|
+
const elPath = Number(name.split(".").slice(-1).join(""));
|
|
199
|
+
|
|
200
|
+
const getArrItem = get(oldValues, `${formName}.${arrPath}`);
|
|
201
|
+
|
|
202
|
+
if (isNumber(elPath)) {
|
|
203
|
+
getArrItem.splice(elPath, 1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// console.log(JSON.parse(JSON.stringify(oldValues)));
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
|
|
188
212
|
setFormState({ formName, isInitied, submitState }) {
|
|
189
213
|
return storeSet(
|
|
190
214
|
produce<any>((state: any) => {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Button, Checkbox, Input } from "antd";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
import FormItem from "../components/Form/FormItem";
|
|
4
|
+
import FormList from "../components/Form/FormList";
|
|
5
|
+
import InputWrapper from "../components/Form/InputWrapper";
|
|
6
|
+
import Form from "../providers/Form";
|
|
7
|
+
|
|
8
|
+
type Props = {};
|
|
9
|
+
|
|
10
|
+
function CommonTest({}: Props) {
|
|
11
|
+
const [form] = Form.useForm("form1");
|
|
12
|
+
|
|
13
|
+
const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Form
|
|
17
|
+
initialValues={{
|
|
18
|
+
TestData: "",
|
|
19
|
+
numericCode: "",
|
|
20
|
+
// arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
21
|
+
}}
|
|
22
|
+
onFinish={(values) => {
|
|
23
|
+
console.log(values);
|
|
24
|
+
}}
|
|
25
|
+
formName={"form1"}
|
|
26
|
+
// hidden
|
|
27
|
+
>
|
|
28
|
+
<FormItem
|
|
29
|
+
name={"username"}
|
|
30
|
+
rules={[
|
|
31
|
+
{
|
|
32
|
+
required: true,
|
|
33
|
+
message: "Test",
|
|
34
|
+
},
|
|
35
|
+
]}
|
|
36
|
+
initialValue={"283746"}
|
|
37
|
+
// hidden
|
|
38
|
+
>
|
|
39
|
+
<InputWrapper>
|
|
40
|
+
<Input />
|
|
41
|
+
</InputWrapper>
|
|
42
|
+
</FormItem>
|
|
43
|
+
|
|
44
|
+
{/* Numberic test */}
|
|
45
|
+
<FormItem
|
|
46
|
+
name={"numericCode"}
|
|
47
|
+
rules={[
|
|
48
|
+
{ required: true, message: "Vui lòng nhập mã" },
|
|
49
|
+
{
|
|
50
|
+
pattern: /^\d+$/,
|
|
51
|
+
message: "Chỉ cho phép chuỗi số",
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
<InputWrapper>
|
|
56
|
+
<Input placeholder="Mã chỉ gồm số" style={{ width: 200 }} />
|
|
57
|
+
</InputWrapper>
|
|
58
|
+
</FormItem>
|
|
59
|
+
|
|
60
|
+
{/* Motion Test */}
|
|
61
|
+
<motion.div
|
|
62
|
+
initial={{ opacity: 0 }}
|
|
63
|
+
animate={{ opacity: 1 }}
|
|
64
|
+
exit={{ opacity: 0 }}
|
|
65
|
+
transition={{ duration: 1 }}
|
|
66
|
+
>
|
|
67
|
+
<FormItem name="motionTest" controlAfterInit initialValue={"1234134"}>
|
|
68
|
+
<InputWrapper>
|
|
69
|
+
<Input placeholder="Motion Test" style={{ width: 200 }} />
|
|
70
|
+
</InputWrapper>
|
|
71
|
+
</FormItem>
|
|
72
|
+
</motion.div>
|
|
73
|
+
<FormList
|
|
74
|
+
initialValues={[
|
|
75
|
+
{
|
|
76
|
+
el: "sdfsdf",
|
|
77
|
+
d: { child: "Test Child" },
|
|
78
|
+
},
|
|
79
|
+
]}
|
|
80
|
+
name="arr"
|
|
81
|
+
>
|
|
82
|
+
{(fields, { add, remove, move }) => (
|
|
83
|
+
<div>
|
|
84
|
+
{fields.map((field, index) => (
|
|
85
|
+
<div key={field.key} style={{ marginBottom: 8 }}>
|
|
86
|
+
<FormItem name={`${field.name}.el`} initialValue={"Chém gió"}>
|
|
87
|
+
<InputWrapper>
|
|
88
|
+
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
89
|
+
</InputWrapper>
|
|
90
|
+
</FormItem>
|
|
91
|
+
<FormItem
|
|
92
|
+
name={`${field.name}.d.child`}
|
|
93
|
+
initialValue={"Con của item"}
|
|
94
|
+
>
|
|
95
|
+
<InputWrapper>
|
|
96
|
+
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
97
|
+
</InputWrapper>
|
|
98
|
+
</FormItem>
|
|
99
|
+
<Button
|
|
100
|
+
onClick={() => remove({ index })}
|
|
101
|
+
style={{ marginLeft: 8 }}
|
|
102
|
+
>
|
|
103
|
+
Remove
|
|
104
|
+
</Button>
|
|
105
|
+
{index > 0 && (
|
|
106
|
+
<Button
|
|
107
|
+
onClick={() => move({ from: index, to: index - 1 })}
|
|
108
|
+
style={{ marginLeft: 8 }}
|
|
109
|
+
>
|
|
110
|
+
Move Up
|
|
111
|
+
</Button>
|
|
112
|
+
)}
|
|
113
|
+
{index < fields.length - 1 && (
|
|
114
|
+
<Button
|
|
115
|
+
onClick={() => move({ from: index, to: index + 1 })}
|
|
116
|
+
style={{ marginLeft: 8 }}
|
|
117
|
+
>
|
|
118
|
+
Move Down
|
|
119
|
+
</Button>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
<Button onClick={() => add(fields.length)}>Add Item</Button>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</FormList>
|
|
127
|
+
<Button
|
|
128
|
+
onClick={() => {
|
|
129
|
+
form?.setFieldValue("arr", [
|
|
130
|
+
{ el: "Set Item 1" },
|
|
131
|
+
{ el: "Set Item 2" },
|
|
132
|
+
{ el: "Set Item 3" },
|
|
133
|
+
]);
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
Test set array list value
|
|
137
|
+
</Button>
|
|
138
|
+
<motion.div
|
|
139
|
+
initial={{ opacity: 0 }}
|
|
140
|
+
animate={{ opacity: 1 }}
|
|
141
|
+
exit={{ opacity: 0 }}
|
|
142
|
+
transition={{ duration: 1.5 }}
|
|
143
|
+
>
|
|
144
|
+
<Form.Item
|
|
145
|
+
valuePropName="checked"
|
|
146
|
+
getValueFromEvent={(_, checked) => checked}
|
|
147
|
+
name="checkControlledAfterInit"
|
|
148
|
+
controlAfterInit={true}
|
|
149
|
+
initialValue={true}
|
|
150
|
+
hidden
|
|
151
|
+
>
|
|
152
|
+
<Checkbox />
|
|
153
|
+
</Form.Item>
|
|
154
|
+
</motion.div>
|
|
155
|
+
{/* <TestFormWatch /> */}
|
|
156
|
+
<Button
|
|
157
|
+
onClick={() => {
|
|
158
|
+
const current = form?.getFieldValue("checkControlledAfterInit");
|
|
159
|
+
console.log("Toggle controlled after init: ", current);
|
|
160
|
+
form?.setFieldValue("checkControlledAfterInit", !current);
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
Toggle
|
|
164
|
+
</Button>
|
|
165
|
+
<Button htmlType="submit">Submit</Button>
|
|
166
|
+
<Button
|
|
167
|
+
onClick={() => {
|
|
168
|
+
form?.resetFields?.();
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
Reset
|
|
172
|
+
</Button>
|
|
173
|
+
</Form>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default CommonTest;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Button, Col, Input, Row } from "antd";
|
|
2
|
+
import { useToggle } from "minh-custom-hooks-release";
|
|
3
|
+
import Form from "../providers/Form";
|
|
4
|
+
|
|
5
|
+
type Props = {};
|
|
6
|
+
|
|
7
|
+
function TestWrapperFormItem({}: Props) {
|
|
8
|
+
const { state, toggle } = useToggle(false);
|
|
9
|
+
return (
|
|
10
|
+
<Form
|
|
11
|
+
onFinish={(values, all) => {
|
|
12
|
+
console.log(values, all);
|
|
13
|
+
}}
|
|
14
|
+
formName="testWrapper"
|
|
15
|
+
>
|
|
16
|
+
<Row>
|
|
17
|
+
<Form.Item
|
|
18
|
+
hidden={!state}
|
|
19
|
+
name="test"
|
|
20
|
+
collectOnHidden={false}
|
|
21
|
+
initialValue={"test"}
|
|
22
|
+
Component={Col}
|
|
23
|
+
xs={12}
|
|
24
|
+
>
|
|
25
|
+
<Input />
|
|
26
|
+
</Form.Item>
|
|
27
|
+
</Row>
|
|
28
|
+
<Button htmlType="submit">Submit</Button>
|
|
29
|
+
<Button onClick={toggle}>{state ? "Off" : "On"}</Button>
|
|
30
|
+
</Form>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default TestWrapperFormItem;
|