@formhaus/core 0.12.0 → 0.13.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 (45) hide show
  1. package/README.md +1 -0
  2. package/dist/{check-definition-B-YU_XN6.d.ts → check-definition-DuLExofm.d.ts} +2 -2
  3. package/dist/chunk-35SAVPYL.js +2 -0
  4. package/dist/chunk-35SAVPYL.js.map +1 -0
  5. package/dist/chunk-76YV4J3Q.js +3 -0
  6. package/dist/chunk-76YV4J3Q.js.map +1 -0
  7. package/dist/chunk-PBTYUSZR.js +2 -0
  8. package/dist/chunk-PBTYUSZR.js.map +1 -0
  9. package/dist/chunk-PZEBYRGE.js +2 -0
  10. package/dist/chunk-PZEBYRGE.js.map +1 -0
  11. package/dist/chunk-RN5GQHKN.js +5 -0
  12. package/dist/chunk-RN5GQHKN.js.map +1 -0
  13. package/dist/{chunk-KXLWZLOO.js → chunk-YTUVSJUF.js} +3 -3
  14. package/dist/{chunk-KXLWZLOO.js.map → chunk-YTUVSJUF.js.map} +1 -1
  15. package/dist/cloud.d.ts +3 -3
  16. package/dist/cloud.js +1 -1
  17. package/dist/{controller-DXb698eH.d.ts → controller-Ds_kbXSU.d.ts} +1 -1
  18. package/dist/default-messages-yS4QtrYi.d.ts +23 -0
  19. package/dist/{definition-PdtKhxBh.d.ts → definition-B4OIy_wH.d.ts} +44 -1
  20. package/dist/i18n.d.ts +60 -0
  21. package/dist/i18n.js +2 -0
  22. package/dist/i18n.js.map +1 -0
  23. package/dist/index.d.ts +11 -12
  24. package/dist/index.js +1 -1
  25. package/dist/internal.d.ts +24 -6
  26. package/dist/internal.js +1 -1
  27. package/dist/internal.js.map +1 -1
  28. package/dist/{runtime-Dn2_UIxt.d.ts → runtime-BN2PuKD7.d.ts} +6 -2
  29. package/dist/server.d.ts +6 -4
  30. package/dist/server.iife.js +4 -4
  31. package/dist/server.iife.js.map +1 -1
  32. package/dist/server.js +1 -1
  33. package/dist/server.js.map +1 -1
  34. package/dist/validate-field-ND_UxoJU.d.ts +7 -0
  35. package/fixtures/submissions/choices.json +3 -8
  36. package/fixtures/submissions/messages.json +155 -0
  37. package/fixtures/submissions/single-step.json +3 -4
  38. package/package.json +6 -1
  39. package/schema/form-definition.schema.json +236 -0
  40. package/schema/form-translation.schema.json +222 -0
  41. package/dist/chunk-OBVVPPVA.js +0 -3
  42. package/dist/chunk-OBVVPPVA.js.map +0 -1
  43. package/dist/chunk-OZ352IOK.js +0 -5
  44. package/dist/chunk-OZ352IOK.js.map +0 -1
  45. package/dist/validate-field-dZd5yZAp.d.ts +0 -19
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- import {d,g,f}from'./chunk-OZ352IOK.js';export{a as FormDefinitionError,c as checkDefinition,d as parseDefinition}from'./chunk-OZ352IOK.js';import {b,l,m as m$1,B}from'./chunk-OBVVPPVA.js';var V='Pass the values object as the second argument, not the request body with "values" and "skippedSteps".',h=()=>null,m=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);function D(e,t,n){let o=n!==null&&typeof n=="object"&&!Array.isArray(n)?n:{},i=f(e),r=[...new Set(t.map(({key:a})=>a))].filter(a=>m(o,a)),c={values:{},active:new Map};for(let a=0;a<=t.length;a++){let l$1={};for(let s of r)l$1[s]=l(c.active.get(s)??t.find(f=>f.key===s),o[s]);let u=new Map;for(let s of r){let f=i.activeField(s,l$1);f&&u.set(s,f);}let p=r.every(s=>u.get(s)===c.active.get(s));if(c={values:l$1,active:u},p)break}return c}function R({values:e,active:t}){let n=new Map;for(let[o,i]of t){let r=m$1(i,e[o]);r&&n.set(o,r);}return n}function A(e,t){let n=i=>i.map(r=>t.has(r.key)?{...r,validation:void 0}:r),o=e.steps?.map(i=>({...i,fields:n(i.fields)}));return {...e,...e.fields&&{fields:n(e.fields)},...o&&{steps:o}}}function E(e,t={}){let n={};for(let{validation:o}of e)o?.validator&&(n[o.validator]=h);return {...n,...t}}function O(e,t){return t===null||typeof t!="object"||!m(t,"values")?false:!e.some(n=>n.key==="values"||n.key==="skippedSteps")}function M(e,t){if(!Array.isArray(t))return;let n=new Set(t);for(let o=0;o<e.visibleSteps.length;o++){let i=e.visibleSteps[o];i.skip&&n.has(i.id)&&B(e,o);}}function x(e,t,n={}){d(e);let o=b(e),i=D(e,o,t),r=R(i),c={validators:E(o,n.validators),onWarning:()=>{}},a=new g(A(e,r),i.values,c);M(a,n.skippedSteps);let l=a.validate(),u=a.getSubmitValues();for(let[p,s]of r)m(u,p)&&(l[p]=s);return O(o,t)&&(l.values=V),{values:u,errors:l}}export{x as validateSubmission};//# sourceMappingURL=server.js.map
1
+ import {c,b}from'./chunk-PBTYUSZR.js';import {r}from'./chunk-76YV4J3Q.js';import {k}from'./chunk-RN5GQHKN.js';export{a as FormDefinitionError,j as checkDefinition,k as parseDefinition}from'./chunk-RN5GQHKN.js';import {g,d as d$1,p,q}from'./chunk-35SAVPYL.js';var h='Pass the values object as the second argument, not the request body with "values" and "skippedSteps".',D=()=>null,d=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);function A(e,t,s){let o=s!==null&&typeof s=="object"&&!Array.isArray(s)?s:{},n=b(e),r=[...new Set(t.map(({key:l})=>l))].filter(l=>d(o,l)),a={values:{},active:new Map};for(let l=0;l<=t.length;l++){let c={};for(let i of r){let p$1=p(a.active.get(i)??t.find(w=>w.key===i),o[i]);p$1!==void 0&&(c[i]=p$1);}let u=new Map;for(let i of r){let p=n.activeField(i,c);p&&u.set(i,p);}let f=r.every(i=>u.get(i)===a.active.get(i));if(a={values:c,active:u},f)break}return a}function M({values:e,active:t},s){let o=new Map;for(let[n,r]of t){let a=q(r,e[n],s);a&&o.set(n,a);}return o}function E(e,t){let s=n=>n.map(r=>t.has(r.key)?{...r,validation:void 0}:r),o=e.steps?.map(n=>({...n,fields:s(n.fields)}));return {...e,...e.fields&&{fields:s(e.fields)},...o&&{steps:o}}}function O(e,t={}){let s={};for(let{validation:o}of e)o?.validator&&(s[o.validator]=D);return {...s,...t}}function x(e,t){return t===null||typeof t!="object"||!d(t,"values")?false:!e.some(s=>s.key==="values"||s.key==="skippedSteps")}function j(e,t){if(!Array.isArray(t))return;let s=new Set(t);for(let o=0;o<e.visibleSteps.length;o++){let n=e.visibleSteps[o];n.skip&&s.has(n.id)&&r(e,o);}}function I(e,t,s={}){k(e);let o=g(e),n=A(e,o,t),r=d$1(s.messages,e.messages),a=M(n,r),l={validators:O(o,s.validators),messages:r,onWarning:()=>{}},c$1=new c(E(e,a),n.values,l);j(c$1,s.skippedSteps);let u=c$1.validate(),f=c$1.getSubmitValues();for(let[i,p]of a)d(f,i)&&(u[i]=p);return x(o,t)&&(u.values=h),{values:f,errors:u}}export{I as validateSubmission};//# sourceMappingURL=server.js.map
2
2
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/validate-submission.ts"],"names":["BODY_MESSAGE","allow","hasOwn","source","key","activeValues","definition","fields","input","visibility","createVisibility","keys","result","round","values","normalizeValue","field","active","settled","typeErrors","errors","error","typeError","withoutRules","invalid","strip","steps","step","ignoredValidators","given","validators","validation","isBody","applySkips","engine","skippedSteps","requested","index","skipStepAt","validateSubmission","options","parseDefinition","getAllFields","resolved","engineOptions","FormEngine","submitted"],"mappings":"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","file":"server.js","sourcesContent":["import type { FormDefinition, FormField } from '../types';\nimport type { ValidatorFn } from '../validation';\nimport { parseDefinition } from '../definition-validation';\nimport { getAllFields } from '../engine/engine-utils';\nimport type { EngineInternals } from '../engine/runtime-internals';\nimport { FormEngine } from '../engine/runtime';\nimport { skipStepAt } from '../engine/step-skip';\nimport { createVisibility } from '../engine/visibility-state';\nimport { normalizeValue, typeError } from '../validation/type-checks';\n\nexport interface SubmissionOptions {\n skippedSteps?: string[];\n validators?: Record<string, ValidatorFn>;\n}\n\nconst BODY_MESSAGE = 'Pass the values object as the second argument, not the request body with \"values\" and \"skippedSteps\".';\n\nexport interface SubmissionResult {\n values: Record<string, unknown>;\n errors: Record<string, string>;\n}\n\nconst allow: ValidatorFn = () => null;\n\nconst hasOwn = (source: object, key: string) => Object.prototype.hasOwnProperty.call(source, key);\n\ninterface ActiveValues {\n values: Record<string, unknown>;\n active: Map<string, FormField>;\n}\n\nfunction activeValues(definition: FormDefinition, fields: FormField[], input: unknown): ActiveValues {\n const source = input !== null && typeof input === 'object' && !Array.isArray(input) ? input as Record<string, unknown> : {};\n const visibility = createVisibility(definition);\n const keys = [...new Set(fields.map(({ key }) => key))].filter((key) => hasOwn(source, key));\n let result: ActiveValues = { values: {}, active: new Map() };\n for (let round = 0; round <= fields.length; round++) {\n const values: Record<string, unknown> = {};\n for (const key of keys) values[key] = normalizeValue(result.active.get(key) ?? fields.find((field) => field.key === key)!, source[key]);\n const active = new Map<string, FormField>();\n for (const key of keys) {\n const field = visibility.activeField(key, values);\n if (field) active.set(key, field);\n }\n const settled = keys.every((key) => active.get(key) === result.active.get(key));\n result = { values, active };\n if (settled) break;\n }\n return result;\n}\n\nfunction typeErrors({ values, active }: ActiveValues): Map<string, string> {\n const errors = new Map<string, string>();\n for (const [key, field] of active) {\n const error = typeError(field, values[key]);\n if (error) errors.set(key, error);\n }\n return errors;\n}\n\nfunction withoutRules(definition: FormDefinition, invalid: Map<string, string>): FormDefinition {\n const strip = (fields: FormField[]) => fields.map((field) => (invalid.has(field.key) ? { ...field, validation: undefined } : field));\n const steps = definition.steps?.map((step) => ({ ...step, fields: strip(step.fields) }));\n return { ...definition, ...(definition.fields && { fields: strip(definition.fields) }), ...(steps && { steps }) };\n}\n\nfunction ignoredValidators(fields: FormField[], given: Record<string, ValidatorFn> = {}): Record<string, ValidatorFn> {\n const validators: Record<string, ValidatorFn> = {};\n for (const { validation } of fields) if (validation?.validator) validators[validation.validator] = allow;\n return { ...validators, ...given };\n}\n\nfunction isBody(fields: FormField[], input: unknown): boolean {\n if (input === null || typeof input !== 'object' || !hasOwn(input, 'values')) return false;\n return !fields.some((field) => field.key === 'values' || field.key === 'skippedSteps');\n}\n\nfunction applySkips(engine: FormEngine, skippedSteps: unknown): void {\n if (!Array.isArray(skippedSteps)) return;\n const requested = new Set(skippedSteps);\n for (let index = 0; index < engine.visibleSteps.length; index++) {\n const step = engine.visibleSteps[index];\n if (step.skip && requested.has(step.id)) skipStepAt(engine as unknown as EngineInternals, index);\n }\n}\n\nexport function validateSubmission(\n definition: FormDefinition,\n values: unknown,\n options: SubmissionOptions = {},\n): SubmissionResult {\n parseDefinition(definition);\n const fields = getAllFields(definition);\n const resolved = activeValues(definition, fields, values);\n const invalid = typeErrors(resolved);\n const engineOptions = { validators: ignoredValidators(fields, options.validators), onWarning: () => {} };\n const engine = new FormEngine(withoutRules(definition, invalid), resolved.values, engineOptions);\n applySkips(engine, options.skippedSteps);\n const errors = engine.validate();\n const submitted = engine.getSubmitValues();\n for (const [key, error] of invalid) if (hasOwn(submitted, key)) errors[key] = error;\n if (isBody(fields, values)) errors.values = BODY_MESSAGE;\n return { values: submitted, errors };\n}\n"]}
1
+ {"version":3,"sources":["../src/server/validate-submission.ts"],"names":["BODY_MESSAGE","allow","hasOwn","source","key","activeValues","definition","fields","input","visibility","createVisibility","keys","result","round","values","value","normalizeValue","field","active","settled","typeErrors","resolve","errors","error","typeError","withoutRules","invalid","strip","steps","step","ignoredValidators","given","validators","validation","isBody","applySkips","engine","skippedSteps","requested","index","skipStepAt","validateSubmission","options","parseDefinition","getAllFields","resolved","messages","messageResolver","engineOptions","FormEngine","submitted"],"mappings":"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","file":"server.js","sourcesContent":["import type { FormDefinition, FormField } from '../types';\nimport type { MessageSource, ValidatorFn } from '../validation';\nimport { messageResolver, type ResolveMessage } from '../validation/default-messages';\nimport { parseDefinition } from '../definition-validation';\nimport { getAllFields } from '../engine/engine-utils';\nimport type { EngineInternals } from '../engine/runtime-internals';\nimport { FormEngine } from '../engine/runtime';\nimport { skipStepAt } from '../engine/step-skip';\nimport { createVisibility } from '../engine/visibility-state';\nimport { normalizeValue, typeError } from '../validation/type-checks';\n\nexport interface SubmissionOptions {\n skippedSteps?: string[];\n validators?: Record<string, ValidatorFn>;\n messages?: MessageSource;\n}\n\nconst BODY_MESSAGE = 'Pass the values object as the second argument, not the request body with \"values\" and \"skippedSteps\".';\n\nexport interface SubmissionResult {\n values: Record<string, unknown>;\n errors: Record<string, string>;\n}\n\nconst allow: ValidatorFn = () => null;\n\nconst hasOwn = (source: object, key: string) => Object.prototype.hasOwnProperty.call(source, key);\n\ninterface ActiveValues {\n values: Record<string, unknown>;\n active: Map<string, FormField>;\n}\n\nfunction activeValues(definition: FormDefinition, fields: FormField[], input: unknown): ActiveValues {\n const source = input !== null && typeof input === 'object' && !Array.isArray(input) ? input as Record<string, unknown> : {};\n const visibility = createVisibility(definition);\n const keys = [...new Set(fields.map(({ key }) => key))].filter((key) => hasOwn(source, key));\n let result: ActiveValues = { values: {}, active: new Map() };\n for (let round = 0; round <= fields.length; round++) {\n const values: Record<string, unknown> = {};\n for (const key of keys) {\n const value = normalizeValue(result.active.get(key) ?? fields.find((field) => field.key === key)!, source[key]);\n if (value !== undefined) values[key] = value;\n }\n const active = new Map<string, FormField>();\n for (const key of keys) {\n const field = visibility.activeField(key, values);\n if (field) active.set(key, field);\n }\n const settled = keys.every((key) => active.get(key) === result.active.get(key));\n result = { values, active };\n if (settled) break;\n }\n return result;\n}\n\nfunction typeErrors({ values, active }: ActiveValues, resolve: ResolveMessage): Map<string, string> {\n const errors = new Map<string, string>();\n for (const [key, field] of active) {\n const error = typeError(field, values[key], resolve);\n if (error) errors.set(key, error);\n }\n return errors;\n}\n\nfunction withoutRules(definition: FormDefinition, invalid: Map<string, string>): FormDefinition {\n const strip = (fields: FormField[]) => fields.map((field) => (invalid.has(field.key) ? { ...field, validation: undefined } : field));\n const steps = definition.steps?.map((step) => ({ ...step, fields: strip(step.fields) }));\n return { ...definition, ...(definition.fields && { fields: strip(definition.fields) }), ...(steps && { steps }) };\n}\n\nfunction ignoredValidators(fields: FormField[], given: Record<string, ValidatorFn> = {}): Record<string, ValidatorFn> {\n const validators: Record<string, ValidatorFn> = {};\n for (const { validation } of fields) if (validation?.validator) validators[validation.validator] = allow;\n return { ...validators, ...given };\n}\n\nfunction isBody(fields: FormField[], input: unknown): boolean {\n if (input === null || typeof input !== 'object' || !hasOwn(input, 'values')) return false;\n return !fields.some((field) => field.key === 'values' || field.key === 'skippedSteps');\n}\n\nfunction applySkips(engine: FormEngine, skippedSteps: unknown): void {\n if (!Array.isArray(skippedSteps)) return;\n const requested = new Set(skippedSteps);\n for (let index = 0; index < engine.visibleSteps.length; index++) {\n const step = engine.visibleSteps[index];\n if (step.skip && requested.has(step.id)) skipStepAt(engine as unknown as EngineInternals, index);\n }\n}\n\nexport function validateSubmission(\n definition: FormDefinition,\n values: unknown,\n options: SubmissionOptions = {},\n): SubmissionResult {\n parseDefinition(definition);\n const fields = getAllFields(definition);\n const resolved = activeValues(definition, fields, values);\n const messages = messageResolver(options.messages, definition.messages);\n const invalid = typeErrors(resolved, messages);\n const engineOptions = { validators: ignoredValidators(fields, options.validators), messages, onWarning: () => {} };\n const engine = new FormEngine(withoutRules(definition, invalid), resolved.values, engineOptions);\n applySkips(engine, options.skippedSteps);\n const errors = engine.validate();\n const submitted = engine.getSubmitValues();\n for (const [key, error] of invalid) if (hasOwn(submitted, key)) errors[key] = error;\n if (isBody(fields, values)) errors.values = BODY_MESSAGE;\n return { values: submitted, errors };\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { F as FormField } from './definition-B4OIy_wH.js';
2
+ import { M as MessageSource } from './default-messages-yS4QtrYi.js';
3
+
4
+ type ValidatorFn = (value: unknown, allValues: Record<string, unknown>) => string | null;
5
+ declare function validateFieldValue(field: FormField, input: unknown, allValues: Record<string, unknown>, validators?: Record<string, ValidatorFn>, messages?: MessageSource): string | null;
6
+
7
+ export { type ValidatorFn as V, validateFieldValue as v };
@@ -148,7 +148,7 @@
148
148
  }
149
149
  },
150
150
  {
151
- "name": "an empty array is blank only for multiselect",
151
+ "name": "an empty array is a missing answer except in multiselect",
152
152
  "input": {
153
153
  "values": {
154
154
  "tags": [],
@@ -159,14 +159,9 @@
159
159
  },
160
160
  "expected": {
161
161
  "values": {
162
- "tags": [],
163
- "color": [],
164
- "agree": []
162
+ "tags": []
165
163
  },
166
- "errors": {
167
- "color": "Select one of the available options",
168
- "agree": "Must be true or false"
169
- }
164
+ "errors": {}
170
165
  }
171
166
  },
172
167
  {
@@ -0,0 +1,155 @@
1
+ {
2
+ "name": "messages",
3
+ "definition": {
4
+ "id": "anketa",
5
+ "lang": "ru",
6
+ "title": "Анкета",
7
+ "submit": {
8
+ "label": "Отправить"
9
+ },
10
+ "messages": {
11
+ "required": "Обязательное поле",
12
+ "minLength": "Минимум {min} символов",
13
+ "maxItems": "Не более {max}",
14
+ "email": "Введите корректный email",
15
+ "min": "Не меньше {min}"
16
+ },
17
+ "i18n": {
18
+ "messages": {
19
+ "required": "errors.required"
20
+ }
21
+ },
22
+ "fields": [
23
+ {
24
+ "key": "name",
25
+ "type": "text",
26
+ "label": "Имя",
27
+ "i18n": {
28
+ "label": "form.name"
29
+ },
30
+ "validation": {
31
+ "required": true,
32
+ "minLength": 2
33
+ }
34
+ },
35
+ {
36
+ "key": "email",
37
+ "type": "email",
38
+ "label": "Email",
39
+ "validation": {
40
+ "required": "Укажите email"
41
+ }
42
+ },
43
+ {
44
+ "key": "age",
45
+ "type": "number",
46
+ "label": "Возраст",
47
+ "validation": {
48
+ "min": 18
49
+ }
50
+ },
51
+ {
52
+ "key": "topics",
53
+ "type": "multiselect",
54
+ "label": "Темы",
55
+ "options": [
56
+ {
57
+ "value": "news",
58
+ "label": "Новости"
59
+ },
60
+ {
61
+ "value": "events",
62
+ "label": "События"
63
+ },
64
+ {
65
+ "value": "jobs",
66
+ "label": "Вакансии"
67
+ }
68
+ ],
69
+ "validation": {
70
+ "maxLength": 2
71
+ }
72
+ }
73
+ ]
74
+ },
75
+ "cases": [
76
+ {
77
+ "name": "reports catalog messages with params",
78
+ "input": {
79
+ "values": {
80
+ "name": "А",
81
+ "email": "",
82
+ "age": 16,
83
+ "topics": [
84
+ "news",
85
+ "events",
86
+ "jobs"
87
+ ]
88
+ },
89
+ "skippedSteps": []
90
+ },
91
+ "expected": {
92
+ "values": {
93
+ "name": "А",
94
+ "email": "",
95
+ "age": 16,
96
+ "topics": [
97
+ "news",
98
+ "events",
99
+ "jobs"
100
+ ]
101
+ },
102
+ "errors": {
103
+ "name": "Минимум 2 символов",
104
+ "email": "Укажите email",
105
+ "age": "Не меньше 18",
106
+ "topics": "Не более 2"
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "name": "reports a format error from the catalog",
112
+ "input": {
113
+ "values": {
114
+ "name": "Аня",
115
+ "email": "nope"
116
+ },
117
+ "skippedSteps": []
118
+ },
119
+ "expected": {
120
+ "values": {
121
+ "name": "Аня",
122
+ "email": "nope"
123
+ },
124
+ "errors": {
125
+ "email": "Введите корректный email"
126
+ }
127
+ }
128
+ },
129
+ {
130
+ "name": "accepts valid answers",
131
+ "input": {
132
+ "values": {
133
+ "name": "Аня",
134
+ "email": "anya@example.com",
135
+ "age": 30,
136
+ "topics": [
137
+ "news"
138
+ ]
139
+ },
140
+ "skippedSteps": []
141
+ },
142
+ "expected": {
143
+ "values": {
144
+ "name": "Аня",
145
+ "email": "anya@example.com",
146
+ "age": 30,
147
+ "topics": [
148
+ "news"
149
+ ]
150
+ },
151
+ "errors": {}
152
+ }
153
+ }
154
+ ]
155
+ }
@@ -209,7 +209,7 @@
209
209
  }
210
210
  },
211
211
  {
212
- "name": "null is blank and an empty array is a type error",
212
+ "name": "null and an empty array are blank",
213
213
  "input": {
214
214
  "values": {
215
215
  "name": null,
@@ -219,12 +219,11 @@
219
219
  },
220
220
  "expected": {
221
221
  "values": {
222
- "name": null,
223
- "email": []
222
+ "name": null
224
223
  },
225
224
  "errors": {
226
225
  "name": "This field is required",
227
- "email": "Enter a valid email"
226
+ "email": "This field is required"
228
227
  }
229
228
  }
230
229
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formhaus/core",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "JSON form definition engine: validation, conditional visibility, multi-step and branching routes. Zero dependencies, works in React, Vue, Svelte or vanilla.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -27,9 +27,14 @@
27
27
  "types": "./dist/internal.d.ts",
28
28
  "import": "./dist/internal.js"
29
29
  },
30
+ "./i18n": {
31
+ "types": "./dist/i18n.d.ts",
32
+ "import": "./dist/i18n.js"
33
+ },
30
34
  "./server.iife.js": "./dist/server.iife.js",
31
35
  "./style.css": "./style.css",
32
36
  "./schema.json": "./schema/form-definition.schema.json",
37
+ "./translation.schema.json": "./schema/form-translation.schema.json",
33
38
  "./fixtures/submissions/*": "./fixtures/submissions/*",
34
39
  "./fixtures/cloud/*": "./fixtures/cloud/*"
35
40
  },
@@ -16,6 +16,10 @@
16
16
  "type": "string",
17
17
  "description": "Unique form identifier."
18
18
  },
19
+ "lang": {
20
+ "type": "string",
21
+ "description": "Language of the texts, a BCP 47 tag such as \"en\" or \"pt-BR\". Renderers set it as the lang attribute of the form."
22
+ },
19
23
  "title": {
20
24
  "type": "string",
21
25
  "description": "Form title for host UI and Figma output. Renderers do not display it."
@@ -25,6 +29,18 @@
25
29
  "description": "Plain text shown under the title by the host UI and Figma output. Renderers do not display it. Up to 1000 characters.",
26
30
  "maxLength": 1000
27
31
  },
32
+ "messages": {
33
+ "$ref": "#/definitions/MessageCatalog",
34
+ "description": "Validation message templates by message key. {min} and {max} are replaced. Field-level messages win over these."
35
+ },
36
+ "labels": {
37
+ "$ref": "#/definitions/LabelCatalog",
38
+ "description": "Renderer label templates: continue, back, stepOf ({current}, {total}) and errorSummary."
39
+ },
40
+ "i18n": {
41
+ "$ref": "#/definitions/FormI18n",
42
+ "description": "Translation keys for the form texts and the messages and labels catalogs, resolved by the app translate function."
43
+ },
28
44
  "submit": {
29
45
  "$ref": "#/definitions/FormAction",
30
46
  "description": "Submit button on the last step."
@@ -56,6 +72,130 @@
56
72
  "additionalProperties": false,
57
73
  "description": "A Formhaus form. Use fields for a single-step form or steps for a multi-step form, not both.",
58
74
  "definitions": {
75
+ "MessageCatalog": {
76
+ "type": "object",
77
+ "properties": {
78
+ "required": {
79
+ "type": "string",
80
+ "minLength": 1
81
+ },
82
+ "minLength": {
83
+ "type": "string",
84
+ "minLength": 1
85
+ },
86
+ "maxLength": {
87
+ "type": "string",
88
+ "minLength": 1
89
+ },
90
+ "minItems": {
91
+ "type": "string",
92
+ "minLength": 1
93
+ },
94
+ "maxItems": {
95
+ "type": "string",
96
+ "minLength": 1
97
+ },
98
+ "min": {
99
+ "type": "string",
100
+ "minLength": 1
101
+ },
102
+ "max": {
103
+ "type": "string",
104
+ "minLength": 1
105
+ },
106
+ "pattern": {
107
+ "type": "string",
108
+ "minLength": 1
109
+ },
110
+ "matchField": {
111
+ "type": "string",
112
+ "minLength": 1
113
+ },
114
+ "email": {
115
+ "type": "string",
116
+ "minLength": 1
117
+ },
118
+ "number": {
119
+ "type": "string",
120
+ "minLength": 1
121
+ },
122
+ "date": {
123
+ "type": "string",
124
+ "minLength": 1
125
+ },
126
+ "datetime": {
127
+ "type": "string",
128
+ "minLength": 1
129
+ },
130
+ "option": {
131
+ "type": "string",
132
+ "minLength": 1
133
+ },
134
+ "options": {
135
+ "type": "string",
136
+ "minLength": 1
137
+ },
138
+ "boolean": {
139
+ "type": "string",
140
+ "minLength": 1
141
+ },
142
+ "string": {
143
+ "type": "string",
144
+ "minLength": 1
145
+ },
146
+ "file": {
147
+ "type": "string",
148
+ "minLength": 1
149
+ },
150
+ "value": {
151
+ "type": "string",
152
+ "minLength": 1
153
+ }
154
+ },
155
+ "additionalProperties": false
156
+ },
157
+ "LabelCatalog": {
158
+ "type": "object",
159
+ "properties": {
160
+ "continue": {
161
+ "type": "string",
162
+ "minLength": 1
163
+ },
164
+ "back": {
165
+ "type": "string",
166
+ "minLength": 1
167
+ },
168
+ "stepOf": {
169
+ "type": "string",
170
+ "minLength": 1
171
+ },
172
+ "errorSummary": {
173
+ "type": "string",
174
+ "minLength": 1
175
+ }
176
+ },
177
+ "additionalProperties": false
178
+ },
179
+ "FormI18n": {
180
+ "type": "object",
181
+ "properties": {
182
+ "title": {
183
+ "type": "string",
184
+ "minLength": 1
185
+ },
186
+ "description": {
187
+ "type": "string",
188
+ "minLength": 1
189
+ },
190
+ "messages": {
191
+ "$ref": "#/definitions/MessageCatalog"
192
+ },
193
+ "labels": {
194
+ "$ref": "#/definitions/LabelCatalog"
195
+ }
196
+ },
197
+ "additionalProperties": false
198
+ },
59
199
  "FormAction": {
60
200
  "type": "object",
61
201
  "properties": {
@@ -75,6 +215,10 @@
75
215
  "items": {
76
216
  "$ref": "#/definitions/ShowCondition"
77
217
  }
218
+ },
219
+ "i18n": {
220
+ "$ref": "#/definitions/LabelI18n",
221
+ "description": "Translation key for the button label."
78
222
  }
79
223
  },
80
224
  "required": [
@@ -154,6 +298,16 @@
154
298
  "minProperties": 2,
155
299
  "maxProperties": 2
156
300
  },
301
+ "LabelI18n": {
302
+ "type": "object",
303
+ "properties": {
304
+ "label": {
305
+ "type": "string",
306
+ "minLength": 1
307
+ }
308
+ },
309
+ "additionalProperties": false
310
+ },
157
311
  "FormField": {
158
312
  "type": "object",
159
313
  "properties": {
@@ -263,6 +417,10 @@
263
417
  "url",
264
418
  "search"
265
419
  ]
420
+ },
421
+ "i18n": {
422
+ "$ref": "#/definitions/FieldI18n",
423
+ "description": "Translation keys for label, placeholder and helperText. The literal texts stay the fallback."
266
424
  }
267
425
  },
268
426
  "required": [
@@ -350,6 +508,44 @@
350
508
  },
351
509
  "validator": {
352
510
  "type": "string"
511
+ },
512
+ "i18n": {
513
+ "$ref": "#/definitions/ValidationI18n",
514
+ "description": "Translation keys for the messages of the validation rules, by rule name."
515
+ }
516
+ },
517
+ "additionalProperties": false
518
+ },
519
+ "ValidationI18n": {
520
+ "type": "object",
521
+ "properties": {
522
+ "required": {
523
+ "type": "string",
524
+ "minLength": 1
525
+ },
526
+ "minLength": {
527
+ "type": "string",
528
+ "minLength": 1
529
+ },
530
+ "maxLength": {
531
+ "type": "string",
532
+ "minLength": 1
533
+ },
534
+ "min": {
535
+ "type": "string",
536
+ "minLength": 1
537
+ },
538
+ "max": {
539
+ "type": "string",
540
+ "minLength": 1
541
+ },
542
+ "pattern": {
543
+ "type": "string",
544
+ "minLength": 1
545
+ },
546
+ "matchField": {
547
+ "type": "string",
548
+ "minLength": 1
353
549
  }
354
550
  },
355
551
  "additionalProperties": false
@@ -362,6 +558,10 @@
362
558
  },
363
559
  "label": {
364
560
  "type": "string"
561
+ },
562
+ "i18n": {
563
+ "$ref": "#/definitions/LabelI18n",
564
+ "description": "Translation key for the option label."
365
565
  }
366
566
  },
367
567
  "required": [
@@ -370,6 +570,24 @@
370
570
  ],
371
571
  "additionalProperties": false
372
572
  },
573
+ "FieldI18n": {
574
+ "type": "object",
575
+ "properties": {
576
+ "label": {
577
+ "type": "string",
578
+ "minLength": 1
579
+ },
580
+ "placeholder": {
581
+ "type": "string",
582
+ "minLength": 1
583
+ },
584
+ "helperText": {
585
+ "type": "string",
586
+ "minLength": 1
587
+ }
588
+ },
589
+ "additionalProperties": false
590
+ },
373
591
  "FormStep": {
374
592
  "type": "object",
375
593
  "properties": {
@@ -437,6 +655,10 @@
437
655
  "skip": {
438
656
  "$ref": "#/definitions/FormAction",
439
657
  "description": "Shows a Skip button that resets the step fields and moves forward without validation."
658
+ },
659
+ "i18n": {
660
+ "$ref": "#/definitions/StepI18n",
661
+ "description": "Translation keys for the step title and description. The literal texts stay the fallback."
440
662
  }
441
663
  },
442
664
  "required": [
@@ -476,6 +698,20 @@
476
698
  ],
477
699
  "additionalProperties": false,
478
700
  "description": "A forward destination for a step."
701
+ },
702
+ "StepI18n": {
703
+ "type": "object",
704
+ "properties": {
705
+ "title": {
706
+ "type": "string",
707
+ "minLength": 1
708
+ },
709
+ "description": {
710
+ "type": "string",
711
+ "minLength": 1
712
+ }
713
+ },
714
+ "additionalProperties": false
479
715
  }
480
716
  }
481
717
  }