react-form-manage 1.0.8-beta.16 → 1.0.8-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -4
- package/README.md +1 -0
- package/dist/components/Form/FormList.d.ts +1 -1
- package/dist/hooks/useFormItemControl.js +7 -4
- package/dist/hooks/useFormListControl.d.ts +1 -0
- package/dist/hooks/useFormListControl.js +53 -3
- package/dist/index.cjs.d.ts +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.esm.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/providers/Form.js +48 -4
- package/dist/stores/formStore.d.ts +3 -0
- package/dist/stores/formStore.js +7 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/public.d.ts +1 -1
- package/dist/utils/obj.util.d.ts +1 -1
- package/dist/utils/obj.util.js +16 -5
- package/package.json +1 -1
- package/src/App.tsx +23 -3
- package/src/components/Form/FormList.tsx +1 -1
- package/src/hooks/useFormItemControl.ts +8 -4
- package/src/hooks/useFormListControl.ts +83 -14
- package/src/index.ts +19 -17
- package/src/providers/Form.tsx +77 -6
- package/src/stores/formStore.ts +12 -2
- package/src/types/index.ts +1 -1
- package/src/types/public.ts +1 -1
- package/src/utils/obj.util.ts +44 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,20 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8-beta.17] - 2026-02-01
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- Add nested path onChange trigger: `setFieldValue` now triggers onChange for child paths when parent path has no listener
|
|
10
|
+
- Support setting array values in FormList with automatic re-render of list items and value updates
|
|
11
|
+
- Enhanced listener types with `type` field (normal/array) and `onArrayChange` callback support
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
|
|
15
|
+
- `useFormListControl` now tracks item values and handles cache properly
|
|
16
|
+
- Improved `useFormItemControl` with cache path validation using `lodash.has`
|
|
17
|
+
|
|
18
|
+
## [1.0.8-beta.16] - 2026-02-01
|
|
19
|
+
|
|
20
|
+
### Fixes
|
|
21
|
+
|
|
22
|
+
- Fix: Guard listener registration to prevent pushing incomplete listeners without name/formName
|
|
23
|
+
- Fix: Guard cleanup execution - only cleanup listeners that actually exist in the store
|
|
24
|
+
- Add formItemId to dependency array for proper listener re-registration in strict mode
|
|
25
|
+
|
|
26
|
+
## [1.0.8-beta.15] - 2026-02-01
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
- Export `useFormStore` and types (`FormInstance`, `ListenerItem`, `CleanUpItem`) to public API
|
|
31
|
+
- Allow advanced users to access and manipulate form state directly
|
|
32
|
+
|
|
33
|
+
## [1.0.8-beta.13] - 2026-02-01
|
|
34
|
+
|
|
35
|
+
### Refactoring
|
|
36
|
+
|
|
37
|
+
- Combine separate Zustand stores (`useFormListeners`, `useFormCleanUp`) into single unified `useFormStore`
|
|
38
|
+
- Implement slice-based store pattern for better code organization and maintainability
|
|
39
|
+
- Update all internal imports to use unified store across hooks and components
|
|
40
|
+
- Maintain backward compatibility with deprecated store aliases
|
|
41
|
+
|
|
42
|
+
## [1.0.8-beta.12] - 2026-01-27
|
|
43
|
+
|
|
44
|
+
### Features
|
|
45
|
+
|
|
46
|
+
- Add slice-based store architecture using Zustand for better scalability
|
|
47
|
+
- Refactor form state management with proper separation of concerns
|
|
48
|
+
|
|
5
49
|
## [1.0.8-beta.3] - 2026-01-22
|
|
6
50
|
|
|
7
51
|
## [1.0.8-beta.7] - 2026-01-24
|
|
52
|
+
|
|
8
53
|
- Add control flag for FormItem to support rendering MUI uncontrolled components when initial value is undefined (control on init).
|
|
9
54
|
- Fix: `onReset` did not restore listener state to init.
|
|
10
55
|
- Fix: reset did not return Form submit state to `idle`.
|
|
11
56
|
- Add `hidden` prop to allow hiding components while still assigning a value.
|
|
12
57
|
|
|
13
58
|
Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
59
|
+
|
|
14
60
|
- Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
|
|
15
61
|
|
|
16
62
|
- Add isTouched field to FormItem for tracking user interaction state
|
|
17
63
|
|
|
18
64
|
## [1.0.8-beta.10] - 2026-01-27
|
|
65
|
+
|
|
19
66
|
- `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`).
|
|
20
67
|
- `FormList.add` khi không truyền `index` sẽ mặc định append vào cuối danh sách.
|
|
21
68
|
- Docs: cập nhật hướng dẫn cho `useForm` và `FormList.add`.
|
|
@@ -23,6 +70,7 @@ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
|
23
70
|
## [1.0.8-beta.1] - 2026-01-22
|
|
24
71
|
|
|
25
72
|
- Fix UseFormItemControlReturn errors type to properly export FormFieldError[]
|
|
73
|
+
|
|
26
74
|
## [1.0.7-beta.1] - 2026-01-22
|
|
27
75
|
|
|
28
76
|
- Add `FormFieldError` type export for typed error handling
|
|
@@ -103,7 +151,3 @@ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
|
|
|
103
151
|
## [1.0.5] - 2026-01-21
|
|
104
152
|
|
|
105
153
|
- Export `Form` as a named export in addition to default to improve import ergonomics for consumers.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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";
|
|
@@ -319,10 +319,13 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
319
319
|
const cacheData = getCacheData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName));
|
|
320
320
|
if (cacheData) {
|
|
321
321
|
const getNewDataFromCache = get(cacheData, name);
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
const isIncludeDirectoryInCache = has(cacheData, name);
|
|
323
|
+
if (!isIncludeDirectoryInCache && isNil(getNewDataFromCache)) {
|
|
324
|
+
onChange(initialValue, {
|
|
325
|
+
notTriggerDirty: true
|
|
326
|
+
});
|
|
324
327
|
} else
|
|
325
|
-
onChange(getNewDataFromCache);
|
|
328
|
+
onChange(getNewDataFromCache, { notTriggerDirty: true });
|
|
326
329
|
}
|
|
327
330
|
}, [name, formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]);
|
|
328
331
|
useEffect(() => {
|
|
@@ -5,13 +5,18 @@ import { useShallow } from "zustand/react/shallow";
|
|
|
5
5
|
import { useFormContext } from "../providers/Form";
|
|
6
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
21
|
const { setCleanUpStack } = useFormStore(useShallow((state) => ({
|
|
17
22
|
setCleanUpStack: state.setCleanUpStack
|
|
@@ -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) =>
|
|
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) {
|
|
@@ -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,11 +1,11 @@
|
|
|
1
|
-
import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type ValidationRule, type FormFieldError, type SubmitState, type UseFormItemStateWatchReturn } 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
|
-
import { useFormStore, type
|
|
10
|
-
export { Form, FormItem, FormList, Input, InputWrapper,
|
|
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, };
|
|
11
11
|
export default Form;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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
9
|
import { useFormStore } from "./stores/formStore";
|
package/dist/providers/Form.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cloneDeep, get, isEqual, isNil, last, set, uniqBy } from "lodash";
|
|
2
|
+
import { cloneDeep, get, isEqual, isNil, isPlainObject, last, set, uniqBy } from "lodash";
|
|
3
3
|
import { useTask } from "minh-custom-hooks-release";
|
|
4
4
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
5
5
|
import { flushSync } from "react-dom";
|
|
@@ -47,9 +47,36 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
47
47
|
const setFieldValue = (name, value, options) => {
|
|
48
48
|
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
49
49
|
if (listener) {
|
|
50
|
-
listener.
|
|
50
|
+
if (listener.type === "array") {
|
|
51
|
+
if (!isEqual(getFormItemValue(formName, name), value)) {
|
|
52
|
+
listener.onArrayChange(value, options);
|
|
53
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
54
|
+
allStringPath.forEach((p) => {
|
|
55
|
+
const findListener = getListeners().find((l) => l.name === `${name}.${p}` && l.formName === formName);
|
|
56
|
+
if (findListener) {
|
|
57
|
+
findListener.onChange(get(value, p), options);
|
|
58
|
+
} else {
|
|
59
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
listener.onChange(value, options);
|
|
65
|
+
}
|
|
51
66
|
} else {
|
|
52
|
-
|
|
67
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
68
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
69
|
+
allStringPath.forEach((p) => {
|
|
70
|
+
const findListener = getListeners().find((l) => l.name === `${name}.${p}` && l.formName === formName);
|
|
71
|
+
if (findListener) {
|
|
72
|
+
findListener.onChange(get(value, p), options);
|
|
73
|
+
} else {
|
|
74
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
setData(formName, name, value);
|
|
79
|
+
}
|
|
53
80
|
}
|
|
54
81
|
};
|
|
55
82
|
const setFieldValues = (values, options = { notTriggerDirty: false }) => {
|
|
@@ -57,7 +84,24 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
57
84
|
allStringPath.forEach((p) => {
|
|
58
85
|
const listener = getListeners().find((l) => l.name === p && l.formName === formName);
|
|
59
86
|
if (listener) {
|
|
60
|
-
listener.
|
|
87
|
+
if (listener.type === "array") {
|
|
88
|
+
if (!isEqual(getFormItemValue(formName, p), get(values, p))) {
|
|
89
|
+
listener.onArrayChange(get(values, listener.name), options);
|
|
90
|
+
const nestedAllStringPath = getAllNoneObjStringPath(get(values, p));
|
|
91
|
+
nestedAllStringPath.forEach((np) => {
|
|
92
|
+
{
|
|
93
|
+
const findListener = getListeners().find((l) => l.name === `${p}.${np}` && l.formName === formName);
|
|
94
|
+
if (findListener) {
|
|
95
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
96
|
+
} else {
|
|
97
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
listener.onChange(get(values, listener.name), options);
|
|
104
|
+
}
|
|
61
105
|
} else {
|
|
62
106
|
setData(formName, p, get(values, p));
|
|
63
107
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type ListenerFormItemType = "normal" | "array";
|
|
1
2
|
export interface FormInstance {
|
|
2
3
|
formName: string;
|
|
3
4
|
resetFields: (values?: any) => void;
|
|
@@ -20,11 +21,13 @@ export interface ListenerItem {
|
|
|
20
21
|
isDirty?: boolean;
|
|
21
22
|
formItemId?: string;
|
|
22
23
|
internalErrors?: any;
|
|
24
|
+
onArrayChange?: any;
|
|
23
25
|
onChange?: any;
|
|
24
26
|
onReset?: any;
|
|
25
27
|
onFocus?: any;
|
|
26
28
|
emitFocus?: any;
|
|
27
29
|
isInitied?: boolean;
|
|
30
|
+
type?: ListenerFormItemType;
|
|
28
31
|
}
|
|
29
32
|
export interface CleanUpItem {
|
|
30
33
|
name?: string;
|
package/dist/stores/formStore.js
CHANGED
|
@@ -200,7 +200,7 @@ const createListenersSlice = (storeSet, storeGet, api) => ({
|
|
|
200
200
|
getListeners() {
|
|
201
201
|
return storeGet().listeners;
|
|
202
202
|
},
|
|
203
|
-
setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus, isInitied }) {
|
|
203
|
+
setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus, isInitied, type, onArrayChange }) {
|
|
204
204
|
return storeSet(produce((state) => {
|
|
205
205
|
const storeListeners = state.listeners;
|
|
206
206
|
const findListenerIndex = state.listeners.findIndex((l) => l.formItemId === formItemId);
|
|
@@ -235,6 +235,9 @@ const createListenersSlice = (storeSet, storeGet, api) => ({
|
|
|
235
235
|
if (!isNil(isInitied)) {
|
|
236
236
|
storeListeners[findListenerIndex].isInitied = isInitied;
|
|
237
237
|
}
|
|
238
|
+
if (!isNil(onArrayChange)) {
|
|
239
|
+
storeListeners[findListenerIndex].onArrayChange = onArrayChange;
|
|
240
|
+
}
|
|
238
241
|
return;
|
|
239
242
|
}
|
|
240
243
|
if (name && formName) {
|
|
@@ -247,7 +250,9 @@ const createListenersSlice = (storeSet, storeGet, api) => ({
|
|
|
247
250
|
internalErrors,
|
|
248
251
|
onChange,
|
|
249
252
|
onReset,
|
|
250
|
-
isInitied: Boolean(isInitied)
|
|
253
|
+
isInitied: Boolean(isInitied),
|
|
254
|
+
type: type || "normal",
|
|
255
|
+
onArrayChange
|
|
251
256
|
});
|
|
252
257
|
}
|
|
253
258
|
}));
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./public";
|
package/dist/types/public.d.ts
CHANGED
package/dist/utils/obj.util.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function getAllNoneObjStringPath(value: any, prevPath?: string):
|
|
1
|
+
export declare function getAllNoneObjStringPath(value: any, prevPath?: string): string[];
|
|
2
2
|
export declare function getAllStringPath(value: any, prevPath?: string): any;
|
package/dist/utils/obj.util.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { filter, isNil, join } from "lodash";
|
|
1
|
+
import { filter, isNil, isPlainObject, join } from "lodash";
|
|
2
2
|
function getAllNoneObjStringPath(value, prevPath = "") {
|
|
3
|
-
if (
|
|
4
|
-
return
|
|
3
|
+
if (value === null || value === void 0 || typeof value !== "object" || typeof value === "function") {
|
|
4
|
+
return [prevPath];
|
|
5
|
+
}
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.reduce((prev, item, index) => {
|
|
5
8
|
return [
|
|
6
9
|
...prev,
|
|
7
|
-
...getAllNoneObjStringPath(
|
|
10
|
+
...getAllNoneObjStringPath(item, join(filter([prevPath, String(index)], (v) => !isNil(v) && v !== ""), "."))
|
|
8
11
|
];
|
|
9
12
|
}, []);
|
|
10
13
|
}
|
|
11
|
-
|
|
14
|
+
if (!isPlainObject(value)) {
|
|
15
|
+
return [prevPath];
|
|
16
|
+
}
|
|
17
|
+
return Object.keys(value).reduce((prev, cur) => {
|
|
18
|
+
return [
|
|
19
|
+
...prev,
|
|
20
|
+
...getAllNoneObjStringPath(value[cur], join(filter([prevPath, cur], (v) => !isNil(v) && v !== ""), "."))
|
|
21
|
+
];
|
|
22
|
+
}, []);
|
|
12
23
|
}
|
|
13
24
|
function getAllStringPath(value, prevPath = "") {
|
|
14
25
|
if (typeof value === "object") {
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -47,7 +47,7 @@ const App = () => {
|
|
|
47
47
|
initialValues={{
|
|
48
48
|
TestData: "",
|
|
49
49
|
numericCode: "",
|
|
50
|
-
arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
50
|
+
// arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
51
51
|
}}
|
|
52
52
|
onFinish={(values) => {
|
|
53
53
|
console.log(values);
|
|
@@ -103,7 +103,8 @@ const App = () => {
|
|
|
103
103
|
<FormList
|
|
104
104
|
initialValues={[
|
|
105
105
|
{
|
|
106
|
-
el: "",
|
|
106
|
+
el: "sdfsdf",
|
|
107
|
+
d: { child: "Test Child" },
|
|
107
108
|
},
|
|
108
109
|
]}
|
|
109
110
|
name="arr"
|
|
@@ -112,7 +113,15 @@ const App = () => {
|
|
|
112
113
|
<div>
|
|
113
114
|
{fields.map((field, index) => (
|
|
114
115
|
<div key={field.key} style={{ marginBottom: 8 }}>
|
|
115
|
-
<FormItem name={`${field.name}.el`}>
|
|
116
|
+
<FormItem name={`${field.name}.el`} initialValue={"Chém gió"}>
|
|
117
|
+
<InputWrapper>
|
|
118
|
+
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
119
|
+
</InputWrapper>
|
|
120
|
+
</FormItem>
|
|
121
|
+
<FormItem
|
|
122
|
+
name={`${field.name}.d.child`}
|
|
123
|
+
initialValue={"Con của item"}
|
|
124
|
+
>
|
|
116
125
|
<InputWrapper>
|
|
117
126
|
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
118
127
|
</InputWrapper>
|
|
@@ -145,6 +154,17 @@ const App = () => {
|
|
|
145
154
|
</div>
|
|
146
155
|
)}
|
|
147
156
|
</FormList>
|
|
157
|
+
<Button
|
|
158
|
+
onClick={() => {
|
|
159
|
+
form?.setFieldValue("arr", [
|
|
160
|
+
{ el: "Set Item 1" },
|
|
161
|
+
{ el: "Set Item 2" },
|
|
162
|
+
{ el: "Set Item 3" },
|
|
163
|
+
]);
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
Test set array list value
|
|
167
|
+
</Button>
|
|
148
168
|
<motion.div
|
|
149
169
|
initial={{ opacity: 0 }}
|
|
150
170
|
animate={{ opacity: 1 }}
|
|
@@ -9,7 +9,7 @@ export interface FormListProps<T = any> {
|
|
|
9
9
|
children: (
|
|
10
10
|
fields: Array<{ name: string; key: string }>,
|
|
11
11
|
operations: {
|
|
12
|
-
add: (index
|
|
12
|
+
add: (index?: number) => void;
|
|
13
13
|
remove: (opts: { index?: number; key?: string }) => void;
|
|
14
14
|
move: (opts: { from?: number; fromKey?: string; to: number }) => void;
|
|
15
15
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
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, type RefObject } from "react";
|
|
4
4
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
@@ -595,13 +595,17 @@ export default function useFormItemControl<T = any>({
|
|
|
595
595
|
// console.log("Get cache Data after list change: ", cacheData);
|
|
596
596
|
|
|
597
597
|
if (cacheData) {
|
|
598
|
+
console.log("Cache data found when form item change: ", name, cacheData);
|
|
598
599
|
const getNewDataFromCache = get(cacheData, name);
|
|
600
|
+
const isIncludeDirectoryInCache = has(cacheData, name);
|
|
599
601
|
|
|
600
602
|
// console.log("Init data when change form ite: ", name, cacheData);
|
|
601
603
|
|
|
602
|
-
if (!getNewDataFromCache) {
|
|
603
|
-
onChange(initialValue
|
|
604
|
-
|
|
604
|
+
if (!isIncludeDirectoryInCache && isNil(getNewDataFromCache)) {
|
|
605
|
+
onChange(initialValue, {
|
|
606
|
+
notTriggerDirty: true,
|
|
607
|
+
});
|
|
608
|
+
} else onChange(getNewDataFromCache, { notTriggerDirty: true });
|
|
605
609
|
}
|
|
606
610
|
}, [name, formName || form?.formName || contextForm?.formName]);
|
|
607
611
|
|
|
@@ -7,7 +7,7 @@ import { useFormStore } from "../stores/formStore";
|
|
|
7
7
|
|
|
8
8
|
import type { FormInstance } from "../stores/formStore";
|
|
9
9
|
|
|
10
|
-
type ListField = { name: string; key: string };
|
|
10
|
+
type ListField = { name: string; key: string; value?: any };
|
|
11
11
|
|
|
12
12
|
interface UseFormListControlProps {
|
|
13
13
|
name?: string;
|
|
@@ -29,18 +29,25 @@ export default function useFormListControl<T = any>({
|
|
|
29
29
|
initialValues,
|
|
30
30
|
formName,
|
|
31
31
|
}: UseFormListControlProps): UseFormListControlReturn {
|
|
32
|
+
const [formItemId] = useState<string>(v4());
|
|
32
33
|
const contextForm = useFormContext();
|
|
33
34
|
const getFormValues = useFormStore((state) => state.getFormValues);
|
|
34
35
|
const [listFormInitValues, setListFormInitValues] = useState<
|
|
35
36
|
any[] | undefined
|
|
36
37
|
>(undefined);
|
|
37
|
-
const { clearCacheData, setCacheData } =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const { clearCacheData, setCacheData, setListener, getListener } =
|
|
39
|
+
useFormStore(
|
|
40
|
+
useShallow((state) => ({
|
|
41
|
+
// Cache
|
|
42
|
+
cacheData: state.cacheData,
|
|
43
|
+
clearCacheData: state.clearCacheData,
|
|
44
|
+
setCacheData: state.setCacheData,
|
|
45
|
+
|
|
46
|
+
// Listener
|
|
47
|
+
setListener: state.setListener,
|
|
48
|
+
getListener: state.getListener,
|
|
49
|
+
})),
|
|
50
|
+
);
|
|
44
51
|
const { setCleanUpStack } = useFormStore(
|
|
45
52
|
useShallow((state) => ({
|
|
46
53
|
setCleanUpStack: state.setCleanUpStack,
|
|
@@ -94,9 +101,20 @@ export default function useFormListControl<T = any>({
|
|
|
94
101
|
})
|
|
95
102
|
.filter(Boolean);
|
|
96
103
|
|
|
97
|
-
const mapCurWithKey = cur.map(
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
const mapCurWithKey = cur.map((c) => {
|
|
105
|
+
const find = mapPrevWithKey.find((m) => m.key === c.key);
|
|
106
|
+
|
|
107
|
+
if (find) {
|
|
108
|
+
return {
|
|
109
|
+
key: find.key,
|
|
110
|
+
value: isNil(c.value) ? find.value : c.value,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return c;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
console.log("compare prev cur", { prev, cur });
|
|
100
118
|
|
|
101
119
|
const getNewValueCache = mapCurWithKey.filter(Boolean).map((c) => c.value);
|
|
102
120
|
|
|
@@ -109,7 +127,7 @@ export default function useFormListControl<T = any>({
|
|
|
109
127
|
// console.log("Mapping Cur value with prev fields: ", mapCurWithKey);
|
|
110
128
|
// console.log("After change arr value: ", getNewValueCache);
|
|
111
129
|
|
|
112
|
-
// Nếu số phần tử trước khi thay đổi mảng lớn hơn thì đẩy
|
|
130
|
+
// Nếu số phần tử trước khi thay đổi mảng lớn hơn thì đẩy các phần tử còn lại vào clean up stack để clear
|
|
113
131
|
if (startRemoveIndex > 0) {
|
|
114
132
|
Array.from(Array(startRemoveIndex))
|
|
115
133
|
.map((_, index) => {
|
|
@@ -129,6 +147,12 @@ export default function useFormListControl<T = any>({
|
|
|
129
147
|
});
|
|
130
148
|
}
|
|
131
149
|
|
|
150
|
+
console.log("Set cache data for form list: ", {
|
|
151
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
152
|
+
name,
|
|
153
|
+
getNewValueCache,
|
|
154
|
+
});
|
|
155
|
+
|
|
132
156
|
// console.log({ getNewValueCache });
|
|
133
157
|
setCacheData(
|
|
134
158
|
formName || form?.formName || contextForm?.formName,
|
|
@@ -228,7 +252,7 @@ export default function useFormListControl<T = any>({
|
|
|
228
252
|
to: number;
|
|
229
253
|
}) => {
|
|
230
254
|
setListFields((prev) => {
|
|
231
|
-
console.log("move list item: ", { from, to });
|
|
255
|
+
// console.log("move list item: ", { from, to });
|
|
232
256
|
if (
|
|
233
257
|
from >= listFields.length ||
|
|
234
258
|
from < 0 ||
|
|
@@ -237,7 +261,7 @@ export default function useFormListControl<T = any>({
|
|
|
237
261
|
from === to
|
|
238
262
|
)
|
|
239
263
|
return prev;
|
|
240
|
-
console.log("Trigger move item: ");
|
|
264
|
+
// console.log("Trigger move item: ");
|
|
241
265
|
|
|
242
266
|
if (!isNil(fromKey)) {
|
|
243
267
|
const findItemIndex = prev.findIndex((p) => p.key === fromKey);
|
|
@@ -383,5 +407,50 @@ export default function useFormListControl<T = any>({
|
|
|
383
407
|
};
|
|
384
408
|
}, [listFields]);
|
|
385
409
|
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (!getListener(formItemId)) {
|
|
412
|
+
setListener({
|
|
413
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
414
|
+
name: name || "",
|
|
415
|
+
formItemId: formItemId,
|
|
416
|
+
type: "array",
|
|
417
|
+
onArrayChange: (newArr) => {
|
|
418
|
+
setListFields((prev) => {
|
|
419
|
+
const result = newArr.map((_, i) => {
|
|
420
|
+
const itemName = `${name}.${i}`;
|
|
421
|
+
const existingItem = prev[i];
|
|
422
|
+
return {
|
|
423
|
+
key: existingItem ? existingItem.key : v4(),
|
|
424
|
+
name: itemName,
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
handleCacheListField(
|
|
429
|
+
prev,
|
|
430
|
+
result.map((r, i) => {
|
|
431
|
+
return { ...r, value: newArr[i] };
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
return result;
|
|
435
|
+
});
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return () => {
|
|
440
|
+
// Remove listener on unmount
|
|
441
|
+
if (getListener(formItemId)) {
|
|
442
|
+
console.log("Remove listener for form list: ", {
|
|
443
|
+
formItemId,
|
|
444
|
+
});
|
|
445
|
+
setListener({
|
|
446
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
447
|
+
name: name || "",
|
|
448
|
+
formItemId: formItemId,
|
|
449
|
+
onArrayChange: undefined,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}, []);
|
|
454
|
+
|
|
386
455
|
return { listFields, move, add, remove };
|
|
387
456
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
|
+
import { SUBMIT_STATE } from "./constants/form";
|
|
1
2
|
import Form, {
|
|
2
3
|
useForm,
|
|
3
|
-
useWatch,
|
|
4
|
-
useSubmitDataWatch,
|
|
5
4
|
useFormStateWatch,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
useSubmitDataWatch,
|
|
6
|
+
useWatch,
|
|
8
7
|
type FormFieldError,
|
|
8
|
+
type FormProps,
|
|
9
9
|
type SubmitState,
|
|
10
10
|
type UseFormItemStateWatchReturn,
|
|
11
|
+
type ValidationRule,
|
|
11
12
|
} from "./providers/Form";
|
|
12
|
-
import { SUBMIT_STATE } from "./constants/form";
|
|
13
13
|
|
|
14
14
|
import FormItem, { type FormItemProps } from "./components/Form/FormItem";
|
|
15
15
|
import FormList, { type FormListProps } from "./components/Form/FormList";
|
|
16
|
+
import InputWrapper, {
|
|
17
|
+
type InputWrapperProps,
|
|
18
|
+
} from "./components/Form/InputWrapper";
|
|
16
19
|
import Input from "./components/Input";
|
|
17
|
-
import InputWrapper, { type InputWrapperProps } from "./components/Form/InputWrapper";
|
|
18
20
|
|
|
19
21
|
import useFormItemControl from "./hooks/useFormItemControl";
|
|
20
22
|
import useFormListControl from "./hooks/useFormListControl";
|
|
21
23
|
import {
|
|
22
24
|
useFormStore,
|
|
25
|
+
type CleanUpItem,
|
|
23
26
|
type FormInstance,
|
|
24
27
|
type ListenerItem,
|
|
25
|
-
type CleanUpItem,
|
|
26
28
|
} from "./stores/formStore";
|
|
27
29
|
|
|
28
30
|
export {
|
|
@@ -31,25 +33,25 @@ export {
|
|
|
31
33
|
FormList,
|
|
32
34
|
Input,
|
|
33
35
|
InputWrapper,
|
|
36
|
+
SUBMIT_STATE,
|
|
37
|
+
useForm,
|
|
34
38
|
useFormItemControl,
|
|
35
39
|
useFormListControl,
|
|
36
|
-
useForm,
|
|
37
|
-
useWatch,
|
|
38
|
-
useSubmitDataWatch,
|
|
39
40
|
useFormStateWatch,
|
|
40
41
|
useFormStore,
|
|
41
|
-
|
|
42
|
+
useSubmitDataWatch,
|
|
43
|
+
useWatch,
|
|
44
|
+
type CleanUpItem,
|
|
45
|
+
type FormFieldError,
|
|
46
|
+
type FormInstance,
|
|
42
47
|
type FormItemProps,
|
|
43
48
|
type FormListProps,
|
|
49
|
+
type FormProps,
|
|
44
50
|
type InputWrapperProps,
|
|
45
|
-
type
|
|
46
|
-
type FormFieldError,
|
|
51
|
+
type ListenerItem,
|
|
47
52
|
type SubmitState,
|
|
48
53
|
type UseFormItemStateWatchReturn,
|
|
49
|
-
type
|
|
50
|
-
type ListenerItem,
|
|
51
|
-
type CleanUpItem,
|
|
52
|
-
SUBMIT_STATE,
|
|
54
|
+
type ValidationRule,
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
export default Form;
|
package/src/providers/Form.tsx
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
cloneDeep,
|
|
3
|
+
get,
|
|
4
|
+
isEqual,
|
|
5
|
+
isNil,
|
|
6
|
+
isPlainObject,
|
|
7
|
+
last,
|
|
8
|
+
set,
|
|
9
|
+
uniqBy,
|
|
10
|
+
} from "lodash";
|
|
2
11
|
import { useTask } from "minh-custom-hooks-release";
|
|
3
12
|
import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
4
13
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
@@ -6,7 +15,7 @@ import { flushSync } from "react-dom";
|
|
|
6
15
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
7
16
|
import FormCleanUp from "../components/Form/FormCleanUp";
|
|
8
17
|
import { SUBMIT_STATE } from "../constants/form";
|
|
9
|
-
import { useFormStore } from "../stores/formStore";
|
|
18
|
+
import { ListenerItem, useFormStore } from "../stores/formStore";
|
|
10
19
|
import type {
|
|
11
20
|
PublicFormInstance,
|
|
12
21
|
UseFormItemStateWatchReturn,
|
|
@@ -90,13 +99,51 @@ export default function Form<T = any>({
|
|
|
90
99
|
);
|
|
91
100
|
|
|
92
101
|
const setFieldValue = (name, value, options) => {
|
|
93
|
-
const listener = getListeners().find(
|
|
102
|
+
const listener: ListenerItem | null = getListeners().find(
|
|
94
103
|
(l) => l.name === name && l.formName === formName,
|
|
95
104
|
);
|
|
96
105
|
if (listener) {
|
|
97
|
-
listener
|
|
106
|
+
// Nếu loại listener là array thì gọi onArrayChange
|
|
107
|
+
if (listener.type === "array") {
|
|
108
|
+
// Do nothing if the value is the same for array item to prevent unnecessary re-renders
|
|
109
|
+
if (!isEqual(getFormItemValue(formName, name), value)) {
|
|
110
|
+
listener.onArrayChange(value, options);
|
|
111
|
+
|
|
112
|
+
// Kiểm tra từng path con của array item xem có listener nào không, nếu có thì gọi onChange của listener đó
|
|
113
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
114
|
+
allStringPath.forEach((p) => {
|
|
115
|
+
const findListener = getListeners().find(
|
|
116
|
+
(l) => l.name === `${name}.${p}` && l.formName === formName,
|
|
117
|
+
);
|
|
118
|
+
if (findListener) {
|
|
119
|
+
findListener.onChange(get(value, p), options);
|
|
120
|
+
} else {
|
|
121
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
listener.onChange(value, options);
|
|
127
|
+
}
|
|
98
128
|
} else {
|
|
99
|
-
|
|
129
|
+
// set data for non-listener field
|
|
130
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
131
|
+
// Nếu là object hoặc array thì set từng path con
|
|
132
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
133
|
+
|
|
134
|
+
allStringPath.forEach((p) => {
|
|
135
|
+
const findListener = getListeners().find(
|
|
136
|
+
(l) => l.name === `${name}.${p}` && l.formName === formName,
|
|
137
|
+
);
|
|
138
|
+
if (findListener) {
|
|
139
|
+
findListener.onChange(get(value, p), options);
|
|
140
|
+
} else {
|
|
141
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
setData(formName, name, value);
|
|
146
|
+
}
|
|
100
147
|
}
|
|
101
148
|
};
|
|
102
149
|
|
|
@@ -108,7 +155,31 @@ export default function Form<T = any>({
|
|
|
108
155
|
(l) => l.name === p && l.formName === formName,
|
|
109
156
|
);
|
|
110
157
|
if (listener) {
|
|
111
|
-
listener.
|
|
158
|
+
if (listener.type === "array") {
|
|
159
|
+
// Do nothing if the value is the same for array item to prevent unnecessary re-renders
|
|
160
|
+
if (!isEqual(getFormItemValue(formName, p), get(values, p))) {
|
|
161
|
+
listener.onArrayChange(get(values, listener.name), options);
|
|
162
|
+
|
|
163
|
+
// Kiểm tra từng path con của array item xem có listener nào không, nếu có thì gọi onChange của listener đó
|
|
164
|
+
const nestedAllStringPath: string[] = getAllNoneObjStringPath(
|
|
165
|
+
get(values, p),
|
|
166
|
+
);
|
|
167
|
+
nestedAllStringPath.forEach((np) => {
|
|
168
|
+
{
|
|
169
|
+
const findListener = getListeners().find(
|
|
170
|
+
(l) => l.name === `${p}.${np}` && l.formName === formName,
|
|
171
|
+
);
|
|
172
|
+
if (findListener) {
|
|
173
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
174
|
+
} else {
|
|
175
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
listener.onChange(get(values, listener.name), options);
|
|
182
|
+
}
|
|
112
183
|
} else {
|
|
113
184
|
setData(formName, p, get(values, p));
|
|
114
185
|
}
|
package/src/stores/formStore.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { cloneDeep, get, isNil, isNumber, last, set, unset } from "lodash";
|
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { create } from "zustand";
|
|
5
5
|
import { getAllNoneObjStringPath } from "../utils/obj.util";
|
|
6
|
-
|
|
6
|
+
type ListenerFormItemType = "normal" | "array";
|
|
7
7
|
export interface FormInstance {
|
|
8
8
|
formName: string;
|
|
9
9
|
resetFields: (values?: any) => void;
|
|
@@ -24,11 +24,13 @@ export interface ListenerItem {
|
|
|
24
24
|
isDirty?: boolean;
|
|
25
25
|
formItemId?: string;
|
|
26
26
|
internalErrors?: any;
|
|
27
|
+
onArrayChange?: any;
|
|
27
28
|
onChange?: any;
|
|
28
29
|
onReset?: any;
|
|
29
30
|
onFocus?: any;
|
|
30
31
|
emitFocus?: any;
|
|
31
32
|
isInitied?: boolean;
|
|
33
|
+
type?: ListenerFormItemType;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface CleanUpItem {
|
|
@@ -379,7 +381,9 @@ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
379
381
|
onFocus,
|
|
380
382
|
emitFocus,
|
|
381
383
|
isInitied,
|
|
382
|
-
|
|
384
|
+
type,
|
|
385
|
+
onArrayChange,
|
|
386
|
+
}: Partial<ListenerItem> & { formItemId: string }) {
|
|
383
387
|
return storeSet(
|
|
384
388
|
produce<any>((state: any) => {
|
|
385
389
|
const storeListeners = state.listeners;
|
|
@@ -420,6 +424,10 @@ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
420
424
|
storeListeners[findListenerIndex].isInitied = isInitied;
|
|
421
425
|
}
|
|
422
426
|
|
|
427
|
+
if (!isNil(onArrayChange)) {
|
|
428
|
+
storeListeners[findListenerIndex].onArrayChange = onArrayChange;
|
|
429
|
+
}
|
|
430
|
+
|
|
423
431
|
return;
|
|
424
432
|
}
|
|
425
433
|
if (name && formName) {
|
|
@@ -433,6 +441,8 @@ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
433
441
|
onChange,
|
|
434
442
|
onReset,
|
|
435
443
|
isInitied: Boolean(isInitied),
|
|
444
|
+
type: type || "normal",
|
|
445
|
+
onArrayChange,
|
|
436
446
|
});
|
|
437
447
|
}
|
|
438
448
|
}),
|
package/src/types/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./public";
|
package/src/types/public.ts
CHANGED
package/src/utils/obj.util.ts
CHANGED
|
@@ -1,22 +1,53 @@
|
|
|
1
|
-
import { filter, isNil, join } from "lodash";
|
|
1
|
+
import { filter, isNil, isPlainObject, join } from "lodash";
|
|
2
2
|
|
|
3
|
-
export function getAllNoneObjStringPath(
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export function getAllNoneObjStringPath(
|
|
4
|
+
value: any,
|
|
5
|
+
prevPath: string = "",
|
|
6
|
+
): string[] {
|
|
7
|
+
// primitive / function / null / undefined => dừng
|
|
8
|
+
if (
|
|
9
|
+
value === null ||
|
|
10
|
+
value === undefined ||
|
|
11
|
+
typeof value !== "object" ||
|
|
12
|
+
typeof value === "function"
|
|
13
|
+
) {
|
|
14
|
+
return [prevPath];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// array thì đi sâu
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.reduce((prev: string[], item, index) => {
|
|
6
20
|
return [
|
|
7
21
|
...prev,
|
|
8
|
-
|
|
9
22
|
...getAllNoneObjStringPath(
|
|
10
|
-
|
|
23
|
+
item,
|
|
11
24
|
join(
|
|
12
|
-
filter([prevPath,
|
|
13
|
-
"."
|
|
14
|
-
)
|
|
25
|
+
filter([prevPath, String(index)], (v) => !isNil(v) && v !== ""),
|
|
26
|
+
".",
|
|
27
|
+
),
|
|
15
28
|
),
|
|
16
29
|
];
|
|
17
30
|
}, []);
|
|
18
31
|
}
|
|
19
|
-
|
|
32
|
+
|
|
33
|
+
// class instance (non-plain object) => dừng
|
|
34
|
+
if (!isPlainObject(value)) {
|
|
35
|
+
return [prevPath];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// plain object => đi sâu
|
|
39
|
+
return Object.keys(value).reduce((prev: string[], cur) => {
|
|
40
|
+
return [
|
|
41
|
+
...prev,
|
|
42
|
+
...getAllNoneObjStringPath(
|
|
43
|
+
value[cur],
|
|
44
|
+
join(
|
|
45
|
+
filter([prevPath, cur], (v) => !isNil(v) && v !== ""),
|
|
46
|
+
".",
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
];
|
|
50
|
+
}, []);
|
|
20
51
|
}
|
|
21
52
|
|
|
22
53
|
export function getAllStringPath(value: any, prevPath: string = "") {
|
|
@@ -26,14 +57,14 @@ export function getAllStringPath(value: any, prevPath: string = "") {
|
|
|
26
57
|
...prev,
|
|
27
58
|
join(
|
|
28
59
|
filter([prevPath, cur], (v) => !isNil(v) && v !== ""),
|
|
29
|
-
"."
|
|
60
|
+
".",
|
|
30
61
|
),
|
|
31
62
|
...getAllStringPath(
|
|
32
63
|
value[cur],
|
|
33
64
|
join(
|
|
34
65
|
filter([prevPath, cur], (v) => !isNil(v) && v !== ""),
|
|
35
|
-
"."
|
|
36
|
-
)
|
|
66
|
+
".",
|
|
67
|
+
),
|
|
37
68
|
),
|
|
38
69
|
];
|
|
39
70
|
}, []);
|