@formhaus/core 0.1.0
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/engine/form-engine.d.ts +45 -0
- package/dist/engine/form-engine.d.ts.map +1 -0
- package/dist/engine/form-engine.js +278 -0
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/schema-validation/index.d.ts +2 -0
- package/dist/schema-validation/index.d.ts.map +1 -0
- package/dist/schema-validation/index.js +1 -0
- package/dist/schema-validation/validate-schema.d.ts +3 -0
- package/dist/schema-validation/validate-schema.d.ts.map +1 -0
- package/dist/schema-validation/validate-schema.js +69 -0
- package/dist/types/action.d.ts +8 -0
- package/dist/types/action.d.ts.map +1 -0
- package/dist/types/action.js +1 -0
- package/dist/types/analytics.d.ts +27 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +1 -0
- package/dist/types/field.d.ts +48 -0
- package/dist/types/field.d.ts.map +1 -0
- package/dist/types/field.js +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/schema.d.ts +21 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +1 -0
- package/dist/validation/default-messages.d.ts +2 -0
- package/dist/validation/default-messages.d.ts.map +1 -0
- package/dist/validation/default-messages.js +20 -0
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +3 -0
- package/dist/validation/validate-field.d.ts +4 -0
- package/dist/validation/validate-field.d.ts.map +1 -0
- package/dist/validation/validate-field.js +61 -0
- package/dist/validation/validate-step.d.ts +5 -0
- package/dist/validation/validate-step.d.ts.map +1 -0
- package/dist/validation/validate-step.js +26 -0
- package/dist/visibility/evaluate-condition.d.ts +3 -0
- package/dist/visibility/evaluate-condition.d.ts.map +1 -0
- package/dist/visibility/evaluate-condition.js +19 -0
- package/dist/visibility/index.d.ts +4 -0
- package/dist/visibility/index.d.ts.map +1 -0
- package/dist/visibility/index.js +3 -0
- package/dist/visibility/is-step-visible.d.ts +3 -0
- package/dist/visibility/is-step-visible.d.ts.map +1 -0
- package/dist/visibility/is-step-visible.js +4 -0
- package/dist/visibility/is-visible.d.ts +3 -0
- package/dist/visibility/is-visible.d.ts.map +1 -0
- package/dist/visibility/is-visible.js +14 -0
- package/fixtures/basic-form.json +46 -0
- package/fixtures/conditional-fields.json +84 -0
- package/fixtures/conditional-steps.json +79 -0
- package/fixtures/dispute-form.json +91 -0
- package/fixtures/multi-step-form.json +108 -0
- package/fixtures/validation-form.json +62 -0
- package/package.json +33 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FormField, FormSchema, FormStep } from '../types';
|
|
2
|
+
import type { ValidatorFn } from '../validation';
|
|
3
|
+
export interface FormEngineOptions {
|
|
4
|
+
validators?: Record<string, ValidatorFn>;
|
|
5
|
+
}
|
|
6
|
+
export declare class FormEngine {
|
|
7
|
+
readonly schema: FormSchema;
|
|
8
|
+
values: Record<string, unknown>;
|
|
9
|
+
errors: Record<string, string>;
|
|
10
|
+
topLevelErrors: string[];
|
|
11
|
+
currentStepIndex: number;
|
|
12
|
+
fieldLoading: Record<string, boolean>;
|
|
13
|
+
private _version;
|
|
14
|
+
private _listeners;
|
|
15
|
+
private _validators;
|
|
16
|
+
private _allFields;
|
|
17
|
+
constructor(schema: FormSchema, initialValues?: Record<string, unknown>, options?: FormEngineOptions);
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
getSnapshot(): number;
|
|
20
|
+
get isMultiStep(): boolean;
|
|
21
|
+
get visibleSteps(): FormStep[];
|
|
22
|
+
get currentStep(): FormStep | null;
|
|
23
|
+
get visibleFields(): FormField[];
|
|
24
|
+
get isFirstStep(): boolean;
|
|
25
|
+
get isLastStep(): boolean;
|
|
26
|
+
get canGoNext(): boolean;
|
|
27
|
+
get progress(): {
|
|
28
|
+
current: number;
|
|
29
|
+
total: number;
|
|
30
|
+
};
|
|
31
|
+
setValue(key: string, value: unknown): void;
|
|
32
|
+
setErrors(errors: Record<string, string>): void;
|
|
33
|
+
setFieldLoading(key: string, loading: boolean): void;
|
|
34
|
+
nextStep(): boolean;
|
|
35
|
+
prevStep(): void;
|
|
36
|
+
validate(): Record<string, string>;
|
|
37
|
+
validateField(key: string): string | null;
|
|
38
|
+
getSubmitValues(): Record<string, unknown>;
|
|
39
|
+
goToStepWithField(fieldKey: string): void;
|
|
40
|
+
reset(values?: Record<string, unknown>): void;
|
|
41
|
+
private _computeAllFields;
|
|
42
|
+
private _cascadeClearHiddenFields;
|
|
43
|
+
private _notify;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=form-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-engine.d.ts","sourceRoot":"","sources":["../../src/engine/form-engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAMjD,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC1C;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAE5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEtC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,UAAU,CAAc;gBAG9B,MAAM,EAAE,UAAU,EAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,OAAO,CAAC,EAAE,iBAAiB;IA0C7B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAO3C,WAAW,IAAI,MAAM;IAMrB,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,YAAY,IAAI,QAAQ,EAAE,CAI7B;IAED,IAAI,WAAW,IAAI,QAAQ,GAAG,IAAI,CAKjC;IAED,IAAI,aAAa,IAAI,SAAS,EAAE,CAO/B;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,SAAS,IAAI,OAAO,CAMvB;IAED,IAAI,QAAQ,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGjD;IAID,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAY3C,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAyB/C,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IASpD,QAAQ,IAAI,OAAO;IAsBnB,QAAQ,IAAI,IAAI;IAQhB,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAkBlC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAezC,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA6B1C,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAezC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAkB7C,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,yBAAyB;IAgBjC,OAAO,CAAC,OAAO;CAMhB"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { validateSchema } from '../schema-validation';
|
|
2
|
+
import { validateField, validateFields, validateStep } from '../validation';
|
|
3
|
+
import { isStepVisible, isVisible } from '../visibility';
|
|
4
|
+
const MAX_CASCADE_ITERATIONS = 50;
|
|
5
|
+
export class FormEngine {
|
|
6
|
+
constructor(schema, initialValues, options) {
|
|
7
|
+
// Validate schema structure
|
|
8
|
+
const hasFields = schema.fields && schema.fields.length > 0;
|
|
9
|
+
const hasSteps = schema.steps && schema.steps.length > 0;
|
|
10
|
+
if (hasFields && hasSteps) {
|
|
11
|
+
throw new Error('FormSchema cannot have both "fields" and "steps" as non-empty arrays.');
|
|
12
|
+
}
|
|
13
|
+
// Run schema validation (warnings only, don't throw)
|
|
14
|
+
const warnings = validateSchema(schema);
|
|
15
|
+
if (warnings.length > 0) {
|
|
16
|
+
for (const w of warnings) {
|
|
17
|
+
console.warn(`[FormEngine] ${w}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
this.schema = schema;
|
|
21
|
+
this._validators = options?.validators ?? {};
|
|
22
|
+
this._version = 0;
|
|
23
|
+
this._listeners = new Set();
|
|
24
|
+
this.currentStepIndex = 0;
|
|
25
|
+
this.errors = {};
|
|
26
|
+
this.topLevelErrors = [];
|
|
27
|
+
this.fieldLoading = {};
|
|
28
|
+
this._allFields = this._computeAllFields();
|
|
29
|
+
// Initialize values with defaults, then overlay initialValues
|
|
30
|
+
this.values = {};
|
|
31
|
+
for (const field of this._allFields) {
|
|
32
|
+
if (field.defaultValue !== undefined) {
|
|
33
|
+
this.values[field.key] = field.defaultValue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (initialValues) {
|
|
37
|
+
Object.assign(this.values, initialValues);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// --- React integration ---
|
|
41
|
+
subscribe(listener) {
|
|
42
|
+
this._listeners.add(listener);
|
|
43
|
+
return () => {
|
|
44
|
+
this._listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
getSnapshot() {
|
|
48
|
+
return this._version;
|
|
49
|
+
}
|
|
50
|
+
// --- Computed getters ---
|
|
51
|
+
get isMultiStep() {
|
|
52
|
+
return (this.schema.steps ?? []).length > 0;
|
|
53
|
+
}
|
|
54
|
+
get visibleSteps() {
|
|
55
|
+
if (!this.isMultiStep)
|
|
56
|
+
return [];
|
|
57
|
+
const steps = this.schema.steps ?? [];
|
|
58
|
+
return steps.filter((s) => isStepVisible(s, this.values));
|
|
59
|
+
}
|
|
60
|
+
get currentStep() {
|
|
61
|
+
const steps = this.visibleSteps;
|
|
62
|
+
if (steps.length === 0)
|
|
63
|
+
return null;
|
|
64
|
+
// Find the step at currentStepIndex within visible steps
|
|
65
|
+
return steps[this.currentStepIndex] ?? steps[steps.length - 1] ?? null;
|
|
66
|
+
}
|
|
67
|
+
get visibleFields() {
|
|
68
|
+
if (this.isMultiStep) {
|
|
69
|
+
const step = this.currentStep;
|
|
70
|
+
if (!step)
|
|
71
|
+
return [];
|
|
72
|
+
return step.fields.filter((f) => isVisible(f, this.values));
|
|
73
|
+
}
|
|
74
|
+
return (this.schema.fields ?? []).filter((f) => isVisible(f, this.values));
|
|
75
|
+
}
|
|
76
|
+
get isFirstStep() {
|
|
77
|
+
return this.currentStepIndex === 0;
|
|
78
|
+
}
|
|
79
|
+
get isLastStep() {
|
|
80
|
+
return this.currentStepIndex >= this.visibleSteps.length - 1;
|
|
81
|
+
}
|
|
82
|
+
get canGoNext() {
|
|
83
|
+
if (!this.isMultiStep)
|
|
84
|
+
return false;
|
|
85
|
+
const step = this.currentStep;
|
|
86
|
+
if (!step)
|
|
87
|
+
return false;
|
|
88
|
+
const stepErrors = validateStep(step, this.values, this._validators);
|
|
89
|
+
return Object.keys(stepErrors).length === 0;
|
|
90
|
+
}
|
|
91
|
+
get progress() {
|
|
92
|
+
const total = this.visibleSteps.length;
|
|
93
|
+
return { current: Math.min(this.currentStepIndex + 1, total), total };
|
|
94
|
+
}
|
|
95
|
+
// --- Mutations ---
|
|
96
|
+
setValue(key, value) {
|
|
97
|
+
this.values[key] = value;
|
|
98
|
+
// Clear error for this field
|
|
99
|
+
delete this.errors[key];
|
|
100
|
+
// Cascade: clear hidden fields, loop until stable
|
|
101
|
+
this._cascadeClearHiddenFields();
|
|
102
|
+
this._notify();
|
|
103
|
+
}
|
|
104
|
+
setErrors(errors) {
|
|
105
|
+
this.topLevelErrors = [];
|
|
106
|
+
for (const [key, message] of Object.entries(errors)) {
|
|
107
|
+
// Check if field is visible
|
|
108
|
+
const field = this._allFields.find((f) => f.key === key);
|
|
109
|
+
if (field && isVisible(field, this.values)) {
|
|
110
|
+
this.errors[key] = message;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Field is hidden or doesn't exist: surface as top-level error
|
|
114
|
+
this.topLevelErrors.push(message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Navigate to step containing first visible error
|
|
118
|
+
if (this.isMultiStep) {
|
|
119
|
+
const firstErrorKey = Object.keys(errors).find((k) => this.errors[k] !== undefined);
|
|
120
|
+
if (firstErrorKey) {
|
|
121
|
+
this.goToStepWithField(firstErrorKey);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this._notify();
|
|
125
|
+
}
|
|
126
|
+
setFieldLoading(key, loading) {
|
|
127
|
+
if (loading) {
|
|
128
|
+
this.fieldLoading[key] = true;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
delete this.fieldLoading[key];
|
|
132
|
+
}
|
|
133
|
+
this._notify();
|
|
134
|
+
}
|
|
135
|
+
nextStep() {
|
|
136
|
+
if (!this.isMultiStep)
|
|
137
|
+
return false;
|
|
138
|
+
const step = this.currentStep;
|
|
139
|
+
if (!step)
|
|
140
|
+
return false;
|
|
141
|
+
// Validate current step
|
|
142
|
+
const stepErrors = validateStep(step, this.values, this._validators);
|
|
143
|
+
if (Object.keys(stepErrors).length > 0) {
|
|
144
|
+
Object.assign(this.errors, stepErrors);
|
|
145
|
+
this._notify();
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
// Move to next visible step
|
|
149
|
+
if (this.isLastStep)
|
|
150
|
+
return false;
|
|
151
|
+
this.currentStepIndex++;
|
|
152
|
+
this._notify();
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
prevStep() {
|
|
156
|
+
if (!this.isMultiStep)
|
|
157
|
+
return;
|
|
158
|
+
if (this.isFirstStep)
|
|
159
|
+
return;
|
|
160
|
+
this.currentStepIndex--;
|
|
161
|
+
this._notify();
|
|
162
|
+
}
|
|
163
|
+
validate() {
|
|
164
|
+
if (this.isMultiStep) {
|
|
165
|
+
const allErrors = {};
|
|
166
|
+
for (const step of this.visibleSteps) {
|
|
167
|
+
const stepErrors = validateStep(step, this.values, this._validators);
|
|
168
|
+
Object.assign(allErrors, stepErrors);
|
|
169
|
+
}
|
|
170
|
+
this.errors = allErrors;
|
|
171
|
+
this._notify();
|
|
172
|
+
return allErrors;
|
|
173
|
+
}
|
|
174
|
+
const errors = validateFields(this.schema.fields ?? [], this.values, this._validators);
|
|
175
|
+
this.errors = errors;
|
|
176
|
+
this._notify();
|
|
177
|
+
return errors;
|
|
178
|
+
}
|
|
179
|
+
validateField(key) {
|
|
180
|
+
const field = this._allFields.find((f) => f.key === key);
|
|
181
|
+
if (!field)
|
|
182
|
+
return null;
|
|
183
|
+
if (!isVisible(field, this.values))
|
|
184
|
+
return null;
|
|
185
|
+
const error = validateField(field, this.values[key], this.values, this._validators);
|
|
186
|
+
if (error) {
|
|
187
|
+
this.errors[key] = error;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
delete this.errors[key];
|
|
191
|
+
}
|
|
192
|
+
this._notify();
|
|
193
|
+
return error;
|
|
194
|
+
}
|
|
195
|
+
getSubmitValues() {
|
|
196
|
+
const result = {};
|
|
197
|
+
const visibleKeys = new Set();
|
|
198
|
+
if (this.isMultiStep) {
|
|
199
|
+
for (const step of this.visibleSteps) {
|
|
200
|
+
for (const field of step.fields) {
|
|
201
|
+
if (isVisible(field, this.values)) {
|
|
202
|
+
visibleKeys.add(field.key);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
for (const field of this.schema.fields ?? []) {
|
|
209
|
+
if (isVisible(field, this.values)) {
|
|
210
|
+
visibleKeys.add(field.key);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const key of visibleKeys) {
|
|
215
|
+
if (this.values[key] !== undefined) {
|
|
216
|
+
result[key] = this.values[key];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
goToStepWithField(fieldKey) {
|
|
222
|
+
if (!this.isMultiStep)
|
|
223
|
+
return;
|
|
224
|
+
const visibleSteps = this.visibleSteps;
|
|
225
|
+
for (let i = 0; i < visibleSteps.length; i++) {
|
|
226
|
+
const hasField = visibleSteps[i].fields.some((f) => f.key === fieldKey);
|
|
227
|
+
if (hasField) {
|
|
228
|
+
this.currentStepIndex = i;
|
|
229
|
+
this._notify();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Field not found in any visible step: no-op
|
|
234
|
+
}
|
|
235
|
+
reset(values) {
|
|
236
|
+
this.values = {};
|
|
237
|
+
for (const field of this._allFields) {
|
|
238
|
+
if (field.defaultValue !== undefined) {
|
|
239
|
+
this.values[field.key] = field.defaultValue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (values) {
|
|
243
|
+
Object.assign(this.values, values);
|
|
244
|
+
}
|
|
245
|
+
this.errors = {};
|
|
246
|
+
this.topLevelErrors = [];
|
|
247
|
+
this.currentStepIndex = 0;
|
|
248
|
+
this._notify();
|
|
249
|
+
}
|
|
250
|
+
// --- Private ---
|
|
251
|
+
_computeAllFields() {
|
|
252
|
+
if (this.isMultiStep) {
|
|
253
|
+
const steps = this.schema.steps ?? [];
|
|
254
|
+
return steps.flatMap((s) => s.fields);
|
|
255
|
+
}
|
|
256
|
+
return this.schema.fields ?? [];
|
|
257
|
+
}
|
|
258
|
+
_cascadeClearHiddenFields() {
|
|
259
|
+
for (let i = 0; i < MAX_CASCADE_ITERATIONS; i++) {
|
|
260
|
+
let changed = false;
|
|
261
|
+
for (const field of this._allFields) {
|
|
262
|
+
if (!isVisible(field, this.values) && this.values[field.key] !== undefined) {
|
|
263
|
+
delete this.values[field.key];
|
|
264
|
+
delete this.errors[field.key];
|
|
265
|
+
changed = true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!changed)
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
_notify() {
|
|
273
|
+
this._version++;
|
|
274
|
+
for (const listener of this._listeners) {
|
|
275
|
+
listener();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FormEngine } from './form-engine';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { FieldOption, FieldType, FieldValidation, FormAction, FormAnalyticsEvent, FormField, FormSchema, FormStep, ShowCondition, } from './types';
|
|
2
|
+
export { FormEngine, type FormEngineOptions } from './engine';
|
|
3
|
+
export { evaluateCondition, isStepVisible, isVisible } from './visibility';
|
|
4
|
+
export { type ValidatorFn, getDefaultMessage, validateField, validateFields, validateStep, } from './validation';
|
|
5
|
+
export { validateSchema } from './schema-validation';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,SAAS,EACT,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,UAAU,EACV,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG9D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG3E,OAAO,EACL,KAAK,WAAW,EAChB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Engine
|
|
2
|
+
export { FormEngine } from './engine';
|
|
3
|
+
// Visibility
|
|
4
|
+
export { evaluateCondition, isStepVisible, isVisible } from './visibility';
|
|
5
|
+
// Validation
|
|
6
|
+
export { getDefaultMessage, validateField, validateFields, validateStep, } from './validation';
|
|
7
|
+
// Schema validation
|
|
8
|
+
export { validateSchema } from './schema-validation';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema-validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { validateSchema } from './validate-schema';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-schema.d.ts","sourceRoot":"","sources":["../../src/schema-validation/validate-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,UAAU,EAAiB,MAAM,UAAU,CAAC;AA+CrE,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAwC3D"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function extractDependencies(conditions) {
|
|
2
|
+
if (!conditions)
|
|
3
|
+
return [];
|
|
4
|
+
return conditions.map((c) => c.field);
|
|
5
|
+
}
|
|
6
|
+
function getAllFields(schema) {
|
|
7
|
+
if (schema.steps && schema.steps.length > 0) {
|
|
8
|
+
return schema.steps.flatMap((s) => s.fields);
|
|
9
|
+
}
|
|
10
|
+
return schema.fields ?? [];
|
|
11
|
+
}
|
|
12
|
+
function detectCycles(graph) {
|
|
13
|
+
const visited = new Set();
|
|
14
|
+
const inStack = new Set();
|
|
15
|
+
const cycles = [];
|
|
16
|
+
function dfs(node, path) {
|
|
17
|
+
if (inStack.has(node)) {
|
|
18
|
+
const cycleStart = path.indexOf(node);
|
|
19
|
+
cycles.push([...path.slice(cycleStart), node]);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (visited.has(node))
|
|
23
|
+
return;
|
|
24
|
+
visited.add(node);
|
|
25
|
+
inStack.add(node);
|
|
26
|
+
path.push(node);
|
|
27
|
+
const neighbors = graph.get(node) ?? [];
|
|
28
|
+
for (const neighbor of neighbors) {
|
|
29
|
+
dfs(neighbor, path);
|
|
30
|
+
}
|
|
31
|
+
path.pop();
|
|
32
|
+
inStack.delete(node);
|
|
33
|
+
}
|
|
34
|
+
for (const node of graph.keys()) {
|
|
35
|
+
dfs(node, []);
|
|
36
|
+
}
|
|
37
|
+
return cycles;
|
|
38
|
+
}
|
|
39
|
+
export function validateSchema(schema) {
|
|
40
|
+
const warnings = [];
|
|
41
|
+
// Check fields/steps mutual exclusivity
|
|
42
|
+
const hasFields = schema.fields && schema.fields.length > 0;
|
|
43
|
+
const hasSteps = schema.steps && schema.steps.length > 0;
|
|
44
|
+
if (hasFields && hasSteps) {
|
|
45
|
+
warnings.push('Schema has both "fields" and "steps". Only "steps" will be used.');
|
|
46
|
+
}
|
|
47
|
+
// Build dependency graph from show conditions
|
|
48
|
+
const allFields = getAllFields(schema);
|
|
49
|
+
const fieldKeys = new Set(allFields.map((f) => f.key));
|
|
50
|
+
const graph = new Map();
|
|
51
|
+
for (const field of allFields) {
|
|
52
|
+
const deps = [...extractDependencies(field.show), ...extractDependencies(field.showAny)];
|
|
53
|
+
// Warn about references to non-existent fields
|
|
54
|
+
for (const dep of deps) {
|
|
55
|
+
if (!fieldKeys.has(dep)) {
|
|
56
|
+
warnings.push(`Field "${field.key}" has show condition referencing non-existent field "${dep}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (deps.length > 0) {
|
|
60
|
+
graph.set(field.key, deps);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Detect cycles
|
|
64
|
+
const cycles = detectCycles(graph);
|
|
65
|
+
for (const cycle of cycles) {
|
|
66
|
+
warnings.push(`Circular show condition detected: ${cycle.join(' -> ')}`);
|
|
67
|
+
}
|
|
68
|
+
return warnings;
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/types/action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type FormAnalyticsEvent = {
|
|
2
|
+
type: 'step_viewed';
|
|
3
|
+
stepId: string;
|
|
4
|
+
stepIndex: number;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'field_focused';
|
|
7
|
+
fieldKey: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'field_blurred';
|
|
10
|
+
fieldKey: string;
|
|
11
|
+
hasValue: boolean;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'field_error';
|
|
14
|
+
fieldKey: string;
|
|
15
|
+
error: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'step_completed';
|
|
18
|
+
stepId: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'form_submitted';
|
|
21
|
+
fieldCount: number;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'form_abandoned';
|
|
24
|
+
lastStepId: string;
|
|
25
|
+
filledFields: number;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/types/analytics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type FieldType = 'text' | 'email' | 'phone' | 'number' | 'password' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'file' | 'date' | 'textarea';
|
|
2
|
+
export interface FieldOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FormField {
|
|
7
|
+
key: string;
|
|
8
|
+
type: FieldType;
|
|
9
|
+
label: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
helperText?: string;
|
|
12
|
+
defaultValue?: unknown;
|
|
13
|
+
show?: ShowCondition[];
|
|
14
|
+
showAny?: ShowCondition[];
|
|
15
|
+
validation?: FieldValidation;
|
|
16
|
+
options?: FieldOption[];
|
|
17
|
+
optionsFrom?: string;
|
|
18
|
+
optionsDependsOn?: string[];
|
|
19
|
+
accept?: string;
|
|
20
|
+
rows?: number;
|
|
21
|
+
mask?: string;
|
|
22
|
+
inputMode?: 'text' | 'numeric' | 'tel' | 'email';
|
|
23
|
+
}
|
|
24
|
+
export interface ShowCondition {
|
|
25
|
+
field: string;
|
|
26
|
+
eq?: string | number | boolean;
|
|
27
|
+
neq?: string | number | boolean;
|
|
28
|
+
in?: (string | number)[];
|
|
29
|
+
notIn?: (string | number)[];
|
|
30
|
+
notEmpty?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface FieldValidation {
|
|
33
|
+
required?: boolean | string;
|
|
34
|
+
minLength?: number;
|
|
35
|
+
minLengthMessage?: string;
|
|
36
|
+
maxLength?: number;
|
|
37
|
+
maxLengthMessage?: string;
|
|
38
|
+
min?: number;
|
|
39
|
+
minMessage?: string;
|
|
40
|
+
max?: number;
|
|
41
|
+
maxMessage?: string;
|
|
42
|
+
pattern?: string;
|
|
43
|
+
patternMessage?: string;
|
|
44
|
+
matchField?: string;
|
|
45
|
+
matchFieldMessage?: string;
|
|
46
|
+
validator?: string;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/types/field.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,OAAO,GACP,OAAO,GACP,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,aAAa,GACb,UAAU,GACV,OAAO,GACP,QAAQ,GACR,MAAM,GACN,MAAM,GACN,UAAU,CAAC;AAEf,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAChC,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { FormAction } from './action';
|
|
2
|
+
export type { FormAnalyticsEvent } from './analytics';
|
|
3
|
+
export type { FieldOption, FieldType, FieldValidation, FormField, ShowCondition, } from './field';
|
|
4
|
+
export type { FormSchema, FormStep } from './schema';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EACV,WAAW,EACX,SAAS,EACT,eAAe,EACf,SAAS,EACT,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FormAction } from './action';
|
|
2
|
+
import type { FormField, ShowCondition } from './field';
|
|
3
|
+
export interface FormSchema {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
submit: FormAction;
|
|
7
|
+
cancel?: FormAction;
|
|
8
|
+
fields?: FormField[];
|
|
9
|
+
steps?: FormStep[];
|
|
10
|
+
}
|
|
11
|
+
export interface FormStep {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
fields: FormField[];
|
|
16
|
+
show?: ShowCondition[];
|
|
17
|
+
showAny?: ShowCondition[];
|
|
18
|
+
next?: FormAction;
|
|
19
|
+
back?: FormAction | false;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-messages.d.ts","sourceRoot":"","sources":["../../src/validation/default-messages.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAmBxF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function getDefaultMessage(rule, params) {
|
|
2
|
+
switch (rule) {
|
|
3
|
+
case 'required':
|
|
4
|
+
return 'This field is required';
|
|
5
|
+
case 'minLength':
|
|
6
|
+
return `Must be at least ${params?.min} characters`;
|
|
7
|
+
case 'maxLength':
|
|
8
|
+
return `Must be at most ${params?.max} characters`;
|
|
9
|
+
case 'min':
|
|
10
|
+
return `Must be at least ${params?.min}`;
|
|
11
|
+
case 'max':
|
|
12
|
+
return `Must be at most ${params?.max}`;
|
|
13
|
+
case 'pattern':
|
|
14
|
+
return 'Invalid format';
|
|
15
|
+
case 'matchField':
|
|
16
|
+
return 'Fields must match';
|
|
17
|
+
default:
|
|
18
|
+
return 'Invalid value';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FormField } from '../types';
|
|
2
|
+
export type ValidatorFn = (value: unknown, allValues: Record<string, unknown>) => string | null;
|
|
3
|
+
export declare function validateField(field: FormField, value: unknown, allValues: Record<string, unknown>, validators?: Record<string, ValidatorFn>): string | null;
|
|
4
|
+
//# sourceMappingURL=validate-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-field.d.ts","sourceRoot":"","sources":["../../src/validation/validate-field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,UAAU,CAAC;AAG3D,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;AAMhG,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACvC,MAAM,GAAG,IAAI,CA+Df"}
|