@idem.agency/form-builder 0.0.13 → 0.0.15
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/dist/index.cjs +542 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -35
- package/dist/index.d.mts +101 -35
- package/dist/index.mjs +541 -208
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -6
- package/src/index.ts +0 -20
package/dist/index.mjs
CHANGED
|
@@ -3,72 +3,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import { z } from "zod";
|
|
5
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 () => {
|
|
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
|
-
});
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/shared/model/store/createStoreContext.tsx
|
|
39
|
-
function createStoreContext(reducer, defaultState) {
|
|
40
|
-
const StoreContext = createContext(null);
|
|
41
|
-
const Provider = ({ children, initialState }) => {
|
|
42
|
-
const storeRef = useRef(createStore(reducer, initialState ?? defaultState));
|
|
43
|
-
return /* @__PURE__ */ jsx(StoreContext.Provider, {
|
|
44
|
-
value: storeRef.current,
|
|
45
|
-
children
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
function useStore(selector) {
|
|
49
|
-
const store = useContext(StoreContext);
|
|
50
|
-
if (!store) throw new Error("StoreProvider missing");
|
|
51
|
-
return useSyncExternalStore(store.subscribe, () => selector(store.getState()), () => selector(store.getState()));
|
|
52
|
-
}
|
|
53
|
-
function useDispatch() {
|
|
54
|
-
const store = useContext(StoreContext);
|
|
55
|
-
if (!store) throw new Error("StoreProvider missing");
|
|
56
|
-
return store.dispatch;
|
|
57
|
-
}
|
|
58
|
-
function useStoreInstance() {
|
|
59
|
-
const store = useContext(StoreContext);
|
|
60
|
-
if (!store) throw new Error("StoreProvider missing");
|
|
61
|
-
return store;
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
Provider,
|
|
65
|
-
useStore,
|
|
66
|
-
useDispatch,
|
|
67
|
-
useStoreInstance
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
//#endregion
|
|
72
6
|
//#region src/shared/utils.ts
|
|
73
7
|
function updateNestedValue(obj, path, value) {
|
|
74
8
|
if (!path) return value;
|
|
@@ -93,43 +27,6 @@ function getNestedValue(obj, path) {
|
|
|
93
27
|
return path.split(".").reduce((acc, key) => acc && acc[key] !== void 0 ? acc[key] : void 0, obj);
|
|
94
28
|
}
|
|
95
29
|
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/shared/model/store/index.tsx
|
|
98
|
-
function reducer(state, action) {
|
|
99
|
-
const newData = { ...state };
|
|
100
|
-
switch (action.type) {
|
|
101
|
-
case "setValue":
|
|
102
|
-
newData.formData = updateNestedValue(newData.formData, action.path, action.value);
|
|
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;
|
|
108
|
-
case "setError":
|
|
109
|
-
newData.errors = updateNestedValue(newData.errors, action.path, action.value);
|
|
110
|
-
break;
|
|
111
|
-
case "reset":
|
|
112
|
-
newData.formData = {};
|
|
113
|
-
newData.errors = {};
|
|
114
|
-
break;
|
|
115
|
-
case "setErrors":
|
|
116
|
-
newData.errors = { ...action.errors };
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
return newData;
|
|
120
|
-
}
|
|
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
30
|
//#endregion
|
|
134
31
|
//#region src/plugins/validation/rules/confirm.ts
|
|
135
32
|
const confirm = {
|
|
@@ -215,47 +112,276 @@ var Validation = class {
|
|
|
215
112
|
};
|
|
216
113
|
|
|
217
114
|
//#endregion
|
|
218
|
-
//#region src/plugins/validation/
|
|
219
|
-
function
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
115
|
+
//#region src/plugins/validation/index.ts
|
|
116
|
+
function createValidationPlugin(config) {
|
|
117
|
+
const engine = new Validation(config?.onSubmit ?? false, config?.rules ?? []);
|
|
118
|
+
return {
|
|
119
|
+
name: "validation",
|
|
120
|
+
install(ctx) {
|
|
121
|
+
ctx.events.tap("field:register", ({ path, field }) => {
|
|
122
|
+
engine.registerField(path, field.validation ?? []);
|
|
123
|
+
});
|
|
124
|
+
ctx.pipeline.use("field:change", (data, next) => {
|
|
125
|
+
const errors = engine.validate(data.field.validation ?? [], data.value, data.formData);
|
|
126
|
+
return next({
|
|
127
|
+
...data,
|
|
128
|
+
errors
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
ctx.pipeline.use("form:validate", (data, next) => {
|
|
132
|
+
const errors = engine.validateAll(data.formData);
|
|
133
|
+
return next({
|
|
134
|
+
...data,
|
|
135
|
+
errors
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
230
139
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/plugins/visibility/core.ts
|
|
144
|
+
var VisibilityCore = class VisibilityCore {
|
|
145
|
+
registerFields = {};
|
|
146
|
+
registerField(path, rule) {
|
|
147
|
+
this.registerFields[path] = rule;
|
|
148
|
+
}
|
|
149
|
+
static isGroupRule(i) {
|
|
150
|
+
return "rules" in i;
|
|
151
|
+
}
|
|
152
|
+
static isInOperator(i) {
|
|
153
|
+
return i.operator == "in";
|
|
154
|
+
}
|
|
155
|
+
static isEqualOperand(i) {
|
|
156
|
+
return i.operator == "=";
|
|
157
|
+
}
|
|
158
|
+
static checkGroup(groupRule, formData) {
|
|
159
|
+
const logic = groupRule.logic;
|
|
160
|
+
const items = [];
|
|
161
|
+
groupRule.rules.forEach((rule) => {
|
|
162
|
+
if (this.isGroupRule(rule)) items.push(this.checkGroup(rule, formData));
|
|
163
|
+
else {
|
|
164
|
+
const value = getNestedValue(formData, rule.field);
|
|
165
|
+
if (this.isInOperator(rule)) items.push(rule.value.indexOf(value) !== -1);
|
|
166
|
+
else if (this.isEqualOperand(rule)) items.push(rule.value == value);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
if (logic == "or") return items.indexOf(true) != -1;
|
|
170
|
+
else return items.indexOf(false) == -1;
|
|
171
|
+
}
|
|
172
|
+
isView(path, data) {
|
|
173
|
+
const rule = this.checkInRule(path);
|
|
174
|
+
if (!this.checkInRule(path)) return true;
|
|
175
|
+
return VisibilityCore.checkGroup(rule, data);
|
|
176
|
+
}
|
|
177
|
+
checkInRule(path) {
|
|
178
|
+
return this.registerFields[path];
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/plugins/visibility/index.ts
|
|
184
|
+
function createVisibilityPlugin() {
|
|
185
|
+
const engine = new VisibilityCore();
|
|
186
|
+
return {
|
|
187
|
+
name: "visibility",
|
|
188
|
+
install(ctx) {
|
|
189
|
+
ctx.events.tap("field:register", ({ path, field }) => {
|
|
190
|
+
if (field.visibility) engine.registerField(path, field.visibility);
|
|
191
|
+
});
|
|
192
|
+
ctx.pipeline.useSync("field:visible", (data, next) => {
|
|
193
|
+
const visible = engine.isView(data.path, data.formData);
|
|
194
|
+
return next({
|
|
195
|
+
...data,
|
|
196
|
+
visible
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
237
200
|
};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/entity/inputs/ui/input/index.tsx
|
|
205
|
+
function isTextFieldConfig(field) {
|
|
206
|
+
return field.type === "text" || field.type === "email" || field.type === "password";
|
|
207
|
+
}
|
|
208
|
+
const TextField = ({ field, value, errors, onChange }) => {
|
|
209
|
+
if (!isTextFieldConfig(field)) {
|
|
210
|
+
console.warn(`TextField received an invalid field config for type: ${field.type}`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const id = useId();
|
|
214
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
215
|
+
style: { marginBottom: "15px" },
|
|
216
|
+
children: [
|
|
217
|
+
/* @__PURE__ */ jsxs("label", {
|
|
218
|
+
htmlFor: id,
|
|
219
|
+
children: [field.label, ":"]
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ jsx("input", {
|
|
222
|
+
type: field.type,
|
|
223
|
+
id,
|
|
224
|
+
name: field.name,
|
|
225
|
+
placeholder: field.placeholder,
|
|
226
|
+
value: value || "",
|
|
227
|
+
onChange: (e) => onChange(e.target.value),
|
|
228
|
+
style: { borderColor: errors ? "red" : "#ccc" }
|
|
229
|
+
}),
|
|
230
|
+
errors && /* @__PURE__ */ jsx("p", {
|
|
231
|
+
style: {
|
|
232
|
+
color: "red",
|
|
233
|
+
fontSize: "0.8em"
|
|
234
|
+
},
|
|
235
|
+
children: Object.values(errors).join(", ")
|
|
236
|
+
})
|
|
237
|
+
]
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/shared/model/store/store.ts
|
|
243
|
+
function createStore(reducer, initialState) {
|
|
244
|
+
let state = initialState;
|
|
245
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
246
|
+
return {
|
|
247
|
+
getState() {
|
|
248
|
+
return state;
|
|
249
|
+
},
|
|
250
|
+
dispatch(action) {
|
|
251
|
+
state = reducer(state, action);
|
|
252
|
+
listeners.forEach((l) => l());
|
|
253
|
+
},
|
|
254
|
+
subscribe(listener) {
|
|
255
|
+
listeners.add(listener);
|
|
256
|
+
return () => {
|
|
257
|
+
listeners.delete(listener);
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
subscribeSelector(selector, listener) {
|
|
261
|
+
let prev = selector(state);
|
|
262
|
+
return this.subscribe(() => {
|
|
263
|
+
const next = selector(state);
|
|
264
|
+
if (next !== prev) {
|
|
265
|
+
prev = next;
|
|
266
|
+
listener(next);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
243
270
|
};
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/shared/model/store/createStoreContext.tsx
|
|
275
|
+
function createStoreContext(reducer, defaultState) {
|
|
276
|
+
const StoreContext = createContext(null);
|
|
277
|
+
const Provider = ({ children, initialState }) => {
|
|
278
|
+
const storeRef = useRef(createStore(reducer, initialState ?? defaultState));
|
|
279
|
+
return /* @__PURE__ */ jsx(StoreContext.Provider, {
|
|
280
|
+
value: storeRef.current,
|
|
281
|
+
children
|
|
282
|
+
});
|
|
248
283
|
};
|
|
284
|
+
function useStore(selector) {
|
|
285
|
+
const store = useContext(StoreContext);
|
|
286
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
287
|
+
return useSyncExternalStore(store.subscribe, () => selector(store.getState()), () => selector(store.getState()));
|
|
288
|
+
}
|
|
289
|
+
function useDispatch() {
|
|
290
|
+
const store = useContext(StoreContext);
|
|
291
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
292
|
+
return store.dispatch;
|
|
293
|
+
}
|
|
294
|
+
function useStoreInstance() {
|
|
295
|
+
const store = useContext(StoreContext);
|
|
296
|
+
if (!store) throw new Error("StoreProvider missing");
|
|
297
|
+
return store;
|
|
298
|
+
}
|
|
249
299
|
return {
|
|
250
300
|
Provider,
|
|
251
|
-
|
|
252
|
-
|
|
301
|
+
useStore,
|
|
302
|
+
useDispatch,
|
|
303
|
+
useStoreInstance
|
|
253
304
|
};
|
|
254
305
|
}
|
|
255
306
|
|
|
256
307
|
//#endregion
|
|
257
|
-
//#region src/plugins/
|
|
258
|
-
const
|
|
308
|
+
//#region src/shared/model/plugins/context.tsx
|
|
309
|
+
const PluginSystemContext = createContext(null);
|
|
310
|
+
function usePluginManager() {
|
|
311
|
+
const ctx = useContext(PluginSystemContext);
|
|
312
|
+
if (!ctx) throw new Error("PluginSystemContext not found");
|
|
313
|
+
return ctx;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/shared/model/store/index.tsx
|
|
318
|
+
function reducer(state, action) {
|
|
319
|
+
const newData = { ...state };
|
|
320
|
+
switch (action.type) {
|
|
321
|
+
case "setValue":
|
|
322
|
+
newData.formData = updateNestedValue(newData.formData, action.path, action.value);
|
|
323
|
+
break;
|
|
324
|
+
case "setFieldValue":
|
|
325
|
+
newData.formData = updateNestedValue(newData.formData, action.path, action.value);
|
|
326
|
+
newData.errors = updateNestedValue(newData.errors, action.path, action?.errors?.length ? action.errors : null);
|
|
327
|
+
break;
|
|
328
|
+
case "setError":
|
|
329
|
+
newData.errors = updateNestedValue(newData.errors, action.path, action.value);
|
|
330
|
+
break;
|
|
331
|
+
case "reset":
|
|
332
|
+
newData.formData = {};
|
|
333
|
+
newData.errors = {};
|
|
334
|
+
break;
|
|
335
|
+
case "setErrors":
|
|
336
|
+
newData.errors = { ...action.errors };
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
return newData;
|
|
340
|
+
}
|
|
341
|
+
function composeMiddleware(middlewares, baseDispatch, getState) {
|
|
342
|
+
if (middlewares.length === 0) return baseDispatch;
|
|
343
|
+
return middlewares.reduceRight((next, m) => (action) => m(action, next, getState), baseDispatch);
|
|
344
|
+
}
|
|
345
|
+
const { Provider, useStore: useFormStore, useDispatch: _useRawDispatch, useStoreInstance: useFormStoreInstance } = createStoreContext(reducer, {
|
|
346
|
+
formData: {},
|
|
347
|
+
errors: {}
|
|
348
|
+
});
|
|
349
|
+
const DispatchContext = createContext(null);
|
|
350
|
+
const DispatchEnhancer = ({ children }) => {
|
|
351
|
+
const pluginManager = useContext(PluginSystemContext);
|
|
352
|
+
const store = useFormStoreInstance();
|
|
353
|
+
const rawDispatch = _useRawDispatch();
|
|
354
|
+
const middlewares = pluginManager ? pluginManager.getMiddleware() : [];
|
|
355
|
+
const dispatch = useMemo(() => composeMiddleware(middlewares, rawDispatch, store.getState), []);
|
|
356
|
+
return /* @__PURE__ */ jsx(DispatchContext.Provider, {
|
|
357
|
+
value: dispatch,
|
|
358
|
+
children
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
const FormStoreProvider = ({ children, formData }) => /* @__PURE__ */ jsx(Provider, {
|
|
362
|
+
initialState: {
|
|
363
|
+
formData: formData ?? {},
|
|
364
|
+
errors: {}
|
|
365
|
+
},
|
|
366
|
+
children: /* @__PURE__ */ jsx(DispatchEnhancer, { children })
|
|
367
|
+
});
|
|
368
|
+
function useFormDispatch() {
|
|
369
|
+
const ctx = useContext(DispatchContext);
|
|
370
|
+
if (!ctx) throw new Error("FormStoreProvider missing");
|
|
371
|
+
return ctx;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/shared/model/plugins/hooks.ts
|
|
376
|
+
function useCallEvents() {
|
|
377
|
+
return usePluginManager().callEvents;
|
|
378
|
+
}
|
|
379
|
+
function useRunPipeline() {
|
|
380
|
+
return usePluginManager().runPipeline;
|
|
381
|
+
}
|
|
382
|
+
function useRunPipelineSync() {
|
|
383
|
+
return usePluginManager().runPipelineSync;
|
|
384
|
+
}
|
|
259
385
|
|
|
260
386
|
//#endregion
|
|
261
387
|
//#region src/entity/dynamicBuilder/element.tsx
|
|
@@ -263,23 +389,51 @@ const DynamicBuilderElement = (props) => {
|
|
|
263
389
|
const Element = props.element;
|
|
264
390
|
const field = props.field;
|
|
265
391
|
const currentFieldPath = props.path ? `${props.path}.${field.name}` : field.name;
|
|
266
|
-
const value = useFormStore((
|
|
267
|
-
const errors = useFormStore((
|
|
392
|
+
const value = useFormStore((s) => getNestedValue(s.formData, currentFieldPath));
|
|
393
|
+
const errors = useFormStore((s) => s.errors[currentFieldPath]);
|
|
268
394
|
const dispatch = useFormDispatch();
|
|
269
395
|
const store = useFormStoreInstance();
|
|
270
|
-
const
|
|
396
|
+
const callEvents = useCallEvents();
|
|
397
|
+
const runPipeline = useRunPipeline();
|
|
398
|
+
const runPipelineSync = useRunPipelineSync();
|
|
399
|
+
useEffect(() => {
|
|
400
|
+
callEvents("field:register", {
|
|
401
|
+
path: currentFieldPath,
|
|
402
|
+
field
|
|
403
|
+
});
|
|
404
|
+
}, [currentFieldPath]);
|
|
405
|
+
if (!useFormStore((s) => runPipelineSync("field:visible", {
|
|
406
|
+
visible: true,
|
|
407
|
+
field,
|
|
408
|
+
path: currentFieldPath,
|
|
409
|
+
formData: s.formData
|
|
410
|
+
}).visible)) return null;
|
|
271
411
|
return /* @__PURE__ */ jsx(Element, {
|
|
272
412
|
field: { ...field },
|
|
273
413
|
path: currentFieldPath,
|
|
274
414
|
value,
|
|
275
415
|
errors,
|
|
276
|
-
onChange: (value) => {
|
|
416
|
+
onChange: async (value) => {
|
|
277
417
|
const data = store.getState().formData;
|
|
418
|
+
const { value: finalValue, errors } = await runPipeline("field:change", {
|
|
419
|
+
value,
|
|
420
|
+
field,
|
|
421
|
+
path: currentFieldPath,
|
|
422
|
+
formData: data,
|
|
423
|
+
errors: []
|
|
424
|
+
});
|
|
278
425
|
dispatch({
|
|
279
426
|
type: "setFieldValue",
|
|
280
427
|
path: currentFieldPath,
|
|
281
|
-
value,
|
|
282
|
-
errors
|
|
428
|
+
value: finalValue,
|
|
429
|
+
errors
|
|
430
|
+
});
|
|
431
|
+
await callEvents("field:change", {
|
|
432
|
+
value: finalValue,
|
|
433
|
+
prevValue: value,
|
|
434
|
+
field,
|
|
435
|
+
path: currentFieldPath,
|
|
436
|
+
formData: store.getState().formData
|
|
283
437
|
});
|
|
284
438
|
}
|
|
285
439
|
});
|
|
@@ -345,26 +499,57 @@ const DynamicBuilder = (props) => {
|
|
|
345
499
|
});
|
|
346
500
|
};
|
|
347
501
|
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/entity/inputs/ui/group/index.tsx
|
|
504
|
+
function isGroupConfig(field) {
|
|
505
|
+
return Array.isArray(field.fields);
|
|
506
|
+
}
|
|
507
|
+
const FormGroup = ({ field, path }) => {
|
|
508
|
+
if (!isGroupConfig(field)) return null;
|
|
509
|
+
const Builder = useBuilder()(field.fields, path);
|
|
510
|
+
const className = (field.variant ?? "col") == "col" ? "flex-col" : "flex-row";
|
|
511
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: field.label }), /* @__PURE__ */ jsx("div", {
|
|
512
|
+
className: clsx(className, "flex"),
|
|
513
|
+
children: Builder
|
|
514
|
+
})] });
|
|
515
|
+
};
|
|
516
|
+
|
|
348
517
|
//#endregion
|
|
349
518
|
//#region src/widgets/form/form.tsx
|
|
350
519
|
const Form = forwardRef((props, ref) => {
|
|
351
520
|
const dispatch = useFormDispatch();
|
|
352
521
|
const store = useFormStoreInstance();
|
|
353
|
-
const
|
|
522
|
+
const callEvents = useCallEvents();
|
|
523
|
+
const runPipeline = useRunPipeline();
|
|
354
524
|
useEffect(() => {
|
|
355
|
-
return store.subscribeSelector((s) => s.formData, (formData) =>
|
|
356
|
-
}, [
|
|
357
|
-
const
|
|
358
|
-
const submitHdl = useCallback(() => {
|
|
525
|
+
return store.subscribeSelector((s) => s.formData, (formData) => props.onChange ? props.onChange?.(formData) : null);
|
|
526
|
+
}, [props.onChange]);
|
|
527
|
+
const submitHdl = useCallback(async () => {
|
|
359
528
|
if (props.onSubmit) {
|
|
360
|
-
const
|
|
361
|
-
|
|
529
|
+
const currentFormData = store.getState().formData;
|
|
530
|
+
await callEvents("form:submit:before", { formData: currentFormData });
|
|
531
|
+
const { formData: transformed } = await runPipeline("form:submit", { formData: currentFormData });
|
|
532
|
+
const { errors } = await runPipeline("form:validate", {
|
|
533
|
+
formData: transformed,
|
|
534
|
+
errors: {}
|
|
535
|
+
});
|
|
536
|
+
if (Object.keys(errors).length === 0) props.onSubmit(transformed);
|
|
362
537
|
else dispatch({
|
|
363
538
|
type: "setErrors",
|
|
364
539
|
errors
|
|
365
540
|
});
|
|
541
|
+
await callEvents("form:submit:after", {
|
|
542
|
+
formData: transformed,
|
|
543
|
+
errors
|
|
544
|
+
});
|
|
366
545
|
}
|
|
367
|
-
}, [
|
|
546
|
+
}, [
|
|
547
|
+
props.onSubmit,
|
|
548
|
+
callEvents,
|
|
549
|
+
runPipeline,
|
|
550
|
+
store,
|
|
551
|
+
dispatch
|
|
552
|
+
]);
|
|
368
553
|
useImperativeHandle(ref, () => ({
|
|
369
554
|
reset: () => dispatch({ type: "reset" }),
|
|
370
555
|
submit: () => submitHdl(),
|
|
@@ -375,9 +560,9 @@ const Form = forwardRef((props, ref) => {
|
|
|
375
560
|
store
|
|
376
561
|
]);
|
|
377
562
|
return /* @__PURE__ */ jsxs("form", {
|
|
378
|
-
onSubmit: (e) => {
|
|
563
|
+
onSubmit: async (e) => {
|
|
379
564
|
e.preventDefault();
|
|
380
|
-
submitHdl();
|
|
565
|
+
await submitHdl();
|
|
381
566
|
},
|
|
382
567
|
className: props?.className,
|
|
383
568
|
children: [
|
|
@@ -394,14 +579,204 @@ const Form = forwardRef((props, ref) => {
|
|
|
394
579
|
});
|
|
395
580
|
});
|
|
396
581
|
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/shared/model/plugins/HookRegistry.ts
|
|
584
|
+
var HookRegistry = class {
|
|
585
|
+
handlers = /* @__PURE__ */ new Map();
|
|
586
|
+
tap(hook, handler) {
|
|
587
|
+
if (!this.handlers.has(hook)) this.handlers.set(hook, /* @__PURE__ */ new Set());
|
|
588
|
+
this.handlers.get(hook).add(handler);
|
|
589
|
+
return () => {
|
|
590
|
+
this.handlers.get(hook)?.delete(handler);
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
async call(hook, data) {
|
|
594
|
+
const set = this.handlers.get(hook);
|
|
595
|
+
if (!set) return;
|
|
596
|
+
await Promise.all([...set].map((h) => h(data)));
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/shared/model/plugins/PipelineRegistry.ts
|
|
602
|
+
var PipelineRegistry = class {
|
|
603
|
+
stages = /* @__PURE__ */ new Map();
|
|
604
|
+
syncStages = /* @__PURE__ */ new Map();
|
|
605
|
+
use(pipeline, stage, priority = 10) {
|
|
606
|
+
if (!this.stages.has(pipeline)) this.stages.set(pipeline, []);
|
|
607
|
+
const arr = this.stages.get(pipeline);
|
|
608
|
+
const entry = {
|
|
609
|
+
stage,
|
|
610
|
+
priority
|
|
611
|
+
};
|
|
612
|
+
arr.push(entry);
|
|
613
|
+
arr.sort((a, b) => b.priority - a.priority);
|
|
614
|
+
return () => {
|
|
615
|
+
const i = arr.indexOf(entry);
|
|
616
|
+
if (i >= 0) arr.splice(i, 1);
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
useSync(pipeline, stage, priority = 10) {
|
|
620
|
+
if (!this.syncStages.has(pipeline)) this.syncStages.set(pipeline, []);
|
|
621
|
+
const arr = this.syncStages.get(pipeline);
|
|
622
|
+
const entry = {
|
|
623
|
+
stage,
|
|
624
|
+
priority
|
|
625
|
+
};
|
|
626
|
+
arr.push(entry);
|
|
627
|
+
arr.sort((a, b) => b.priority - a.priority);
|
|
628
|
+
return () => {
|
|
629
|
+
const i = arr.indexOf(entry);
|
|
630
|
+
if (i >= 0) arr.splice(i, 1);
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
async run(name, initialData) {
|
|
634
|
+
const arr = this.stages.get(name) ?? [];
|
|
635
|
+
const compose = (index) => (data) => {
|
|
636
|
+
if (index >= arr.length) return Promise.resolve(data);
|
|
637
|
+
return arr[index].stage(data, compose(index + 1));
|
|
638
|
+
};
|
|
639
|
+
return compose(0)(initialData);
|
|
640
|
+
}
|
|
641
|
+
runSync(name, initialData) {
|
|
642
|
+
const arr = this.syncStages.get(name) ?? [];
|
|
643
|
+
const compose = (index) => (data) => {
|
|
644
|
+
if (index >= arr.length) return data;
|
|
645
|
+
return arr[index].stage(data, compose(index + 1));
|
|
646
|
+
};
|
|
647
|
+
return compose(0)(initialData);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/shared/model/plugins/EventBus.ts
|
|
653
|
+
var EventBus = class {
|
|
654
|
+
listeners = /* @__PURE__ */ new Map();
|
|
655
|
+
on(event, handler) {
|
|
656
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
657
|
+
this.listeners.get(event).add(handler);
|
|
658
|
+
return () => this.listeners.get(event)?.delete(handler);
|
|
659
|
+
}
|
|
660
|
+
emit(event, data) {
|
|
661
|
+
this.listeners.get(event)?.forEach((h) => h(data));
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
//#endregion
|
|
666
|
+
//#region src/shared/model/plugins/MiddlewareRegistry.ts
|
|
667
|
+
var MiddlewareRegistry = class {
|
|
668
|
+
middlewares = [];
|
|
669
|
+
use(middleware) {
|
|
670
|
+
this.middlewares.push(middleware);
|
|
671
|
+
return () => {
|
|
672
|
+
const i = this.middlewares.indexOf(middleware);
|
|
673
|
+
if (i >= 0) this.middlewares.splice(i, 1);
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
getAll() {
|
|
677
|
+
return [...this.middlewares];
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region src/shared/model/plugins/FieldRegistry.ts
|
|
683
|
+
var FieldRegistry = class {
|
|
684
|
+
components = /* @__PURE__ */ new Map();
|
|
685
|
+
register(type, component) {
|
|
686
|
+
this.components.set(type, component);
|
|
687
|
+
return () => this.components.delete(type);
|
|
688
|
+
}
|
|
689
|
+
getAll() {
|
|
690
|
+
return Object.fromEntries(this.components);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/shared/model/plugins/PluginManager.ts
|
|
696
|
+
var PluginManager = class {
|
|
697
|
+
eventRegistry = new HookRegistry();
|
|
698
|
+
pipelineRegistry = new PipelineRegistry();
|
|
699
|
+
eventBus = new EventBus();
|
|
700
|
+
middlewareRegistry = new MiddlewareRegistry();
|
|
701
|
+
fieldRegistry = new FieldRegistry();
|
|
702
|
+
unsubscribers = [];
|
|
703
|
+
installed = false;
|
|
704
|
+
constructor(plugins) {
|
|
705
|
+
this.plugins = plugins;
|
|
706
|
+
}
|
|
707
|
+
install() {
|
|
708
|
+
if (this.installed) return;
|
|
709
|
+
const context = {
|
|
710
|
+
events: { tap: (hook, handler) => {
|
|
711
|
+
const unsub = this.eventRegistry.tap(hook, handler);
|
|
712
|
+
this.unsubscribers.push(unsub);
|
|
713
|
+
return unsub;
|
|
714
|
+
} },
|
|
715
|
+
pipeline: {
|
|
716
|
+
use: (pipeline, stage, priority) => {
|
|
717
|
+
const unsub = this.pipelineRegistry.use(pipeline, stage, priority);
|
|
718
|
+
this.unsubscribers.push(unsub);
|
|
719
|
+
return unsub;
|
|
720
|
+
},
|
|
721
|
+
useSync: (pipeline, stage, priority) => {
|
|
722
|
+
const unsub = this.pipelineRegistry.useSync(pipeline, stage, priority);
|
|
723
|
+
this.unsubscribers.push(unsub);
|
|
724
|
+
return unsub;
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
eventBus: {
|
|
728
|
+
on: (event, handler) => {
|
|
729
|
+
const unsub = this.eventBus.on(event, handler);
|
|
730
|
+
this.unsubscribers.push(unsub);
|
|
731
|
+
return unsub;
|
|
732
|
+
},
|
|
733
|
+
emit: (event, data) => this.eventBus.emit(event, data)
|
|
734
|
+
},
|
|
735
|
+
fields: { register: (type, component) => {
|
|
736
|
+
const unsub = this.fieldRegistry.register(type, component);
|
|
737
|
+
this.unsubscribers.push(unsub);
|
|
738
|
+
return unsub;
|
|
739
|
+
} },
|
|
740
|
+
middleware: { use: (middleware) => {
|
|
741
|
+
const unsub = this.middlewareRegistry.use(middleware);
|
|
742
|
+
this.unsubscribers.push(unsub);
|
|
743
|
+
return unsub;
|
|
744
|
+
} }
|
|
745
|
+
};
|
|
746
|
+
this.installed = true;
|
|
747
|
+
for (const plugin of this.plugins) plugin.install(context);
|
|
748
|
+
}
|
|
749
|
+
uninstall() {
|
|
750
|
+
if (!this.installed) return;
|
|
751
|
+
this.installed = false;
|
|
752
|
+
[...this.unsubscribers].reverse().forEach((fn) => fn());
|
|
753
|
+
this.unsubscribers = [];
|
|
754
|
+
[...this.plugins].reverse().forEach((plugin) => plugin.uninstall?.());
|
|
755
|
+
}
|
|
756
|
+
callEvents = this.eventRegistry.call.bind(this.eventRegistry);
|
|
757
|
+
runPipeline = this.pipelineRegistry.run.bind(this.pipelineRegistry);
|
|
758
|
+
runPipelineSync = this.pipelineRegistry.runSync.bind(this.pipelineRegistry);
|
|
759
|
+
getMiddleware = this.middlewareRegistry.getAll.bind(this.middlewareRegistry);
|
|
760
|
+
getFields = this.fieldRegistry.getAll.bind(this.fieldRegistry);
|
|
761
|
+
};
|
|
762
|
+
|
|
397
763
|
//#endregion
|
|
398
764
|
//#region src/app/index.tsx
|
|
399
765
|
const FormBuilder = forwardRef((props, ref) => {
|
|
400
766
|
const { formData, ...innerProps } = props;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
767
|
+
const pluginManager = useMemo(() => {
|
|
768
|
+
const pm = new PluginManager([createValidationPlugin(props.validator), ...props.plugins ?? []]);
|
|
769
|
+
pm.install();
|
|
770
|
+
return pm;
|
|
771
|
+
}, []);
|
|
772
|
+
useEffect(() => {
|
|
773
|
+
pluginManager.install();
|
|
774
|
+
return () => pluginManager.uninstall();
|
|
775
|
+
}, [pluginManager]);
|
|
776
|
+
return /* @__PURE__ */ jsx(PluginSystemContext.Provider, {
|
|
777
|
+
value: pluginManager,
|
|
778
|
+
children: /* @__PURE__ */ jsx(FormStoreProvider, {
|
|
779
|
+
formData,
|
|
405
780
|
children: /* @__PURE__ */ jsx(Form, {
|
|
406
781
|
...innerProps,
|
|
407
782
|
ref
|
|
@@ -410,59 +785,6 @@ const FormBuilder = forwardRef((props, ref) => {
|
|
|
410
785
|
});
|
|
411
786
|
});
|
|
412
787
|
|
|
413
|
-
//#endregion
|
|
414
|
-
//#region src/entity/inputs/ui/input/index.tsx
|
|
415
|
-
function isTextFieldConfig(field) {
|
|
416
|
-
return field.type === "text" || field.type === "email" || field.type === "password";
|
|
417
|
-
}
|
|
418
|
-
const TextField = ({ field, value, errors, onChange }) => {
|
|
419
|
-
if (!isTextFieldConfig(field)) {
|
|
420
|
-
console.warn(`TextField received an invalid field config for type: ${field.type}`);
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
const id = useId();
|
|
424
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
425
|
-
style: { marginBottom: "15px" },
|
|
426
|
-
children: [
|
|
427
|
-
/* @__PURE__ */ jsxs("label", {
|
|
428
|
-
htmlFor: id,
|
|
429
|
-
children: [field.label, ":"]
|
|
430
|
-
}),
|
|
431
|
-
/* @__PURE__ */ jsx("input", {
|
|
432
|
-
type: field.type,
|
|
433
|
-
id,
|
|
434
|
-
name: field.name,
|
|
435
|
-
placeholder: field.placeholder,
|
|
436
|
-
value: value || "",
|
|
437
|
-
onChange: (e) => onChange(e.target.value),
|
|
438
|
-
style: { borderColor: errors ? "red" : "#ccc" }
|
|
439
|
-
}),
|
|
440
|
-
errors && /* @__PURE__ */ jsx("p", {
|
|
441
|
-
style: {
|
|
442
|
-
color: "red",
|
|
443
|
-
fontSize: "0.8em"
|
|
444
|
-
},
|
|
445
|
-
children: Object.values(errors).join(", ")
|
|
446
|
-
})
|
|
447
|
-
]
|
|
448
|
-
});
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
//#endregion
|
|
452
|
-
//#region src/entity/inputs/ui/group/index.tsx
|
|
453
|
-
function isGroupConfig(field) {
|
|
454
|
-
return Array.isArray(field.fields);
|
|
455
|
-
}
|
|
456
|
-
const FormGroup = ({ field, path }) => {
|
|
457
|
-
if (!isGroupConfig(field)) return null;
|
|
458
|
-
const Builder = useBuilder()(field.fields, path);
|
|
459
|
-
const className = (field.variant ?? "col") == "col" ? "flex-col" : "flex-row";
|
|
460
|
-
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: field.label }), /* @__PURE__ */ jsx("div", {
|
|
461
|
-
className: clsx(className, "flex"),
|
|
462
|
-
children: Builder
|
|
463
|
-
})] });
|
|
464
|
-
};
|
|
465
|
-
|
|
466
788
|
//#endregion
|
|
467
789
|
//#region src/shared/model/index.ts
|
|
468
790
|
const fieldShema = z.object({
|
|
@@ -472,5 +794,16 @@ const fieldShema = z.object({
|
|
|
472
794
|
});
|
|
473
795
|
|
|
474
796
|
//#endregion
|
|
475
|
-
|
|
797
|
+
//#region src/index.ts
|
|
798
|
+
const plugins = {
|
|
799
|
+
createValidationPlugin,
|
|
800
|
+
createVisibilityPlugin
|
|
801
|
+
};
|
|
802
|
+
const inputs = {
|
|
803
|
+
FormGroup,
|
|
804
|
+
TextField
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
//#endregion
|
|
808
|
+
export { FormBuilder, fieldShema, inputs, plugins };
|
|
476
809
|
//# sourceMappingURL=index.mjs.map
|