@formhaus/core 0.3.0 → 0.3.1

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +170 -6
  3. package/dist/index.js +1 -8
  4. package/package.json +31 -17
  5. package/dist/engine/form-engine.d.ts +0 -48
  6. package/dist/engine/form-engine.d.ts.map +0 -1
  7. package/dist/engine/form-engine.js +0 -298
  8. package/dist/engine/index.d.ts +0 -2
  9. package/dist/engine/index.d.ts.map +0 -1
  10. package/dist/engine/index.js +0 -1
  11. package/dist/index.d.ts.map +0 -1
  12. package/dist/schema-validation/index.d.ts +0 -2
  13. package/dist/schema-validation/index.d.ts.map +0 -1
  14. package/dist/schema-validation/index.js +0 -1
  15. package/dist/schema-validation/validate-schema.d.ts +0 -3
  16. package/dist/schema-validation/validate-schema.d.ts.map +0 -1
  17. package/dist/schema-validation/validate-schema.js +0 -79
  18. package/dist/types/action.d.ts +0 -8
  19. package/dist/types/action.d.ts.map +0 -1
  20. package/dist/types/action.js +0 -1
  21. package/dist/types/analytics.d.ts +0 -27
  22. package/dist/types/analytics.d.ts.map +0 -1
  23. package/dist/types/analytics.js +0 -1
  24. package/dist/types/field.d.ts +0 -49
  25. package/dist/types/field.d.ts.map +0 -1
  26. package/dist/types/field.js +0 -1
  27. package/dist/types/index.d.ts +0 -5
  28. package/dist/types/index.d.ts.map +0 -1
  29. package/dist/types/index.js +0 -1
  30. package/dist/types/schema.d.ts +0 -21
  31. package/dist/types/schema.d.ts.map +0 -1
  32. package/dist/types/schema.js +0 -1
  33. package/dist/validation/default-messages.d.ts +0 -2
  34. package/dist/validation/default-messages.d.ts.map +0 -1
  35. package/dist/validation/default-messages.js +0 -20
  36. package/dist/validation/index.d.ts +0 -4
  37. package/dist/validation/index.d.ts.map +0 -1
  38. package/dist/validation/index.js +0 -3
  39. package/dist/validation/validate-field.d.ts +0 -4
  40. package/dist/validation/validate-field.d.ts.map +0 -1
  41. package/dist/validation/validate-field.js +0 -67
  42. package/dist/validation/validate-step.d.ts +0 -5
  43. package/dist/validation/validate-step.d.ts.map +0 -1
  44. package/dist/validation/validate-step.js +0 -26
  45. package/dist/visibility/evaluate-condition.d.ts +0 -3
  46. package/dist/visibility/evaluate-condition.d.ts.map +0 -1
  47. package/dist/visibility/evaluate-condition.js +0 -19
  48. package/dist/visibility/index.d.ts +0 -4
  49. package/dist/visibility/index.d.ts.map +0 -1
  50. package/dist/visibility/index.js +0 -3
  51. package/dist/visibility/is-step-visible.d.ts +0 -3
  52. package/dist/visibility/is-step-visible.d.ts.map +0 -1
  53. package/dist/visibility/is-step-visible.js +0 -4
  54. package/dist/visibility/is-visible.d.ts +0 -3
  55. package/dist/visibility/is-visible.d.ts.map +0 -1
  56. package/dist/visibility/is-visible.js +0 -14
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 formhaus contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,170 @@
1
- export type { DefaultFieldType, 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
1
+ type DefaultFieldType = 'text' | 'email' | 'phone' | 'number' | 'password' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'file' | 'date' | 'textarea';
2
+ type FieldType = DefaultFieldType | (string & {});
3
+ interface FieldOption {
4
+ value: string;
5
+ label: string;
6
+ }
7
+ interface FormField {
8
+ key: string;
9
+ type: FieldType;
10
+ label: string;
11
+ placeholder?: string;
12
+ helperText?: string;
13
+ defaultValue?: unknown;
14
+ show?: ShowCondition[];
15
+ showAny?: ShowCondition[];
16
+ validation?: FieldValidation;
17
+ options?: FieldOption[];
18
+ optionsFrom?: string;
19
+ optionsDependsOn?: string[];
20
+ accept?: string;
21
+ rows?: number;
22
+ mask?: string;
23
+ inputMode?: 'text' | 'numeric' | 'tel' | 'email';
24
+ }
25
+ interface ShowCondition {
26
+ field: string;
27
+ eq?: string | number | boolean;
28
+ neq?: string | number | boolean;
29
+ in?: (string | number)[];
30
+ notIn?: (string | number)[];
31
+ notEmpty?: boolean;
32
+ }
33
+ interface FieldValidation {
34
+ required?: boolean | string;
35
+ minLength?: number;
36
+ minLengthMessage?: string;
37
+ maxLength?: number;
38
+ maxLengthMessage?: string;
39
+ min?: number;
40
+ minMessage?: string;
41
+ max?: number;
42
+ maxMessage?: string;
43
+ pattern?: string;
44
+ patternMessage?: string;
45
+ matchField?: string;
46
+ matchFieldMessage?: string;
47
+ validator?: string;
48
+ }
49
+
50
+ interface FormAction {
51
+ label: string;
52
+ variant?: 'primary' | 'secondary' | 'text';
53
+ action?: string;
54
+ disabled?: ShowCondition[];
55
+ }
56
+
57
+ type FormAnalyticsEvent = {
58
+ type: 'step_viewed';
59
+ stepId: string;
60
+ stepIndex: number;
61
+ } | {
62
+ type: 'field_focused';
63
+ fieldKey: string;
64
+ } | {
65
+ type: 'field_blurred';
66
+ fieldKey: string;
67
+ hasValue: boolean;
68
+ } | {
69
+ type: 'field_error';
70
+ fieldKey: string;
71
+ error: string;
72
+ } | {
73
+ type: 'step_completed';
74
+ stepId: string;
75
+ } | {
76
+ type: 'form_submitted';
77
+ fieldCount: number;
78
+ } | {
79
+ type: 'form_abandoned';
80
+ lastStepId: string;
81
+ filledFields: number;
82
+ };
83
+
84
+ interface FormDefinition {
85
+ id: string;
86
+ title: string;
87
+ submit: FormAction;
88
+ cancel?: FormAction;
89
+ fields?: FormField[];
90
+ steps?: FormStep[];
91
+ }
92
+ interface FormStep {
93
+ id: string;
94
+ title: string;
95
+ description?: string;
96
+ fields: FormField[];
97
+ show?: ShowCondition[];
98
+ showAny?: ShowCondition[];
99
+ next?: FormAction;
100
+ back?: FormAction | false;
101
+ }
102
+
103
+ declare function getDefaultMessage(rule: string, params?: Record<string, unknown>): string;
104
+
105
+ type ValidatorFn = (value: unknown, allValues: Record<string, unknown>) => string | null;
106
+ declare function validateField(field: FormField, value: unknown, allValues: Record<string, unknown>, validators?: Record<string, ValidatorFn>): string | null;
107
+
108
+ declare function validateStep(step: FormStep, values: Record<string, unknown>, validators?: Record<string, ValidatorFn>): Record<string, string>;
109
+ declare function validateFields(fields: FormField[], values: Record<string, unknown>, validators?: Record<string, ValidatorFn>): Record<string, string>;
110
+
111
+ type StepValidateFn = (stepId: string, values: Record<string, unknown>) => Promise<Record<string, string> | null | void>;
112
+ interface FormEngineOptions {
113
+ validators?: Record<string, ValidatorFn>;
114
+ onStepValidate?: StepValidateFn;
115
+ }
116
+ declare class FormEngine {
117
+ readonly definition: FormDefinition;
118
+ values: Record<string, unknown>;
119
+ errors: Record<string, string>;
120
+ topLevelErrors: string[];
121
+ currentStepIndex: number;
122
+ fieldLoading: Record<string, boolean>;
123
+ stepValidating: boolean;
124
+ private _version;
125
+ private _listeners;
126
+ private _validators;
127
+ private _onStepValidate?;
128
+ private _allFields;
129
+ private _isDirty;
130
+ private _cache;
131
+ constructor(definition: FormDefinition, initialValues?: Record<string, unknown>, options?: FormEngineOptions);
132
+ subscribe(listener: () => void): () => void;
133
+ getSnapshot(): number;
134
+ get isMultiStep(): boolean;
135
+ get visibleSteps(): FormStep[];
136
+ get currentStep(): FormStep | null;
137
+ get visibleFields(): FormField[];
138
+ get isFirstStep(): boolean;
139
+ get isLastStep(): boolean;
140
+ get canGoNext(): boolean;
141
+ get progress(): {
142
+ current: number;
143
+ total: number;
144
+ };
145
+ setValue(key: string, value: unknown): void;
146
+ setErrors(errors: Record<string, string>): void;
147
+ setFieldLoading(key: string, loading: boolean): void;
148
+ nextStep(): boolean;
149
+ nextStepAsync(): Promise<boolean>;
150
+ prevStep(): void;
151
+ validate(): Record<string, string>;
152
+ validateField(key: string): string | null;
153
+ getSubmitValues(): Record<string, unknown>;
154
+ goToStepWithField(fieldKey: string): void;
155
+ reset(values?: Record<string, unknown>): void;
156
+ private _computeAllFields;
157
+ private _cascadeClearHiddenFields;
158
+ private _recomputeIfDirty;
159
+ private _notify;
160
+ }
161
+
162
+ declare function evaluateCondition(condition: ShowCondition, values: Record<string, unknown>): boolean;
163
+
164
+ declare function isStepVisible(step: FormStep, values: Record<string, unknown>): boolean;
165
+
166
+ declare function isVisible(field: Pick<FormField, 'show' | 'showAny'>, values: Record<string, unknown>): boolean;
167
+
168
+ declare function validateDefinition(definition: FormDefinition): string[];
169
+
170
+ export { type DefaultFieldType, type FieldOption, type FieldType, type FieldValidation, type FormAction, type FormAnalyticsEvent, type FormDefinition, FormEngine, type FormEngineOptions, type FormField, type FormStep, type ShowCondition, type StepValidateFn, type ValidatorFn, evaluateCondition, getDefaultMessage, isStepVisible, isVisible, validateDefinition, validateField, validateFields, validateStep };
package/dist/index.js CHANGED
@@ -1,8 +1 @@
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';
1
+ function S(r){return r?r.map(e=>e.field):[]}function x(r){return (r.steps?.length??0)>0?r.steps.flatMap(e=>e.fields):r.fields??[]}function w(r){let e=new Set,t=new Set,s=[];function i(n,a){if(t.has(n)){let o=a.indexOf(n);s.push([...a.slice(o),n]);return}if(e.has(n))return;e.add(n),t.add(n),a.push(n);let d=r.get(n)??[];for(let o of d)i(o,a);a.pop(),t.delete(n);}for(let n of r.keys())i(n,[]);return s}function g(r){let e=[],t=(r.fields?.length??0)>0,s=(r.steps?.length??0)>0;t&&s&&e.push('Definition has both "fields" and "steps". Only "steps" will be used.');let i=x(r),n=new Set,a=new Map;for(let o of i)n.has(o.key)&&e.push(`Duplicate field key "${o.key}" \u2014 fields will share state and overwrite each other`),n.add(o.key);for(let o of i){let F=[...S(o.show),...S(o.showAny)];for(let v of F)n.has(v)||e.push(`Field "${o.key}" has show condition referencing non-existent field "${v}"`);if(o.validation?.pattern!==void 0)try{new RegExp(o.validation.pattern);}catch{e.push(`Field "${o.key}" has invalid regex pattern: "${o.validation.pattern}"`);}F.length>0&&a.set(o.key,F);}let d=w(a);for(let o of d)e.push(`Circular show condition detected: ${o.join(" -> ")}`);return e}function f(r,e){switch(r){case "required":return "This field is required";case "minLength":return `Must be at least ${e?.min} ${e?.unit??"characters"}`;case "maxLength":return `Must be at most ${e?.max} ${e?.unit??"characters"}`;case "min":return `Must be at least ${e?.min}`;case "max":return `Must be at most ${e?.max}`;case "pattern":return "Invalid format";case "matchField":return "Fields must match";default:return "Invalid value"}}function _(r){return !!(r==null||r===""||Array.isArray(r)&&r.length===0)}function u(r,e,t,s){let i=r.validation;if(!i)return null;if(i.required&&_(e))return typeof i.required=="string"?i.required:f("required");if(_(e))return null;if(i.minLength!==void 0&&(typeof e=="string"||Array.isArray(e))&&e.length<i.minLength){let n=Array.isArray(e)?"items":void 0;return i.minLengthMessage??f("minLength",{min:i.minLength,unit:n})}if(i.maxLength!==void 0&&(typeof e=="string"||Array.isArray(e))&&e.length>i.maxLength){let n=Array.isArray(e)?"items":void 0;return i.maxLengthMessage??f("maxLength",{max:i.maxLength,unit:n})}if(i.min!==void 0&&typeof e=="number"&&e<i.min)return i.minMessage??f("min",{min:i.min});if(i.max!==void 0&&typeof e=="number"&&e>i.max)return i.maxMessage??f("max",{max:i.max});if(i.pattern!==void 0)try{if(!new RegExp(i.pattern).test(String(e)))return i.patternMessage??f("pattern")}catch{}if(i.matchField!==void 0){let n=t[i.matchField];if(e!==n)return i.matchFieldMessage??f("matchField")}if(i.validator!==void 0&&s){let n=s[i.validator];if(n)return n(e,t)}return null}function c(r,e){let t=e[r.field];return r.eq!==void 0?t===r.eq:r.neq!==void 0?t!==r.neq:r.in!==void 0?r.in.includes(t):r.notIn!==void 0?!r.notIn.includes(t):r.notEmpty===true?t!=null&&t!=="":true}function b(r,e,t){return !r||r.length===0?true:e==="and"?r.every(s=>c(s,t)):r.some(s=>c(s,t))}function l(r,e){let t=b(r.show,"and",e),s=b(r.showAny,"or",e);return t&&s}function p(r,e){return l(r,e)}function h(r,e,t){let s={};for(let i of r.fields){if(!l(i,e))continue;let n=u(i,e[i.key],e,t);n&&(s[i.key]=n);}return s}function m(r,e,t){let s={};for(let i of r){if(!l(i,e))continue;let n=u(i,e[i.key],e,t);n&&(s[i.key]=n);}return s}var k=50,y=class{constructor(e,t,s){let i=(e.fields?.length??0)>0,n=(e.steps?.length??0)>0;if(i&&n)throw new Error('FormDefinition cannot have both "fields" and "steps" as non-empty arrays.');let a=g(e);if(a.length>0)for(let d of a)console.warn(`[FormEngine] ${d}`);this.definition=e,this._validators=s?.validators??{},this._onStepValidate=s?.onStepValidate,this._version=0,this._listeners=new Set,this.currentStepIndex=0,this.errors={},this.topLevelErrors=[],this.fieldLoading={},this.stepValidating=false,this._allFields=this._computeAllFields(),this._isDirty=true,this._cache={visibleSteps:[],currentStep:null,visibleFields:[],canGoNext:false},this.values={};for(let d of this._allFields)d.defaultValue!==void 0&&(this.values[d.key]=d.defaultValue);t&&Object.assign(this.values,t);}subscribe(e){return this._listeners.add(e),()=>{this._listeners.delete(e);}}getSnapshot(){return this._version}get isMultiStep(){return (this.definition.steps??[]).length>0}get visibleSteps(){return this._recomputeIfDirty(),this._cache.visibleSteps}get currentStep(){return this._recomputeIfDirty(),this._cache.currentStep}get visibleFields(){return this._recomputeIfDirty(),this._cache.visibleFields}get isFirstStep(){return this.currentStepIndex===0}get isLastStep(){return this.currentStepIndex>=this.visibleSteps.length-1}get canGoNext(){return this._recomputeIfDirty(),this._cache.canGoNext}get progress(){let e=this.visibleSteps.length;return {current:Math.min(this.currentStepIndex+1,e),total:e}}setValue(e,t){this.values[e]=t,delete this.errors[e],this._cascadeClearHiddenFields(),this._notify();}setErrors(e){this.errors={},this.topLevelErrors=[];for(let[t,s]of Object.entries(e)){let i=this._allFields.find(n=>n.key===t);i&&l(i,this.values)?this.errors[t]=s:this.topLevelErrors.push(s);}if(this.isMultiStep){let t=Object.keys(e).find(s=>this.errors[s]!==void 0);t&&this.goToStepWithField(t);}this._notify();}setFieldLoading(e,t){t?this.fieldLoading[e]=true:delete this.fieldLoading[e],this._notify();}nextStep(){if(!this.isMultiStep)return false;let e=this.currentStep;if(!e)return false;let t=h(e,this.values,this._validators);return Object.keys(t).length>0?(Object.assign(this.errors,t),this._notify(),false):this.isLastStep?false:(this.currentStepIndex++,this._notify(),true)}async nextStepAsync(){if(!this.isMultiStep||this.stepValidating)return false;let e=this.currentStep;if(!e)return false;let t=h(e,this.values,this._validators);if(Object.keys(t).length>0)return Object.assign(this.errors,t),this._notify(),false;if(!this._onStepValidate)return this.isLastStep?false:(this.currentStepIndex++,this._notify(),true);let s=this.currentStepIndex;this.stepValidating=true,this._notify();try{let i=await this._onStepValidate(e.id,this.values);if(this.currentStepIndex!==s)return this.stepValidating=!1,this._notify(),!1;if(i&&Object.keys(i).length>0){this.errors={},this.topLevelErrors=[];for(let[n,a]of Object.entries(i)){let d=this._allFields.find(o=>o.key===n);d&&l(d,this.values)?this.errors[n]=a:this.topLevelErrors.push(a);}return this.stepValidating=!1,this._notify(),!1}return this.isLastStep?(this.stepValidating=!1,this._notify(),!1):(this.stepValidating=!1,this.currentStepIndex++,this._notify(),!0)}catch(i){throw this.stepValidating=false,this._notify(),i}}prevStep(){this.isMultiStep&&(this.isFirstStep||(this.currentStepIndex--,this._notify()));}validate(){if(this.isMultiStep){let t={};for(let s of this.visibleSteps){let i=h(s,this.values,this._validators);Object.assign(t,i);}return this.errors=t,this._notify(),t}let e=m(this.definition.fields??[],this.values,this._validators);return this.errors=e,this._notify(),e}validateField(e){let t=this._allFields.find(i=>i.key===e);if(!t||!l(t,this.values))return null;let s=u(t,this.values[e],this.values,this._validators);return s?this.errors[e]=s:delete this.errors[e],this._notify(),s}getSubmitValues(){let e={},t=new Set;if(this.isMultiStep)for(let s of this.visibleSteps)for(let i of s.fields)l(i,this.values)&&t.add(i.key);else for(let s of this.definition.fields??[])l(s,this.values)&&t.add(s.key);for(let s of t)this.values[s]!==void 0&&(e[s]=this.values[s]);return e}goToStepWithField(e){if(!this.isMultiStep)return;let t=this.visibleSteps;for(let s=0;s<t.length;s++)if(t[s].fields.some(n=>n.key===e)){this.currentStepIndex=s,this._notify();return}}reset(e){this.values={};for(let t of this._allFields)t.defaultValue!==void 0&&(this.values[t.key]=t.defaultValue);e&&Object.assign(this.values,e),this.errors={},this.topLevelErrors=[],this.currentStepIndex=0,this._notify();}_computeAllFields(){return this.isMultiStep?(this.definition.steps??[]).flatMap(t=>t.fields):this.definition.fields??[]}_cascadeClearHiddenFields(){let e=new Set;if(this.isMultiStep){for(let t of this.definition.steps??[])if(!p(t,this.values))for(let s of t.fields)e.add(s.key);}for(let t=0;t<k;t++){let s=false;for(let i of this._allFields)(!l(i,this.values)||e.has(i.key))&&this.values[i.key]!==void 0&&(delete this.values[i.key],delete this.errors[i.key],s=true);if(!s)break}}_recomputeIfDirty(){if(!this._isDirty)return;if(this._isDirty=false,!this.isMultiStep){this._cache.visibleSteps=[],this._cache.currentStep=null,this._cache.visibleFields=(this.definition.fields??[]).filter(n=>l(n,this.values)),this._cache.canGoNext=false;return}let e=this.definition.steps??[];this._cache.visibleSteps=e.filter(n=>p(n,this.values));let t=this._cache.visibleSteps;this._cache.currentStep=t[this.currentStepIndex]??t[t.length-1]??null;let s=this._cache.currentStep;this._cache.visibleFields=s?s.fields.filter(n=>l(n,this.values)):[];let i=s?h(s,this.values,this._validators):{};this._cache.canGoNext=Object.keys(i).length===0;}_notify(){this._isDirty=true,this._version++;for(let e of this._listeners)e();}};export{y as FormEngine,c as evaluateCondition,f as getDefaultMessage,p as isStepVisible,l as isVisible,g as validateDefinition,u as validateField,m as validateFields,h as validateStep};
package/package.json CHANGED
@@ -1,33 +1,47 @@
1
1
  {
2
2
  "name": "@formhaus/core",
3
- "version": "0.3.0",
4
- "description": "Framework-agnostic JSON-schema-driven form engine with validation, conditional visibility, and multi-step support",
3
+ "version": "0.3.1",
4
+ "description": "Framework-agnostic form engine with validation, conditional visibility, and multi-step support. Define forms as plain JSON, render anywhere.",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
6
+ "sideEffects": false,
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
9
10
  "exports": {
10
11
  ".": {
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
13
14
  }
14
15
  },
15
- "files": ["dist", "fixtures"],
16
- "scripts": {
17
- "build": "tsc",
18
- "test": "vitest run",
19
- "test:watch": "vitest",
20
- "clean": "rm -rf dist"
21
- },
16
+ "files": [
17
+ "dist",
18
+ "fixtures"
19
+ ],
22
20
  "devDependencies": {
21
+ "tsup": "^8.5.1",
23
22
  "typescript": "^5.7.3",
24
23
  "vitest": "^4.1.2"
25
24
  },
26
25
  "license": "MIT",
27
26
  "repository": {
28
27
  "type": "git",
29
- "url": "https://github.com/ignsm/formhaus.git",
28
+ "url": "git+https://github.com/ignsm/formhaus.git",
30
29
  "directory": "packages/core"
31
30
  },
32
- "keywords": ["form", "json-schema", "validation", "form-engine", "multi-step", "conditional-forms"]
33
- }
31
+ "keywords": [
32
+ "form",
33
+ "form-engine",
34
+ "form-builder",
35
+ "validation",
36
+ "multi-step",
37
+ "conditional-forms",
38
+ "json"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "test:resolve": "node --input-type=module -e \"import('./dist/index.js').then(m => { if (!m.FormEngine) process.exit(1); console.log('ESM resolve OK'); })\"",
45
+ "clean": "rm -rf dist"
46
+ }
47
+ }
@@ -1,48 +0,0 @@
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
- private _isDirty;
18
- private _cache;
19
- constructor(schema: FormSchema, initialValues?: Record<string, unknown>, options?: FormEngineOptions);
20
- subscribe(listener: () => void): () => void;
21
- getSnapshot(): number;
22
- get isMultiStep(): boolean;
23
- get visibleSteps(): FormStep[];
24
- get currentStep(): FormStep | null;
25
- get visibleFields(): FormField[];
26
- get isFirstStep(): boolean;
27
- get isLastStep(): boolean;
28
- get canGoNext(): boolean;
29
- get progress(): {
30
- current: number;
31
- total: number;
32
- };
33
- setValue(key: string, value: unknown): void;
34
- setErrors(errors: Record<string, string>): void;
35
- setFieldLoading(key: string, loading: boolean): void;
36
- nextStep(): boolean;
37
- prevStep(): void;
38
- validate(): Record<string, string>;
39
- validateField(key: string): string | null;
40
- getSubmitValues(): Record<string, unknown>;
41
- goToStepWithField(fieldKey: string): void;
42
- reset(values?: Record<string, unknown>): void;
43
- private _computeAllFields;
44
- private _cascadeClearHiddenFields;
45
- private _recomputeIfDirty;
46
- private _notify;
47
- }
48
- //# sourceMappingURL=form-engine.d.ts.map
@@ -1 +0,0 @@
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;IAChC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,MAAM,CAKZ;gBAGA,MAAM,EAAE,UAAU,EAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,OAAO,CAAC,EAAE,iBAAiB;IA4C7B,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,CAG7B;IAED,IAAI,WAAW,IAAI,QAAQ,GAAG,IAAI,CAGjC;IAED,IAAI,aAAa,IAAI,SAAS,EAAE,CAG/B;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,SAAS,IAAI,OAAO,CAGvB;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;IA0B/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;IA4BjC,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,OAAO;CAOhB"}
@@ -1,298 +0,0 @@
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?.length ?? 0) > 0;
9
- const hasSteps = (schema.steps?.length ?? 0) > 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
- this._isDirty = true;
30
- this._cache = { visibleSteps: [], currentStep: null, visibleFields: [], canGoNext: false };
31
- // Initialize values with defaults, then overlay initialValues
32
- this.values = {};
33
- for (const field of this._allFields) {
34
- if (field.defaultValue !== undefined) {
35
- this.values[field.key] = field.defaultValue;
36
- }
37
- }
38
- if (initialValues) {
39
- Object.assign(this.values, initialValues);
40
- }
41
- }
42
- // --- React integration ---
43
- subscribe(listener) {
44
- this._listeners.add(listener);
45
- return () => {
46
- this._listeners.delete(listener);
47
- };
48
- }
49
- getSnapshot() {
50
- return this._version;
51
- }
52
- // --- Computed getters ---
53
- get isMultiStep() {
54
- return (this.schema.steps ?? []).length > 0;
55
- }
56
- get visibleSteps() {
57
- this._recomputeIfDirty();
58
- return this._cache.visibleSteps;
59
- }
60
- get currentStep() {
61
- this._recomputeIfDirty();
62
- return this._cache.currentStep;
63
- }
64
- get visibleFields() {
65
- this._recomputeIfDirty();
66
- return this._cache.visibleFields;
67
- }
68
- get isFirstStep() {
69
- return this.currentStepIndex === 0;
70
- }
71
- get isLastStep() {
72
- return this.currentStepIndex >= this.visibleSteps.length - 1;
73
- }
74
- get canGoNext() {
75
- this._recomputeIfDirty();
76
- return this._cache.canGoNext;
77
- }
78
- get progress() {
79
- const total = this.visibleSteps.length;
80
- return { current: Math.min(this.currentStepIndex + 1, total), total };
81
- }
82
- // --- Mutations ---
83
- setValue(key, value) {
84
- this.values[key] = value;
85
- // Clear error for this field
86
- delete this.errors[key];
87
- // Cascade: clear hidden fields, loop until stable
88
- this._cascadeClearHiddenFields();
89
- this._notify();
90
- }
91
- setErrors(errors) {
92
- this.errors = {};
93
- this.topLevelErrors = [];
94
- for (const [key, message] of Object.entries(errors)) {
95
- // Check if field is visible
96
- const field = this._allFields.find((f) => f.key === key);
97
- if (field && isVisible(field, this.values)) {
98
- this.errors[key] = message;
99
- }
100
- else {
101
- // Field is hidden or doesn't exist: surface as top-level error
102
- this.topLevelErrors.push(message);
103
- }
104
- }
105
- // Navigate to step containing first visible error
106
- if (this.isMultiStep) {
107
- const firstErrorKey = Object.keys(errors).find((k) => this.errors[k] !== undefined);
108
- if (firstErrorKey) {
109
- this.goToStepWithField(firstErrorKey);
110
- }
111
- }
112
- this._notify();
113
- }
114
- setFieldLoading(key, loading) {
115
- if (loading) {
116
- this.fieldLoading[key] = true;
117
- }
118
- else {
119
- delete this.fieldLoading[key];
120
- }
121
- this._notify();
122
- }
123
- nextStep() {
124
- if (!this.isMultiStep)
125
- return false;
126
- const step = this.currentStep;
127
- if (!step)
128
- return false;
129
- // Validate current step
130
- const stepErrors = validateStep(step, this.values, this._validators);
131
- if (Object.keys(stepErrors).length > 0) {
132
- Object.assign(this.errors, stepErrors);
133
- this._notify();
134
- return false;
135
- }
136
- // Move to next visible step
137
- if (this.isLastStep)
138
- return false;
139
- this.currentStepIndex++;
140
- this._notify();
141
- return true;
142
- }
143
- prevStep() {
144
- if (!this.isMultiStep)
145
- return;
146
- if (this.isFirstStep)
147
- return;
148
- this.currentStepIndex--;
149
- this._notify();
150
- }
151
- validate() {
152
- if (this.isMultiStep) {
153
- const allErrors = {};
154
- for (const step of this.visibleSteps) {
155
- const stepErrors = validateStep(step, this.values, this._validators);
156
- Object.assign(allErrors, stepErrors);
157
- }
158
- this.errors = allErrors;
159
- this._notify();
160
- return allErrors;
161
- }
162
- const errors = validateFields(this.schema.fields ?? [], this.values, this._validators);
163
- this.errors = errors;
164
- this._notify();
165
- return errors;
166
- }
167
- validateField(key) {
168
- const field = this._allFields.find((f) => f.key === key);
169
- if (!field)
170
- return null;
171
- if (!isVisible(field, this.values))
172
- return null;
173
- const error = validateField(field, this.values[key], this.values, this._validators);
174
- if (error) {
175
- this.errors[key] = error;
176
- }
177
- else {
178
- delete this.errors[key];
179
- }
180
- this._notify();
181
- return error;
182
- }
183
- getSubmitValues() {
184
- const result = {};
185
- const visibleKeys = new Set();
186
- if (this.isMultiStep) {
187
- for (const step of this.visibleSteps) {
188
- for (const field of step.fields) {
189
- if (isVisible(field, this.values)) {
190
- visibleKeys.add(field.key);
191
- }
192
- }
193
- }
194
- }
195
- else {
196
- for (const field of this.schema.fields ?? []) {
197
- if (isVisible(field, this.values)) {
198
- visibleKeys.add(field.key);
199
- }
200
- }
201
- }
202
- for (const key of visibleKeys) {
203
- if (this.values[key] !== undefined) {
204
- result[key] = this.values[key];
205
- }
206
- }
207
- return result;
208
- }
209
- goToStepWithField(fieldKey) {
210
- if (!this.isMultiStep)
211
- return;
212
- const visibleSteps = this.visibleSteps;
213
- for (let i = 0; i < visibleSteps.length; i++) {
214
- const hasField = visibleSteps[i].fields.some((f) => f.key === fieldKey);
215
- if (hasField) {
216
- this.currentStepIndex = i;
217
- this._notify();
218
- return;
219
- }
220
- }
221
- // Field not found in any visible step: no-op
222
- }
223
- reset(values) {
224
- this.values = {};
225
- for (const field of this._allFields) {
226
- if (field.defaultValue !== undefined) {
227
- this.values[field.key] = field.defaultValue;
228
- }
229
- }
230
- if (values) {
231
- Object.assign(this.values, values);
232
- }
233
- this.errors = {};
234
- this.topLevelErrors = [];
235
- this.currentStepIndex = 0;
236
- this._notify();
237
- }
238
- // --- Private ---
239
- _computeAllFields() {
240
- if (this.isMultiStep) {
241
- const steps = this.schema.steps ?? [];
242
- return steps.flatMap((s) => s.fields);
243
- }
244
- return this.schema.fields ?? [];
245
- }
246
- _cascadeClearHiddenFields() {
247
- const hiddenStepFields = new Set();
248
- if (this.isMultiStep) {
249
- for (const step of this.schema.steps ?? []) {
250
- if (!isStepVisible(step, this.values)) {
251
- for (const field of step.fields) {
252
- hiddenStepFields.add(field.key);
253
- }
254
- }
255
- }
256
- }
257
- for (let i = 0; i < MAX_CASCADE_ITERATIONS; i++) {
258
- let changed = false;
259
- for (const field of this._allFields) {
260
- const fieldHidden = !isVisible(field, this.values) || hiddenStepFields.has(field.key);
261
- if (fieldHidden && this.values[field.key] !== undefined) {
262
- delete this.values[field.key];
263
- delete this.errors[field.key];
264
- changed = true;
265
- }
266
- }
267
- if (!changed)
268
- break;
269
- }
270
- }
271
- _recomputeIfDirty() {
272
- if (!this._isDirty)
273
- return;
274
- this._isDirty = false;
275
- if (!this.isMultiStep) {
276
- this._cache.visibleSteps = [];
277
- this._cache.currentStep = null;
278
- this._cache.visibleFields = (this.schema.fields ?? []).filter((f) => isVisible(f, this.values));
279
- this._cache.canGoNext = false;
280
- return;
281
- }
282
- const steps = this.schema.steps ?? [];
283
- this._cache.visibleSteps = steps.filter((s) => isStepVisible(s, this.values));
284
- const vs = this._cache.visibleSteps;
285
- this._cache.currentStep = vs[this.currentStepIndex] ?? vs[vs.length - 1] ?? null;
286
- const step = this._cache.currentStep;
287
- this._cache.visibleFields = step ? step.fields.filter((f) => isVisible(f, this.values)) : [];
288
- const stepErrors = step ? validateStep(step, this.values, this._validators) : {};
289
- this._cache.canGoNext = Object.keys(stepErrors).length === 0;
290
- }
291
- _notify() {
292
- this._isDirty = true;
293
- this._version++;
294
- for (const listener of this._listeners) {
295
- listener();
296
- }
297
- }
298
- }
@@ -1,2 +0,0 @@
1
- export { FormEngine, type FormEngineOptions } from './form-engine';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- export { FormEngine } from './form-engine';
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,gBAAgB,EAChB,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"}
@@ -1,2 +0,0 @@
1
- export { validateSchema } from './validate-schema';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema-validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1 +0,0 @@
1
- export { validateSchema } from './validate-schema';
@@ -1,3 +0,0 @@
1
- import type { FormSchema } from '../types';
2
- export declare function validateSchema(schema: FormSchema): string[];
3
- //# sourceMappingURL=validate-schema.d.ts.map
@@ -1 +0,0 @@
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,CAqD3D"}
@@ -1,79 +0,0 @@
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?.length ?? 0) > 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
- const hasFields = (schema.fields?.length ?? 0) > 0;
42
- const hasSteps = (schema.steps?.length ?? 0) > 0;
43
- if (hasFields && hasSteps) {
44
- warnings.push('Schema has both "fields" and "steps". Only "steps" will be used.');
45
- }
46
- const allFields = getAllFields(schema);
47
- const fieldKeys = new Set();
48
- const graph = new Map();
49
- for (const field of allFields) {
50
- if (fieldKeys.has(field.key)) {
51
- warnings.push(`Duplicate field key "${field.key}" — fields will share state and overwrite each other`);
52
- }
53
- fieldKeys.add(field.key);
54
- }
55
- for (const field of allFields) {
56
- const deps = [...extractDependencies(field.show), ...extractDependencies(field.showAny)];
57
- for (const dep of deps) {
58
- if (!fieldKeys.has(dep)) {
59
- warnings.push(`Field "${field.key}" has show condition referencing non-existent field "${dep}"`);
60
- }
61
- }
62
- if (field.validation?.pattern !== undefined) {
63
- try {
64
- new RegExp(field.validation.pattern);
65
- }
66
- catch {
67
- warnings.push(`Field "${field.key}" has invalid regex pattern: "${field.validation.pattern}"`);
68
- }
69
- }
70
- if (deps.length > 0) {
71
- graph.set(field.key, deps);
72
- }
73
- }
74
- const cycles = detectCycles(graph);
75
- for (const cycle of cycles) {
76
- warnings.push(`Circular show condition detected: ${cycle.join(' -> ')}`);
77
- }
78
- return warnings;
79
- }
@@ -1,8 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,27 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,49 +0,0 @@
1
- export type DefaultFieldType = 'text' | 'email' | 'phone' | 'number' | 'password' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'file' | 'date' | 'textarea';
2
- export type FieldType = DefaultFieldType | (string & {});
3
- export interface FieldOption {
4
- value: string;
5
- label: string;
6
- }
7
- export interface FormField {
8
- key: string;
9
- type: FieldType;
10
- label: string;
11
- placeholder?: string;
12
- helperText?: string;
13
- defaultValue?: unknown;
14
- show?: ShowCondition[];
15
- showAny?: ShowCondition[];
16
- validation?: FieldValidation;
17
- options?: FieldOption[];
18
- optionsFrom?: string;
19
- optionsDependsOn?: string[];
20
- accept?: string;
21
- rows?: number;
22
- mask?: string;
23
- inputMode?: 'text' | 'numeric' | 'tel' | 'email';
24
- }
25
- export interface ShowCondition {
26
- field: string;
27
- eq?: string | number | boolean;
28
- neq?: string | number | boolean;
29
- in?: (string | number)[];
30
- notIn?: (string | number)[];
31
- notEmpty?: boolean;
32
- }
33
- export interface FieldValidation {
34
- required?: boolean | string;
35
- minLength?: number;
36
- minLengthMessage?: string;
37
- maxLength?: number;
38
- maxLengthMessage?: string;
39
- min?: number;
40
- minMessage?: string;
41
- max?: number;
42
- maxMessage?: string;
43
- pattern?: string;
44
- patternMessage?: string;
45
- matchField?: string;
46
- matchFieldMessage?: string;
47
- validator?: string;
48
- }
49
- //# sourceMappingURL=field.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/types/field.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,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,MAAM,SAAS,GAAG,gBAAgB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEzD,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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- export type { FormAction } from './action';
2
- export type { FormAnalyticsEvent } from './analytics';
3
- export type { DefaultFieldType, FieldOption, FieldType, FieldValidation, FormField, ShowCondition, } from './field';
4
- export type { FormSchema, FormStep } from './schema';
5
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
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,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,eAAe,EACf,SAAS,EACT,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export declare function getDefaultMessage(rule: string, params?: Record<string, unknown>): string;
2
- //# sourceMappingURL=default-messages.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,20 +0,0 @@
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} ${params?.unit ?? 'characters'}`;
7
- case 'maxLength':
8
- return `Must be at most ${params?.max} ${params?.unit ?? '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
- }
@@ -1,4 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,3 +0,0 @@
1
- export { getDefaultMessage } from './default-messages';
2
- export { validateField } from './validate-field';
3
- export { validateFields, validateStep } from './validate-step';
@@ -1,4 +0,0 @@
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
@@ -1 +0,0 @@
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;AAQhG,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,CAiEf"}
@@ -1,67 +0,0 @@
1
- import { getDefaultMessage } from './default-messages';
2
- function isEmpty(value) {
3
- if (value === undefined || value === null || value === '')
4
- return true;
5
- if (Array.isArray(value) && value.length === 0)
6
- return true;
7
- return false;
8
- }
9
- export function validateField(field, value, allValues, validators) {
10
- const rules = field.validation;
11
- if (!rules)
12
- return null;
13
- if (rules.required) {
14
- if (isEmpty(value)) {
15
- return typeof rules.required === 'string' ? rules.required : getDefaultMessage('required');
16
- }
17
- }
18
- // Skip remaining rules if value is empty and not required
19
- if (isEmpty(value))
20
- return null;
21
- if (rules.minLength !== undefined && (typeof value === 'string' || Array.isArray(value))) {
22
- if (value.length < rules.minLength) {
23
- const unit = Array.isArray(value) ? 'items' : undefined;
24
- return rules.minLengthMessage ?? getDefaultMessage('minLength', { min: rules.minLength, unit });
25
- }
26
- }
27
- if (rules.maxLength !== undefined && (typeof value === 'string' || Array.isArray(value))) {
28
- if (value.length > rules.maxLength) {
29
- const unit = Array.isArray(value) ? 'items' : undefined;
30
- return rules.maxLengthMessage ?? getDefaultMessage('maxLength', { max: rules.maxLength, unit });
31
- }
32
- }
33
- if (rules.min !== undefined && typeof value === 'number') {
34
- if (value < rules.min) {
35
- return rules.minMessage ?? getDefaultMessage('min', { min: rules.min });
36
- }
37
- }
38
- if (rules.max !== undefined && typeof value === 'number') {
39
- if (value > rules.max) {
40
- return rules.maxMessage ?? getDefaultMessage('max', { max: rules.max });
41
- }
42
- }
43
- if (rules.pattern !== undefined) {
44
- try {
45
- const regex = new RegExp(rules.pattern);
46
- if (!regex.test(String(value))) {
47
- return rules.patternMessage ?? getDefaultMessage('pattern');
48
- }
49
- }
50
- catch {
51
- // Invalid regex: skip validation silently
52
- }
53
- }
54
- if (rules.matchField !== undefined) {
55
- const otherValue = allValues[rules.matchField];
56
- if (value !== otherValue) {
57
- return rules.matchFieldMessage ?? getDefaultMessage('matchField');
58
- }
59
- }
60
- if (rules.validator !== undefined && validators) {
61
- const customValidator = validators[rules.validator];
62
- if (customValidator) {
63
- return customValidator(value, allValues);
64
- }
65
- }
66
- return null;
67
- }
@@ -1,5 +0,0 @@
1
- import type { FormField, FormStep } from '../types';
2
- import { type ValidatorFn } from './validate-field';
3
- export declare function validateStep(step: FormStep, values: Record<string, unknown>, validators?: Record<string, ValidatorFn>): Record<string, string>;
4
- export declare function validateFields(fields: FormField[], values: Record<string, unknown>, validators?: Record<string, ValidatorFn>): Record<string, string>;
5
- //# sourceMappingURL=validate-step.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-step.d.ts","sourceRoot":"","sources":["../../src/validation/validate-step.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpD,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,kBAAkB,CAAC;AAEnE,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACvC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,EAAE,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACvC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB"}
@@ -1,26 +0,0 @@
1
- import { isVisible } from '../visibility';
2
- import { validateField } from './validate-field';
3
- export function validateStep(step, values, validators) {
4
- const errors = {};
5
- for (const field of step.fields) {
6
- if (!isVisible(field, values))
7
- continue;
8
- const error = validateField(field, values[field.key], values, validators);
9
- if (error) {
10
- errors[field.key] = error;
11
- }
12
- }
13
- return errors;
14
- }
15
- export function validateFields(fields, values, validators) {
16
- const errors = {};
17
- for (const field of fields) {
18
- if (!isVisible(field, values))
19
- continue;
20
- const error = validateField(field, values[field.key], values, validators);
21
- if (error) {
22
- errors[field.key] = error;
23
- }
24
- }
25
- return errors;
26
- }
@@ -1,3 +0,0 @@
1
- import type { ShowCondition } from '../types';
2
- export declare function evaluateCondition(condition: ShowCondition, values: Record<string, unknown>): boolean;
3
- //# sourceMappingURL=evaluate-condition.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"evaluate-condition.d.ts","sourceRoot":"","sources":["../../src/visibility/evaluate-condition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAwBT"}
@@ -1,19 +0,0 @@
1
- export function evaluateCondition(condition, values) {
2
- const value = values[condition.field];
3
- if (condition.eq !== undefined) {
4
- return value === condition.eq;
5
- }
6
- if (condition.neq !== undefined) {
7
- return value !== condition.neq;
8
- }
9
- if (condition.in !== undefined) {
10
- return condition.in.includes(value);
11
- }
12
- if (condition.notIn !== undefined) {
13
- return !condition.notIn.includes(value);
14
- }
15
- if (condition.notEmpty === true) {
16
- return value !== undefined && value !== null && value !== '';
17
- }
18
- return true;
19
- }
@@ -1,4 +0,0 @@
1
- export { evaluateCondition } from './evaluate-condition';
2
- export { isStepVisible } from './is-step-visible';
3
- export { isVisible } from './is-visible';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visibility/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
@@ -1,3 +0,0 @@
1
- export { evaluateCondition } from './evaluate-condition';
2
- export { isStepVisible } from './is-step-visible';
3
- export { isVisible } from './is-visible';
@@ -1,3 +0,0 @@
1
- import type { FormStep } from '../types';
2
- export declare function isStepVisible(step: FormStep, values: Record<string, unknown>): boolean;
3
- //# sourceMappingURL=is-step-visible.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"is-step-visible.d.ts","sourceRoot":"","sources":["../../src/visibility/is-step-visible.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGzC,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAEtF"}
@@ -1,4 +0,0 @@
1
- import { isVisible } from './is-visible';
2
- export function isStepVisible(step, values) {
3
- return isVisible(step, values);
4
- }
@@ -1,3 +0,0 @@
1
- import type { FormField } from '../types';
2
- export declare function isVisible(field: Pick<FormField, 'show' | 'showAny'>, values: Record<string, unknown>): boolean;
3
- //# sourceMappingURL=is-visible.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"is-visible.d.ts","sourceRoot":"","sources":["../../src/visibility/is-visible.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,UAAU,CAAC;AAgBzD,wBAAgB,SAAS,CACvB,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,EAC1C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAIT"}
@@ -1,14 +0,0 @@
1
- import { evaluateCondition } from './evaluate-condition';
2
- function evaluateConditions(conditions, mode, values) {
3
- if (!conditions || conditions.length === 0)
4
- return true;
5
- if (mode === 'and') {
6
- return conditions.every((c) => evaluateCondition(c, values));
7
- }
8
- return conditions.some((c) => evaluateCondition(c, values));
9
- }
10
- export function isVisible(field, values) {
11
- const showResult = evaluateConditions(field.show, 'and', values);
12
- const showAnyResult = evaluateConditions(field.showAny, 'or', values);
13
- return showResult && showAnyResult;
14
- }