react-form-manage 1.0.8-beta.7 → 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.
Files changed (94) hide show
  1. package/CHANGELOG.md +173 -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 +15 -2
  18. package/dist/providers/Form.js +226 -41
  19. package/dist/stores/formStore.d.ts +44 -4
  20. package/dist/stores/formStore.js +42 -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/testSetValue/TestCase10_SetFieldValues_ComplexNested.d.ts +21 -0
  36. package/dist/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.js +61 -0
  37. package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.d.ts +16 -0
  38. package/dist/test/testSetValue/TestCase1_PlainObjectToPrimitives.js +18 -0
  39. package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.d.ts +21 -0
  40. package/dist/test/testSetValue/TestCase2_PlainObjectToFormList.js +33 -0
  41. package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.d.ts +21 -0
  42. package/dist/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.js +26 -0
  43. package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.d.ts +20 -0
  44. package/dist/test/testSetValue/TestCase4_PlainObjectRemovedFields.js +32 -0
  45. package/dist/test/testSetValue/TestCase5_FormListRemovedItems.d.ts +22 -0
  46. package/dist/test/testSetValue/TestCase5_FormListRemovedItems.js +29 -0
  47. package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.d.ts +28 -0
  48. package/dist/test/testSetValue/TestCase6_NestedFormListRemoved.js +36 -0
  49. package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.d.ts +17 -0
  50. package/dist/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.js +33 -0
  51. package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.d.ts +27 -0
  52. package/dist/test/testSetValue/TestCase8_SetFieldValues_NestedObject.js +57 -0
  53. package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.d.ts +25 -0
  54. package/dist/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.js +46 -0
  55. package/dist/test/testSetValue/index.d.ts +2 -0
  56. package/dist/test/testSetValue/index.js +28 -0
  57. package/dist/types/index.d.ts +1 -1
  58. package/dist/types/public.d.ts +1 -1
  59. package/dist/utils/obj.util.d.ts +29 -1
  60. package/dist/utils/obj.util.js +59 -5
  61. package/package.json +2 -1
  62. package/src/App.tsx +39 -156
  63. package/src/DEEP_TRIGGER_LOGIC.md +573 -0
  64. package/src/components/Form/FormCleanUp.tsx +4 -8
  65. package/src/components/Form/FormItem.tsx +174 -57
  66. package/src/components/Form/FormList.tsx +17 -4
  67. package/src/constants/form.ts +1 -1
  68. package/src/hooks/useFormItemControl.ts +78 -32
  69. package/src/hooks/useFormListControl.ts +133 -43
  70. package/src/index.ts +25 -13
  71. package/src/main.tsx +6 -1
  72. package/src/providers/Form.tsx +454 -26
  73. package/src/stores/formStore.ts +363 -283
  74. package/src/test/CommonTest.tsx +177 -0
  75. package/src/test/TestDialog.tsx +52 -0
  76. package/src/test/TestListener.tsx +21 -0
  77. package/src/test/TestNotFormWrapper.tsx +43 -0
  78. package/src/test/TestSelect.tsx +38 -0
  79. package/src/test/TestWatchNormalize.tsx +32 -0
  80. package/src/test/TestWrapperFormItem.tsx +34 -0
  81. package/src/test/testSetValue/TestCase10_SetFieldValues_ComplexNested.tsx +203 -0
  82. package/src/test/testSetValue/TestCase1_PlainObjectToPrimitives.tsx +72 -0
  83. package/src/test/testSetValue/TestCase2_PlainObjectToFormList.tsx +114 -0
  84. package/src/test/testSetValue/TestCase3_ArrayNonListenerToPrimitives.tsx +99 -0
  85. package/src/test/testSetValue/TestCase4_PlainObjectRemovedFields.tsx +112 -0
  86. package/src/test/testSetValue/TestCase5_FormListRemovedItems.tsx +119 -0
  87. package/src/test/testSetValue/TestCase6_NestedFormListRemoved.tsx +185 -0
  88. package/src/test/testSetValue/TestCase7_SetFieldValues_MixedStructure.tsx +110 -0
  89. package/src/test/testSetValue/TestCase8_SetFieldValues_NestedObject.tsx +162 -0
  90. package/src/test/testSetValue/TestCase9_SetFieldValues_MultipleArrays.tsx +169 -0
  91. package/src/test/testSetValue/index.tsx +100 -0
  92. package/src/types/index.ts +1 -1
  93. package/src/types/public.ts +1 -1
  94. 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("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;