@idem.agency/form-builder 0.0.9 → 0.0.11
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 +1 -25
- package/dist/index.cjs +325 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +86 -0
- package/dist/index.d.mts +86 -0
- package/dist/index.mjs +292 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +12 -4
- package/{index.html → public/index.html} +1 -2
- package/public/main.tsx +90 -0
- package/src/app/debug.tsx +0 -89
- package/src/app/index.tsx +51 -40
- package/src/{widgets/form → entity/inputs}/ui/group/index.tsx +4 -3
- package/src/{widgets/form → entity/inputs}/ui/input/index.tsx +5 -3
- package/src/index.ts +2 -2
- package/src/shared/lib/validation/core.ts +3 -3
- package/src/shared/lib/validation/rules/confirm.ts +2 -2
- package/src/shared/lib/validation/rules/email.ts +1 -1
- package/src/shared/lib/validation/rules/require.ts +1 -1
- package/src/shared/model/builder/createContext.tsx +40 -0
- package/src/shared/model/builder/index.ts +6 -0
- package/src/shared/model/index.ts +0 -1
- package/src/shared/model/store/createStoreContext.tsx +74 -0
- package/src/shared/model/store/index.ts +46 -0
- package/src/shared/model/store/store.ts +27 -0
- package/src/shared/types/common.ts +1 -1
- package/src/widgets/dynamicBuilder/element.tsx +31 -0
- package/src/widgets/dynamicBuilder/index.tsx +28 -58
- package/tsconfig.json +22 -5
- package/tsdown.config.ts +10 -0
- package/vite.config.ts +7 -16
- package/src/main.tsx +0 -9
- package/src/shared/model/store.tsx +0 -52
- package/tsconfig.app.json +0 -26
- package/tsconfig.node.json +0 -24
- package/vite-env.d.ts +0 -1
- /package/src/{widgets/form → entity/inputs}/index.ts +0 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { createContext, forwardRef, useContext, useId, useImperativeHandle, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/shared/model/store/store.ts
|
|
7
|
+
function createStore(reducer, initialState) {
|
|
8
|
+
let state = initialState;
|
|
9
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
10
|
+
return {
|
|
11
|
+
getState() {
|
|
12
|
+
return state;
|
|
13
|
+
},
|
|
14
|
+
dispatch(action) {
|
|
15
|
+
state = reducer(state, action);
|
|
16
|
+
listeners.forEach((l) => l());
|
|
17
|
+
},
|
|
18
|
+
subscribe(listener) {
|
|
19
|
+
listeners.add(listener);
|
|
20
|
+
return () => listeners.delete(listener);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/shared/model/store/createStoreContext.tsx
|
|
27
|
+
function createStoreContext$1(reducer, initialState) {
|
|
28
|
+
const StoreContext = createContext(null);
|
|
29
|
+
const Provider = ({ children }) => {
|
|
30
|
+
const storeRef = useRef(null);
|
|
31
|
+
if (!storeRef.current) storeRef.current = createStore(reducer, initialState);
|
|
32
|
+
return /* @__PURE__ */ jsx(StoreContext.Provider, {
|
|
33
|
+
value: storeRef.current,
|
|
34
|
+
children
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
function useStore(selector) {
|
|
38
|
+
const store = useContext(StoreContext);
|
|
39
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
40
|
+
return useSyncExternalStore(store.subscribe, () => selector(store.getState()), () => selector(store.getState()));
|
|
41
|
+
}
|
|
42
|
+
function useDispatch() {
|
|
43
|
+
const store = useContext(StoreContext);
|
|
44
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
45
|
+
return store.dispatch;
|
|
46
|
+
}
|
|
47
|
+
function useSubmit(onSubmit) {
|
|
48
|
+
const store = useContext(StoreContext);
|
|
49
|
+
return () => {
|
|
50
|
+
if (store) onSubmit(store.getState());
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
Provider,
|
|
55
|
+
useStore,
|
|
56
|
+
useSubmit,
|
|
57
|
+
useDispatch
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/shared/utils.ts
|
|
63
|
+
function updateNestedValue(obj, path, value) {
|
|
64
|
+
if (path.length === 0) return value;
|
|
65
|
+
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
66
|
+
const currentKey = path[0];
|
|
67
|
+
if (path.length === 1) newObj[currentKey] = value;
|
|
68
|
+
else {
|
|
69
|
+
const remainingPath = path.slice(1);
|
|
70
|
+
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);
|
|
73
|
+
}
|
|
74
|
+
return newObj;
|
|
75
|
+
}
|
|
76
|
+
function getNestedValue(obj, path) {
|
|
77
|
+
return path.reduce((acc, key) => acc && acc[key] !== void 0 ? acc[key] : void 0, obj);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/shared/model/store/index.ts
|
|
82
|
+
const initialState = {
|
|
83
|
+
formData: {},
|
|
84
|
+
errors: {}
|
|
85
|
+
};
|
|
86
|
+
function reducer(state, action) {
|
|
87
|
+
const newData = { ...state };
|
|
88
|
+
switch (action.type) {
|
|
89
|
+
case "setValue":
|
|
90
|
+
newData.formData = updateNestedValue(newData.formData, action.path, action.value);
|
|
91
|
+
break;
|
|
92
|
+
case "setError":
|
|
93
|
+
newData.errors = updateNestedValue(newData.errors, action.path, action.value);
|
|
94
|
+
break;
|
|
95
|
+
case "reset":
|
|
96
|
+
newData.formData = {};
|
|
97
|
+
newData.errors = {};
|
|
98
|
+
break;
|
|
99
|
+
case "setErrors":
|
|
100
|
+
newData.errors = { ...action.errors };
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
return newData;
|
|
104
|
+
}
|
|
105
|
+
const { Provider: FormStoreProvider, useStore: useFormStore, useDispatch: useFormDispatch, useSubmit } = createStoreContext$1(reducer, initialState);
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/widgets/dynamicBuilder/element.tsx
|
|
109
|
+
const BuilderElement = (props) => {
|
|
110
|
+
const Element = props.element;
|
|
111
|
+
const field = props.field;
|
|
112
|
+
const currentFieldPath = [...props.path, field.name];
|
|
113
|
+
const value = useFormStore(((s) => getNestedValue(s.formData, currentFieldPath)));
|
|
114
|
+
const errors = useFormStore(((s) => getNestedValue(s.errors, currentFieldPath)));
|
|
115
|
+
const dispatch = useFormDispatch();
|
|
116
|
+
return /* @__PURE__ */ jsx(Element, {
|
|
117
|
+
field: { ...field },
|
|
118
|
+
builder: props.DynamicBuilder,
|
|
119
|
+
path: currentFieldPath,
|
|
120
|
+
plugins: props.plugins,
|
|
121
|
+
value,
|
|
122
|
+
errors,
|
|
123
|
+
onChange: (value) => {
|
|
124
|
+
dispatch({
|
|
125
|
+
type: "setValue",
|
|
126
|
+
path: currentFieldPath,
|
|
127
|
+
value
|
|
128
|
+
});
|
|
129
|
+
dispatch({
|
|
130
|
+
type: "setError",
|
|
131
|
+
path: currentFieldPath,
|
|
132
|
+
value: ""
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
//#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() {
|
|
159
|
+
const BuilderContext = createContext(null);
|
|
160
|
+
const Provider = ({ plugins, children }) => {
|
|
161
|
+
const storeRef = useRef(null);
|
|
162
|
+
if (!storeRef.current) storeRef.current = (layout, path, children) => {
|
|
163
|
+
return /* @__PURE__ */ jsx(DynamicBuilder, {
|
|
164
|
+
layout,
|
|
165
|
+
path,
|
|
166
|
+
plugins,
|
|
167
|
+
children
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
return /* @__PURE__ */ jsx(BuilderContext.Provider, {
|
|
171
|
+
value: storeRef.current,
|
|
172
|
+
children
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
function useBuilder() {
|
|
176
|
+
const store = useContext(BuilderContext);
|
|
177
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
178
|
+
return store;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
Provider,
|
|
182
|
+
useBuilder
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/shared/model/builder/index.ts
|
|
188
|
+
const { Provider: BuilderProvider, useBuilder } = createStoreContext();
|
|
189
|
+
|
|
190
|
+
//#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);
|
|
195
|
+
});
|
|
196
|
+
useImperativeHandle(ref, () => ({
|
|
197
|
+
reset: () => {},
|
|
198
|
+
submit: () => {
|
|
199
|
+
submitHdl();
|
|
200
|
+
},
|
|
201
|
+
errors: () => {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
}), [props.onSubmit]);
|
|
205
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
206
|
+
onSubmit: (e) => {
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
submitHdl();
|
|
209
|
+
},
|
|
210
|
+
className: props?.className,
|
|
211
|
+
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
|
+
}) }),
|
|
219
|
+
/* @__PURE__ */ jsx("input", {
|
|
220
|
+
type: "submit",
|
|
221
|
+
style: { display: "none" }
|
|
222
|
+
}),
|
|
223
|
+
props.children
|
|
224
|
+
]
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/entity/inputs/ui/input/index.tsx
|
|
230
|
+
function isTextFieldConfig(field) {
|
|
231
|
+
return field.type === "text" || field.type === "email" || field.type === "password";
|
|
232
|
+
}
|
|
233
|
+
const TextField = ({ field, value, errors, onChange }) => {
|
|
234
|
+
if (!isTextFieldConfig(field)) {
|
|
235
|
+
console.warn(`TextField received an invalid field config for type: ${field.type}`);
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const id = useId();
|
|
239
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
240
|
+
style: { marginBottom: "15px" },
|
|
241
|
+
children: [
|
|
242
|
+
/* @__PURE__ */ jsxs("label", {
|
|
243
|
+
htmlFor: id,
|
|
244
|
+
children: [field.label, ":"]
|
|
245
|
+
}),
|
|
246
|
+
/* @__PURE__ */ jsx("input", {
|
|
247
|
+
type: field.type,
|
|
248
|
+
id,
|
|
249
|
+
name: field.name,
|
|
250
|
+
placeholder: field.placeholder,
|
|
251
|
+
value: value || "",
|
|
252
|
+
onChange: (e) => onChange(e.target.value),
|
|
253
|
+
style: { borderColor: errors ? "red" : "#ccc" }
|
|
254
|
+
}),
|
|
255
|
+
errors && /* @__PURE__ */ jsx("p", {
|
|
256
|
+
style: {
|
|
257
|
+
color: "red",
|
|
258
|
+
fontSize: "0.8em"
|
|
259
|
+
},
|
|
260
|
+
children: Object.values(errors).join(", ")
|
|
261
|
+
})
|
|
262
|
+
]
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/entity/inputs/ui/group/index.tsx
|
|
268
|
+
function isGroupConfig(field) {
|
|
269
|
+
return field.fields;
|
|
270
|
+
}
|
|
271
|
+
const FormGroup = ({ field, path }) => {
|
|
272
|
+
if (!isGroupConfig(field)) return null;
|
|
273
|
+
const Builder = useBuilder()(field.fields, path);
|
|
274
|
+
const className = (field.variant ?? "col") == "col" ? "flex-col" : "flex-row";
|
|
275
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: field.label }), /* @__PURE__ */ jsx("div", {
|
|
276
|
+
className: clsx(className, "flex"),
|
|
277
|
+
children: Builder
|
|
278
|
+
})] });
|
|
279
|
+
};
|
|
280
|
+
FormGroup.fieldProps = ["fields"];
|
|
281
|
+
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/shared/model/index.ts
|
|
284
|
+
const fieldShema = z.object({
|
|
285
|
+
name: z.string(),
|
|
286
|
+
label: z.string().optional(),
|
|
287
|
+
type: z.string()
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
export { FormBuilder, FormGroup, TextField, fieldShema };
|
|
292
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["createStoreContext","createStoreContext"],"sources":["../src/shared/model/store/store.ts","../src/shared/model/store/createStoreContext.tsx","../src/shared/utils.ts","../src/shared/model/store/index.ts","../src/widgets/dynamicBuilder/element.tsx","../src/widgets/dynamicBuilder/index.tsx","../src/shared/model/builder/createContext.tsx","../src/shared/model/builder/index.ts","../src/app/index.tsx","../src/entity/inputs/ui/input/index.tsx","../src/entity/inputs/ui/group/index.tsx","../src/shared/model/index.ts"],"sourcesContent":["export type Reducer<S, A> = (state: S, action: A) => S;\n\nexport function createStore<S, A>(\n reducer: Reducer<S, A>,\n initialState: S\n) {\n let state = initialState;\n const listeners = new Set<() => void>();\n \n return {\n getState(): S {\n return state;\n },\n \n dispatch(action: A) {\n state = reducer(state, action);\n listeners.forEach(l => l());\n },\n \n subscribe(listener: () => void) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n }\n };\n}\n\nexport type Store<S, A> = ReturnType<typeof createStore<S, A>>;\n","import {\n createContext,\n useContext,\n useRef,\n useSyncExternalStore\n} from \"react\";\nimport { createStore, type Reducer, type Store } from \"./store\";\n\nexport function createStoreContext<S, A>(\n reducer: Reducer<S, A>,\n initialState: S\n) {\n const StoreContext = createContext<Store<S, A> | null>(null);\n \n const Provider: React.FC<{ children: React.ReactNode }> = ({\n children\n }) => {\n const storeRef = useRef<Store<S, A>>(null);\n \n if (!storeRef.current) {\n storeRef.current = createStore(reducer, initialState);\n }\n\n return (\n <StoreContext.Provider value={storeRef.current}>\n {children}\n </StoreContext.Provider>\n );\n };\n \n function useStore<T>(\n selector: (state: S) => T\n ): T {\n const store = useContext(StoreContext);\n\n if (!store) {\n throw new Error(\"StoreProvider missing\");\n }\n \n return useSyncExternalStore(\n store.subscribe,\n () => selector(store.getState()),\n () => selector(store.getState())\n );\n }\n \n function useDispatch() {\n const store = useContext(StoreContext);\n \n if (!store) {\n throw new Error(\"StoreProvider missing\");\n }\n \n return store.dispatch;\n }\n \n function useSubmit(onSubmit: (state: S) => void) {\n const store = useContext(StoreContext);\n \n return () => {\n if (store) {\n const state = store.getState();\n onSubmit(state);\n }\n };\n }\n \n return {\n Provider,\n useStore,\n useSubmit,\n useDispatch\n };\n}\n","export function updateNestedValue(obj: any, path: string[], value: any): any {\n if (path.length === 0) {\n return value;\n }\n \n const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; // Создаем копию\n const currentKey = path[0];\n \n if (path.length === 1) {\n // Последний элемент пути, просто обновляем значение\n newObj[currentKey] = value;\n } else {\n const remainingPath = path.slice(1);\n const nestedObj = newObj[currentKey];\n \n if (typeof nestedObj === 'undefined' || nestedObj === null) {\n newObj[currentKey] = typeof remainingPath[0] === 'number' ? [] : {};\n }\n newObj[currentKey] = updateNestedValue(newObj[currentKey], remainingPath, value);\n }\n return newObj;\n}\nexport function getNestedValue(obj: any, path: string[]): any {\n return path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj);\n}\n","import { createStoreContext } from \"./createStoreContext\";\nimport type {FormData} from \"@/shared/types/common\";\nimport {updateNestedValue} from \"@/shared/utils\";\n\ntype State = {\n formData: FormData\n errors: Record<string, any>\n};\n\ntype Action =\n | { type: \"setValue\"; path: string[]; value: unknown }\n | { type: \"setError\"; path: string[]; value?: string }\n | { type: \"reset\"; }\n | { type: \"setErrors\"; errors?: object };\n\nconst initialState: State = {\n formData: {},\n errors: {}\n};\n\nfunction reducer(state: State, action: Action): State {\n const newData = {...state};\n switch (action.type) {\n case 'setValue':\n newData.formData = updateNestedValue(newData.formData, action.path, action.value);\n break;\n case 'setError':\n newData.errors = updateNestedValue(newData.errors, action.path, action.value);\n break;\n case 'reset':\n newData.formData = {};\n newData.errors = {};\n break;\n case 'setErrors':\n newData.errors = {...action.errors};\n break;\n }\n return newData;\n}\n\nexport const {\n Provider: FormStoreProvider,\n useStore: useFormStore,\n useDispatch: useFormDispatch,\n useSubmit,\n} = createStoreContext(reducer, initialState);\n","import {useFormStore, useFormDispatch} from \"@/shared/model/store\";\nimport {getNestedValue} from \"@/shared/utils\";\n\nexport const BuilderElement = (props: any) => {\n const Element = props.element;\n const field = props.field;\n const currentFieldPath = [...props.path, field.name];\n const value = useFormStore((s=> getNestedValue(s.formData, currentFieldPath)));\n const errors = useFormStore((s=> getNestedValue(s.errors, currentFieldPath)));\n const dispatch= useFormDispatch();\n return <Element\n field={{...field}}\n builder={props.DynamicBuilder}\n path={currentFieldPath}\n plugins={props.plugins}\n value={value}\n errors={errors}\n onChange={(value: any) => {\n dispatch({\n type: 'setValue',\n path: currentFieldPath,\n value\n });\n dispatch({\n type: 'setError',\n path: currentFieldPath,\n value: ''\n });\n }}\n />;\n}\n","import type {TDynamicBuilder} from \"@/shared/types/common\";\nimport {BuilderElement} from \"@/widgets/dynamicBuilder/element\";\n// import {getNestedValue} from \"@/shared/utils\";\n// import {useCallback} from \"react\";\n// import {VisibleCore} from \"@/shared/lib/VisibleCore\";\n// import {use} from \"@/shared/model/store\";\n\nexport const DynamicBuilder: TDynamicBuilder = (props) => {\n const path = props.path ?? [];\n \n // const isShow = useCallback((field: FormFieldConfig) => {\n // let result = true;\n //\n // if (field.viewConfig) {\n // result = VisibleCore.isVisible(field.viewConfig, state.formData);\n // }\n // return result;\n // }, [state]);\n \n \n return props.layout.map((field, index) => {\n const FormElement = props.plugins[field.type];\n \n if (!FormElement) {\n console.warn(`Неизвестный тип поля: ${field.type}. Проверьте formRegistry.`);\n return null;\n }\n \n // const currentValue = getNestedValue(state.formData, currentFieldPath);\n // const currentErrors = getNestedValue(state.errors, currentFieldPath) as (Record<string, string> | undefined);\n return <BuilderElement key={`${field.name}${index}`} element={FormElement} path={path} field={field}/>\n })\n}\n","import React, {createContext, type ReactNode, useContext, useRef,} from \"react\";\nimport {DynamicBuilder} from \"@/widgets/dynamicBuilder\";\n\nexport function createStoreContext() {\n const BuilderContext = createContext<((layout: any, path: string[]|undefined, children?: ReactNode) => ReactNode) | null>(null);\n \n const Provider: React.FC<{ children: React.ReactNode, plugins: any }> = ({\n plugins,\n children\n }) => {\n const storeRef = useRef<(layout: any, path: string[]|undefined, children?: ReactNode) => ReactNode>(null);\n \n if (!storeRef.current) {\n storeRef.current = (layout: any, path: string[]|undefined, children?: ReactNode) => {\n return <DynamicBuilder layout={layout} path={path} plugins={plugins}>{children}</DynamicBuilder>\n };\n }\n \n return (\n <BuilderContext.Provider value={storeRef.current}>\n {children}\n </BuilderContext.Provider>\n );\n };\n \n function useBuilder(): (layout: any, path: string[]|undefined, children?: ReactNode) => ReactNode {\n const store = useContext(BuilderContext);\n \n if (!store) {\n throw new Error(\"StoreProvider missing\");\n }\n \n return store\n }\n \n return {\n Provider,\n useBuilder\n };\n}\n","import {createStoreContext} from \"@/shared/model/builder/createContext\";\n\nexport const {\n Provider: BuilderProvider,\n useBuilder,\n} = createStoreContext();\n","'use client';\n\nimport type {FormBuilderRef, TFormBuilder} from \"../shared/types/common\";\n// import {StoreContext, storeReducer,} from \"@/shared/model\";\n\nimport {DynamicBuilder} from \"../widgets/dynamicBuilder\";\nimport { forwardRef, useImperativeHandle} from \"react\";\n// import {ValidationCore} from \"@/shared/lib/validation/core\";\n// import { useUpdateEffect } from '@/shared/hook/useUpdateEffect';\nimport {FormStoreProvider, useSubmit} from \"@/shared/model/store\";\nimport {BuilderProvider} from \"@/shared/model/builder\";\n\nexport const FormBuilder = forwardRef<FormBuilderRef, TFormBuilder>((props, ref) => {\n // const validator = useMemo(() => {\n // return (new ValidationCore(props.layout, props.plugins))\n // }, [props.layout, props.plugins]);\n\n // const [state, dispatch] = useReducer(storeReducer, {\n // formData: props.formData ?? {},\n // errors: {},\n // });\n //\n // useUpdateEffect(() => {\n // if (props.onChange) {\n // props.onChange(state.formData);\n // }\n // }, [state.formData, props.onChange]);\n const submitHdl = useSubmit((data) => {\n if (props.onSubmit) {\n props.onSubmit(data);\n }\n });\n \n // const submit = () => {\n //\n // // const errors = validator.validate(state.formData);\n // // if (Object.keys(errors).length == 0) {\n // // if (props.onSubmit) {\n // // props.onSubmit(state.formData)\n // // }\n // // } else {\n // // dispatch({\n // // type: 'setErrors',\n // // payload: {\n // // errors\n // // }\n // // });\n // // }\n // }\n\n useImperativeHandle(ref, () => ({\n reset: () => {\n // dispatch({type: 'reset'})\n },\n submit: () => {\n submitHdl();\n },\n errors: () => {\n return {}\n // return state?.errors ?? {};\n }\n }), [props.onSubmit]);\n\n return <form onSubmit={(e) => {\n e.preventDefault();\n submitHdl();\n }}\n className={props?.className}\n >\n <FormStoreProvider>\n <BuilderProvider plugins={props.plugins}>\n <DynamicBuilder layout={props.layout} plugins={props.plugins} />\n </BuilderProvider>\n </FormStoreProvider>\n <input type=\"submit\" style={{ display: 'none' }}/>\n {props.children}\n </form>\n});\n\nexport default FormBuilder;\n","import type { FormElementProps, FormFieldConfig, FormFieldBase, RC } from '../../../../shared/types/common';\nimport { useId } from \"react\";\n\nexport type TextFieldConfig = FormFieldBase & { type: 'text' | 'email' | 'password'; placeholder?: string; };\n\nfunction isTextFieldConfig(field: FormFieldConfig): field is TextFieldConfig {\n return field.type === 'text' || field.type === 'email' || field.type === 'password';\n}\n\nexport const TextField: RC<FormElementProps> = ({ field, value, errors, onChange }) => {\n if (!isTextFieldConfig(field)) {\n console.warn(`TextField received an invalid field config for type: ${field.type}`);\n return null;\n }\n const id = useId();\n return (\n <div style={{ marginBottom: '15px' }}>\n <label htmlFor={id}>{field.label}:</label>\n <input\n type={field.type}\n id={id}\n name={field.name}\n placeholder={field.placeholder}\n value={value || ''}\n onChange={(e) => onChange(e.target.value)}\n style={{ borderColor: errors ? 'red' : '#ccc' }}\n />\n {errors && <p style={{ color: 'red', fontSize: '0.8em' }}>{Object.values(errors).join(', ')}</p>}\n </div>\n );\n};\n","import type {FormElementProps, FormFieldBase, FormFieldConfig, RC} from \"../../../../shared/types/common.ts\";\nimport clsx from \"clsx\";\nimport {useBuilder} from \"@/shared/model/builder\";\nexport type FormGroupConfig = FormFieldBase & { variant?: 'row' | 'col', fields: FormFieldConfig[] };\n\nfunction isGroupConfig(field: FormFieldConfig): field is FormGroupConfig {\n return field.fields;\n}\n\n\nexport const FormGroup: RC<FormElementProps<FormGroupConfig>> = ({field, path}) => {\n if (!isGroupConfig(field)) {\n return null;\n }\n const Builder = useBuilder()(field.fields, path);\n const variant = field.variant ?? 'col';\n \n const className = variant == 'col' ? 'flex-col' : 'flex-row';\n\n return <div>\n <div>{field.label}</div>\n <div className={clsx(className, 'flex')}>\n {Builder}\n </div>\n </div>;\n};\n\nFormGroup.fieldProps = ['fields'];\n","import { z } from 'zod';\n\nconst fieldShema = z.object({\n name: z.string(),\n label: z.string().optional(),\n type: z.string(),\n});\n\ntype TField = z.infer<typeof fieldShema>;\n\nexport type { TField };\nexport { fieldShema };\n"],"mappings":";;;;;;AAEA,SAAgB,YACd,SACA,cACA;CACA,IAAI,QAAQ;CACZ,MAAM,4BAAY,IAAI,KAAiB;AAEvC,QAAO;EACL,WAAc;AACZ,UAAO;;EAGT,SAAS,QAAW;AAClB,WAAQ,QAAQ,OAAO,OAAO;AAC9B,aAAU,SAAQ,MAAK,GAAG,CAAC;;EAG7B,UAAU,UAAsB;AAC9B,aAAU,IAAI,SAAS;AACvB,gBAAa,UAAU,OAAO,SAAS;;EAE1C;;;;;ACfH,SAAgBA,qBACd,SACA,cACA;CACA,MAAM,eAAe,cAAkC,KAAK;CAE5D,MAAM,YAAqD,EACE,eACI;EAC/D,MAAM,WAAW,OAAoB,KAAK;AAE1C,MAAI,CAAC,SAAS,QACZ,UAAS,UAAU,YAAY,SAAS,aAAa;AAGvD,SACE,oBAAC,aAAa;GAAS,OAAO,SAAS;GACpC;IACqB;;CAI5B,SAAS,SACP,UACG;EACH,MAAM,QAAQ,WAAW,aAAa;AAEtC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,wBAAwB;AAG1C,SAAO,qBACL,MAAM,iBACA,SAAS,MAAM,UAAU,CAAC,QAC1B,SAAS,MAAM,UAAU,CAAC,CACjC;;CAGH,SAAS,cAAc;EACrB,MAAM,QAAQ,WAAW,aAAa;AAEtC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,wBAAwB;AAG1C,SAAO,MAAM;;CAGf,SAAS,UAAU,UAA8B;EAC/C,MAAM,QAAQ,WAAW,aAAa;AAEtC,eAAa;AACX,OAAI,MAEF,UADc,MAAM,UAAU,CACf;;;AAKrB,QAAO;EACL;EACA;EACA;EACA;EACD;;;;;ACxEH,SAAgB,kBAAkB,KAAU,MAAgB,OAAiB;AAC3E,KAAI,KAAK,WAAW,EAClB,QAAO;CAGT,MAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,GAAG,KAAK;CACzD,MAAM,aAAa,KAAK;AAExB,KAAI,KAAK,WAAW,EAElB,QAAO,cAAc;MAChB;EACL,MAAM,gBAAgB,KAAK,MAAM,EAAE;EACnC,MAAM,YAAY,OAAO;AAEzB,MAAI,OAAO,cAAc,eAAe,cAAc,KACpD,QAAO,cAAc,OAAO,cAAc,OAAO,WAAW,EAAE,GAAG,EAAE;AAErE,SAAO,cAAc,kBAAkB,OAAO,aAAa,eAAe,MAAM;;AAElF,QAAO;;AAET,SAAgB,eAAe,KAAU,MAAqB;AAC5D,QAAO,KAAK,QAAQ,KAAK,QAAS,OAAO,IAAI,SAAS,SAAY,IAAI,OAAO,QAAY,IAAI;;;;;ACR/F,MAAM,eAAsB;CAC1B,UAAU,EAAE;CACZ,QAAQ,EAAE;CACX;AAED,SAAS,QAAQ,OAAc,QAAuB;CACpD,MAAM,UAAU,EAAC,GAAG,OAAM;AAC1B,SAAQ,OAAO,MAAf;EACE,KAAK;AACH,WAAQ,WAAW,kBAAkB,QAAQ,UAAU,OAAO,MAAM,OAAO,MAAM;AACjF;EACF,KAAK;AACH,WAAQ,SAAS,kBAAkB,QAAQ,QAAQ,OAAO,MAAM,OAAO,MAAM;AAC7E;EACF,KAAK;AACH,WAAQ,WAAW,EAAE;AACrB,WAAQ,SAAS,EAAE;AACnB;EACF,KAAK;AACH,WAAQ,SAAS,EAAC,GAAG,OAAO,QAAO;AACnC;;AAEJ,QAAO;;AAGT,MAAa,EACX,UAAU,mBACV,UAAU,cACV,aAAa,iBACb,cACEC,qBAAmB,SAAS,aAAa;;;;AC1C7C,MAAa,kBAAkB,UAAe;CAC5C,MAAM,UAAU,MAAM;CACtB,MAAM,QAAQ,MAAM;CACpB,MAAM,mBAAmB,CAAC,GAAG,MAAM,MAAM,MAAM,KAAK;CACpD,MAAM,QAAQ,eAAc,MAAI,eAAe,EAAE,UAAU,iBAAiB,EAAE;CAC9E,MAAM,SAAS,eAAc,MAAI,eAAe,EAAE,QAAQ,iBAAiB,EAAE;CAC7E,MAAM,WAAU,iBAAiB;AACjC,QAAO,oBAAC;EACN,OAAO,EAAC,GAAG,OAAM;EACjB,SAAS,MAAM;EACf,MAAM;EACN,SAAS,MAAM;EACR;EACC;EACR,WAAW,UAAe;AACxB,YAAS;IACP,MAAM;IACN,MAAM;IACN;IACD,CAAC;AACF,YAAS;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACR,CAAC;;GAEJ;;;;;ACtBJ,MAAa,kBAAmC,UAAU;CACxD,MAAM,OAAO,MAAM,QAAQ,EAAE;AAY7B,QAAO,MAAM,OAAO,KAAK,OAAO,UAAU;EACxC,MAAM,cAAc,MAAM,QAAQ,MAAM;AAExC,MAAI,CAAC,aAAa;AAChB,WAAQ,KAAK,yBAAyB,MAAM,KAAK,2BAA2B;AAC5E,UAAO;;AAKT,SAAO,oBAAC;GAA6C,SAAS;GAAmB;GAAa;KAAlE,GAAG,MAAM,OAAO,QAA0D;GACtG;;;;;AC5BJ,SAAgB,qBAAqB;CACnC,MAAM,iBAAiB,cAAmG,KAAK;CAE/H,MAAM,YAAmE,EACE,SACF,eACI;EAC3E,MAAM,WAAW,OAAmF,KAAK;AAEzG,MAAI,CAAC,SAAS,QACZ,UAAS,WAAW,QAAa,MAA0B,aAAyB;AAClF,UAAO,oBAAC;IAAuB;IAAc;IAAe;IAAU;KAA0B;;AAIpG,SACE,oBAAC,eAAe;GAAS,OAAO,SAAS;GACtC;IACuB;;CAI9B,SAAS,aAAyF;EAChG,MAAM,QAAQ,WAAW,eAAe;AAExC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,wBAAwB;AAG1C,SAAO;;AAGT,QAAO;EACL;EACA;EACD;;;;;ACpCH,MAAa,EACX,UAAU,iBACV,eACE,oBAAoB;;;;ACOxB,MAAa,cAAc,YAA0C,OAAO,QAAQ;CAelF,MAAM,YAAY,WAAW,SAAS;AACpC,MAAI,MAAM,SACR,OAAM,SAAS,KAAK;GAEtB;AAmBF,qBAAoB,YAAY;EAC9B,aAAa;EAGb,cAAc;AACZ,cAAW;;EAEb,cAAc;AACZ,UAAO,EAAE;;EAGZ,GAAG,CAAC,MAAM,SAAS,CAAC;AAErB,QAAO,qBAAC;EAAK,WAAW,MAAM;AAC1B,KAAE,gBAAgB;AAClB,cAAW;;EAEb,WAAW,OAAO;;GAEhB,oBAAC,+BACC,oBAAC;IAAgB,SAAS,MAAM;cAC9B,oBAAC;KAAe,QAAQ,MAAM;KAAQ,SAAS,MAAM;MAAW;KAChD,GACA;GACpB,oBAAC;IAAM,MAAK;IAAS,OAAO,EAAE,SAAS,QAAQ;KAAG;GACjD,MAAM;;GACF;EACT;;;;ACxEF,SAAS,kBAAkB,OAAkD;AAC3E,QAAO,MAAM,SAAS,UAAU,MAAM,SAAS,WAAW,MAAM,SAAS;;AAG3E,MAAa,aAAmC,EAAE,OAAO,OAAO,QAAQ,eAAe;AACrF,KAAI,CAAC,kBAAkB,MAAM,EAAE;AAC7B,UAAQ,KAAK,wDAAwD,MAAM,OAAO;AAClF,SAAO;;CAET,MAAM,KAAK,OAAO;AAClB,QACE,qBAAC;EAAI,OAAO,EAAE,cAAc,QAAQ;;GAClC,qBAAC;IAAM,SAAS;eAAK,MAAM,OAAM;KAAS;GAC1C,oBAAC;IACC,MAAM,MAAM;IACR;IACJ,MAAM,MAAM;IACZ,aAAa,MAAM;IACnB,OAAO,SAAS;IAChB,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM;IACzC,OAAO,EAAE,aAAa,SAAS,QAAQ,QAAQ;KAC/C;GACD,UAAU,oBAAC;IAAE,OAAO;KAAE,OAAO;KAAO,UAAU;KAAS;cAAG,OAAO,OAAO,OAAO,CAAC,KAAK,KAAK;KAAK;;GAC5F;;;;;ACvBV,SAAS,cAAc,OAAkD;AACvE,QAAO,MAAM;;AAIf,MAAa,aAAoD,EAAC,OAAO,WAAU;AACjF,KAAI,CAAC,cAAc,MAAM,CACvB,QAAO;CAET,MAAM,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;CAGhD,MAAM,aAFU,MAAM,WAAW,UAEJ,QAAQ,aAAa;AAElD,QAAO,qBAAC,oBACN,oBAAC,mBAAK,MAAM,QAAY,EACxB,oBAAC;EAAI,WAAW,KAAK,WAAW,OAAO;YACpC;GACG,IACF;;AAGR,UAAU,aAAa,CAAC,SAAS;;;;ACzBjC,MAAM,aAAa,EAAE,OAAO;CAC1B,MAAM,EAAE,QAAQ;CAChB,OAAO,EAAE,QAAQ,CAAC,UAAU;CAC5B,MAAM,EAAE,QAAQ;CACjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idem.agency/form-builder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Построитель форм",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"author": "idem.agency",
|
|
7
7
|
"license": "ISC",
|
|
8
8
|
"type": "module",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"dev": "vite",
|
|
11
|
-
"build": "
|
|
14
|
+
"build": "tsdown",
|
|
15
|
+
"dev:lib": "tsdown --watch",
|
|
12
16
|
"lint": "eslint .",
|
|
13
17
|
"preview": "vite preview",
|
|
14
18
|
"test": "vitest run"
|
|
@@ -16,7 +20,8 @@
|
|
|
16
20
|
"dependencies": {
|
|
17
21
|
"clsx": "2.1.1",
|
|
18
22
|
"react": "19.2.3",
|
|
19
|
-
"react-dom": "19.2.3"
|
|
23
|
+
"react-dom": "19.2.3",
|
|
24
|
+
"zod": "4.3.6"
|
|
20
25
|
},
|
|
21
26
|
"devDependencies": {
|
|
22
27
|
"@eslint/js": "9.39.2",
|
|
@@ -30,10 +35,13 @@
|
|
|
30
35
|
"eslint-plugin-react-refresh": "0.4.26",
|
|
31
36
|
"globals": "17.0.0",
|
|
32
37
|
"tailwindcss": "4.1.18",
|
|
38
|
+
"tsdown": "0.20.3",
|
|
33
39
|
"typescript": "5.9.3",
|
|
34
40
|
"typescript-eslint": "8.53.0",
|
|
35
41
|
"vite": "7.3.1",
|
|
36
42
|
"vite-plugin-dts": "4.5.4",
|
|
43
|
+
"vite-tsconfig-paths": "6.0.5",
|
|
37
44
|
"vitest": "4.0.17"
|
|
38
|
-
}
|
|
45
|
+
},
|
|
46
|
+
"gitHead": "5c2212eb5f6bc9bf7e422e68b90700cfecd03f41"
|
|
39
47
|
}
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Виджет для генерации форм</title>
|
|
8
|
-
<link href="/src/app/test.css" rel="stylesheet">
|
|
9
8
|
</head>
|
|
10
9
|
<body>
|
|
11
10
|
<div id="root"></div>
|
|
12
|
-
<script type="module" src="
|
|
11
|
+
<script type="module" src="main.tsx"></script>
|
|
13
12
|
</body>
|
|
14
13
|
</html>
|
package/public/main.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {StrictMode} from 'react'
|
|
2
|
+
import {createRoot} from 'react-dom/client'
|
|
3
|
+
import { FormBuilder, FormGroup, TextField } from "~/src/index";
|
|
4
|
+
import type {FormBuilderRef, FormElementRegistry, FormFieldConfig} from "~/src/index";
|
|
5
|
+
import {useRef} from "react";
|
|
6
|
+
|
|
7
|
+
function Debug() {
|
|
8
|
+
|
|
9
|
+
const plugins: FormElementRegistry = {
|
|
10
|
+
text: TextField,
|
|
11
|
+
group: FormGroup,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const layout: FormFieldConfig[] = [
|
|
15
|
+
{
|
|
16
|
+
name: 'username',
|
|
17
|
+
label: 'Имя пользователя',
|
|
18
|
+
type: 'text',
|
|
19
|
+
placeholder: 'Введите ваше имя',
|
|
20
|
+
defaultValue: 'Мое имя',
|
|
21
|
+
validation: ['required', 'confirm:personal.username_2,Имя 2 пользователя'],
|
|
22
|
+
viewConfig: {
|
|
23
|
+
logic: 'and',
|
|
24
|
+
rules: [
|
|
25
|
+
{
|
|
26
|
+
field: 'personal.username_2',
|
|
27
|
+
operator: 'in',
|
|
28
|
+
value: ['Alex']
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'personal',
|
|
35
|
+
label: 'Группа',
|
|
36
|
+
type: 'group',
|
|
37
|
+
variant: 'col',
|
|
38
|
+
fields: [
|
|
39
|
+
{
|
|
40
|
+
id: 'username_2',
|
|
41
|
+
name: 'username_2',
|
|
42
|
+
label: 'Имя 2 пользователя',
|
|
43
|
+
type: 'text',
|
|
44
|
+
placeholder: 'Введите ваше имя 2',
|
|
45
|
+
defaultValue: 'Мое имя',
|
|
46
|
+
validation: ['required'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'username_3',
|
|
50
|
+
label: 'Имя 3 пользователя',
|
|
51
|
+
type: 'text',
|
|
52
|
+
placeholder: 'Введите ваше имя 2',
|
|
53
|
+
defaultValue: 'Мое имя',
|
|
54
|
+
validation: ['required'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'username_4',
|
|
58
|
+
label: 'Имя 4 пользователя',
|
|
59
|
+
type: 'text',
|
|
60
|
+
placeholder: 'Введите ваше имя 2',
|
|
61
|
+
defaultValue: 'Мое имя',
|
|
62
|
+
validation: ['required'],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const ref = useRef<FormBuilderRef>(null);
|
|
69
|
+
return <div>
|
|
70
|
+
<FormBuilder ref={ref} layout={layout} plugins={plugins} formData={{
|
|
71
|
+
personal: {
|
|
72
|
+
username_2: "Alex"
|
|
73
|
+
}
|
|
74
|
+
}}></FormBuilder>
|
|
75
|
+
<button onClick={() => {
|
|
76
|
+
ref.current?.reset();
|
|
77
|
+
}}>Сбросить
|
|
78
|
+
</button>
|
|
79
|
+
<button onClick={() => {
|
|
80
|
+
ref.current?.submit();
|
|
81
|
+
}}>Отправить
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
createRoot(document.getElementById('root')!).render(
|
|
87
|
+
<StrictMode>
|
|
88
|
+
<Debug/>
|
|
89
|
+
</StrictMode>,
|
|
90
|
+
)
|
package/src/app/debug.tsx
CHANGED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import FormBuilder from "./index.tsx";
|
|
2
|
-
import type {FormBuilderRef, FormElementRegistry, FormFieldConfig} from "../shared/types/common.ts";
|
|
3
|
-
import {FormGroup, TextField} from "../widgets/form";
|
|
4
|
-
import {useRef} from "react";
|
|
5
|
-
|
|
6
|
-
function Debug() {
|
|
7
|
-
|
|
8
|
-
const plugins: FormElementRegistry = {
|
|
9
|
-
text: TextField,
|
|
10
|
-
group: FormGroup,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const layout: FormFieldConfig[] = [
|
|
14
|
-
{
|
|
15
|
-
id: 'username',
|
|
16
|
-
name: 'username',
|
|
17
|
-
label: 'Имя пользователя',
|
|
18
|
-
type: 'text',
|
|
19
|
-
placeholder: 'Введите ваше имя',
|
|
20
|
-
defaultValue: 'Мое имя',
|
|
21
|
-
validation: ['required', 'confirm:personal.username_2,Имя 2 пользователя'],
|
|
22
|
-
viewConfig: {
|
|
23
|
-
logic: 'and',
|
|
24
|
-
rules: [
|
|
25
|
-
{
|
|
26
|
-
field: 'personal.username_2',
|
|
27
|
-
operator: 'in',
|
|
28
|
-
value: ['Alex']
|
|
29
|
-
},
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: 'personal',
|
|
35
|
-
name: 'personal',
|
|
36
|
-
label: 'Группа',
|
|
37
|
-
type: 'group',
|
|
38
|
-
variant: 'col',
|
|
39
|
-
fields: [
|
|
40
|
-
{
|
|
41
|
-
id: 'username_2',
|
|
42
|
-
name: 'username_2',
|
|
43
|
-
label: 'Имя 2 пользователя',
|
|
44
|
-
type: 'text',
|
|
45
|
-
placeholder: 'Введите ваше имя 2',
|
|
46
|
-
defaultValue: 'Мое имя',
|
|
47
|
-
validation: ['required'],
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'username_3',
|
|
51
|
-
name: 'username_3',
|
|
52
|
-
label: 'Имя 3 пользователя',
|
|
53
|
-
type: 'text',
|
|
54
|
-
placeholder: 'Введите ваше имя 2',
|
|
55
|
-
defaultValue: 'Мое имя',
|
|
56
|
-
validation: ['required'],
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: 'username_4',
|
|
60
|
-
name: 'username_4',
|
|
61
|
-
label: 'Имя 4 пользователя',
|
|
62
|
-
type: 'text',
|
|
63
|
-
placeholder: 'Введите ваше имя 2',
|
|
64
|
-
defaultValue: 'Мое имя',
|
|
65
|
-
validation: ['required'],
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
const ref = useRef<FormBuilderRef>(null);
|
|
72
|
-
return <div>
|
|
73
|
-
<FormBuilder ref={ref} layout={layout} plugins={plugins} formData={{
|
|
74
|
-
personal: {
|
|
75
|
-
username_2: "Alex"
|
|
76
|
-
}
|
|
77
|
-
}}></FormBuilder>
|
|
78
|
-
<button onClick={() => {
|
|
79
|
-
ref.current?.reset();
|
|
80
|
-
}}>Сбросить
|
|
81
|
-
</button>
|
|
82
|
-
<button onClick={() => {
|
|
83
|
-
ref.current?.submit();
|
|
84
|
-
}}>Отправить
|
|
85
|
-
</button>
|
|
86
|
-
</div>
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export default Debug
|