@evanschleret/formforgeclient 1.2.4 → 2.0.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 (83) hide show
  1. package/README.md +10 -0
  2. package/dist/module.cjs +1 -0
  3. package/dist/module.d.cts +1 -0
  4. package/dist/module.d.mts +1 -0
  5. package/dist/module.d.ts +1 -0
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +1 -0
  8. package/dist/runtime/api/client.js +4 -2
  9. package/dist/runtime/api/request.d.ts +1 -0
  10. package/dist/runtime/api/schema.js +4 -4
  11. package/dist/runtime/assets/formforge.css +1 -0
  12. package/dist/runtime/composables/index.d.ts +1 -1
  13. package/dist/runtime/composables/useFormForgeBuilder.d.ts +24 -2
  14. package/dist/runtime/composables/useFormForgeBuilder.js +299 -43
  15. package/dist/runtime/composables/useFormForgeForm.js +15 -5
  16. package/dist/runtime/composables/useFormForgeI18n.d.ts +299 -19
  17. package/dist/runtime/composables/useFormForgeI18n.js +299 -19
  18. package/dist/runtime/composables/useFormForgeSubmit.js +31 -9
  19. package/dist/runtime/index.d.ts +1 -0
  20. package/dist/runtime/renderers/default/FormForgeBuilder.d.vue.ts +21 -2
  21. package/dist/runtime/renderers/default/FormForgeBuilder.vue +689 -738
  22. package/dist/runtime/renderers/default/FormForgeBuilder.vue.d.ts +21 -2
  23. package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.d.vue.ts +17 -0
  24. package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue +32 -0
  25. package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue.d.ts +17 -0
  26. package/dist/runtime/renderers/default/FormForgeRenderer.d.vue.ts +3 -4
  27. package/dist/runtime/renderers/default/FormForgeRenderer.vue +344 -294
  28. package/dist/runtime/renderers/default/FormForgeRenderer.vue.d.ts +3 -4
  29. package/dist/runtime/renderers/default/FormForgeRendererField.d.vue.ts +22 -0
  30. package/dist/runtime/renderers/default/FormForgeRendererField.vue +237 -0
  31. package/dist/runtime/renderers/default/FormForgeRendererField.vue.d.ts +22 -0
  32. package/dist/runtime/renderers/default/FormForgeRendererPage.d.vue.ts +18 -0
  33. package/dist/runtime/renderers/default/FormForgeRendererPage.vue +31 -0
  34. package/dist/runtime/renderers/default/FormForgeRendererPage.vue.d.ts +18 -0
  35. package/dist/runtime/renderers/default/FormForgeResponse.vue +4 -3
  36. package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.d.vue.ts +11 -0
  37. package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue +118 -0
  38. package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue.d.ts +11 -0
  39. package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.d.vue.ts +46 -0
  40. package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue +205 -0
  41. package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue.d.ts +46 -0
  42. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.d.vue.ts +11 -0
  43. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue +37 -0
  44. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue.d.ts +11 -0
  45. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.d.vue.ts +11 -0
  46. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue +195 -0
  47. package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue.d.ts +11 -0
  48. package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.d.vue.ts +14 -0
  49. package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue +91 -0
  50. package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue.d.ts +14 -0
  51. package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.d.vue.ts +13 -0
  52. package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue +387 -0
  53. package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue.d.ts +13 -0
  54. package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.d.vue.ts +44 -0
  55. package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue +328 -0
  56. package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue.d.ts +44 -0
  57. package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.d.vue.ts +11 -0
  58. package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue +47 -0
  59. package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue.d.ts +11 -0
  60. package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.d.vue.ts +14 -0
  61. package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue +595 -0
  62. package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue.d.ts +14 -0
  63. package/dist/runtime/renderers/default/builder/builderFieldHelpers.d.ts +3 -0
  64. package/dist/runtime/renderers/default/builder/builderFieldHelpers.js +4 -0
  65. package/dist/runtime/types/index.d.ts +1 -1
  66. package/dist/runtime/types/management.d.ts +12 -0
  67. package/dist/runtime/types/schema.d.ts +72 -4
  68. package/dist/runtime/utils/defaults.d.ts +7 -0
  69. package/dist/runtime/utils/defaults.js +86 -0
  70. package/dist/runtime/utils/page-logic.d.ts +24 -0
  71. package/dist/runtime/utils/page-logic.js +351 -0
  72. package/dist/runtime/utils/rich-text.d.ts +3 -0
  73. package/dist/runtime/utils/rich-text.js +72 -0
  74. package/dist/runtime/utils/schema.d.ts +1 -1
  75. package/dist/runtime/utils/schema.js +70 -16
  76. package/dist/runtime/utils/temporal.d.ts +10 -0
  77. package/dist/runtime/utils/temporal.js +28 -0
  78. package/dist/runtime/utils/validation.d.ts +5 -0
  79. package/dist/runtime/utils/validation.js +36 -0
  80. package/dist/runtime/validation/zod.d.ts +5 -2
  81. package/dist/runtime/validation/zod.js +563 -54
  82. package/dist/types.d.mts +2 -0
  83. package/package.json +18 -14
@@ -1,95 +1,558 @@
1
1
  import { z } from "zod";
2
- const scalarOptionSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
3
- function applySharedStringRules(baseSchema, rules) {
2
+ import { createDefaultAddressFields } from "../utils/defaults.js";
3
+ import { resolveTemporalMode } from "../utils/temporal.js";
4
+ function normalizeLocale(locale) {
5
+ if (locale === void 0 || locale === "") {
6
+ return "en";
7
+ }
8
+ return locale.toLowerCase().startsWith("fr") ? "fr" : "en";
9
+ }
10
+ function resolveLocaleMessages(locale) {
11
+ const normalizedLocale = normalizeLocale(locale);
12
+ if (normalizedLocale === "fr") {
13
+ return {
14
+ required: "Ce champ est requis",
15
+ validationFailed: "La valeur ne respecte pas les r\xE8gles de validation",
16
+ invalidString: "Veuillez saisir un texte valide",
17
+ invalidNumber: "Veuillez saisir un nombre valide",
18
+ invalidBoolean: "Veuillez saisir une r\xE9ponse valide",
19
+ invalidSelection: "Veuillez s\xE9lectionner une option valide",
20
+ invalidAddress: "Veuillez saisir une adresse valide",
21
+ invalidFile: "Veuillez s\xE9lectionner un fichier valide",
22
+ invalidDate: "Veuillez saisir une date valide",
23
+ invalidTime: "Veuillez saisir une heure valide",
24
+ invalidEmail: "Veuillez saisir une adresse e-mail valide",
25
+ minLength: (value) => `Veuillez saisir au moins ${value} caract\xE8res`,
26
+ maxLength: (value) => `Veuillez saisir au plus ${value} caract\xE8res`,
27
+ minItems: (value) => `Veuillez s\xE9lectionner au moins ${value} \xE9l\xE9ments`,
28
+ maxItems: (value) => `Veuillez s\xE9lectionner au plus ${value} \xE9l\xE9ments`,
29
+ minValue: (value) => `Veuillez saisir une valeur sup\xE9rieure ou \xE9gale \xE0 ${value}`,
30
+ maxValue: (value) => `Veuillez saisir une valeur inf\xE9rieure ou \xE9gale \xE0 ${value}`,
31
+ textMin: (label, value) => `${label} doit contenir au moins ${value} caract\xE8res`,
32
+ textMax: (label, value) => `${label} doit contenir au plus ${value} caract\xE8res`,
33
+ textRegex: (label) => `${label} doit respecter le format attendu`,
34
+ textEquals: (label, value) => `${label} doit \xEAtre \xE9gal \xE0 ${value}`,
35
+ textNotEquals: (label, value) => `${label} ne doit pas \xEAtre \xE9gal \xE0 ${value}`,
36
+ textContains: (label, value) => `${label} doit contenir ${value}`,
37
+ textNotContains: (label, value) => `${label} ne doit pas contenir ${value}`,
38
+ temporalAfter: (label, value) => `${label} doit \xEAtre post\xE9rieur \xE0 ${value}`,
39
+ temporalBefore: (label, value) => `${label} doit \xEAtre ant\xE9rieur \xE0 ${value}`,
40
+ temporalBetween: (label, start, end) => `${label} doit \xEAtre compris entre ${start} et ${end}`,
41
+ temporalNotBetween: (label, start, end) => `${label} ne doit pas \xEAtre compris entre ${start} et ${end}`
42
+ };
43
+ }
44
+ return {
45
+ required: "This field is required",
46
+ validationFailed: "The value does not meet the validation rules",
47
+ invalidString: "Please enter a valid text value",
48
+ invalidNumber: "Please enter a valid number",
49
+ invalidBoolean: "Please enter a valid response",
50
+ invalidSelection: "Please select a valid option",
51
+ invalidAddress: "Please enter a valid address",
52
+ invalidFile: "Please select a valid file",
53
+ invalidDate: "Please enter a valid date",
54
+ invalidTime: "Please enter a valid time",
55
+ invalidEmail: "Please enter a valid email address",
56
+ minLength: (value) => `Please enter at least ${value} characters`,
57
+ maxLength: (value) => `Please enter no more than ${value} characters`,
58
+ minItems: (value) => `Please select at least ${value} items`,
59
+ maxItems: (value) => `Please select no more than ${value} items`,
60
+ minValue: (value) => `Please enter a value greater than or equal to ${value}`,
61
+ maxValue: (value) => `Please enter a value less than or equal to ${value}`,
62
+ textMin: (label, value) => `${label} must contain at least ${value} characters`,
63
+ textMax: (label, value) => `${label} must contain no more than ${value} characters`,
64
+ textRegex: (label) => `${label} must match the expected format`,
65
+ textEquals: (label, value) => `${label} must be equal to ${value}`,
66
+ textNotEquals: (label, value) => `${label} must not be equal to ${value}`,
67
+ textContains: (label, value) => `${label} must contain ${value}`,
68
+ textNotContains: (label, value) => `${label} must not contain ${value}`,
69
+ temporalAfter: (label, value) => `${label} must be after ${value}`,
70
+ temporalBefore: (label, value) => `${label} must be before ${value}`,
71
+ temporalBetween: (label, start, end) => `${label} must be between ${start} and ${end}`,
72
+ temporalNotBetween: (label, start, end) => `${label} must not be between ${start} and ${end}`
73
+ };
74
+ }
75
+ function plainTextLabel(value, fallback) {
76
+ if (typeof value !== "string") {
77
+ return fallback.replaceAll(/[_-]+/g, " ").replace(/\b\w/g, (character) => character.toUpperCase());
78
+ }
79
+ const cleaned = value.replace(/<[^>]*>/g, "").trim();
80
+ if (cleaned !== "") {
81
+ return cleaned;
82
+ }
83
+ return fallback.replaceAll(/[_-]+/g, " ").replace(/\b\w/g, (character) => character.toUpperCase());
84
+ }
85
+ function isMissingValue(value) {
86
+ return value === void 0 || value === null;
87
+ }
88
+ function typeError(requiredMessage, invalidMessage) {
89
+ return (issue) => {
90
+ if (isMissingValue(issue.input)) {
91
+ return requiredMessage;
92
+ }
93
+ return invalidMessage;
94
+ };
95
+ }
96
+ function buildScalarOptionSchema(messages) {
97
+ return z.union([z.string(), z.number(), z.boolean(), z.null()], {
98
+ error: typeError(messages.required, messages.invalidSelection)
99
+ });
100
+ }
101
+ function resolveLocaleErrorMap(locale) {
102
+ const normalizedLocale = normalizeLocale(locale);
103
+ if (normalizedLocale === "fr") {
104
+ return z.locales.fr().localeError;
105
+ }
106
+ return z.locales.en().localeError;
107
+ }
108
+ function isBlankAddressValue(value) {
109
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
110
+ return false;
111
+ }
112
+ return Object.values(value).every((item) => {
113
+ if (item === void 0 || item === null) {
114
+ return true;
115
+ }
116
+ if (typeof item === "string") {
117
+ return item.trim() === "";
118
+ }
119
+ return false;
120
+ });
121
+ }
122
+ function addressFieldDefinitions(field, locale) {
123
+ if (!Array.isArray(field.address_fields) || field.address_fields.length === 0) {
124
+ return createDefaultAddressFields(locale);
125
+ }
126
+ const defaults = createDefaultAddressFields(locale);
127
+ return field.address_fields.map((candidate, index) => ({
128
+ key: candidate.key,
129
+ label: typeof candidate.label === "string" && candidate.label !== "" ? candidate.label : defaults[index]?.label ?? `Field ${index + 1}`,
130
+ visible: candidate.visible !== false,
131
+ required: candidate.required === true
132
+ }));
133
+ }
134
+ function fieldValidationConfig(field) {
135
+ if (typeof field.meta !== "object" || field.meta === null || Array.isArray(field.meta)) {
136
+ return null;
137
+ }
138
+ const validation = field.meta.validation;
139
+ if (validation === void 0 || validation === null || Array.isArray(validation) || typeof validation !== "object") {
140
+ return null;
141
+ }
142
+ const candidate = validation;
143
+ if (candidate.match !== "all" && candidate.match !== "any" || !Array.isArray(candidate.rules)) {
144
+ return null;
145
+ }
146
+ return {
147
+ match: candidate.match,
148
+ rules: candidate.rules
149
+ };
150
+ }
151
+ function isValidationRuleNumeric(rule) {
152
+ return rule.operator === "min" || rule.operator === "max";
153
+ }
154
+ function validationFieldLabel(field) {
155
+ return plainTextLabel(field.label, field.name);
156
+ }
157
+ function validationAddressFieldLabel(field, target, locale) {
158
+ const addressField = addressFieldDefinitions(field, locale).find((candidate) => candidate.key === target);
159
+ if (addressField === void 0) {
160
+ return target;
161
+ }
162
+ return plainTextLabel(addressField.label, target);
163
+ }
164
+ function validationRuleMessage(subjectLabel, rule, messages) {
165
+ if (rule.operator === "min") {
166
+ const value = Number.parseFloat(String(rule.value ?? 0));
167
+ return messages.textMin(subjectLabel, Number.isNaN(value) ? 0 : value);
168
+ }
169
+ if (rule.operator === "max") {
170
+ const value = Number.parseFloat(String(rule.value ?? 0));
171
+ return messages.textMax(subjectLabel, Number.isNaN(value) ? 0 : value);
172
+ }
173
+ if (rule.operator === "regex") {
174
+ return messages.textRegex(subjectLabel);
175
+ }
176
+ if (rule.operator === "eq") {
177
+ return messages.textEquals(subjectLabel, String(rule.value ?? ""));
178
+ }
179
+ if (rule.operator === "neq") {
180
+ return messages.textNotEquals(subjectLabel, String(rule.value ?? ""));
181
+ }
182
+ if (rule.operator === "contains") {
183
+ return messages.textContains(subjectLabel, String(rule.value ?? ""));
184
+ }
185
+ if (rule.operator === "not_contains") {
186
+ return messages.textNotContains(subjectLabel, String(rule.value ?? ""));
187
+ }
188
+ if (rule.operator === "after") {
189
+ return messages.temporalAfter(subjectLabel, String(rule.value ?? ""));
190
+ }
191
+ if (rule.operator === "before") {
192
+ return messages.temporalBefore(subjectLabel, String(rule.value ?? ""));
193
+ }
194
+ if (rule.operator === "between" || rule.operator === "not_between") {
195
+ const rangeValue = temporalValidationRangeValue(rule.value);
196
+ const start = rangeValue?.start ?? "";
197
+ const end = rangeValue?.end ?? "";
198
+ if (rule.operator === "between") {
199
+ return messages.temporalBetween(subjectLabel, start, end);
200
+ }
201
+ return messages.temporalNotBetween(subjectLabel, start, end);
202
+ }
203
+ return messages.validationFailed;
204
+ }
205
+ function parseValidationRuleValue(rule) {
206
+ if (isValidationRuleNumeric(rule)) {
207
+ const parsed = typeof rule.value === "number" ? rule.value : Number.parseFloat(String(rule.value));
208
+ return Number.isNaN(parsed) ? 0 : parsed;
209
+ }
210
+ return typeof rule.value === "string" ? rule.value : String(rule.value ?? "");
211
+ }
212
+ function evaluateTextValidationRule(value, rule) {
213
+ const ruleValue = parseValidationRuleValue(rule);
214
+ if (rule.operator === "min") {
215
+ return value.length >= Number(ruleValue);
216
+ }
217
+ if (rule.operator === "max") {
218
+ return value.length <= Number(ruleValue);
219
+ }
220
+ if (rule.operator === "eq") {
221
+ return value === String(ruleValue);
222
+ }
223
+ if (rule.operator === "neq") {
224
+ return value !== String(ruleValue);
225
+ }
226
+ if (rule.operator === "contains") {
227
+ return value.includes(String(ruleValue));
228
+ }
229
+ if (rule.operator === "not_contains") {
230
+ return !value.includes(String(ruleValue));
231
+ }
232
+ if (rule.operator === "regex") {
233
+ try {
234
+ return new RegExp(String(ruleValue)).test(value);
235
+ } catch {
236
+ return false;
237
+ }
238
+ }
239
+ return null;
240
+ }
241
+ function parseTemporalComparable(value, temporalMode) {
242
+ if (temporalMode === "time") {
243
+ const match2 = value.trim().match(/^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?$/);
244
+ if (match2 === null) {
245
+ return null;
246
+ }
247
+ const hours = Number.parseInt(match2[1] ?? "0", 10);
248
+ const minutes = Number.parseInt(match2[2] ?? "0", 10);
249
+ const seconds = Number.parseInt(match2[3] ?? "0", 10);
250
+ const milliseconds = Number.parseInt((match2[4] ?? "0").padEnd(3, "0"), 10);
251
+ return ((hours * 60 + minutes) * 60 + seconds) * 1e3 + milliseconds;
252
+ }
253
+ const match = value.trim().match(/^(\d{4})-(\d{2})-(\d{2})$/);
254
+ if (match === null) {
255
+ return null;
256
+ }
257
+ const year = Number.parseInt(match[1] ?? "0", 10);
258
+ const month = Number.parseInt(match[2] ?? "0", 10);
259
+ const day = Number.parseInt(match[3] ?? "0", 10);
260
+ return Date.UTC(year, month - 1, day);
261
+ }
262
+ function temporalValidationRangeValue(value) {
263
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
264
+ return null;
265
+ }
266
+ const candidate = value;
267
+ return {
268
+ start: typeof candidate.start === "string" ? candidate.start : null,
269
+ end: typeof candidate.end === "string" ? candidate.end : null
270
+ };
271
+ }
272
+ function evaluateTemporalValidationRule(value, rule, temporalMode) {
273
+ const actualComparable = parseTemporalComparable(value, temporalMode);
274
+ const operator = rule.operator;
275
+ if (operator === "between" || operator === "not_between") {
276
+ const rangeValue = temporalValidationRangeValue(rule.value);
277
+ if (rangeValue === null) {
278
+ return null;
279
+ }
280
+ const startComparable = rangeValue.start === null ? null : parseTemporalComparable(rangeValue.start, temporalMode);
281
+ const endComparable = rangeValue.end === null ? null : parseTemporalComparable(rangeValue.end, temporalMode);
282
+ if (actualComparable === null || startComparable === null || endComparable === null) {
283
+ return null;
284
+ }
285
+ const isBetween = actualComparable >= startComparable && actualComparable <= endComparable;
286
+ return operator === "between" ? isBetween : !isBetween;
287
+ }
288
+ const ruleComparable = parseTemporalComparable(typeof rule.value === "string" ? rule.value : String(rule.value ?? ""), temporalMode);
289
+ if (actualComparable === null || ruleComparable === null) {
290
+ return null;
291
+ }
292
+ if (operator === "after") {
293
+ return actualComparable > ruleComparable;
294
+ }
295
+ if (operator === "before") {
296
+ return actualComparable < ruleComparable;
297
+ }
298
+ if (operator === "min") {
299
+ return actualComparable >= ruleComparable;
300
+ }
301
+ if (operator === "max") {
302
+ return actualComparable <= ruleComparable;
303
+ }
304
+ if (operator === "eq") {
305
+ return actualComparable === ruleComparable;
306
+ }
307
+ if (operator === "neq") {
308
+ return actualComparable !== ruleComparable;
309
+ }
310
+ return null;
311
+ }
312
+ function applyTextValidationRules(baseSchema, field, messages) {
313
+ const config = fieldValidationConfig(field);
314
+ if (config === null || config.rules.length === 0) {
315
+ return baseSchema;
316
+ }
317
+ return baseSchema.superRefine((value, context) => {
318
+ const results = config.rules.map((rule) => ({
319
+ rule,
320
+ result: evaluateTextValidationRule(value, rule)
321
+ }));
322
+ const validResults = results.filter((candidate) => candidate.result !== null);
323
+ if (validResults.length === 0) {
324
+ return;
325
+ }
326
+ if (config.match === "any") {
327
+ if (validResults.some((candidate) => candidate.result === true)) {
328
+ return;
329
+ }
330
+ for (const candidate of validResults) {
331
+ if (candidate.result === false) {
332
+ context.addIssue({
333
+ code: z.ZodIssueCode.custom,
334
+ message: validationRuleMessage(validationFieldLabel(field), candidate.rule, messages)
335
+ });
336
+ }
337
+ }
338
+ return;
339
+ }
340
+ for (const candidate of validResults) {
341
+ if (candidate.result === false) {
342
+ context.addIssue({
343
+ code: z.ZodIssueCode.custom,
344
+ message: validationRuleMessage(validationFieldLabel(field), candidate.rule, messages)
345
+ });
346
+ }
347
+ }
348
+ });
349
+ }
350
+ function applyAddressValidationRules(baseSchema, field, messages, locale) {
351
+ const config = fieldValidationConfig(field);
352
+ if (config === null || config.rules.length === 0) {
353
+ return baseSchema;
354
+ }
355
+ return baseSchema.superRefine((value, context) => {
356
+ const results = config.rules.map((rule) => {
357
+ const target = typeof rule.target === "string" ? rule.target : "";
358
+ if (target === "") {
359
+ return {
360
+ rule,
361
+ result: null
362
+ };
363
+ }
364
+ const actualValue = value[target];
365
+ if (typeof actualValue !== "string") {
366
+ return {
367
+ rule,
368
+ result: false
369
+ };
370
+ }
371
+ return {
372
+ rule,
373
+ result: evaluateTextValidationRule(actualValue, rule)
374
+ };
375
+ });
376
+ const validResults = results.filter((candidate) => candidate.result !== null);
377
+ if (validResults.length === 0) {
378
+ return;
379
+ }
380
+ if (config.match === "any") {
381
+ if (validResults.some((candidate) => candidate.result === true)) {
382
+ return;
383
+ }
384
+ }
385
+ for (const candidate of validResults) {
386
+ if (candidate.result === false) {
387
+ const target = typeof candidate.rule.target === "string" ? candidate.rule.target : "";
388
+ const message = validationRuleMessage(
389
+ validationAddressFieldLabel(field, target, locale),
390
+ candidate.rule,
391
+ messages
392
+ );
393
+ if (target === "") {
394
+ context.addIssue({
395
+ code: z.ZodIssueCode.custom,
396
+ message
397
+ });
398
+ continue;
399
+ }
400
+ context.addIssue({
401
+ code: z.ZodIssueCode.custom,
402
+ path: [target],
403
+ message
404
+ });
405
+ }
406
+ }
407
+ });
408
+ }
409
+ function applySharedStringRules(baseSchema, rules, messages) {
4
410
  let schema = baseSchema;
5
411
  for (const rawRule of rules) {
6
412
  const [ruleName, ruleArgument] = rawRule.split(":");
7
413
  if (ruleName === "email") {
8
- schema = schema.email();
414
+ schema = schema.email({
415
+ error: messages.invalidEmail
416
+ });
9
417
  continue;
10
418
  }
11
419
  if (ruleName === "min" && ruleArgument !== void 0) {
12
420
  const parsedMin = Number.parseInt(ruleArgument, 10);
13
421
  if (!Number.isNaN(parsedMin)) {
14
- schema = schema.min(parsedMin);
422
+ schema = schema.min(parsedMin, {
423
+ error: messages.minLength(parsedMin)
424
+ });
15
425
  }
16
426
  continue;
17
427
  }
18
428
  if (ruleName === "max" && ruleArgument !== void 0) {
19
429
  const parsedMax = Number.parseInt(ruleArgument, 10);
20
430
  if (!Number.isNaN(parsedMax)) {
21
- schema = schema.max(parsedMax);
431
+ schema = schema.max(parsedMax, {
432
+ error: messages.maxLength(parsedMax)
433
+ });
22
434
  }
23
435
  continue;
24
436
  }
25
437
  }
26
438
  return schema;
27
439
  }
28
- function applyArrayRules(baseSchema, rules) {
440
+ function applyArrayRules(baseSchema, rules, messages) {
29
441
  let schema = baseSchema;
30
442
  for (const rawRule of rules) {
31
443
  const [ruleName, ruleArgument] = rawRule.split(":");
32
444
  if (ruleName === "min" && ruleArgument !== void 0) {
33
445
  const parsedMin = Number.parseInt(ruleArgument, 10);
34
446
  if (!Number.isNaN(parsedMin)) {
35
- schema = schema.min(parsedMin);
447
+ schema = schema.min(parsedMin, {
448
+ error: messages.minItems(parsedMin)
449
+ });
36
450
  }
37
451
  continue;
38
452
  }
39
453
  if (ruleName === "max" && ruleArgument !== void 0) {
40
454
  const parsedMax = Number.parseInt(ruleArgument, 10);
41
455
  if (!Number.isNaN(parsedMax)) {
42
- schema = schema.max(parsedMax);
456
+ schema = schema.max(parsedMax, {
457
+ error: messages.maxItems(parsedMax)
458
+ });
43
459
  }
44
460
  continue;
45
461
  }
46
462
  }
47
463
  return schema;
48
464
  }
49
- function applyNumberRules(baseSchema, rules) {
465
+ function applyNumberRules(baseSchema, rules, messages) {
50
466
  let schema = baseSchema;
51
467
  for (const rawRule of rules) {
52
468
  const [ruleName, ruleArgument] = rawRule.split(":");
53
469
  if (ruleName === "min" && ruleArgument !== void 0) {
54
470
  const parsedMin = Number.parseFloat(ruleArgument);
55
471
  if (!Number.isNaN(parsedMin)) {
56
- schema = schema.min(parsedMin);
472
+ schema = schema.min(parsedMin, {
473
+ error: messages.minValue(parsedMin)
474
+ });
57
475
  }
58
476
  continue;
59
477
  }
60
478
  if (ruleName === "max" && ruleArgument !== void 0) {
61
479
  const parsedMax = Number.parseFloat(ruleArgument);
62
480
  if (!Number.isNaN(parsedMax)) {
63
- schema = schema.max(parsedMax);
481
+ schema = schema.max(parsedMax, {
482
+ error: messages.maxValue(parsedMax)
483
+ });
64
484
  }
65
485
  continue;
66
486
  }
67
487
  }
68
488
  return schema;
69
489
  }
70
- function buildFieldBaseSchema(field) {
490
+ function buildFieldBaseSchema(field, messages, locale) {
71
491
  if (field.type === "text" || field.type === "textarea" || field.type === "email") {
72
- return applySharedStringRules(z.string(), field.rules);
492
+ const baseSchema = applySharedStringRules(z.string({
493
+ error: typeError(messages.required, messages.invalidString)
494
+ }), field.rules, messages);
495
+ if (field.type === "email") {
496
+ return applyTextValidationRules(baseSchema.email({
497
+ error: messages.invalidEmail
498
+ }), field, messages);
499
+ }
500
+ return applyTextValidationRules(baseSchema, field, messages);
73
501
  }
74
502
  if (field.type === "number") {
75
- return applyNumberRules(z.number(), field.rules);
503
+ return applyNumberRules(z.number({
504
+ error: typeError(messages.required, messages.invalidNumber)
505
+ }), field.rules, messages);
76
506
  }
77
507
  if (field.type === "select" || field.type === "select_menu" || field.type === "radio") {
78
- return scalarOptionSchema;
508
+ return buildScalarOptionSchema(messages);
79
509
  }
80
- if (field.type === "checkbox" || field.type === "switch") {
81
- return z.boolean();
510
+ if (field.type === "checkbox" || field.type === "consent" || field.type === "switch") {
511
+ return z.boolean({
512
+ error: typeError(messages.required, messages.invalidBoolean)
513
+ });
82
514
  }
83
515
  if (field.type === "checkbox_group") {
84
- return applyArrayRules(z.array(scalarOptionSchema), field.rules);
516
+ return applyArrayRules(z.array(buildScalarOptionSchema(messages), {
517
+ error: typeError(messages.required, messages.invalidSelection)
518
+ }), field.rules, messages);
85
519
  }
86
- if (field.type === "date" || field.type === "time" || field.type === "datetime") {
87
- return applySharedStringRules(z.string(), field.rules);
88
- }
89
- if (field.type === "date_range" || field.type === "datetime_range") {
90
- return z.object({
91
- start: z.string().nullable(),
92
- end: z.string().nullable()
520
+ if (field.type === "temporal" || field.type === "date" || field.type === "time") {
521
+ const temporalMode = resolveTemporalMode(field);
522
+ const invalidMessage = temporalMode === "time" ? messages.invalidTime : messages.invalidDate;
523
+ const baseSchema = applySharedStringRules(z.string({
524
+ error: typeError(messages.required, invalidMessage)
525
+ }), field.rules, messages);
526
+ return baseSchema.refine((value) => {
527
+ return parseTemporalComparable(value, temporalMode) !== null;
528
+ }, {
529
+ message: invalidMessage
530
+ }).superRefine((value, context) => {
531
+ const config = fieldValidationConfig(field);
532
+ if (config === null || config.rules.length === 0) {
533
+ return;
534
+ }
535
+ const results = config.rules.map((rule) => ({
536
+ rule,
537
+ result: evaluateTemporalValidationRule(value, rule, temporalMode)
538
+ }));
539
+ const validResults = results.filter((candidate) => candidate.result !== null);
540
+ if (validResults.length === 0) {
541
+ return;
542
+ }
543
+ if (config.match === "any") {
544
+ if (validResults.some((candidate) => candidate.result === true)) {
545
+ return;
546
+ }
547
+ }
548
+ for (const candidate of validResults) {
549
+ if (candidate.result === false) {
550
+ context.addIssue({
551
+ code: z.ZodIssueCode.custom,
552
+ message: validationRuleMessage(validationFieldLabel(field), candidate.rule, messages)
553
+ });
554
+ }
555
+ }
93
556
  });
94
557
  }
95
558
  if (field.type === "file") {
@@ -98,15 +561,51 @@ function buildFieldBaseSchema(field) {
98
561
  return false;
99
562
  }
100
563
  return value instanceof File;
564
+ }, {
565
+ error: typeError(messages.required, messages.invalidFile)
101
566
  });
102
567
  if (field.multiple === true) {
103
- return z.array(fileSchema);
568
+ return z.array(fileSchema, {
569
+ error: typeError(messages.required, messages.invalidFile)
570
+ });
104
571
  }
105
572
  return fileSchema;
106
573
  }
107
- return scalarOptionSchema;
574
+ if (field.type === "address") {
575
+ const shape = {};
576
+ for (const addressField of addressFieldDefinitions(field, locale)) {
577
+ if (!addressField.visible) {
578
+ continue;
579
+ }
580
+ shape[addressField.key] = z.union([z.string(), z.null()]).optional();
581
+ }
582
+ return applyAddressValidationRules(z.object(shape, {
583
+ error: typeError(messages.required, messages.invalidAddress)
584
+ }).superRefine((value, context) => {
585
+ const topLevelRequired = field.required === true;
586
+ for (const addressField of addressFieldDefinitions(field, locale)) {
587
+ if (!addressField.visible) {
588
+ continue;
589
+ }
590
+ const effectiveRequired = topLevelRequired || addressField.required;
591
+ if (!effectiveRequired) {
592
+ continue;
593
+ }
594
+ const fieldValue = value[addressField.key];
595
+ const isEmpty = fieldValue === void 0 || fieldValue === null || typeof fieldValue === "string" && fieldValue.trim() === "";
596
+ if (isEmpty) {
597
+ context.addIssue({
598
+ code: z.ZodIssueCode.custom,
599
+ path: [addressField.key],
600
+ message: messages.required
601
+ });
602
+ }
603
+ }
604
+ }), field, messages, locale);
605
+ }
606
+ return buildScalarOptionSchema(messages);
108
607
  }
109
- function applyRequiredAndNullable(schema, field) {
608
+ function applyRequiredAndNullable(schema, field, messages, locale) {
110
609
  const optionalSchema = schema.optional();
111
610
  if (field.required === false) {
112
611
  return z.preprocess((value) => {
@@ -116,25 +615,38 @@ function applyRequiredAndNullable(schema, field) {
116
615
  if (typeof value === "string" && value.trim() === "") {
117
616
  return void 0;
118
617
  }
119
- if ((field.type === "date_range" || field.type === "datetime_range") && typeof value === "object" && value !== null) {
120
- const rangeValue = value;
121
- const isStartEmpty = rangeValue.start === void 0 || rangeValue.start === null || rangeValue.start === "";
122
- const isEndEmpty = rangeValue.end === void 0 || rangeValue.end === null || rangeValue.end === "";
123
- if (isStartEmpty && isEndEmpty) {
124
- return void 0;
125
- }
126
- }
127
618
  if (field.type === "checkbox_group" && Array.isArray(value) && value.length === 0) {
128
619
  return void 0;
129
620
  }
621
+ if (field.type === "address" && isBlankAddressValue(value)) {
622
+ const addressFields = addressFieldDefinitions(field, locale);
623
+ const hasRequiredAddressField = addressFields.some((candidate) => candidate.visible !== false && (field.required || candidate.required));
624
+ if (hasRequiredAddressField) {
625
+ return value;
626
+ }
627
+ return void 0;
628
+ }
130
629
  return value;
131
630
  }, optionalSchema);
132
631
  }
133
- if (field.type === "text" || field.type === "textarea" || field.type === "email" || field.type === "date" || field.type === "time" || field.type === "datetime") {
632
+ if (field.type === "number") {
633
+ return z.preprocess((value) => {
634
+ if (typeof value === "string" && value.trim() === "") {
635
+ return void 0;
636
+ }
637
+ return value;
638
+ }, schema);
639
+ }
640
+ if (field.type === "text" || field.type === "textarea" || field.type === "email") {
134
641
  return schema.refine((value) => {
135
642
  return typeof value === "string" && value.trim() !== "";
136
643
  }, {
137
- message: "Required"
644
+ message: messages.required
645
+ });
646
+ }
647
+ if (field.type === "consent") {
648
+ return schema.refine((value) => value === true, {
649
+ message: messages.required
138
650
  });
139
651
  }
140
652
  if (field.type === "select" || field.type === "select_menu" || field.type === "radio") {
@@ -147,36 +659,31 @@ function applyRequiredAndNullable(schema, field) {
147
659
  }
148
660
  return true;
149
661
  }, {
150
- message: "Required"
662
+ message: messages.required
151
663
  });
152
664
  }
153
665
  if (field.type === "checkbox_group") {
154
666
  return schema.refine((value) => {
155
667
  return Array.isArray(value) && value.length > 0;
156
668
  }, {
157
- message: "Required"
669
+ message: messages.required
158
670
  });
159
671
  }
160
- if (field.type === "date_range" || field.type === "datetime_range") {
672
+ if (field.type === "date" || field.type === "time" || field.type === "temporal") {
161
673
  return schema.refine((value) => {
162
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
163
- return false;
164
- }
165
- const rangeValue = value;
166
- const isStartValid = typeof rangeValue.start === "string" && rangeValue.start !== "";
167
- const isEndValid = typeof rangeValue.end === "string" && rangeValue.end !== "";
168
- return isStartValid && isEndValid;
674
+ return typeof value === "string" && value.trim() !== "";
169
675
  }, {
170
- message: "Required"
676
+ message: messages.required
171
677
  });
172
678
  }
173
679
  return schema;
174
680
  }
175
- export function createFormForgeZodSchema(form) {
681
+ export function createFormForgeZodSchema(form, options = {}) {
682
+ const messages = resolveLocaleMessages(options.locale);
176
683
  const shape = {};
177
684
  for (const field of form.fields) {
178
- const baseSchema = buildFieldBaseSchema(field);
179
- shape[field.name] = applyRequiredAndNullable(baseSchema, field);
685
+ const baseSchema = buildFieldBaseSchema(field, messages, options.locale);
686
+ shape[field.name] = applyRequiredAndNullable(baseSchema, field, messages, options.locale);
180
687
  }
181
688
  return z.object(shape);
182
689
  }
@@ -194,8 +701,10 @@ export function mapFormForgeZodIssues(error) {
194
701
  }
195
702
  return fieldErrors;
196
703
  }
197
- export function validateFormForgePayload(schema, payload) {
198
- const result = schema.safeParse(payload);
704
+ export function validateFormForgePayload(schema, payload, options = {}) {
705
+ const result = schema.safeParse(payload, {
706
+ error: resolveLocaleErrorMap(options.locale)
707
+ });
199
708
  if (result.success) {
200
709
  return {};
201
710
  }