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