@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.
Files changed (61) hide show
  1. package/dist/engine/form-engine.d.ts +45 -0
  2. package/dist/engine/form-engine.d.ts.map +1 -0
  3. package/dist/engine/form-engine.js +278 -0
  4. package/dist/engine/index.d.ts +2 -0
  5. package/dist/engine/index.d.ts.map +1 -0
  6. package/dist/engine/index.js +1 -0
  7. package/dist/index.d.ts +6 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +8 -0
  10. package/dist/schema-validation/index.d.ts +2 -0
  11. package/dist/schema-validation/index.d.ts.map +1 -0
  12. package/dist/schema-validation/index.js +1 -0
  13. package/dist/schema-validation/validate-schema.d.ts +3 -0
  14. package/dist/schema-validation/validate-schema.d.ts.map +1 -0
  15. package/dist/schema-validation/validate-schema.js +69 -0
  16. package/dist/types/action.d.ts +8 -0
  17. package/dist/types/action.d.ts.map +1 -0
  18. package/dist/types/action.js +1 -0
  19. package/dist/types/analytics.d.ts +27 -0
  20. package/dist/types/analytics.d.ts.map +1 -0
  21. package/dist/types/analytics.js +1 -0
  22. package/dist/types/field.d.ts +48 -0
  23. package/dist/types/field.d.ts.map +1 -0
  24. package/dist/types/field.js +1 -0
  25. package/dist/types/index.d.ts +5 -0
  26. package/dist/types/index.d.ts.map +1 -0
  27. package/dist/types/index.js +1 -0
  28. package/dist/types/schema.d.ts +21 -0
  29. package/dist/types/schema.d.ts.map +1 -0
  30. package/dist/types/schema.js +1 -0
  31. package/dist/validation/default-messages.d.ts +2 -0
  32. package/dist/validation/default-messages.d.ts.map +1 -0
  33. package/dist/validation/default-messages.js +20 -0
  34. package/dist/validation/index.d.ts +4 -0
  35. package/dist/validation/index.d.ts.map +1 -0
  36. package/dist/validation/index.js +3 -0
  37. package/dist/validation/validate-field.d.ts +4 -0
  38. package/dist/validation/validate-field.d.ts.map +1 -0
  39. package/dist/validation/validate-field.js +61 -0
  40. package/dist/validation/validate-step.d.ts +5 -0
  41. package/dist/validation/validate-step.d.ts.map +1 -0
  42. package/dist/validation/validate-step.js +26 -0
  43. package/dist/visibility/evaluate-condition.d.ts +3 -0
  44. package/dist/visibility/evaluate-condition.d.ts.map +1 -0
  45. package/dist/visibility/evaluate-condition.js +19 -0
  46. package/dist/visibility/index.d.ts +4 -0
  47. package/dist/visibility/index.d.ts.map +1 -0
  48. package/dist/visibility/index.js +3 -0
  49. package/dist/visibility/is-step-visible.d.ts +3 -0
  50. package/dist/visibility/is-step-visible.d.ts.map +1 -0
  51. package/dist/visibility/is-step-visible.js +4 -0
  52. package/dist/visibility/is-visible.d.ts +3 -0
  53. package/dist/visibility/is-visible.d.ts.map +1 -0
  54. package/dist/visibility/is-visible.js +14 -0
  55. package/fixtures/basic-form.json +46 -0
  56. package/fixtures/conditional-fields.json +84 -0
  57. package/fixtures/conditional-steps.json +79 -0
  58. package/fixtures/dispute-form.json +91 -0
  59. package/fixtures/multi-step-form.json +108 -0
  60. package/fixtures/validation-form.json +62 -0
  61. 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,2 @@
1
+ export { FormEngine, type FormEngineOptions } from './form-engine';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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';
@@ -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,2 @@
1
+ export { validateSchema } from './validate-schema';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ import type { FormSchema } from '../types';
2
+ export declare function validateSchema(schema: FormSchema): string[];
3
+ //# sourceMappingURL=validate-schema.d.ts.map
@@ -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,8 @@
1
+ import type { ShowCondition } from './field';
2
+ export interface FormAction {
3
+ label: string;
4
+ variant?: 'primary' | 'secondary' | 'text';
5
+ action?: string;
6
+ disabled?: ShowCondition[];
7
+ }
8
+ //# sourceMappingURL=action.d.ts.map
@@ -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,2 @@
1
+ export declare function getDefaultMessage(rule: string, params?: Record<string, unknown>): string;
2
+ //# sourceMappingURL=default-messages.d.ts.map
@@ -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,4 @@
1
+ export { getDefaultMessage } from './default-messages';
2
+ export { type ValidatorFn, validateField } from './validate-field';
3
+ export { validateFields, validateStep } from './validate-step';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { getDefaultMessage } from './default-messages';
2
+ export { validateField } from './validate-field';
3
+ export { validateFields, validateStep } from './validate-step';
@@ -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"}