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.
Files changed (118) hide show
  1. package/CHANGELOG.md +218 -4
  2. package/README.md +8 -4
  3. package/dist/components/Form/FormCleanUp.js +3 -3
  4. package/dist/components/Form/FormItem.d.ts +10 -4
  5. package/dist/components/Form/FormItem.js +52 -14
  6. package/dist/components/Form/FormList.d.ts +2 -2
  7. package/dist/components/Form/FormList.js +2 -2
  8. package/dist/constants/form.d.ts +1 -1
  9. package/dist/hooks/useFormItemControl.d.ts +8 -3
  10. package/dist/hooks/useFormItemControl.js +64 -28
  11. package/dist/hooks/useFormListControl.d.ts +2 -1
  12. package/dist/hooks/useFormListControl.js +85 -19
  13. package/dist/index.cjs.d.ts +1 -0
  14. package/dist/index.d.ts +4 -3
  15. package/dist/index.esm.d.ts +1 -0
  16. package/dist/index.js +4 -2
  17. package/dist/providers/Form.d.ts +40 -12
  18. package/dist/providers/Form.js +335 -43
  19. package/dist/stores/formStore.d.ts +44 -4
  20. package/dist/stores/formStore.js +45 -7
  21. package/dist/test/CommonTest.d.ts +3 -0
  22. package/dist/test/CommonTest.js +49 -0
  23. package/dist/test/TestDialog.d.ts +3 -0
  24. package/dist/test/TestDialog.js +21 -0
  25. package/dist/test/TestListener.d.ts +3 -0
  26. package/dist/test/TestListener.js +17 -0
  27. package/dist/test/TestNotFormWrapper.d.ts +3 -0
  28. package/dist/test/TestNotFormWrapper.js +15 -0
  29. package/dist/test/TestSelect.d.ts +6 -0
  30. package/dist/test/TestSelect.js +24 -0
  31. package/dist/test/TestWatchNormalize.d.ts +3 -0
  32. package/dist/test/TestWatchNormalize.js +23 -0
  33. package/dist/test/TestWrapperFormItem.d.ts +3 -0
  34. package/dist/test/TestWrapperFormItem.js +13 -0
  35. package/dist/test/testFormInstance/TestCase1_FormWithInstance.d.ts +14 -0
  36. package/dist/test/testFormInstance/TestCase1_FormWithInstance.js +21 -0
  37. package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.d.ts +14 -0
  38. package/dist/test/testFormInstance/TestCase2_AutoGenerateFormName.js +23 -0
  39. package/dist/test/testFormInstance/TestCase3_SafeFormMethods.d.ts +14 -0
  40. package/dist/test/testFormInstance/TestCase3_SafeFormMethods.js +66 -0
  41. package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.d.ts +15 -0
  42. package/dist/test/testFormInstance/TestCase4_UseFormStatusFlag.js +59 -0
  43. package/dist/test/testFormInstance/TestCase5_UseFormUtils.d.ts +1 -0
  44. package/dist/test/testFormInstance/TestCase5_UseFormUtils.js +65 -0
  45. package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.d.ts +15 -0
  46. package/dist/test/testFormInstance/TestCase6_FormWithoutWrapper.js +45 -0
  47. package/dist/test/testFormInstance/index.d.ts +12 -0
  48. package/dist/test/testFormInstance/index.js +49 -0
  49. package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.d.ts +21 -0
  50. package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.js +61 -0
  51. package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.d.ts +16 -0
  52. package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.js +18 -0
  53. package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.d.ts +21 -0
  54. package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.js +33 -0
  55. package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.d.ts +21 -0
  56. package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.js +26 -0
  57. package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.d.ts +20 -0
  58. package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.js +32 -0
  59. package/dist/test/testSetValue/TestCase5_FormListRemovedItems.d.ts +22 -0
  60. package/dist/test/testSetValue/TestCase5_FormListRemovedItems.js +29 -0
  61. package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.d.ts +28 -0
  62. package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.js +36 -0
  63. package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.d.ts +17 -0
  64. package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.js +33 -0
  65. package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.d.ts +27 -0
  66. package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.js +57 -0
  67. package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.d.ts +25 -0
  68. package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.js +46 -0
  69. package/dist/test/testSetValue/TestSetValueInEffect.d.ts +3 -0
  70. package/dist/test/testSetValue/TestSetValueInEffect.js +30 -0
  71. package/dist/test/testSetValue/index.d.ts +2 -0
  72. package/dist/test/testSetValue/index.js +28 -0
  73. package/dist/types/index.d.ts +1 -1
  74. package/dist/types/public.d.ts +2 -2
  75. package/dist/utils/obj.util.d.ts +29 -1
  76. package/dist/utils/obj.util.js +59 -5
  77. package/package.json +3 -1
  78. package/src/App.tsx +38 -163
  79. package/src/DEEP_TRIGGER_LOGIC.md +573 -0
  80. package/src/components/Form/FormCleanUp.tsx +4 -8
  81. package/src/components/Form/FormItem.tsx +174 -57
  82. package/src/components/Form/FormList.tsx +17 -4
  83. package/src/constants/form.ts +1 -1
  84. package/src/hooks/useFormItemControl.ts +78 -32
  85. package/src/hooks/useFormListControl.ts +133 -43
  86. package/src/index.ts +25 -13
  87. package/src/main.tsx +6 -1
  88. package/src/providers/Form.tsx +692 -45
  89. package/src/stores/formStore.ts +365 -283
  90. package/src/test/CommonTest.tsx +177 -0
  91. package/src/test/TestDialog.tsx +52 -0
  92. package/src/test/TestListener.tsx +21 -0
  93. package/src/test/TestNotFormWrapper.tsx +43 -0
  94. package/src/test/TestSelect.tsx +38 -0
  95. package/src/test/TestWatchNormalize.tsx +32 -0
  96. package/src/test/TestWrapperFormItem.tsx +34 -0
  97. package/src/test/testFormInstance/TestCase1_FormWithInstance.tsx +112 -0
  98. package/src/test/testFormInstance/TestCase2_AutoGenerateFormName.tsx +139 -0
  99. package/src/test/testFormInstance/TestCase3_SafeFormMethods.tsx +203 -0
  100. package/src/test/testFormInstance/TestCase4_UseFormStatusFlag.tsx +252 -0
  101. package/src/test/testFormInstance/TestCase5_UseFormUtils.tsx +224 -0
  102. package/src/test/testFormInstance/TestCase6_FormWithoutWrapper.tsx +204 -0
  103. package/src/test/testFormInstance/index.tsx +116 -0
  104. package/src/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.tsx +203 -0
  105. package/src/test/testSetValue/TestCase1_PlainObjectToPrimitives.tsx +72 -0
  106. package/src/test/testSetValue/TestCase2_PlainObjectToFormList.tsx +114 -0
  107. package/src/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.tsx +99 -0
  108. package/src/test/testSetValue/TestCase4_PlainObjectRemovedFields.tsx +112 -0
  109. package/src/test/testSetValue/TestCase5_FormListRemovedItems.tsx +119 -0
  110. package/src/test/testSetValue/TestCase6_NestedFormListRemoved.tsx +185 -0
  111. package/src/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.tsx +110 -0
  112. package/src/test/testSetValue/TestCase8_SetFieldValues_NestedObject.tsx +162 -0
  113. package/src/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.tsx +169 -0
  114. package/src/test/testSetValue/TestSetValueInEffect.tsx +54 -0
  115. package/src/test/testSetValue/index.tsx +100 -0
  116. package/src/types/index.ts +1 -1
  117. package/src/types/public.ts +2 -2
  118. 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("myForm"); // hoặc useForm('myForm')
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)` — trả về form instance đã đăng ký.
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 { useFormCleanUp, useFormListeners, useFormStore } from "../../stores/formStore";
4
+ import { useFormStore } from "../../stores/formStore";
5
5
  const FormCleanUp = () => {
6
- const { cleanUpStack, clearCleanUpStack } = useFormCleanUp(useShallow((state) => ({
6
+ const { cleanUpStack, clearCleanUpStack } = useFormStore(useShallow((state) => ({
7
7
  cleanUpStack: state.cleanUpStack,
8
8
  clearCleanUpStack: state.clearCleanUpStack
9
9
  })));
10
- const { revokeListener } = useFormListeners(useShallow((state) => ({
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 interface FormItemProps {
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
- export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, controlAfterInit, hidden, }: FormItemProps): import("react/jsx-runtime").JSX.Element;
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 { cloneElement, Fragment, useRef, useState } from "react";
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 FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent, controlAfterInit = false, hidden }) {
6
- const elRef = useRef(null);
7
- const [formItemId] = useState(externalFormItemId != null ? externalFormItemId : v4());
8
- const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, isInitied } = useFormItemControl({
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
- initialValue,
12
- formItemId,
13
- rules,
14
- elementRef: elRef
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
- return _jsx(Fragment, { children: !hidden && children ? cloneElement(children, {
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
- // ref: inputRef,
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
- }) : null }, `control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${formItemId}`);
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: number) => void;
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 useTestRrenderListControl from "../../hooks/useFormListControl";
1
+ import useFormListControl from "../../hooks/useFormListControl";
2
2
  const FormList = ({ name, initialValues, form, formName, children }) => {
3
- const { listFields, ...actions } = useTestRrenderListControl({
3
+ const { listFields, ...actions } = useFormListControl({
4
4
  name,
5
5
  initialValues,
6
6
  form,
@@ -4,4 +4,4 @@ export declare const SUBMIT_STATE: {
4
4
  readonly SUBMITTED: "submitted";
5
5
  readonly REJECTED: "rejected";
6
6
  };
7
- export type SubmitState = typeof SUBMIT_STATE[keyof typeof SUBMIT_STATE];
7
+ export type SubmitState = (typeof SUBMIT_STATE)[keyof typeof SUBMIT_STATE];
@@ -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?: AnyObject) => void;
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 { useFormCleanUp, useFormListeners, useFormStore } from "../stores/formStore";
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 { value, setData, getCacheData, getFormValues, getFormState, isStateInitied, submitState } = useFormStore(useShallow((state2) => {
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: state2.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 } = useFormCleanUp(useShallow((state2) => ({
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 } = useFormListeners(useShallow((state2) => {
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
- if (!isNil(initialValue)) {
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
- useEffect(() => {
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
- if (!getNewDataFromCache) {
315
- onChange(initialValue);
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: number) => void;
20
+ add: (index?: number) => void;
20
21
  remove: (opts: {
21
22
  index?: number;
22
23
  key?: string;