react-form-manage 1.0.8-beta.9 → 1.0.8
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 +173 -4
- package/README.md +8 -4
- package/dist/components/Form/FormCleanUp.js +3 -3
- package/dist/components/Form/FormItem.d.ts +10 -4
- package/dist/components/Form/FormItem.js +52 -14
- package/dist/components/Form/FormList.d.ts +2 -2
- package/dist/components/Form/FormList.js +2 -2
- package/dist/constants/form.d.ts +1 -1
- package/dist/hooks/useFormItemControl.d.ts +8 -3
- package/dist/hooks/useFormItemControl.js +64 -28
- package/dist/hooks/useFormListControl.d.ts +2 -1
- package/dist/hooks/useFormListControl.js +85 -19
- 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 +15 -2
- package/dist/providers/Form.js +197 -22
- package/dist/stores/formStore.d.ts +44 -4
- package/dist/stores/formStore.js +42 -7
- package/dist/test/CommonTest.d.ts +3 -0
- package/dist/test/CommonTest.js +49 -0
- 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/TestNotFormWrapper.d.ts +3 -0
- package/dist/test/TestNotFormWrapper.js +15 -0
- package/dist/test/TestSelect.d.ts +6 -0
- package/dist/test/TestSelect.js +24 -0
- package/dist/test/TestWatchNormalize.d.ts +3 -0
- package/dist/test/TestWatchNormalize.js +23 -0
- package/dist/test/TestWrapperFormItem.d.ts +3 -0
- package/dist/test/TestWrapperFormItem.js +13 -0
- package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.d.ts +21 -0
- package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.js +61 -0
- package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.d.ts +16 -0
- package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.js +18 -0
- package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.d.ts +21 -0
- package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.js +33 -0
- package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.d.ts +21 -0
- package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.js +26 -0
- package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.d.ts +20 -0
- package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.js +32 -0
- package/dist/test/testSetValue/TestCase5_FormListRemovedItems.d.ts +22 -0
- package/dist/test/testSetValue/TestCase5_FormListRemovedItems.js +29 -0
- package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.d.ts +28 -0
- package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.js +36 -0
- package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.d.ts +17 -0
- package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.js +33 -0
- package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.d.ts +27 -0
- package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.js +57 -0
- package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.d.ts +25 -0
- package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.js +46 -0
- package/dist/test/testSetValue/index.d.ts +2 -0
- package/dist/test/testSetValue/index.js +28 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/public.d.ts +1 -1
- package/dist/utils/obj.util.d.ts +29 -1
- package/dist/utils/obj.util.js +59 -5
- package/package.json +2 -1
- package/src/App.tsx +38 -163
- package/src/DEEP_TRIGGER_LOGIC.md +573 -0
- package/src/components/Form/FormCleanUp.tsx +4 -8
- package/src/components/Form/FormItem.tsx +174 -57
- package/src/components/Form/FormList.tsx +17 -4
- package/src/constants/form.ts +1 -1
- package/src/hooks/useFormItemControl.ts +78 -32
- package/src/hooks/useFormListControl.ts +133 -43
- package/src/index.ts +25 -13
- package/src/main.tsx +6 -1
- package/src/providers/Form.tsx +451 -23
- package/src/stores/formStore.ts +363 -283
- package/src/test/CommonTest.tsx +177 -0
- package/src/test/TestDialog.tsx +52 -0
- package/src/test/TestListener.tsx +21 -0
- package/src/test/TestNotFormWrapper.tsx +43 -0
- package/src/test/TestSelect.tsx +38 -0
- package/src/test/TestWatchNormalize.tsx +32 -0
- package/src/test/TestWrapperFormItem.tsx +34 -0
- package/src/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.tsx +203 -0
- package/src/test/testSetValue/TestCase1_PlainObjectToPrimitives.tsx +72 -0
- package/src/test/testSetValue/TestCase2_PlainObjectToFormList.tsx +114 -0
- package/src/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.tsx +99 -0
- package/src/test/testSetValue/TestCase4_PlainObjectRemovedFields.tsx +112 -0
- package/src/test/testSetValue/TestCase5_FormListRemovedItems.tsx +119 -0
- package/src/test/testSetValue/TestCase6_NestedFormListRemoved.tsx +185 -0
- package/src/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.tsx +110 -0
- package/src/test/testSetValue/TestCase8_SetFieldValues_NestedObject.tsx +162 -0
- package/src/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.tsx +169 -0
- package/src/test/testSetValue/index.tsx +100 -0
- package/src/types/index.ts +1 -1
- package/src/types/public.ts +1 -1
- package/src/utils/obj.util.ts +153 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,22 +2,195 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2026-02-16
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **Deep Trigger Set Value**: Implement recursive value setting with comprehensive listener triggering
|
|
10
|
+
- `setFieldValue` with `deepTrigger: true` option for recursive listener notification
|
|
11
|
+
- Support for plain objects, arrays, FormLists, and nested structures
|
|
12
|
+
- Proper cleanup for removed fields and array items
|
|
13
|
+
- Edge case handling: removed object fields, nested FormList items
|
|
14
|
+
- **Enhanced setFieldValues**: Improved batch value setting logic
|
|
15
|
+
- Automatically detects arrays and uses deep trigger
|
|
16
|
+
- Traverses object structure and stops at arrays
|
|
17
|
+
- Primitives trigger onChange, arrays use handleDeepTriggerSet
|
|
18
|
+
- **Utility Functions**: New path collection utilities in obj.util.ts
|
|
19
|
+
- `getAllPathsIncludingContainers`: Get all paths including intermediate containers
|
|
20
|
+
- `getAllPathsStopAtArray`: Get paths but stop traversal at arrays
|
|
21
|
+
- **Array Index Notation**: Changed from `[index]` to `.index` notation for consistency
|
|
22
|
+
- **Documentation**: Added comprehensive DEEP_TRIGGER_LOGIC.md with 6 detailed case studies
|
|
23
|
+
- **Test Suite**: Created 10 test cases covering all deep trigger scenarios
|
|
24
|
+
|
|
25
|
+
### Technical Details
|
|
26
|
+
|
|
27
|
+
- Deep trigger handles 5 core value types: primitives, plain objects, array listeners (FormList), non-listener arrays, and class instances
|
|
28
|
+
- Hierarchical trigger order: field level → item/index level → property level
|
|
29
|
+
- Recursive cleanup for removed nested structures
|
|
30
|
+
- Options support for controlling dirty state triggering
|
|
31
|
+
|
|
32
|
+
## [1.0.8-beta.28] - 2026-02-09
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
- **Deep Trigger Set Value**: Implement recursive value setting with comprehensive listener triggering
|
|
37
|
+
- `setFieldValue` with `deepTrigger: true` option for recursive listener notification
|
|
38
|
+
- Support for plain objects, arrays, FormLists, and nested structures
|
|
39
|
+
- Proper cleanup for removed fields and array items
|
|
40
|
+
- Edge case handling: removed object fields, nested FormList items
|
|
41
|
+
- **Enhanced setFieldValues**: Improved batch value setting logic
|
|
42
|
+
- Automatically detects arrays and uses deep trigger
|
|
43
|
+
- Traverses object structure and stops at arrays
|
|
44
|
+
- Primitives trigger onChange, arrays use handleDeepTriggerSet
|
|
45
|
+
- **Utility Functions**: New path collection utilities in obj.util.ts
|
|
46
|
+
- `getAllPathsIncludingContainers`: Get all paths including intermediate containers
|
|
47
|
+
- `getAllPathsStopAtArray`: Get paths but stop traversal at arrays
|
|
48
|
+
- **Array Index Notation**: Changed from `[index]` to `.index` notation for consistency
|
|
49
|
+
- **Documentation**: Added comprehensive DEEP_TRIGGER_LOGIC.md with 6 detailed case studies
|
|
50
|
+
- **Test Suite**: Created 10 test cases covering all deep trigger scenarios
|
|
51
|
+
|
|
52
|
+
### Technical Details
|
|
53
|
+
|
|
54
|
+
- Deep trigger handles 5 core value types: primitives, plain objects, array listeners (FormList), non-listener arrays, and class instances
|
|
55
|
+
- Hierarchical trigger order: field level → item/index level → property level
|
|
56
|
+
- Recursive cleanup for removed nested structures
|
|
57
|
+
- Options support for controlling dirty state triggering
|
|
58
|
+
|
|
59
|
+
## [1.0.8-beta.27] - 2026-02-04
|
|
60
|
+
|
|
61
|
+
### Features
|
|
62
|
+
|
|
63
|
+
- Add `useWatchNormalize` hook for watching and normalizing form data
|
|
64
|
+
- Support custom normalization functions for automatic value transformation
|
|
65
|
+
- Simplifies data format conversion during form value watching
|
|
66
|
+
|
|
67
|
+
## [1.0.8-beta.25] - 2026-02-04
|
|
68
|
+
|
|
69
|
+
### Changes
|
|
70
|
+
|
|
71
|
+
- Remove cleanup stack execution for clearing form items and array values
|
|
72
|
+
|
|
73
|
+
## [1.0.8-beta.24] - 2026-02-04
|
|
74
|
+
|
|
75
|
+
### Fixes
|
|
76
|
+
|
|
77
|
+
- Fix cleanup item execution for proper FormItem unmounting
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
- Add `Component` prop to FormItem for wrapping input with custom elements
|
|
82
|
+
- Support custom wrapper components in FormItem
|
|
83
|
+
|
|
84
|
+
## [1.0.8-beta.23] - 2026-02-04
|
|
85
|
+
|
|
86
|
+
### Features
|
|
87
|
+
|
|
88
|
+
- Add `hidden` prop to FormItem for conditional visibility control
|
|
89
|
+
- Add `collectHiddenValue` option to control whether hidden form items contribute to form data collection
|
|
90
|
+
- FormItem can now be retrieved from Form instance
|
|
91
|
+
- Enhanced form item state management with visibility tracking
|
|
92
|
+
|
|
93
|
+
## [1.0.8-beta.22] - 2026-02-02
|
|
94
|
+
|
|
95
|
+
### Changes
|
|
96
|
+
|
|
97
|
+
- Refactor: Adjust useFormItemControl and main.tsx for better performance
|
|
98
|
+
|
|
99
|
+
## [1.0.8-beta.21] - 2026-02-02
|
|
100
|
+
|
|
101
|
+
### Fixes
|
|
102
|
+
|
|
103
|
+
- Patch: Minor adjustments in useFormItemControl
|
|
104
|
+
|
|
105
|
+
## [1.0.8-beta.20] - 2026-02-02
|
|
106
|
+
|
|
107
|
+
### Features
|
|
108
|
+
|
|
109
|
+
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
110
|
+
|
|
111
|
+
## [1.0.8-beta.19] - 2026-02-02
|
|
112
|
+
|
|
113
|
+
### Features
|
|
114
|
+
|
|
115
|
+
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
116
|
+
|
|
117
|
+
## [1.0.8-beta.18] - 2026-02-01
|
|
118
|
+
|
|
119
|
+
### Features
|
|
120
|
+
|
|
121
|
+
- Avoid deep traversal into class instances/functions in path collection
|
|
122
|
+
- `getAllNoneObjStringPath` now skips non-plain objects and functions, only traverses arrays and plain objects
|
|
123
|
+
- Improves performance and prevents accidental property access on class instances
|
|
124
|
+
|
|
125
|
+
## [1.0.8-beta.17] - 2026-02-01
|
|
126
|
+
|
|
127
|
+
### Features
|
|
128
|
+
|
|
129
|
+
- Add nested path onChange trigger: `setFieldValue` now triggers onChange for child paths when parent path has no listener
|
|
130
|
+
- Support setting array values in FormList with automatic re-render of list items and value updates
|
|
131
|
+
- Enhanced listener types with `type` field (normal/array) and `onArrayChange` callback support
|
|
132
|
+
|
|
133
|
+
### Improvements
|
|
134
|
+
|
|
135
|
+
- `useFormListControl` now tracks item values and handles cache properly
|
|
136
|
+
- Improved `useFormItemControl` with cache path validation using `lodash.has`
|
|
137
|
+
|
|
138
|
+
## [1.0.8-beta.16] - 2026-02-01
|
|
139
|
+
|
|
140
|
+
### Fixes
|
|
141
|
+
|
|
142
|
+
- Fix: Guard listener registration to prevent pushing incomplete listeners without name/formName
|
|
143
|
+
- Fix: Guard cleanup execution - only cleanup listeners that actually exist in the store
|
|
144
|
+
- Add formItemId to dependency array for proper listener re-registration in strict mode
|
|
145
|
+
|
|
146
|
+
## [1.0.8-beta.15] - 2026-02-01
|
|
147
|
+
|
|
148
|
+
### Features
|
|
149
|
+
|
|
150
|
+
- Export `useFormStore` and types (`FormInstance`, `ListenerItem`, `CleanUpItem`) to public API
|
|
151
|
+
- Allow advanced users to access and manipulate form state directly
|
|
152
|
+
|
|
153
|
+
## [1.0.8-beta.13] - 2026-02-01
|
|
154
|
+
|
|
155
|
+
### Refactoring
|
|
156
|
+
|
|
157
|
+
- Combine separate Zustand stores (`useFormListeners`, `useFormCleanUp`) into single unified `useFormStore`
|
|
158
|
+
- Implement slice-based store pattern for better code organization and maintainability
|
|
159
|
+
- Update all internal imports to use unified store across hooks and components
|
|
160
|
+
- Maintain backward compatibility with deprecated store aliases
|
|
161
|
+
|
|
162
|
+
## [1.0.8-beta.12] - 2026-01-27
|
|
163
|
+
|
|
164
|
+
### Features
|
|
165
|
+
|
|
166
|
+
- Add slice-based store architecture using Zustand for better scalability
|
|
167
|
+
- Refactor form state management with proper separation of concerns
|
|
168
|
+
|
|
5
169
|
## [1.0.8-beta.3] - 2026-01-22
|
|
6
170
|
|
|
7
171
|
## [1.0.8-beta.7] - 2026-01-24
|
|
172
|
+
|
|
8
173
|
- Add control flag for FormItem to support rendering MUI uncontrolled components when initial value is undefined (control on init).
|
|
9
174
|
- Fix: `onReset` did not restore listener state to init.
|
|
10
175
|
- Fix: reset did not return Form submit state to `idle`.
|
|
11
176
|
- Add `hidden` prop to allow hiding components while still assigning a value.
|
|
12
177
|
|
|
13
178
|
Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
179
|
+
|
|
14
180
|
- Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
|
|
15
181
|
|
|
16
182
|
- Add isTouched field to FormItem for tracking user interaction state
|
|
17
183
|
|
|
184
|
+
## [1.0.8-beta.10] - 2026-01-27
|
|
185
|
+
|
|
186
|
+
- `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`).
|
|
187
|
+
- `FormList.add` khi không truyền `index` sẽ mặc định append vào cuối danh sách.
|
|
188
|
+
- Docs: cập nhật hướng dẫn cho `useForm` và `FormList.add`.
|
|
189
|
+
|
|
18
190
|
## [1.0.8-beta.1] - 2026-01-22
|
|
19
191
|
|
|
20
192
|
- Fix UseFormItemControlReturn errors type to properly export FormFieldError[]
|
|
193
|
+
|
|
21
194
|
## [1.0.7-beta.1] - 2026-01-22
|
|
22
195
|
|
|
23
196
|
- Add `FormFieldError` type export for typed error handling
|
|
@@ -98,7 +271,3 @@ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
|
98
271
|
## [1.0.5] - 2026-01-21
|
|
99
272
|
|
|
100
273
|
- Export `Form` as a named export in addition to default to improve import ergonomics for consumers.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
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,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;
|
|
@@ -11,5 +11,11 @@ export interface FormItemProps {
|
|
|
11
11
|
getValueFromEvent?: (...args: any[]) => any;
|
|
12
12
|
controlAfterInit?: boolean;
|
|
13
13
|
hidden?: boolean;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
collectOnHidden?: boolean;
|
|
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,21 +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
|
-
|
|
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
|
|
15
37
|
});
|
|
16
|
-
|
|
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, {
|
|
17
41
|
name,
|
|
18
|
-
|
|
42
|
+
ref: elRef,
|
|
19
43
|
[valuePropName]: value,
|
|
20
44
|
onChange: (...args) => {
|
|
21
45
|
let val = args[0];
|
|
@@ -39,10 +63,24 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
|
|
|
39
63
|
errors,
|
|
40
64
|
onFocus,
|
|
41
65
|
validateState: state,
|
|
42
|
-
ref: elRef,
|
|
43
66
|
submitState,
|
|
44
67
|
isTouched
|
|
45
|
-
})
|
|
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}`);
|
|
46
84
|
}
|
|
47
85
|
export {
|
|
48
86
|
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,
|
package/dist/constants/form.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type RefObject } from "react";
|
|
2
2
|
import type { FormInstance } from "../stores/formStore";
|
|
3
3
|
import type { FormFieldError, SubmitState, ValidationRule } from "../types/public";
|
|
4
|
-
type AnyObject = Record<string, any>;
|
|
5
4
|
interface UseFormItemControlProps {
|
|
6
5
|
formName?: string;
|
|
7
6
|
form?: FormInstance;
|
|
@@ -10,10 +9,16 @@ interface UseFormItemControlProps {
|
|
|
10
9
|
formItemId?: string;
|
|
11
10
|
rules?: ValidationRule[];
|
|
12
11
|
elementRef?: RefObject<any> | null;
|
|
12
|
+
hidden?: boolean;
|
|
13
|
+
collectOnHidden?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface OnChangeOptions {
|
|
16
|
+
notTriggerDirty?: boolean;
|
|
17
|
+
initiedData?: boolean;
|
|
13
18
|
}
|
|
14
19
|
export interface UseFormItemControlReturn {
|
|
15
20
|
value: any;
|
|
16
|
-
onChange: (value: any, options?:
|
|
21
|
+
onChange: (value: any, options?: OnChangeOptions) => void;
|
|
17
22
|
state: any;
|
|
18
23
|
errors: FormFieldError[];
|
|
19
24
|
onFocus: () => void;
|
|
@@ -22,5 +27,5 @@ export interface UseFormItemControlReturn {
|
|
|
22
27
|
submitState?: SubmitState;
|
|
23
28
|
isInitied?: boolean;
|
|
24
29
|
}
|
|
25
|
-
export default function useFormItemControl<T = any>({ formName, form, name, initialValue, formItemId, rules, elementRef, }: UseFormItemControlProps): UseFormItemControlReturn;
|
|
30
|
+
export default function useFormItemControl<T = any>({ formName, form, name, initialValue, formItemId, rules, elementRef, hidden, collectOnHidden, }: UseFormItemControlProps): UseFormItemControlReturn;
|
|
26
31
|
export {};
|
|
@@ -1,26 +1,40 @@
|
|
|
1
|
-
import { get, isNil } from "lodash";
|
|
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";
|
|
7
|
-
import {
|
|
7
|
+
import { useFormStore } from "../stores/formStore";
|
|
8
8
|
const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
|
|
9
|
-
function useFormItemControl({ formName, form, name, initialValue, formItemId, rules, elementRef }) {
|
|
9
|
+
function useFormItemControl({ formName, form, name, initialValue, formItemId, rules, elementRef, hidden, collectOnHidden }) {
|
|
10
10
|
const contextForm = useFormContext();
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
value,
|
|
13
|
+
setData,
|
|
14
|
+
getCacheData,
|
|
15
|
+
getFormValues,
|
|
16
|
+
// getFormState,
|
|
17
|
+
isStateInitied,
|
|
18
|
+
submitState,
|
|
19
|
+
getListener,
|
|
20
|
+
revokeListener,
|
|
21
|
+
clearObjKeyItem
|
|
22
|
+
} = useFormStore(useShallow((state2) => {
|
|
12
23
|
var _a, _b, _c, _d;
|
|
13
24
|
return {
|
|
14
25
|
value: get(state2.forms, `${formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)}.${name}`),
|
|
15
26
|
setData: state2.setData,
|
|
16
27
|
getCacheData: state2.getCacheData,
|
|
17
28
|
getFormValues: state2.getFormValues,
|
|
18
|
-
getFormState:
|
|
29
|
+
// getFormState: state.getFormState,
|
|
19
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,
|
|
20
|
-
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
|
|
21
35
|
};
|
|
22
36
|
}));
|
|
23
|
-
const { setCleanUpStack } =
|
|
37
|
+
const { setCleanUpStack } = useFormStore(useShallow((state2) => ({
|
|
24
38
|
setCleanUpStack: state2.setCleanUpStack
|
|
25
39
|
})));
|
|
26
40
|
const { initValue: internalInitValue, setInitData, getInitData } = useFormStore(useShallow((state2) => {
|
|
@@ -30,7 +44,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
30
44
|
getInitData: state2.getInitData
|
|
31
45
|
};
|
|
32
46
|
}));
|
|
33
|
-
const { listener, setListener } =
|
|
47
|
+
const { listener, setListener } = useFormStore(useShallow((state2) => {
|
|
34
48
|
return {
|
|
35
49
|
listener: state2.listeners.find((l) => l.formItemId === formItemId),
|
|
36
50
|
setListener: state2.setListener
|
|
@@ -72,7 +86,8 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
72
86
|
setListener({
|
|
73
87
|
formItemId,
|
|
74
88
|
isDirty: false,
|
|
75
|
-
isTouched: false
|
|
89
|
+
isTouched: false,
|
|
90
|
+
isInitied: false
|
|
76
91
|
});
|
|
77
92
|
onChange(isNil(value2) ? getInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name) : value2, { notTriggerDirty: true, initiedData: true });
|
|
78
93
|
};
|
|
@@ -261,7 +276,8 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
261
276
|
setListener({ formItemId, internalErrors: listErrors });
|
|
262
277
|
return listErrors;
|
|
263
278
|
},
|
|
264
|
-
deps: [internalRules, value],
|
|
279
|
+
deps: [internalRules, value, listener == null ? void 0 : listener.isInitied],
|
|
280
|
+
enabled: Boolean(listener == null ? void 0 : listener.isInitied),
|
|
265
281
|
onError(err) {
|
|
266
282
|
}
|
|
267
283
|
});
|
|
@@ -269,33 +285,55 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
269
285
|
if (isStateInitied) {
|
|
270
286
|
if (isNil(value)) {
|
|
271
287
|
if (isNil(internalInitValue)) {
|
|
272
|
-
|
|
273
|
-
onInitData(initialValue);
|
|
274
|
-
}
|
|
288
|
+
onInitData(initialValue);
|
|
275
289
|
} else {
|
|
276
290
|
onChange(internalInitValue, {
|
|
277
291
|
notTriggerDirty: true,
|
|
278
292
|
initiedData: true
|
|
279
293
|
});
|
|
280
294
|
}
|
|
295
|
+
} else {
|
|
296
|
+
onChange(value, {
|
|
297
|
+
notTriggerDirty: true,
|
|
298
|
+
initiedData: true
|
|
299
|
+
});
|
|
281
300
|
}
|
|
282
301
|
return;
|
|
283
302
|
}
|
|
284
303
|
}, [isStateInitied]);
|
|
285
|
-
|
|
304
|
+
useLayoutEffect(() => {
|
|
286
305
|
if (!listener) {
|
|
287
306
|
setListener({
|
|
288
307
|
onChange,
|
|
289
308
|
emitFocus,
|
|
290
309
|
isTouched: false,
|
|
291
310
|
isDirty: false,
|
|
311
|
+
isInitied: false,
|
|
292
312
|
name,
|
|
293
313
|
formName: formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName),
|
|
294
314
|
formItemId,
|
|
295
|
-
onReset
|
|
315
|
+
onReset,
|
|
316
|
+
hidden,
|
|
317
|
+
collectOnHidden
|
|
296
318
|
});
|
|
297
319
|
}
|
|
320
|
+
return () => {
|
|
321
|
+
revokeListener(formItemId, (listener2, same) => {
|
|
322
|
+
if (!same.length) {
|
|
323
|
+
clearObjKeyItem(listener2.formName, listener2.name);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
};
|
|
298
327
|
}, []);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (listener) {
|
|
330
|
+
setListener({
|
|
331
|
+
formItemId,
|
|
332
|
+
hidden,
|
|
333
|
+
collectOnHidden
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}, [hidden, collectOnHidden]);
|
|
299
337
|
useEffect(() => {
|
|
300
338
|
if (listener) {
|
|
301
339
|
setListener({
|
|
@@ -311,21 +349,19 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
311
349
|
const cacheData = getCacheData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName));
|
|
312
350
|
if (cacheData) {
|
|
313
351
|
const getNewDataFromCache = get(cacheData, name);
|
|
314
|
-
|
|
315
|
-
|
|
352
|
+
const isIncludeDirectoryInCache = has(cacheData, name);
|
|
353
|
+
if (!isIncludeDirectoryInCache && isNil(getNewDataFromCache)) {
|
|
354
|
+
onChange(initialValue, {
|
|
355
|
+
notTriggerDirty: true,
|
|
356
|
+
initiedData: true
|
|
357
|
+
});
|
|
316
358
|
} else
|
|
317
|
-
onChange(getNewDataFromCache
|
|
359
|
+
onChange(getNewDataFromCache, {
|
|
360
|
+
notTriggerDirty: true,
|
|
361
|
+
initiedData: true
|
|
362
|
+
});
|
|
318
363
|
}
|
|
319
364
|
}, [name, formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]);
|
|
320
|
-
useEffect(() => {
|
|
321
|
-
return () => {
|
|
322
|
-
setCleanUpStack({
|
|
323
|
-
itemKey: formItemId
|
|
324
|
-
});
|
|
325
|
-
};
|
|
326
|
-
}, []);
|
|
327
|
-
useEffect(() => {
|
|
328
|
-
}, [submitState]);
|
|
329
365
|
return {
|
|
330
366
|
value,
|
|
331
367
|
onChange,
|
|
@@ -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;
|