react-form-manage 1.0.8-beta.9 → 1.1.0-beta.1
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 +218 -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 +40 -12
- package/dist/providers/Form.js +335 -43
- package/dist/stores/formStore.d.ts +44 -4
- package/dist/stores/formStore.js +45 -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/testFormInstance/TestCase1_FormWithInstance.d.ts +14 -0
- package/dist/test/testFormInstance/TestCase1_FormWithInstance.js +21 -0
- package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.d.ts +14 -0
- package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.js +23 -0
- package/dist/test/testFormInstance/TestCase3_SafeFormMethods.d.ts +14 -0
- package/dist/test/testFormInstance/TestCase3_SafeFormMethods.js +66 -0
- package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.d.ts +15 -0
- package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.js +59 -0
- package/dist/test/testFormInstance/TestCase5_UseFormUtils.d.ts +1 -0
- package/dist/test/testFormInstance/TestCase5_UseFormUtils.js +65 -0
- package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.d.ts +15 -0
- package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.js +45 -0
- package/dist/test/testFormInstance/index.d.ts +12 -0
- package/dist/test/testFormInstance/index.js +49 -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/TestSetValueInEffect.d.ts +3 -0
- package/dist/test/testSetValue/TestSetValueInEffect.js +30 -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 +2 -2
- package/dist/utils/obj.util.d.ts +29 -1
- package/dist/utils/obj.util.js +59 -5
- package/package.json +3 -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 +692 -45
- package/src/stores/formStore.ts +365 -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/testFormInstance/TestCase1_FormWithInstance.tsx +112 -0
- package/src/test/testFormInstance/TestCase2_AutoGenerateFormName.tsx +139 -0
- package/src/test/testFormInstance/TestCase3_SafeFormMethods.tsx +203 -0
- package/src/test/testFormInstance/TestCase4_UseFormStatusFlag.tsx +252 -0
- package/src/test/testFormInstance/TestCase5_UseFormUtils.tsx +224 -0
- package/src/test/testFormInstance/TestCase6_FormWithoutWrapper.tsx +204 -0
- package/src/test/testFormInstance/index.tsx +116 -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/TestSetValueInEffect.tsx +54 -0
- package/src/test/testSetValue/index.tsx +100 -0
- package/src/types/index.ts +1 -1
- package/src/types/public.ts +2 -2
- package/src/utils/obj.util.ts +153 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,22 +2,240 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.1.0-beta.1] - 2026-03-03
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- **Form Component Props**: Form component can now accept `form` instance prop instead of only `formName`
|
|
10
|
+
- New usage: `<Form form={formInstance}>` alongside existing `<Form formName="myForm">`
|
|
11
|
+
- Form instance can be created with `Form.useForm()` before Form component mounts
|
|
12
|
+
- Methods are bound to external instance when provided
|
|
13
|
+
- **useForm Return Type**: `useForm()` now returns tuple `[instance, isReady]` instead of `[instance]`
|
|
14
|
+
- `isReady` flag indicates whether Form component is mounted and ready
|
|
15
|
+
- Can be used to conditionally enable/disable features based on Form state
|
|
16
|
+
- **useWatch Type**: `name` parameter now accepts `keyof T | (string & {})` for dynamic field names
|
|
17
|
+
|
|
18
|
+
### New Features
|
|
19
|
+
|
|
20
|
+
- **Auto-generate FormName**: Form.useForm() automatically generates UUID if no parameter provided
|
|
21
|
+
- No need to manually manage formName for simple cases
|
|
22
|
+
- Multiple forms can coexist without name conflicts
|
|
23
|
+
- **Safe Form Methods**: Form instance methods can be called safely before Form mounts
|
|
24
|
+
- Proxy instance automatically finds real instance when Form mounts
|
|
25
|
+
- Warnings logged instead of errors when methods called before mount
|
|
26
|
+
- **useFormUtils Hook**: New hook for external form manipulation
|
|
27
|
+
- `getFormInstance(formName)`: Get form instance from store
|
|
28
|
+
- `setFieldValue(formName, name, value, options)`: Set field value externally
|
|
29
|
+
- `setFieldValues(formName, values, options)`: Set multiple values externally
|
|
30
|
+
- Useful for global control panels, external triggers, cross-component manipulation
|
|
31
|
+
- **Headless Forms**: Form instance can work independently as state manager
|
|
32
|
+
- Methods work without Form component wrapper
|
|
33
|
+
- Useful for custom implementations, multi-step forms, form wizards
|
|
34
|
+
- **Test Suite**: Created 6 comprehensive test cases for Form Instance features
|
|
35
|
+
- TestCase1: Form with Instance Prop
|
|
36
|
+
- TestCase2: Auto-generate FormName
|
|
37
|
+
- TestCase3: Safe Form Methods
|
|
38
|
+
- TestCase4: useForm Status Flag
|
|
39
|
+
- TestCase5: useFormUtils Hook
|
|
40
|
+
- TestCase6: Form Without Wrapper
|
|
41
|
+
|
|
42
|
+
### Technical Details
|
|
43
|
+
|
|
44
|
+
- Form component uses internal instance or creates new one based on prop type
|
|
45
|
+
- Proxy instance pattern for safe method calls before mount
|
|
46
|
+
- UUID generation using `uuid/v4` for auto-generated form names
|
|
47
|
+
- FormInstance methods rebind on mount to support external instance pattern
|
|
48
|
+
- Type overloads for Form props: `FormPropsWithInstance` and `FormPropsWithoutInstance`
|
|
49
|
+
|
|
50
|
+
## [1.0.8] - 2026-02-16
|
|
51
|
+
|
|
52
|
+
### Features
|
|
53
|
+
|
|
54
|
+
- **Deep Trigger Set Value**: Implement recursive value setting with comprehensive listener triggering
|
|
55
|
+
- `setFieldValue` with `deepTrigger: true` option for recursive listener notification
|
|
56
|
+
- Support for plain objects, arrays, FormLists, and nested structures
|
|
57
|
+
- Proper cleanup for removed fields and array items
|
|
58
|
+
- Edge case handling: removed object fields, nested FormList items
|
|
59
|
+
- **Enhanced setFieldValues**: Improved batch value setting logic
|
|
60
|
+
- Automatically detects arrays and uses deep trigger
|
|
61
|
+
- Traverses object structure and stops at arrays
|
|
62
|
+
- Primitives trigger onChange, arrays use handleDeepTriggerSet
|
|
63
|
+
- **Utility Functions**: New path collection utilities in obj.util.ts
|
|
64
|
+
- `getAllPathsIncludingContainers`: Get all paths including intermediate containers
|
|
65
|
+
- `getAllPathsStopAtArray`: Get paths but stop traversal at arrays
|
|
66
|
+
- **Array Index Notation**: Changed from `[index]` to `.index` notation for consistency
|
|
67
|
+
- **Documentation**: Added comprehensive DEEP_TRIGGER_LOGIC.md with 6 detailed case studies
|
|
68
|
+
- **Test Suite**: Created 10 test cases covering all deep trigger scenarios
|
|
69
|
+
|
|
70
|
+
### Technical Details
|
|
71
|
+
|
|
72
|
+
- Deep trigger handles 5 core value types: primitives, plain objects, array listeners (FormList), non-listener arrays, and class instances
|
|
73
|
+
- Hierarchical trigger order: field level → item/index level → property level
|
|
74
|
+
- Recursive cleanup for removed nested structures
|
|
75
|
+
- Options support for controlling dirty state triggering
|
|
76
|
+
|
|
77
|
+
## [1.0.8-beta.28] - 2026-02-09
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
- **Deep Trigger Set Value**: Implement recursive value setting with comprehensive listener triggering
|
|
82
|
+
- `setFieldValue` with `deepTrigger: true` option for recursive listener notification
|
|
83
|
+
- Support for plain objects, arrays, FormLists, and nested structures
|
|
84
|
+
- Proper cleanup for removed fields and array items
|
|
85
|
+
- Edge case handling: removed object fields, nested FormList items
|
|
86
|
+
- **Enhanced setFieldValues**: Improved batch value setting logic
|
|
87
|
+
- Automatically detects arrays and uses deep trigger
|
|
88
|
+
- Traverses object structure and stops at arrays
|
|
89
|
+
- Primitives trigger onChange, arrays use handleDeepTriggerSet
|
|
90
|
+
- **Utility Functions**: New path collection utilities in obj.util.ts
|
|
91
|
+
- `getAllPathsIncludingContainers`: Get all paths including intermediate containers
|
|
92
|
+
- `getAllPathsStopAtArray`: Get paths but stop traversal at arrays
|
|
93
|
+
- **Array Index Notation**: Changed from `[index]` to `.index` notation for consistency
|
|
94
|
+
- **Documentation**: Added comprehensive DEEP_TRIGGER_LOGIC.md with 6 detailed case studies
|
|
95
|
+
- **Test Suite**: Created 10 test cases covering all deep trigger scenarios
|
|
96
|
+
|
|
97
|
+
### Technical Details
|
|
98
|
+
|
|
99
|
+
- Deep trigger handles 5 core value types: primitives, plain objects, array listeners (FormList), non-listener arrays, and class instances
|
|
100
|
+
- Hierarchical trigger order: field level → item/index level → property level
|
|
101
|
+
- Recursive cleanup for removed nested structures
|
|
102
|
+
- Options support for controlling dirty state triggering
|
|
103
|
+
|
|
104
|
+
## [1.0.8-beta.27] - 2026-02-04
|
|
105
|
+
|
|
106
|
+
### Features
|
|
107
|
+
|
|
108
|
+
- Add `useWatchNormalize` hook for watching and normalizing form data
|
|
109
|
+
- Support custom normalization functions for automatic value transformation
|
|
110
|
+
- Simplifies data format conversion during form value watching
|
|
111
|
+
|
|
112
|
+
## [1.0.8-beta.25] - 2026-02-04
|
|
113
|
+
|
|
114
|
+
### Changes
|
|
115
|
+
|
|
116
|
+
- Remove cleanup stack execution for clearing form items and array values
|
|
117
|
+
|
|
118
|
+
## [1.0.8-beta.24] - 2026-02-04
|
|
119
|
+
|
|
120
|
+
### Fixes
|
|
121
|
+
|
|
122
|
+
- Fix cleanup item execution for proper FormItem unmounting
|
|
123
|
+
|
|
124
|
+
### Features
|
|
125
|
+
|
|
126
|
+
- Add `Component` prop to FormItem for wrapping input with custom elements
|
|
127
|
+
- Support custom wrapper components in FormItem
|
|
128
|
+
|
|
129
|
+
## [1.0.8-beta.23] - 2026-02-04
|
|
130
|
+
|
|
131
|
+
### Features
|
|
132
|
+
|
|
133
|
+
- Add `hidden` prop to FormItem for conditional visibility control
|
|
134
|
+
- Add `collectHiddenValue` option to control whether hidden form items contribute to form data collection
|
|
135
|
+
- FormItem can now be retrieved from Form instance
|
|
136
|
+
- Enhanced form item state management with visibility tracking
|
|
137
|
+
|
|
138
|
+
## [1.0.8-beta.22] - 2026-02-02
|
|
139
|
+
|
|
140
|
+
### Changes
|
|
141
|
+
|
|
142
|
+
- Refactor: Adjust useFormItemControl and main.tsx for better performance
|
|
143
|
+
|
|
144
|
+
## [1.0.8-beta.21] - 2026-02-02
|
|
145
|
+
|
|
146
|
+
### Fixes
|
|
147
|
+
|
|
148
|
+
- Patch: Minor adjustments in useFormItemControl
|
|
149
|
+
|
|
150
|
+
## [1.0.8-beta.20] - 2026-02-02
|
|
151
|
+
|
|
152
|
+
### Features
|
|
153
|
+
|
|
154
|
+
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
155
|
+
|
|
156
|
+
## [1.0.8-beta.19] - 2026-02-02
|
|
157
|
+
|
|
158
|
+
### Features
|
|
159
|
+
|
|
160
|
+
- Trigger `initied` flag for onChange in `useFormItemControl` to properly mark form items as initialized
|
|
161
|
+
|
|
162
|
+
## [1.0.8-beta.18] - 2026-02-01
|
|
163
|
+
|
|
164
|
+
### Features
|
|
165
|
+
|
|
166
|
+
- Avoid deep traversal into class instances/functions in path collection
|
|
167
|
+
- `getAllNoneObjStringPath` now skips non-plain objects and functions, only traverses arrays and plain objects
|
|
168
|
+
- Improves performance and prevents accidental property access on class instances
|
|
169
|
+
|
|
170
|
+
## [1.0.8-beta.17] - 2026-02-01
|
|
171
|
+
|
|
172
|
+
### Features
|
|
173
|
+
|
|
174
|
+
- Add nested path onChange trigger: `setFieldValue` now triggers onChange for child paths when parent path has no listener
|
|
175
|
+
- Support setting array values in FormList with automatic re-render of list items and value updates
|
|
176
|
+
- Enhanced listener types with `type` field (normal/array) and `onArrayChange` callback support
|
|
177
|
+
|
|
178
|
+
### Improvements
|
|
179
|
+
|
|
180
|
+
- `useFormListControl` now tracks item values and handles cache properly
|
|
181
|
+
- Improved `useFormItemControl` with cache path validation using `lodash.has`
|
|
182
|
+
|
|
183
|
+
## [1.0.8-beta.16] - 2026-02-01
|
|
184
|
+
|
|
185
|
+
### Fixes
|
|
186
|
+
|
|
187
|
+
- Fix: Guard listener registration to prevent pushing incomplete listeners without name/formName
|
|
188
|
+
- Fix: Guard cleanup execution - only cleanup listeners that actually exist in the store
|
|
189
|
+
- Add formItemId to dependency array for proper listener re-registration in strict mode
|
|
190
|
+
|
|
191
|
+
## [1.0.8-beta.15] - 2026-02-01
|
|
192
|
+
|
|
193
|
+
### Features
|
|
194
|
+
|
|
195
|
+
- Export `useFormStore` and types (`FormInstance`, `ListenerItem`, `CleanUpItem`) to public API
|
|
196
|
+
- Allow advanced users to access and manipulate form state directly
|
|
197
|
+
|
|
198
|
+
## [1.0.8-beta.13] - 2026-02-01
|
|
199
|
+
|
|
200
|
+
### Refactoring
|
|
201
|
+
|
|
202
|
+
- Combine separate Zustand stores (`useFormListeners`, `useFormCleanUp`) into single unified `useFormStore`
|
|
203
|
+
- Implement slice-based store pattern for better code organization and maintainability
|
|
204
|
+
- Update all internal imports to use unified store across hooks and components
|
|
205
|
+
- Maintain backward compatibility with deprecated store aliases
|
|
206
|
+
|
|
207
|
+
## [1.0.8-beta.12] - 2026-01-27
|
|
208
|
+
|
|
209
|
+
### Features
|
|
210
|
+
|
|
211
|
+
- Add slice-based store architecture using Zustand for better scalability
|
|
212
|
+
- Refactor form state management with proper separation of concerns
|
|
213
|
+
|
|
5
214
|
## [1.0.8-beta.3] - 2026-01-22
|
|
6
215
|
|
|
7
216
|
## [1.0.8-beta.7] - 2026-01-24
|
|
217
|
+
|
|
8
218
|
- Add control flag for FormItem to support rendering MUI uncontrolled components when initial value is undefined (control on init).
|
|
9
219
|
- Fix: `onReset` did not restore listener state to init.
|
|
10
220
|
- Fix: reset did not return Form submit state to `idle`.
|
|
11
221
|
- Add `hidden` prop to allow hiding components while still assigning a value.
|
|
12
222
|
|
|
13
223
|
Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
224
|
+
|
|
14
225
|
- Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
|
|
15
226
|
|
|
16
227
|
- Add isTouched field to FormItem for tracking user interaction state
|
|
17
228
|
|
|
229
|
+
## [1.0.8-beta.10] - 2026-01-27
|
|
230
|
+
|
|
231
|
+
- `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`).
|
|
232
|
+
- `FormList.add` khi không truyền `index` sẽ mặc định append vào cuối danh sách.
|
|
233
|
+
- Docs: cập nhật hướng dẫn cho `useForm` và `FormList.add`.
|
|
234
|
+
|
|
18
235
|
## [1.0.8-beta.1] - 2026-01-22
|
|
19
236
|
|
|
20
237
|
- Fix UseFormItemControlReturn errors type to properly export FormFieldError[]
|
|
238
|
+
|
|
21
239
|
## [1.0.7-beta.1] - 2026-01-22
|
|
22
240
|
|
|
23
241
|
- Add `FormFieldError` type export for typed error handling
|
|
@@ -98,7 +316,3 @@ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
|
98
316
|
## [1.0.5] - 2026-01-21
|
|
99
317
|
|
|
100
318
|
- 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;
|