react-form-manage 1.0.8-beta.2 → 1.0.8-beta.21
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 +85 -5
- package/README.md +8 -4
- package/dist/components/Form/FormCleanUp.js +3 -3
- package/dist/components/Form/FormItem.d.ts +4 -2
- package/dist/components/Form/FormItem.js +7 -5
- package/dist/components/Form/FormList.d.ts +2 -2
- package/dist/components/Form/FormList.js +2 -2
- package/dist/hooks/useFormItemControl.d.ts +1 -0
- package/dist/hooks/useFormItemControl.js +60 -23
- package/dist/hooks/useFormListControl.d.ts +2 -1
- package/dist/hooks/useFormListControl.js +60 -10
- package/dist/index.cjs.d.ts +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.esm.d.ts +1 -0
- package/dist/index.js +4 -2
- package/dist/providers/Form.d.ts +4 -2
- package/dist/providers/Form.js +98 -28
- package/dist/stores/formStore.d.ts +27 -2
- package/dist/stores/formStore.js +25 -9
- package/dist/test/TestDialog.d.ts +3 -0
- package/dist/test/TestDialog.js +21 -0
- package/dist/test/TestListener.d.ts +3 -0
- package/dist/test/TestListener.js +17 -0
- package/dist/test/TestSelect.d.ts +6 -0
- package/dist/test/TestSelect.js +24 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/public.d.ts +6 -1
- package/dist/utils/obj.util.d.ts +1 -1
- package/dist/utils/obj.util.js +16 -5
- package/package.json +3 -1
- package/src/App.tsx +98 -4
- package/src/components/Form/FormCleanUp.tsx +3 -7
- package/src/components/Form/FormItem.tsx +56 -31
- package/src/components/Form/FormList.tsx +17 -4
- package/src/components/Form/InputWrapper.tsx +5 -0
- package/src/hooks/useFormItemControl.ts +70 -29
- package/src/hooks/useFormListControl.ts +104 -26
- package/src/index.ts +27 -13
- package/src/providers/Form.tsx +126 -17
- package/src/stores/formStore.ts +319 -288
- package/src/test/TestDialog.tsx +52 -0
- package/src/test/TestListener.tsx +21 -0
- package/src/test/TestSelect.tsx +38 -0
- package/src/types/index.ts +1 -1
- package/src/types/public.ts +7 -1
- package/src/utils/obj.util.ts +44 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [1.0.8-beta.
|
|
5
|
+
## [1.0.8-beta.21] - 2026-02-02
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
- Patch: Minor adjustments in useFormItemControl
|
|
9
|
+
|
|
10
|
+
## [1.0.8-beta.20] - 2026-02-02
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
14
|
+
|
|
15
|
+
## [1.0.8-beta.19] - 2026-02-02
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
19
|
+
|
|
20
|
+
## [1.0.8-beta.18] - 2026-02-01
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
- Avoid deep traversal into class instances/functions in path collection
|
|
24
|
+
- `getAllNoneObjStringPath` now skips non-plain objects and functions, only traverses arrays and plain objects
|
|
25
|
+
- Improves performance and prevents accidental property access on class instances
|
|
26
|
+
|
|
27
|
+
## [1.0.8-beta.17] - 2026-02-01
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
- Add nested path onChange trigger: `setFieldValue` now triggers onChange for child paths when parent path has no listener
|
|
32
|
+
- Support setting array values in FormList with automatic re-render of list items and value updates
|
|
33
|
+
- Enhanced listener types with `type` field (normal/array) and `onArrayChange` callback support
|
|
34
|
+
|
|
35
|
+
### Improvements
|
|
36
|
+
|
|
37
|
+
- `useFormListControl` now tracks item values and handles cache properly
|
|
38
|
+
- Improved `useFormItemControl` with cache path validation using `lodash.has`
|
|
39
|
+
|
|
40
|
+
## [1.0.8-beta.16] - 2026-02-01
|
|
41
|
+
|
|
42
|
+
### Fixes
|
|
43
|
+
|
|
44
|
+
- Fix: Guard listener registration to prevent pushing incomplete listeners without name/formName
|
|
45
|
+
- Fix: Guard cleanup execution - only cleanup listeners that actually exist in the store
|
|
46
|
+
- Add formItemId to dependency array for proper listener re-registration in strict mode
|
|
47
|
+
|
|
48
|
+
## [1.0.8-beta.15] - 2026-02-01
|
|
49
|
+
|
|
50
|
+
### Features
|
|
51
|
+
|
|
52
|
+
- Export `useFormStore` and types (`FormInstance`, `ListenerItem`, `CleanUpItem`) to public API
|
|
53
|
+
- Allow advanced users to access and manipulate form state directly
|
|
54
|
+
|
|
55
|
+
## [1.0.8-beta.13] - 2026-02-01
|
|
56
|
+
|
|
57
|
+
### Refactoring
|
|
58
|
+
|
|
59
|
+
- Combine separate Zustand stores (`useFormListeners`, `useFormCleanUp`) into single unified `useFormStore`
|
|
60
|
+
- Implement slice-based store pattern for better code organization and maintainability
|
|
61
|
+
- Update all internal imports to use unified store across hooks and components
|
|
62
|
+
- Maintain backward compatibility with deprecated store aliases
|
|
63
|
+
|
|
64
|
+
## [1.0.8-beta.12] - 2026-01-27
|
|
65
|
+
|
|
66
|
+
### Features
|
|
67
|
+
|
|
68
|
+
- Add slice-based store architecture using Zustand for better scalability
|
|
69
|
+
- Refactor form state management with proper separation of concerns
|
|
70
|
+
|
|
71
|
+
## [1.0.8-beta.3] - 2026-01-22
|
|
72
|
+
|
|
73
|
+
## [1.0.8-beta.7] - 2026-01-24
|
|
74
|
+
|
|
75
|
+
- Add control flag for FormItem to support rendering MUI uncontrolled components when initial value is undefined (control on init).
|
|
76
|
+
- Fix: `onReset` did not restore listener state to init.
|
|
77
|
+
- Fix: reset did not return Form submit state to `idle`.
|
|
78
|
+
- Add `hidden` prop to allow hiding components while still assigning a value.
|
|
79
|
+
|
|
80
|
+
Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
81
|
+
|
|
82
|
+
- Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
|
|
6
83
|
|
|
7
84
|
- Add isTouched field to FormItem for tracking user interaction state
|
|
8
85
|
|
|
86
|
+
## [1.0.8-beta.10] - 2026-01-27
|
|
87
|
+
|
|
88
|
+
- `useForm()` giờ có thể trả về form instance từ provider lân cận nếu không truyền `formName` (không còn bắt buộc truyền tên form khi đã wrap trong `Form`).
|
|
89
|
+
- `FormList.add` khi không truyền `index` sẽ mặc định append vào cuối danh sách.
|
|
90
|
+
- Docs: cập nhật hướng dẫn cho `useForm` và `FormList.add`.
|
|
91
|
+
|
|
9
92
|
## [1.0.8-beta.1] - 2026-01-22
|
|
10
93
|
|
|
11
94
|
- Fix UseFormItemControlReturn errors type to properly export FormFieldError[]
|
|
95
|
+
|
|
12
96
|
## [1.0.7-beta.1] - 2026-01-22
|
|
13
97
|
|
|
14
98
|
- Add `FormFieldError` type export for typed error handling
|
|
@@ -89,7 +173,3 @@ All notable changes to this project will be documented in this file.
|
|
|
89
173
|
## [1.0.5] - 2026-01-21
|
|
90
174
|
|
|
91
175
|
- Export `Form` as a named export in addition to default to improve import ergonomics for consumers.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Thư viện form nhẹ dành cho React — hỗ trợ form, field, list (array fields), và cơ chế listener để giảm re-render.
|
|
4
4
|
|
|
5
|
-
Phiên bản này xuất phát từ mã nguồn JS. Typings (.d.ts) được cung cấp trong `src/types/components.d.ts`.
|
|
6
|
-
|
|
7
5
|
---
|
|
8
6
|
|
|
9
7
|
## Cài đặt
|
|
@@ -80,6 +78,8 @@ export default Example;
|
|
|
80
78
|
|
|
81
79
|
Bạn có thể lấy instance form để gọi phương thức chương trình như `submit`, `resetFields`, `setFieldValue`…
|
|
82
80
|
|
|
81
|
+
`useForm` ưu tiên lấy instance từ provider gần nhất nếu không truyền `formName`, giúp bạn không cần truyền tên form khi đã bọc trong `Form`.
|
|
82
|
+
|
|
83
83
|
Ví dụ sử dụng hook attached trên `Form`:
|
|
84
84
|
|
|
85
85
|
```jsx
|
|
@@ -87,7 +87,7 @@ import React from "react";
|
|
|
87
87
|
import Form from "react-form-manage";
|
|
88
88
|
|
|
89
89
|
function CtrlExample() {
|
|
90
|
-
const [form] = Form.useForm(
|
|
90
|
+
const [form] = Form.useForm(); // hoặc Form.useForm('myForm') khi muốn chỉ định form khác
|
|
91
91
|
|
|
92
92
|
return (
|
|
93
93
|
<div>
|
|
@@ -156,6 +156,10 @@ Custom validator (async):
|
|
|
156
156
|
|
|
157
157
|
`FormList` là render-prop component cung cấp `listFields` và các action `add`, `remove`, `move`.
|
|
158
158
|
|
|
159
|
+
Ghi chú:
|
|
160
|
+
|
|
161
|
+
- `add()` nếu không truyền `index` sẽ tự động append phần tử mới vào cuối danh sách.
|
|
162
|
+
|
|
159
163
|
Ví dụ:
|
|
160
164
|
|
|
161
165
|
```jsx
|
|
@@ -192,7 +196,7 @@ Lưu ý: `FormList` quản lý các `key` nội bộ và push các mục cần c
|
|
|
192
196
|
|
|
193
197
|
## Hooks (tóm tắt)
|
|
194
198
|
|
|
195
|
-
- `useForm(formNameOrFormInstance)` —
|
|
199
|
+
- `useForm(formNameOrFormInstance?)` — nếu không truyền thì lấy instance từ `Form` provider gần nhất; nếu truyền `formName` thì chọn form tương ứng.
|
|
196
200
|
- `useWatch(name, formNameOrFormInstance)` — subscribe giá trị field.
|
|
197
201
|
- `useSubmitDataWatch` — watch dữ liệu submit cuối cùng.
|
|
198
202
|
- `useFormStateWatch` — watch state của form (isInitied, submitState…).
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { useShallow } from "zustand/react/shallow";
|
|
4
|
-
import {
|
|
4
|
+
import { useFormStore } from "../../stores/formStore";
|
|
5
5
|
const FormCleanUp = () => {
|
|
6
|
-
const { cleanUpStack, clearCleanUpStack } =
|
|
6
|
+
const { cleanUpStack, clearCleanUpStack } = useFormStore(useShallow((state) => ({
|
|
7
7
|
cleanUpStack: state.cleanUpStack,
|
|
8
8
|
clearCleanUpStack: state.clearCleanUpStack
|
|
9
9
|
})));
|
|
10
|
-
const { revokeListener } =
|
|
10
|
+
const { revokeListener } = useFormStore(useShallow((state) => ({
|
|
11
11
|
revokeListener: state.revokeListener
|
|
12
12
|
})));
|
|
13
13
|
const { clearObjKeyItem, clearArrItem } = useFormStore(useShallow((state) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import type { ValidationRule } from "../../types/public";
|
|
3
3
|
export interface FormItemProps {
|
|
4
|
-
children: ReactElement
|
|
4
|
+
children: ReactElement<any>;
|
|
5
5
|
name: string;
|
|
6
6
|
formName?: string;
|
|
7
7
|
initialValue?: any;
|
|
@@ -9,5 +9,7 @@ export interface FormItemProps {
|
|
|
9
9
|
rules?: ValidationRule[];
|
|
10
10
|
valuePropName?: string;
|
|
11
11
|
getValueFromEvent?: (...args: any[]) => any;
|
|
12
|
+
controlAfterInit?: boolean;
|
|
13
|
+
hidden?: boolean;
|
|
12
14
|
}
|
|
13
|
-
export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, }: FormItemProps):
|
|
15
|
+
export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, controlAfterInit, hidden, }: FormItemProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cloneElement, Fragment, useRef, useState } from "react";
|
|
2
3
|
import { v4 } from "uuid";
|
|
3
4
|
import useFormItemControl from "../../hooks/useFormItemControl";
|
|
4
|
-
function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent }) {
|
|
5
|
+
function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent, controlAfterInit = false, hidden }) {
|
|
5
6
|
const elRef = useRef(null);
|
|
6
7
|
const [formItemId] = useState(externalFormItemId != null ? externalFormItemId : v4());
|
|
7
|
-
const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched } = useFormItemControl({
|
|
8
|
+
const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, isInitied } = useFormItemControl({
|
|
8
9
|
formName,
|
|
9
10
|
name,
|
|
10
11
|
initialValue,
|
|
@@ -12,7 +13,8 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
|
|
|
12
13
|
rules,
|
|
13
14
|
elementRef: elRef
|
|
14
15
|
});
|
|
15
|
-
return cloneElement(children, {
|
|
16
|
+
return _jsx(Fragment, { children: !hidden && children ? cloneElement(children, {
|
|
17
|
+
name,
|
|
16
18
|
// ref: inputRef,
|
|
17
19
|
[valuePropName]: value,
|
|
18
20
|
onChange: (...args) => {
|
|
@@ -40,7 +42,7 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
|
|
|
40
42
|
ref: elRef,
|
|
41
43
|
submitState,
|
|
42
44
|
isTouched
|
|
43
|
-
});
|
|
45
|
+
}) : null }, `control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${formItemId}`);
|
|
44
46
|
}
|
|
45
47
|
export {
|
|
46
48
|
FormItem as default
|
|
@@ -8,7 +8,7 @@ export interface FormListProps<T = any> {
|
|
|
8
8
|
name: string;
|
|
9
9
|
key: string;
|
|
10
10
|
}>, operations: {
|
|
11
|
-
add: (index
|
|
11
|
+
add: (index?: number) => void;
|
|
12
12
|
remove: (opts: {
|
|
13
13
|
index?: number;
|
|
14
14
|
key?: string;
|
|
@@ -20,5 +20,5 @@ export interface FormListProps<T = any> {
|
|
|
20
20
|
}) => void;
|
|
21
21
|
}) => React.ReactNode;
|
|
22
22
|
}
|
|
23
|
-
declare const FormList: <T = any>({ name, initialValues, form, formName, children }: FormListProps<T>) => import("react").ReactNode;
|
|
23
|
+
declare const FormList: <T = any>({ name, initialValues, form, formName, children, }: FormListProps<T>) => import("react").ReactNode;
|
|
24
24
|
export default FormList;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import useFormListControl from "../../hooks/useFormListControl";
|
|
2
2
|
const FormList = ({ name, initialValues, form, formName, children }) => {
|
|
3
|
-
const { listFields, ...actions } =
|
|
3
|
+
const { listFields, ...actions } = useFormListControl({
|
|
4
4
|
name,
|
|
5
5
|
initialValues,
|
|
6
6
|
form,
|
|
@@ -20,6 +20,7 @@ export interface UseFormItemControlReturn {
|
|
|
20
20
|
isDirty?: boolean;
|
|
21
21
|
isTouched?: boolean;
|
|
22
22
|
submitState?: SubmitState;
|
|
23
|
+
isInitied?: boolean;
|
|
23
24
|
}
|
|
24
25
|
export default function useFormItemControl<T = any>({ formName, form, name, initialValue, formItemId, rules, elementRef, }: UseFormItemControlProps): UseFormItemControlReturn;
|
|
25
26
|
export {};
|
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
import { get, isNil } from "lodash";
|
|
1
|
+
import { get, has, isNil } from "lodash";
|
|
2
2
|
import { useTaskEffect } from "minh-custom-hooks-release";
|
|
3
3
|
import { useEffect, 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";
|
|
7
|
-
import {
|
|
7
|
+
import { useFormStore } from "../stores/formStore";
|
|
8
8
|
const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
|
|
9
9
|
function useFormItemControl({ formName, form, name, initialValue, formItemId, rules, elementRef }) {
|
|
10
10
|
const contextForm = useFormContext();
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
value,
|
|
13
|
+
setData,
|
|
14
|
+
getCacheData,
|
|
15
|
+
getFormValues,
|
|
16
|
+
// getFormState,
|
|
17
|
+
isStateInitied,
|
|
18
|
+
submitState
|
|
19
|
+
} = useFormStore(useShallow((state2) => {
|
|
12
20
|
var _a, _b, _c, _d;
|
|
13
21
|
return {
|
|
14
22
|
value: get(state2.forms, `${formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)}.${name}`),
|
|
15
23
|
setData: state2.setData,
|
|
16
24
|
getCacheData: state2.getCacheData,
|
|
17
25
|
getFormValues: state2.getFormValues,
|
|
18
|
-
getFormState:
|
|
26
|
+
// getFormState: state.getFormState,
|
|
19
27
|
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,
|
|
20
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
|
|
21
29
|
};
|
|
22
30
|
}));
|
|
23
|
-
const { setCleanUpStack } =
|
|
31
|
+
const { setCleanUpStack } = useFormStore(useShallow((state2) => ({
|
|
24
32
|
setCleanUpStack: state2.setCleanUpStack
|
|
25
33
|
})));
|
|
26
34
|
const { initValue: internalInitValue, setInitData, getInitData } = useFormStore(useShallow((state2) => {
|
|
@@ -30,7 +38,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
30
38
|
getInitData: state2.getInitData
|
|
31
39
|
};
|
|
32
40
|
}));
|
|
33
|
-
const { listener, setListener } =
|
|
41
|
+
const { listener, setListener } = useFormStore(useShallow((state2) => {
|
|
34
42
|
return {
|
|
35
43
|
listener: state2.listeners.find((l) => l.formItemId === formItemId),
|
|
36
44
|
setListener: state2.setListener
|
|
@@ -38,7 +46,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
38
46
|
}));
|
|
39
47
|
const onInitData = (value2) => {
|
|
40
48
|
setInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, value2);
|
|
41
|
-
onChange(value2, { notTriggerDirty: true });
|
|
49
|
+
onChange(value2, { notTriggerDirty: true, initiedData: true });
|
|
42
50
|
};
|
|
43
51
|
const onFocus = () => {
|
|
44
52
|
setListener({
|
|
@@ -60,14 +68,27 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
60
68
|
isTouched: listener == null ? void 0 : listener.isTouched
|
|
61
69
|
});
|
|
62
70
|
}
|
|
71
|
+
if ((options == null ? void 0 : options.initiedData) === true) {
|
|
72
|
+
setListener({
|
|
73
|
+
formItemId,
|
|
74
|
+
isInitied: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
63
77
|
setData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, value2);
|
|
64
78
|
};
|
|
65
79
|
const onReset = (value2) => {
|
|
66
|
-
|
|
80
|
+
setListener({
|
|
81
|
+
formItemId,
|
|
82
|
+
isDirty: false,
|
|
83
|
+
isTouched: false
|
|
84
|
+
});
|
|
85
|
+
onChange(isNil(value2) ? getInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name) : value2, { notTriggerDirty: true, initiedData: true });
|
|
67
86
|
};
|
|
68
87
|
const internalRules = useMemo(() => {
|
|
69
88
|
return rules || [];
|
|
70
89
|
}, [rules]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
}, [value, listener]);
|
|
71
92
|
const { data: errors, state } = useTaskEffect({
|
|
72
93
|
async task() {
|
|
73
94
|
const listErrors = [];
|
|
@@ -250,7 +271,8 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
250
271
|
setListener({ formItemId, internalErrors: listErrors });
|
|
251
272
|
return listErrors;
|
|
252
273
|
},
|
|
253
|
-
deps: [internalRules, value],
|
|
274
|
+
deps: [internalRules, value, listener == null ? void 0 : listener.isInitied],
|
|
275
|
+
enabled: Boolean(listener == null ? void 0 : listener.isInitied),
|
|
254
276
|
onError(err) {
|
|
255
277
|
}
|
|
256
278
|
});
|
|
@@ -262,8 +284,16 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
262
284
|
onInitData(initialValue);
|
|
263
285
|
}
|
|
264
286
|
} else {
|
|
265
|
-
onChange(internalInitValue, {
|
|
287
|
+
onChange(internalInitValue, {
|
|
288
|
+
notTriggerDirty: true,
|
|
289
|
+
initiedData: true
|
|
290
|
+
});
|
|
266
291
|
}
|
|
292
|
+
} else {
|
|
293
|
+
onChange(value, {
|
|
294
|
+
notTriggerDirty: true,
|
|
295
|
+
initiedData: true
|
|
296
|
+
});
|
|
267
297
|
}
|
|
268
298
|
return;
|
|
269
299
|
}
|
|
@@ -281,6 +311,14 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
281
311
|
onReset
|
|
282
312
|
});
|
|
283
313
|
}
|
|
314
|
+
return () => {
|
|
315
|
+
if (listener) {
|
|
316
|
+
setCleanUpStack({
|
|
317
|
+
name: listener.name,
|
|
318
|
+
itemKey: listener.formItemId
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
};
|
|
284
322
|
}, []);
|
|
285
323
|
useEffect(() => {
|
|
286
324
|
if (listener) {
|
|
@@ -297,21 +335,19 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
297
335
|
const cacheData = getCacheData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName));
|
|
298
336
|
if (cacheData) {
|
|
299
337
|
const getNewDataFromCache = get(cacheData, name);
|
|
300
|
-
|
|
301
|
-
|
|
338
|
+
const isIncludeDirectoryInCache = has(cacheData, name);
|
|
339
|
+
if (!isIncludeDirectoryInCache && isNil(getNewDataFromCache)) {
|
|
340
|
+
onChange(initialValue, {
|
|
341
|
+
notTriggerDirty: true,
|
|
342
|
+
initiedData: true
|
|
343
|
+
});
|
|
302
344
|
} else
|
|
303
|
-
onChange(getNewDataFromCache
|
|
345
|
+
onChange(getNewDataFromCache, {
|
|
346
|
+
notTriggerDirty: true,
|
|
347
|
+
initiedData: true
|
|
348
|
+
});
|
|
304
349
|
}
|
|
305
350
|
}, [name, formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]);
|
|
306
|
-
useEffect(() => {
|
|
307
|
-
return () => {
|
|
308
|
-
setCleanUpStack({
|
|
309
|
-
itemKey: formItemId
|
|
310
|
-
});
|
|
311
|
-
};
|
|
312
|
-
}, []);
|
|
313
|
-
useEffect(() => {
|
|
314
|
-
}, [submitState]);
|
|
315
351
|
return {
|
|
316
352
|
value,
|
|
317
353
|
onChange,
|
|
@@ -320,7 +356,8 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
320
356
|
onFocus,
|
|
321
357
|
isDirty: listener == null ? void 0 : listener.isDirty,
|
|
322
358
|
isTouched: listener == null ? void 0 : listener.isTouched,
|
|
323
|
-
submitState
|
|
359
|
+
submitState,
|
|
360
|
+
isInitied: listener == null ? void 0 : listener.isInitied
|
|
324
361
|
};
|
|
325
362
|
}
|
|
326
363
|
export {
|
|
@@ -2,6 +2,7 @@ import type { FormInstance } from "../stores/formStore";
|
|
|
2
2
|
type ListField = {
|
|
3
3
|
name: string;
|
|
4
4
|
key: string;
|
|
5
|
+
value?: any;
|
|
5
6
|
};
|
|
6
7
|
interface UseFormListControlProps {
|
|
7
8
|
name?: string;
|
|
@@ -16,7 +17,7 @@ interface UseFormListControlReturn {
|
|
|
16
17
|
fromKey?: string;
|
|
17
18
|
to: number;
|
|
18
19
|
}) => void;
|
|
19
|
-
add: (index
|
|
20
|
+
add: (index?: number) => void;
|
|
20
21
|
remove: (opts: {
|
|
21
22
|
index?: number;
|
|
22
23
|
key?: string;
|
|
@@ -3,17 +3,22 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { useShallow } from "zustand/react/shallow";
|
|
5
5
|
import { useFormContext } from "../providers/Form";
|
|
6
|
-
import {
|
|
6
|
+
import { useFormStore } from "../stores/formStore";
|
|
7
7
|
function useFormListControl({ name, form, initialValues, formName }) {
|
|
8
|
+
const [formItemId] = useState(v4());
|
|
8
9
|
const contextForm = useFormContext();
|
|
9
10
|
const getFormValues = useFormStore((state) => state.getFormValues);
|
|
10
11
|
const [listFormInitValues, setListFormInitValues] = useState(void 0);
|
|
11
|
-
const { clearCacheData, setCacheData } = useFormStore(useShallow((state) => ({
|
|
12
|
+
const { clearCacheData, setCacheData, setListener, getListener } = useFormStore(useShallow((state) => ({
|
|
13
|
+
// Cache
|
|
12
14
|
cacheData: state.cacheData,
|
|
13
15
|
clearCacheData: state.clearCacheData,
|
|
14
|
-
setCacheData: state.setCacheData
|
|
16
|
+
setCacheData: state.setCacheData,
|
|
17
|
+
// Listener
|
|
18
|
+
setListener: state.setListener,
|
|
19
|
+
getListener: state.getListener
|
|
15
20
|
})));
|
|
16
|
-
const { setCleanUpStack } =
|
|
21
|
+
const { setCleanUpStack } = useFormStore(useShallow((state) => ({
|
|
17
22
|
setCleanUpStack: state.setCleanUpStack
|
|
18
23
|
})));
|
|
19
24
|
const { initValue: internalInitValue, formState } = useFormStore(useShallow((state) => {
|
|
@@ -37,7 +42,16 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
37
42
|
value: d
|
|
38
43
|
};
|
|
39
44
|
}).filter(Boolean);
|
|
40
|
-
const mapCurWithKey = cur.map((c) =>
|
|
45
|
+
const mapCurWithKey = cur.map((c) => {
|
|
46
|
+
const find = mapPrevWithKey.find((m) => m.key === c.key);
|
|
47
|
+
if (find) {
|
|
48
|
+
return {
|
|
49
|
+
key: find.key,
|
|
50
|
+
value: isNil(c.value) ? find.value : c.value
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return c;
|
|
54
|
+
});
|
|
41
55
|
const getNewValueCache = mapCurWithKey.filter(Boolean).map((c) => c.value);
|
|
42
56
|
const startRemoveIndex = formDataBeforeChange.length - getNewValueCache.length;
|
|
43
57
|
if (startRemoveIndex > 0) {
|
|
@@ -55,11 +69,7 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
55
69
|
};
|
|
56
70
|
const add = (index) => {
|
|
57
71
|
setListFields((prev) => {
|
|
58
|
-
if (index
|
|
59
|
-
return prev;
|
|
60
|
-
if (index < 0)
|
|
61
|
-
return prev;
|
|
62
|
-
if (index === prev.length) {
|
|
72
|
+
if (isNil(index) || index === prev.length) {
|
|
63
73
|
const newName = `${name}.${prev.length}`;
|
|
64
74
|
const newKey = v4();
|
|
65
75
|
const result2 = [
|
|
@@ -71,6 +81,10 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
71
81
|
];
|
|
72
82
|
return result2;
|
|
73
83
|
}
|
|
84
|
+
if (index > prev.length)
|
|
85
|
+
return prev;
|
|
86
|
+
if (index < 0)
|
|
87
|
+
return prev;
|
|
74
88
|
const clonePrev = [...prev];
|
|
75
89
|
const result = clonePrev.reduce((prev2, cur, curIndex) => {
|
|
76
90
|
const newKey = v4();
|
|
@@ -218,6 +232,42 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
218
232
|
clearCacheData();
|
|
219
233
|
};
|
|
220
234
|
}, [listFields]);
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (!getListener(formItemId)) {
|
|
237
|
+
setListener({
|
|
238
|
+
formName: formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName),
|
|
239
|
+
name: name || "",
|
|
240
|
+
formItemId,
|
|
241
|
+
type: "array",
|
|
242
|
+
onArrayChange: (newArr) => {
|
|
243
|
+
setListFields((prev) => {
|
|
244
|
+
const result = newArr.map((_, i) => {
|
|
245
|
+
const itemName = `${name}.${i}`;
|
|
246
|
+
const existingItem = prev[i];
|
|
247
|
+
return {
|
|
248
|
+
key: existingItem ? existingItem.key : v4(),
|
|
249
|
+
name: itemName
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
handleCacheListField(prev, result.map((r, i) => {
|
|
253
|
+
return { ...r, value: newArr[i] };
|
|
254
|
+
}));
|
|
255
|
+
return result;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return () => {
|
|
261
|
+
if (getListener(formItemId)) {
|
|
262
|
+
setListener({
|
|
263
|
+
formName: formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName),
|
|
264
|
+
name: name || "",
|
|
265
|
+
formItemId,
|
|
266
|
+
onArrayChange: void 0
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}, []);
|
|
221
271
|
return { listFields, move, add, remove };
|
|
222
272
|
}
|
|
223
273
|
export {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type ValidationRule, type FormFieldError, type SubmitState } from "./providers/Form";
|
|
2
1
|
import { SUBMIT_STATE } from "./constants/form";
|
|
2
|
+
import Form, { useForm, useFormStateWatch, useSubmitDataWatch, useWatch, type FormFieldError, type FormProps, type SubmitState, type UseFormItemStateWatchReturn, type ValidationRule } from "./providers/Form";
|
|
3
3
|
import FormItem, { type FormItemProps } from "./components/Form/FormItem";
|
|
4
4
|
import FormList, { type FormListProps } from "./components/Form/FormList";
|
|
5
|
-
import Input from "./components/Input";
|
|
6
5
|
import InputWrapper, { type InputWrapperProps } from "./components/Form/InputWrapper";
|
|
6
|
+
import Input from "./components/Input";
|
|
7
7
|
import useFormItemControl from "./hooks/useFormItemControl";
|
|
8
8
|
import useFormListControl from "./hooks/useFormListControl";
|
|
9
|
-
|
|
9
|
+
import { useFormStore, type CleanUpItem, type FormInstance, type ListenerItem } from "./stores/formStore";
|
|
10
|
+
export { Form, FormItem, FormList, Input, InputWrapper, SUBMIT_STATE, useForm, useFormItemControl, useFormListControl, useFormStateWatch, useFormStore, useSubmitDataWatch, useWatch, type CleanUpItem, type FormFieldError, type FormInstance, type FormItemProps, type FormListProps, type FormProps, type InputWrapperProps, type ListenerItem, type SubmitState, type UseFormItemStateWatchReturn, type ValidationRule, };
|
|
10
11
|
export default Form;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index";
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch } from "./providers/Form";
|
|
2
1
|
import { SUBMIT_STATE } from "./constants/form";
|
|
2
|
+
import Form, { useForm, useFormStateWatch, useSubmitDataWatch, useWatch } from "./providers/Form";
|
|
3
3
|
import FormItem from "./components/Form/FormItem";
|
|
4
4
|
import FormList from "./components/Form/FormList";
|
|
5
|
-
import Input from "./components/Input";
|
|
6
5
|
import InputWrapper from "./components/Form/InputWrapper";
|
|
6
|
+
import Input from "./components/Input";
|
|
7
7
|
import useFormItemControl from "./hooks/useFormItemControl";
|
|
8
8
|
import useFormListControl from "./hooks/useFormListControl";
|
|
9
|
+
import { useFormStore } from "./stores/formStore";
|
|
9
10
|
var stdin_default = Form;
|
|
10
11
|
export {
|
|
11
12
|
Form,
|
|
@@ -19,6 +20,7 @@ export {
|
|
|
19
20
|
useFormItemControl,
|
|
20
21
|
useFormListControl,
|
|
21
22
|
useFormStateWatch,
|
|
23
|
+
useFormStore,
|
|
22
24
|
useSubmitDataWatch,
|
|
23
25
|
useWatch
|
|
24
26
|
};
|
package/dist/providers/Form.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
2
|
-
import type { PublicFormInstance } from "../types/public";
|
|
3
|
-
export type {
|
|
2
|
+
import type { PublicFormInstance, UseFormItemStateWatchReturn } from "../types/public";
|
|
3
|
+
export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule, } from "../types/public";
|
|
4
4
|
export declare const FormContext: import("react").Context<any>;
|
|
5
5
|
export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
|
|
6
6
|
children: ReactNode;
|
|
@@ -21,6 +21,7 @@ declare namespace Form {
|
|
|
21
21
|
var useWatch: typeof import("./Form").useWatch;
|
|
22
22
|
var useSubmitDataWatch: typeof import("./Form").useSubmitDataWatch;
|
|
23
23
|
var useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
|
|
24
|
+
var useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
|
|
24
25
|
}
|
|
25
26
|
export default Form;
|
|
26
27
|
export declare function useFormContext(): any;
|
|
@@ -32,3 +33,4 @@ export declare function useSubmitDataWatch<T = any>({ formNameOrFormInstance, tr
|
|
|
32
33
|
mapFn?: (v: any, prev: any) => any;
|
|
33
34
|
}): T | undefined;
|
|
34
35
|
export declare const useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
|
|
36
|
+
export declare const useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
|