@idem.agency/form-builder 0.0.11 → 0.0.13
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/README.md +922 -12
- package/dist/index.cjs +272 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -16
- package/dist/index.d.mts +18 -16
- package/dist/index.mjs +273 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +19 -5
- package/CHANGELOG.md +0 -8
- package/eslint.config.js +0 -23
- package/public/index.html +0 -13
- package/public/main.tsx +0 -90
- package/src/app/debug.tsx +0 -0
- package/src/app/index.tsx +0 -80
- package/src/app/test.css +0 -1
- package/src/entity/inputs/index.ts +0 -2
- package/src/entity/inputs/ui/group/index.tsx +0 -28
- package/src/entity/inputs/ui/input/index.tsx +0 -31
- package/src/shared/hook/useUpdateEffect.tsx +0 -23
- package/src/shared/lib/VisibleCore.spec.ts +0 -103
- package/src/shared/lib/VisibleCore.ts +0 -43
- package/src/shared/lib/validation/core.spec.ts +0 -103
- package/src/shared/lib/validation/core.ts +0 -79
- package/src/shared/lib/validation/rules/base.ts +0 -10
- package/src/shared/lib/validation/rules/confirm.spec.ts +0 -17
- package/src/shared/lib/validation/rules/confirm.ts +0 -32
- package/src/shared/lib/validation/rules/email.spec.ts +0 -12
- package/src/shared/lib/validation/rules/email.ts +0 -13
- package/src/shared/lib/validation/rules/require.spec.ts +0 -13
- package/src/shared/lib/validation/rules/require.ts +0 -12
- package/src/shared/model/builder/createContext.tsx +0 -40
- package/src/shared/model/builder/index.ts +0 -6
- package/src/shared/model/index.ts +0 -12
- package/src/shared/model/store/createStoreContext.tsx +0 -74
- package/src/shared/model/store/index.ts +0 -46
- package/src/shared/model/store/store.ts +0 -27
- package/src/shared/types/common.ts +0 -79
- package/src/shared/utils.ts +0 -25
- package/src/widgets/dynamicBuilder/element.tsx +0 -31
- package/src/widgets/dynamicBuilder/index.tsx +0 -33
- package/tsconfig.json +0 -24
- package/tsdown.config.ts +0 -10
- package/vite.config.ts +0 -11
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,19 @@ declare const fieldShema: z.ZodObject<{
|
|
|
10
10
|
}, z.core.$strip>;
|
|
11
11
|
type TField = z.infer<typeof fieldShema>;
|
|
12
12
|
//#endregion
|
|
13
|
+
//#region src/plugins/validation/types/index.d.ts
|
|
14
|
+
interface IUserRule {
|
|
15
|
+
code: string;
|
|
16
|
+
fn: (value: any, data: FormData, args: string[]) => boolean;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/plugins/validation/provider.d.ts
|
|
21
|
+
type TValidator = {
|
|
22
|
+
rules?: IUserRule[];
|
|
23
|
+
onSubmit?: boolean;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
13
26
|
//#region src/shared/types/common.d.ts
|
|
14
27
|
type ConfigFunctionComponent<P> = FC<P> & {
|
|
15
28
|
fieldProps?: string[];
|
|
@@ -19,21 +32,14 @@ type RC<T = {}> = ConfigFunctionComponent<T & {
|
|
|
19
32
|
children?: ReactNode;
|
|
20
33
|
}>;
|
|
21
34
|
type FormData = Record<string, any>;
|
|
22
|
-
interface IRule {
|
|
23
|
-
getName: () => string;
|
|
24
|
-
validate: (value: string, formData: FormData) => boolean;
|
|
25
|
-
message: () => string;
|
|
26
|
-
}
|
|
27
35
|
type FormFieldBase = {
|
|
28
|
-
validation?:
|
|
36
|
+
validation?: string[];
|
|
29
37
|
viewConfig?: TGroupRules;
|
|
30
38
|
} & TField;
|
|
31
39
|
type FormFieldConfig = FormFieldBase & Record<string, any>;
|
|
32
40
|
type FormElementProps<F extends FormFieldConfig = FormFieldConfig> = {
|
|
33
41
|
field: F;
|
|
34
|
-
|
|
35
|
-
plugins: FormElementRegistry;
|
|
36
|
-
path: string[];
|
|
42
|
+
path: string;
|
|
37
43
|
value: any;
|
|
38
44
|
errors?: Record<string, string>;
|
|
39
45
|
onChange: (value: any) => void;
|
|
@@ -44,16 +50,12 @@ type TFormBuilder = {
|
|
|
44
50
|
formData?: FormData;
|
|
45
51
|
className?: string;
|
|
46
52
|
layout: FormFieldConfig[];
|
|
47
|
-
|
|
53
|
+
fields: FormElementRegistry;
|
|
54
|
+
validator?: TValidator;
|
|
48
55
|
onChange?: (formData: any) => void;
|
|
49
56
|
onSubmit?: (formData: any) => void;
|
|
50
57
|
children?: ReactNode;
|
|
51
58
|
};
|
|
52
|
-
type TDynamicBuilder = RC<{
|
|
53
|
-
layout: FormFieldConfig[];
|
|
54
|
-
plugins: FormElementRegistry;
|
|
55
|
-
path?: string[];
|
|
56
|
-
}>;
|
|
57
59
|
type TCommonRule = {
|
|
58
60
|
operator: string;
|
|
59
61
|
field: string;
|
|
@@ -82,5 +84,5 @@ type FormGroupConfig = FormFieldBase & {
|
|
|
82
84
|
};
|
|
83
85
|
declare const FormGroup: RC<FormElementProps<FormGroupConfig>>;
|
|
84
86
|
//#endregion
|
|
85
|
-
export { FormBuilder, type FormBuilderRef, type FormElementProps, type FormElementRegistry, type FormFieldConfig, FormGroup, type
|
|
87
|
+
export { FormBuilder, type FormBuilderRef, type FormElementProps, type FormElementRegistry, type FormFieldConfig, FormGroup, type TGroupRules, TextField, fieldShema };
|
|
86
88
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -10,6 +10,19 @@ declare const fieldShema: z.ZodObject<{
|
|
|
10
10
|
}, z.core.$strip>;
|
|
11
11
|
type TField = z.infer<typeof fieldShema>;
|
|
12
12
|
//#endregion
|
|
13
|
+
//#region src/plugins/validation/types/index.d.ts
|
|
14
|
+
interface IUserRule {
|
|
15
|
+
code: string;
|
|
16
|
+
fn: (value: any, data: FormData, args: string[]) => boolean;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/plugins/validation/provider.d.ts
|
|
21
|
+
type TValidator = {
|
|
22
|
+
rules?: IUserRule[];
|
|
23
|
+
onSubmit?: boolean;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
13
26
|
//#region src/shared/types/common.d.ts
|
|
14
27
|
type ConfigFunctionComponent<P> = FC<P> & {
|
|
15
28
|
fieldProps?: string[];
|
|
@@ -19,21 +32,14 @@ type RC<T = {}> = ConfigFunctionComponent<T & {
|
|
|
19
32
|
children?: ReactNode;
|
|
20
33
|
}>;
|
|
21
34
|
type FormData = Record<string, any>;
|
|
22
|
-
interface IRule {
|
|
23
|
-
getName: () => string;
|
|
24
|
-
validate: (value: string, formData: FormData) => boolean;
|
|
25
|
-
message: () => string;
|
|
26
|
-
}
|
|
27
35
|
type FormFieldBase = {
|
|
28
|
-
validation?:
|
|
36
|
+
validation?: string[];
|
|
29
37
|
viewConfig?: TGroupRules;
|
|
30
38
|
} & TField;
|
|
31
39
|
type FormFieldConfig = FormFieldBase & Record<string, any>;
|
|
32
40
|
type FormElementProps<F extends FormFieldConfig = FormFieldConfig> = {
|
|
33
41
|
field: F;
|
|
34
|
-
|
|
35
|
-
plugins: FormElementRegistry;
|
|
36
|
-
path: string[];
|
|
42
|
+
path: string;
|
|
37
43
|
value: any;
|
|
38
44
|
errors?: Record<string, string>;
|
|
39
45
|
onChange: (value: any) => void;
|
|
@@ -44,16 +50,12 @@ type TFormBuilder = {
|
|
|
44
50
|
formData?: FormData;
|
|
45
51
|
className?: string;
|
|
46
52
|
layout: FormFieldConfig[];
|
|
47
|
-
|
|
53
|
+
fields: FormElementRegistry;
|
|
54
|
+
validator?: TValidator;
|
|
48
55
|
onChange?: (formData: any) => void;
|
|
49
56
|
onSubmit?: (formData: any) => void;
|
|
50
57
|
children?: ReactNode;
|
|
51
58
|
};
|
|
52
|
-
type TDynamicBuilder = RC<{
|
|
53
|
-
layout: FormFieldConfig[];
|
|
54
|
-
plugins: FormElementRegistry;
|
|
55
|
-
path?: string[];
|
|
56
|
-
}>;
|
|
57
59
|
type TCommonRule = {
|
|
58
60
|
operator: string;
|
|
59
61
|
field: string;
|
|
@@ -82,5 +84,5 @@ type FormGroupConfig = FormFieldBase & {
|
|
|
82
84
|
};
|
|
83
85
|
declare const FormGroup: RC<FormElementProps<FormGroupConfig>>;
|
|
84
86
|
//#endregion
|
|
85
|
-
export { FormBuilder, type FormBuilderRef, type FormElementProps, type FormElementRegistry, type FormFieldConfig, FormGroup, type
|
|
87
|
+
export { FormBuilder, type FormBuilderRef, type FormElementProps, type FormElementRegistry, type FormFieldConfig, FormGroup, type TGroupRules, TextField, fieldShema };
|
|
86
88
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, forwardRef, useContext, useId, useImperativeHandle, useRef, useSyncExternalStore } from "react";
|
|
1
|
+
import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useMemo, useRef, useSyncExternalStore } from "react";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -17,18 +17,29 @@ function createStore(reducer, initialState) {
|
|
|
17
17
|
},
|
|
18
18
|
subscribe(listener) {
|
|
19
19
|
listeners.add(listener);
|
|
20
|
-
return () =>
|
|
20
|
+
return () => {
|
|
21
|
+
listeners.delete(listener);
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
subscribeSelector(selector, listener) {
|
|
25
|
+
let prev = selector(state);
|
|
26
|
+
return this.subscribe(() => {
|
|
27
|
+
const next = selector(state);
|
|
28
|
+
if (next !== prev) {
|
|
29
|
+
prev = next;
|
|
30
|
+
listener(next);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
21
33
|
}
|
|
22
34
|
};
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
//#endregion
|
|
26
38
|
//#region src/shared/model/store/createStoreContext.tsx
|
|
27
|
-
function createStoreContext
|
|
39
|
+
function createStoreContext(reducer, defaultState) {
|
|
28
40
|
const StoreContext = createContext(null);
|
|
29
|
-
const Provider = ({ children }) => {
|
|
30
|
-
const storeRef = useRef(
|
|
31
|
-
if (!storeRef.current) storeRef.current = createStore(reducer, initialState);
|
|
41
|
+
const Provider = ({ children, initialState }) => {
|
|
42
|
+
const storeRef = useRef(createStore(reducer, initialState ?? defaultState));
|
|
32
43
|
return /* @__PURE__ */ jsx(StoreContext.Provider, {
|
|
33
44
|
value: storeRef.current,
|
|
34
45
|
children
|
|
@@ -44,51 +55,56 @@ function createStoreContext$1(reducer, initialState) {
|
|
|
44
55
|
if (!store) throw new Error("StoreProvider missing");
|
|
45
56
|
return store.dispatch;
|
|
46
57
|
}
|
|
47
|
-
function
|
|
58
|
+
function useStoreInstance() {
|
|
48
59
|
const store = useContext(StoreContext);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
60
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
61
|
+
return store;
|
|
52
62
|
}
|
|
53
63
|
return {
|
|
54
64
|
Provider,
|
|
55
65
|
useStore,
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
useDispatch,
|
|
67
|
+
useStoreInstance
|
|
58
68
|
};
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
//#endregion
|
|
62
72
|
//#region src/shared/utils.ts
|
|
63
73
|
function updateNestedValue(obj, path, value) {
|
|
64
|
-
if (path
|
|
74
|
+
if (!path) return value;
|
|
75
|
+
return _updateByKeys(obj, path.split("."), value);
|
|
76
|
+
}
|
|
77
|
+
function _updateByKeys(obj, keys, value) {
|
|
78
|
+
if (keys.length === 0) return value;
|
|
65
79
|
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
66
|
-
const currentKey =
|
|
67
|
-
if (
|
|
80
|
+
const currentKey = keys[0];
|
|
81
|
+
if (currentKey === "__proto__" || currentKey === "constructor" || currentKey === "prototype") throw new Error(`Forbidden path key: ${currentKey}`);
|
|
82
|
+
if (keys.length === 1) newObj[currentKey] = value;
|
|
68
83
|
else {
|
|
69
|
-
const
|
|
84
|
+
const remainingKeys = keys.slice(1);
|
|
70
85
|
const nestedObj = newObj[currentKey];
|
|
71
|
-
if (typeof nestedObj === "undefined" || nestedObj === null) newObj[currentKey] = typeof
|
|
72
|
-
newObj[currentKey] =
|
|
86
|
+
if (typeof nestedObj === "undefined" || nestedObj === null) newObj[currentKey] = typeof remainingKeys[0] === "number" ? [] : {};
|
|
87
|
+
newObj[currentKey] = _updateByKeys(newObj[currentKey], remainingKeys, value);
|
|
73
88
|
}
|
|
74
89
|
return newObj;
|
|
75
90
|
}
|
|
76
91
|
function getNestedValue(obj, path) {
|
|
77
|
-
|
|
92
|
+
if (!path) return obj;
|
|
93
|
+
return path.split(".").reduce((acc, key) => acc && acc[key] !== void 0 ? acc[key] : void 0, obj);
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
//#endregion
|
|
81
|
-
//#region src/shared/model/store/index.
|
|
82
|
-
const initialState = {
|
|
83
|
-
formData: {},
|
|
84
|
-
errors: {}
|
|
85
|
-
};
|
|
97
|
+
//#region src/shared/model/store/index.tsx
|
|
86
98
|
function reducer(state, action) {
|
|
87
99
|
const newData = { ...state };
|
|
88
100
|
switch (action.type) {
|
|
89
101
|
case "setValue":
|
|
90
102
|
newData.formData = updateNestedValue(newData.formData, action.path, action.value);
|
|
91
103
|
break;
|
|
104
|
+
case "setFieldValue":
|
|
105
|
+
newData.formData = updateNestedValue(newData.formData, action.path, action.value);
|
|
106
|
+
newData.errors = updateNestedValue(newData.errors, action.path, action?.errors?.length ? action.errors : null);
|
|
107
|
+
break;
|
|
92
108
|
case "setError":
|
|
93
109
|
newData.errors = updateNestedValue(newData.errors, action.path, action.value);
|
|
94
110
|
break;
|
|
@@ -102,106 +118,262 @@ function reducer(state, action) {
|
|
|
102
118
|
}
|
|
103
119
|
return newData;
|
|
104
120
|
}
|
|
105
|
-
const { Provider
|
|
121
|
+
const { Provider, useStore: useFormStore, useDispatch: useFormDispatch, useStoreInstance: useFormStoreInstance } = createStoreContext(reducer, {
|
|
122
|
+
formData: {},
|
|
123
|
+
errors: {}
|
|
124
|
+
});
|
|
125
|
+
const FormStoreProvider = ({ children, formData }) => /* @__PURE__ */ jsx(Provider, {
|
|
126
|
+
initialState: {
|
|
127
|
+
formData: formData ?? {},
|
|
128
|
+
errors: {}
|
|
129
|
+
},
|
|
130
|
+
children
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/plugins/validation/rules/confirm.ts
|
|
135
|
+
const confirm = {
|
|
136
|
+
code: "confirm",
|
|
137
|
+
fn: (value, data, args) => {
|
|
138
|
+
const attr = args[0] ?? false;
|
|
139
|
+
return attr ? value == getNestedValue(data, attr) : false;
|
|
140
|
+
},
|
|
141
|
+
message: "Поле не совпадает с ::attr(1)"
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/plugins/validation/rules/required.ts
|
|
146
|
+
const required = {
|
|
147
|
+
code: "required",
|
|
148
|
+
fn: (value) => {
|
|
149
|
+
return value !== void 0 && value !== null && value.length > 0;
|
|
150
|
+
},
|
|
151
|
+
message: "Поле обязательно для заполнения"
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/plugins/validation/rules/email.ts
|
|
156
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
157
|
+
const email = {
|
|
158
|
+
code: "email",
|
|
159
|
+
fn: (value) => {
|
|
160
|
+
return emailRegex.test(value);
|
|
161
|
+
},
|
|
162
|
+
message: "Поле не является email"
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/plugins/validation/rules/index.ts
|
|
167
|
+
var rules_default = [
|
|
168
|
+
confirm,
|
|
169
|
+
required,
|
|
170
|
+
email
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/plugins/validation/validation.ts
|
|
175
|
+
var Validation = class {
|
|
176
|
+
registry = [];
|
|
177
|
+
registerFields = {};
|
|
178
|
+
constructor(onSubmit, rules = []) {
|
|
179
|
+
this.onSubmit = onSubmit;
|
|
180
|
+
this.registry = [...rules, ...rules_default];
|
|
181
|
+
}
|
|
182
|
+
validateRule(rule, data, formData) {
|
|
183
|
+
const [code, rawArgs] = rule.split(":");
|
|
184
|
+
const args = rawArgs ? rawArgs.split(",") : [];
|
|
185
|
+
const userRule = this.registry.find((i) => i.code == code);
|
|
186
|
+
if (!userRule) return null;
|
|
187
|
+
return !userRule.fn(data, formData, args) ? this.replaceMessageArgs(userRule.message, args) : null;
|
|
188
|
+
}
|
|
189
|
+
replaceMessageArgs(message, args = []) {
|
|
190
|
+
const replaceArgs = args.reduce((acc, arg, index) => {
|
|
191
|
+
acc[`::attr(${index})`] = arg;
|
|
192
|
+
return acc;
|
|
193
|
+
}, {});
|
|
194
|
+
return message.replace(/::attr\(\d\)/g, (i) => replaceArgs[i]);
|
|
195
|
+
}
|
|
196
|
+
registerField(path, validators) {
|
|
197
|
+
this.registerFields[path] = validators;
|
|
198
|
+
}
|
|
199
|
+
_validate(rules, data, formData) {
|
|
200
|
+
return rules.map((rule) => {
|
|
201
|
+
return this.validateRule(rule, data, formData);
|
|
202
|
+
}).filter((i) => i !== null);
|
|
203
|
+
}
|
|
204
|
+
validate(rules, data, formData) {
|
|
205
|
+
return !this.onSubmit ? this._validate(rules, data, formData) : [];
|
|
206
|
+
}
|
|
207
|
+
validateAll(formData) {
|
|
208
|
+
return Object.entries(this.registerFields).reduce((acc, [path, rules]) => {
|
|
209
|
+
const value = getNestedValue(formData, path);
|
|
210
|
+
const validationMessage = this._validate(rules, value, formData);
|
|
211
|
+
if (validationMessage.length) acc[path] = validationMessage;
|
|
212
|
+
return acc;
|
|
213
|
+
}, {});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/plugins/validation/provider.tsx
|
|
219
|
+
function createValidationProvider() {
|
|
220
|
+
const Context = createContext(null);
|
|
221
|
+
const Provider = ({ validator = {
|
|
222
|
+
onSubmit: false,
|
|
223
|
+
rules: []
|
|
224
|
+
}, children }) => {
|
|
225
|
+
const core = useMemo(() => new Validation(validator.onSubmit ?? false, validator.rules ?? []), [validator]);
|
|
226
|
+
return /* @__PURE__ */ jsx(Context.Provider, {
|
|
227
|
+
value: core,
|
|
228
|
+
children
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
const useValidate = (rules) => {
|
|
232
|
+
const core = useContext(Context);
|
|
233
|
+
if (!core) throw new Error("ValidationProvider missing");
|
|
234
|
+
return (data, formData) => {
|
|
235
|
+
return core.validate(rules ?? [], data, formData);
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
const useRegister = (currentFieldPath, field) => {
|
|
239
|
+
const core = useContext(Context);
|
|
240
|
+
if (!core) throw new Error("ValidationProvider missing");
|
|
241
|
+
core.registerField(currentFieldPath, field.validation ?? []);
|
|
242
|
+
return useValidate(field.validation);
|
|
243
|
+
};
|
|
244
|
+
const useSubmitValidation = () => {
|
|
245
|
+
const core = useContext(Context);
|
|
246
|
+
if (!core) throw new Error("ValidationProvider missing");
|
|
247
|
+
return (formData) => core.validateAll(formData);
|
|
248
|
+
};
|
|
249
|
+
return {
|
|
250
|
+
Provider,
|
|
251
|
+
useRegister,
|
|
252
|
+
useSubmitValidation
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/plugins/validation/index.ts
|
|
258
|
+
const { Provider: ValidationProvider, useRegister, useSubmitValidation } = createValidationProvider();
|
|
106
259
|
|
|
107
260
|
//#endregion
|
|
108
|
-
//#region src/
|
|
109
|
-
const
|
|
261
|
+
//#region src/entity/dynamicBuilder/element.tsx
|
|
262
|
+
const DynamicBuilderElement = (props) => {
|
|
110
263
|
const Element = props.element;
|
|
111
264
|
const field = props.field;
|
|
112
|
-
const currentFieldPath =
|
|
265
|
+
const currentFieldPath = props.path ? `${props.path}.${field.name}` : field.name;
|
|
113
266
|
const value = useFormStore(((s) => getNestedValue(s.formData, currentFieldPath)));
|
|
114
|
-
const errors = useFormStore(((s) =>
|
|
267
|
+
const errors = useFormStore(((s) => s.errors[currentFieldPath]));
|
|
115
268
|
const dispatch = useFormDispatch();
|
|
269
|
+
const store = useFormStoreInstance();
|
|
270
|
+
const validator = useRegister(currentFieldPath, field);
|
|
116
271
|
return /* @__PURE__ */ jsx(Element, {
|
|
117
272
|
field: { ...field },
|
|
118
|
-
builder: props.DynamicBuilder,
|
|
119
273
|
path: currentFieldPath,
|
|
120
|
-
plugins: props.plugins,
|
|
121
274
|
value,
|
|
122
275
|
errors,
|
|
123
276
|
onChange: (value) => {
|
|
277
|
+
const data = store.getState().formData;
|
|
124
278
|
dispatch({
|
|
125
|
-
type: "
|
|
279
|
+
type: "setFieldValue",
|
|
126
280
|
path: currentFieldPath,
|
|
127
|
-
value
|
|
128
|
-
|
|
129
|
-
dispatch({
|
|
130
|
-
type: "setError",
|
|
131
|
-
path: currentFieldPath,
|
|
132
|
-
value: ""
|
|
281
|
+
value,
|
|
282
|
+
errors: validator(value, data)
|
|
133
283
|
});
|
|
134
284
|
}
|
|
135
285
|
});
|
|
136
286
|
};
|
|
137
287
|
|
|
138
288
|
//#endregion
|
|
139
|
-
//#region src/
|
|
140
|
-
|
|
141
|
-
const path = props.path ?? [];
|
|
142
|
-
return props.layout.map((field, index) => {
|
|
143
|
-
const FormElement = props.plugins[field.type];
|
|
144
|
-
if (!FormElement) {
|
|
145
|
-
console.warn(`Неизвестный тип поля: ${field.type}. Проверьте formRegistry.`);
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
return /* @__PURE__ */ jsx(BuilderElement, {
|
|
149
|
-
element: FormElement,
|
|
150
|
-
path,
|
|
151
|
-
field
|
|
152
|
-
}, `${field.name}${index}`);
|
|
153
|
-
});
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/shared/model/builder/createContext.tsx
|
|
158
|
-
function createStoreContext() {
|
|
289
|
+
//#region src/entity/dynamicBuilder/model/createBuilderContext.tsx
|
|
290
|
+
function createBuilderContext() {
|
|
159
291
|
const BuilderContext = createContext(null);
|
|
160
|
-
const Provider = ({
|
|
161
|
-
const
|
|
162
|
-
if (!storeRef.current) storeRef.current = (layout, path, children) => {
|
|
292
|
+
const Provider = ({ fields, children }) => {
|
|
293
|
+
const builder = useCallback((layout, path, children) => {
|
|
163
294
|
return /* @__PURE__ */ jsx(DynamicBuilder, {
|
|
164
295
|
layout,
|
|
165
296
|
path,
|
|
166
|
-
plugins,
|
|
167
297
|
children
|
|
168
298
|
});
|
|
169
|
-
};
|
|
299
|
+
}, [fields]);
|
|
170
300
|
return /* @__PURE__ */ jsx(BuilderContext.Provider, {
|
|
171
|
-
value:
|
|
301
|
+
value: {
|
|
302
|
+
builder,
|
|
303
|
+
fields
|
|
304
|
+
},
|
|
172
305
|
children
|
|
173
306
|
});
|
|
174
307
|
};
|
|
175
308
|
function useBuilder() {
|
|
176
309
|
const store = useContext(BuilderContext);
|
|
177
310
|
if (!store) throw new Error("StoreProvider missing");
|
|
178
|
-
return store;
|
|
311
|
+
return store.builder;
|
|
312
|
+
}
|
|
313
|
+
function useFields() {
|
|
314
|
+
const store = useContext(BuilderContext);
|
|
315
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
316
|
+
return store.fields;
|
|
179
317
|
}
|
|
180
318
|
return {
|
|
181
319
|
Provider,
|
|
182
|
-
useBuilder
|
|
320
|
+
useBuilder,
|
|
321
|
+
useFields
|
|
183
322
|
};
|
|
184
323
|
}
|
|
185
324
|
|
|
186
325
|
//#endregion
|
|
187
|
-
//#region src/
|
|
188
|
-
const { Provider: BuilderProvider, useBuilder } =
|
|
326
|
+
//#region src/entity/dynamicBuilder/model/index.ts
|
|
327
|
+
const { Provider: BuilderProvider, useBuilder, useFields } = createBuilderContext();
|
|
189
328
|
|
|
190
329
|
//#endregion
|
|
191
|
-
//#region src/
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
330
|
+
//#region src/entity/dynamicBuilder/index.tsx
|
|
331
|
+
const DynamicBuilder = (props) => {
|
|
332
|
+
const path = props.path ?? "";
|
|
333
|
+
const fields = useFields();
|
|
334
|
+
return props.layout.map((field, index) => {
|
|
335
|
+
const FormElement = fields[field.type];
|
|
336
|
+
if (!FormElement) {
|
|
337
|
+
console.warn(`Неизвестный тип поля: ${field.type}. Проверьте formRegistry.`);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return /* @__PURE__ */ jsx(DynamicBuilderElement, {
|
|
341
|
+
element: FormElement,
|
|
342
|
+
path,
|
|
343
|
+
field
|
|
344
|
+
}, `${field.name}-${index}`);
|
|
195
345
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/widgets/form/form.tsx
|
|
350
|
+
const Form = forwardRef((props, ref) => {
|
|
351
|
+
const dispatch = useFormDispatch();
|
|
352
|
+
const store = useFormStoreInstance();
|
|
353
|
+
const changeRef = useRef(props.onChange);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
return store.subscribeSelector((s) => s.formData, (formData) => changeRef.current?.(formData));
|
|
356
|
+
}, [store]);
|
|
357
|
+
const validation = useSubmitValidation();
|
|
358
|
+
const submitHdl = useCallback(() => {
|
|
359
|
+
if (props.onSubmit) {
|
|
360
|
+
const errors = validation(store.getState().formData);
|
|
361
|
+
if (Object.keys(errors).length == 0) props.onSubmit(store.getState().formData);
|
|
362
|
+
else dispatch({
|
|
363
|
+
type: "setErrors",
|
|
364
|
+
errors
|
|
365
|
+
});
|
|
203
366
|
}
|
|
204
|
-
}
|
|
367
|
+
}, [props.onSubmit, validation]);
|
|
368
|
+
useImperativeHandle(ref, () => ({
|
|
369
|
+
reset: () => dispatch({ type: "reset" }),
|
|
370
|
+
submit: () => submitHdl(),
|
|
371
|
+
errors: () => store.getState().errors
|
|
372
|
+
}), [
|
|
373
|
+
submitHdl,
|
|
374
|
+
dispatch,
|
|
375
|
+
store
|
|
376
|
+
]);
|
|
205
377
|
return /* @__PURE__ */ jsxs("form", {
|
|
206
378
|
onSubmit: (e) => {
|
|
207
379
|
e.preventDefault();
|
|
@@ -209,13 +381,10 @@ const FormBuilder = forwardRef((props, ref) => {
|
|
|
209
381
|
},
|
|
210
382
|
className: props?.className,
|
|
211
383
|
children: [
|
|
212
|
-
/* @__PURE__ */ jsx(
|
|
213
|
-
|
|
214
|
-
children: /* @__PURE__ */ jsx(DynamicBuilder, {
|
|
215
|
-
|
|
216
|
-
plugins: props.plugins
|
|
217
|
-
})
|
|
218
|
-
}) }),
|
|
384
|
+
/* @__PURE__ */ jsx(BuilderProvider, {
|
|
385
|
+
fields: props.fields,
|
|
386
|
+
children: /* @__PURE__ */ jsx(DynamicBuilder, { layout: props.layout })
|
|
387
|
+
}),
|
|
219
388
|
/* @__PURE__ */ jsx("input", {
|
|
220
389
|
type: "submit",
|
|
221
390
|
style: { display: "none" }
|
|
@@ -225,6 +394,22 @@ const FormBuilder = forwardRef((props, ref) => {
|
|
|
225
394
|
});
|
|
226
395
|
});
|
|
227
396
|
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/app/index.tsx
|
|
399
|
+
const FormBuilder = forwardRef((props, ref) => {
|
|
400
|
+
const { formData, ...innerProps } = props;
|
|
401
|
+
return /* @__PURE__ */ jsx(FormStoreProvider, {
|
|
402
|
+
formData,
|
|
403
|
+
children: /* @__PURE__ */ jsx(ValidationProvider, {
|
|
404
|
+
validator: props.validator,
|
|
405
|
+
children: /* @__PURE__ */ jsx(Form, {
|
|
406
|
+
...innerProps,
|
|
407
|
+
ref
|
|
408
|
+
})
|
|
409
|
+
})
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
228
413
|
//#endregion
|
|
229
414
|
//#region src/entity/inputs/ui/input/index.tsx
|
|
230
415
|
function isTextFieldConfig(field) {
|
|
@@ -266,7 +451,7 @@ const TextField = ({ field, value, errors, onChange }) => {
|
|
|
266
451
|
//#endregion
|
|
267
452
|
//#region src/entity/inputs/ui/group/index.tsx
|
|
268
453
|
function isGroupConfig(field) {
|
|
269
|
-
return field.fields;
|
|
454
|
+
return Array.isArray(field.fields);
|
|
270
455
|
}
|
|
271
456
|
const FormGroup = ({ field, path }) => {
|
|
272
457
|
if (!isGroupConfig(field)) return null;
|
|
@@ -277,7 +462,6 @@ const FormGroup = ({ field, path }) => {
|
|
|
277
462
|
children: Builder
|
|
278
463
|
})] });
|
|
279
464
|
};
|
|
280
|
-
FormGroup.fieldProps = ["fields"];
|
|
281
465
|
|
|
282
466
|
//#endregion
|
|
283
467
|
//#region src/shared/model/index.ts
|