react-form-manage 1.0.8-beta.2 → 1.0.8-beta.21

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