@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.
Files changed (44) hide show
  1. package/README.md +922 -12
  2. package/dist/index.cjs +272 -88
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +18 -16
  5. package/dist/index.d.mts +18 -16
  6. package/dist/index.mjs +273 -89
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +5 -2
  9. package/src/index.ts +19 -5
  10. package/CHANGELOG.md +0 -8
  11. package/eslint.config.js +0 -23
  12. package/public/index.html +0 -13
  13. package/public/main.tsx +0 -90
  14. package/src/app/debug.tsx +0 -0
  15. package/src/app/index.tsx +0 -80
  16. package/src/app/test.css +0 -1
  17. package/src/entity/inputs/index.ts +0 -2
  18. package/src/entity/inputs/ui/group/index.tsx +0 -28
  19. package/src/entity/inputs/ui/input/index.tsx +0 -31
  20. package/src/shared/hook/useUpdateEffect.tsx +0 -23
  21. package/src/shared/lib/VisibleCore.spec.ts +0 -103
  22. package/src/shared/lib/VisibleCore.ts +0 -43
  23. package/src/shared/lib/validation/core.spec.ts +0 -103
  24. package/src/shared/lib/validation/core.ts +0 -79
  25. package/src/shared/lib/validation/rules/base.ts +0 -10
  26. package/src/shared/lib/validation/rules/confirm.spec.ts +0 -17
  27. package/src/shared/lib/validation/rules/confirm.ts +0 -32
  28. package/src/shared/lib/validation/rules/email.spec.ts +0 -12
  29. package/src/shared/lib/validation/rules/email.ts +0 -13
  30. package/src/shared/lib/validation/rules/require.spec.ts +0 -13
  31. package/src/shared/lib/validation/rules/require.ts +0 -12
  32. package/src/shared/model/builder/createContext.tsx +0 -40
  33. package/src/shared/model/builder/index.ts +0 -6
  34. package/src/shared/model/index.ts +0 -12
  35. package/src/shared/model/store/createStoreContext.tsx +0 -74
  36. package/src/shared/model/store/index.ts +0 -46
  37. package/src/shared/model/store/store.ts +0 -27
  38. package/src/shared/types/common.ts +0 -79
  39. package/src/shared/utils.ts +0 -25
  40. package/src/widgets/dynamicBuilder/element.tsx +0 -31
  41. package/src/widgets/dynamicBuilder/index.tsx +0 -33
  42. package/tsconfig.json +0 -24
  43. package/tsdown.config.ts +0 -10
  44. 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?: (string | IRule)[];
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
- builder: TDynamicBuilder;
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
- plugins: FormElementRegistry;
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 IRule, type TGroupRules, TextField, fieldShema };
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?: (string | IRule)[];
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
- builder: TDynamicBuilder;
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
- plugins: FormElementRegistry;
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 IRule, type TGroupRules, TextField, fieldShema };
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 () => listeners.delete(listener);
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$1(reducer, initialState) {
39
+ function createStoreContext(reducer, defaultState) {
28
40
  const StoreContext = createContext(null);
29
- const Provider = ({ children }) => {
30
- const storeRef = useRef(null);
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 useSubmit(onSubmit) {
58
+ function useStoreInstance() {
48
59
  const store = useContext(StoreContext);
49
- return () => {
50
- if (store) onSubmit(store.getState());
51
- };
60
+ if (!store) throw new Error("StoreProvider missing");
61
+ return store;
52
62
  }
53
63
  return {
54
64
  Provider,
55
65
  useStore,
56
- useSubmit,
57
- useDispatch
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.length === 0) return value;
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 = path[0];
67
- if (path.length === 1) newObj[currentKey] = value;
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 remainingPath = path.slice(1);
84
+ const remainingKeys = keys.slice(1);
70
85
  const nestedObj = newObj[currentKey];
71
- if (typeof nestedObj === "undefined" || nestedObj === null) newObj[currentKey] = typeof remainingPath[0] === "number" ? [] : {};
72
- newObj[currentKey] = updateNestedValue(newObj[currentKey], remainingPath, value);
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
- return path.reduce((acc, key) => acc && acc[key] !== void 0 ? acc[key] : void 0, obj);
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.ts
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: FormStoreProvider, useStore: useFormStore, useDispatch: useFormDispatch, useSubmit } = createStoreContext$1(reducer, initialState);
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/widgets/dynamicBuilder/element.tsx
109
- const BuilderElement = (props) => {
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 = [...props.path, field.name];
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) => getNestedValue(s.errors, currentFieldPath)));
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: "setValue",
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/widgets/dynamicBuilder/index.tsx
140
- const DynamicBuilder = (props) => {
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 = ({ plugins, children }) => {
161
- const storeRef = useRef(null);
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: storeRef.current,
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/shared/model/builder/index.ts
188
- const { Provider: BuilderProvider, useBuilder } = createStoreContext();
326
+ //#region src/entity/dynamicBuilder/model/index.ts
327
+ const { Provider: BuilderProvider, useBuilder, useFields } = createBuilderContext();
189
328
 
190
329
  //#endregion
191
- //#region src/app/index.tsx
192
- const FormBuilder = forwardRef((props, ref) => {
193
- const submitHdl = useSubmit((data) => {
194
- if (props.onSubmit) props.onSubmit(data);
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
- useImperativeHandle(ref, () => ({
197
- reset: () => {},
198
- submit: () => {
199
- submitHdl();
200
- },
201
- errors: () => {
202
- return {};
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
- }), [props.onSubmit]);
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(FormStoreProvider, { children: /* @__PURE__ */ jsx(BuilderProvider, {
213
- plugins: props.plugins,
214
- children: /* @__PURE__ */ jsx(DynamicBuilder, {
215
- layout: props.layout,
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