@evanschleret/formforgeclient 1.2.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/module.cjs +1 -0
- package/dist/module.d.cts +1 -0
- package/dist/module.d.mts +1 -0
- package/dist/module.d.ts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -0
- package/dist/runtime/api/client.js +4 -2
- package/dist/runtime/api/request.d.ts +1 -0
- package/dist/runtime/api/schema.js +4 -4
- package/dist/runtime/assets/formforge.css +1 -0
- package/dist/runtime/composables/index.d.ts +1 -1
- package/dist/runtime/composables/useFormForgeBuilder.d.ts +24 -2
- package/dist/runtime/composables/useFormForgeBuilder.js +299 -43
- package/dist/runtime/composables/useFormForgeForm.js +15 -5
- package/dist/runtime/composables/useFormForgeI18n.d.ts +245 -19
- package/dist/runtime/composables/useFormForgeI18n.js +245 -19
- package/dist/runtime/composables/useFormForgeSubmit.js +31 -9
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/renderers/default/FormForgeBuilder.d.vue.ts +21 -2
- package/dist/runtime/renderers/default/FormForgeBuilder.vue +689 -738
- package/dist/runtime/renderers/default/FormForgeBuilder.vue.d.ts +21 -2
- package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.d.vue.ts +17 -0
- package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue +32 -0
- package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue.d.ts +17 -0
- package/dist/runtime/renderers/default/FormForgeRenderer.d.vue.ts +3 -4
- package/dist/runtime/renderers/default/FormForgeRenderer.vue +344 -294
- package/dist/runtime/renderers/default/FormForgeRenderer.vue.d.ts +3 -4
- package/dist/runtime/renderers/default/FormForgeRendererField.d.vue.ts +22 -0
- package/dist/runtime/renderers/default/FormForgeRendererField.vue +237 -0
- package/dist/runtime/renderers/default/FormForgeRendererField.vue.d.ts +22 -0
- package/dist/runtime/renderers/default/FormForgeRendererPage.d.vue.ts +18 -0
- package/dist/runtime/renderers/default/FormForgeRendererPage.vue +31 -0
- package/dist/runtime/renderers/default/FormForgeRendererPage.vue.d.ts +18 -0
- package/dist/runtime/renderers/default/FormForgeResponse.vue +4 -3
- package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue +118 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.d.vue.ts +46 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue +205 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue.d.ts +46 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue +37 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue +195 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.d.vue.ts +14 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue +91 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue.d.ts +14 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.d.vue.ts +13 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue +387 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue.d.ts +13 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.d.vue.ts +44 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue +328 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue.d.ts +44 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue +47 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.d.vue.ts +14 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue +595 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue.d.ts +14 -0
- package/dist/runtime/renderers/default/builder/builderFieldHelpers.d.ts +3 -0
- package/dist/runtime/renderers/default/builder/builderFieldHelpers.js +4 -0
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/management.d.ts +12 -0
- package/dist/runtime/types/schema.d.ts +72 -4
- package/dist/runtime/utils/defaults.d.ts +7 -0
- package/dist/runtime/utils/defaults.js +86 -0
- package/dist/runtime/utils/page-logic.d.ts +24 -0
- package/dist/runtime/utils/page-logic.js +351 -0
- package/dist/runtime/utils/rich-text.d.ts +3 -0
- package/dist/runtime/utils/rich-text.js +72 -0
- package/dist/runtime/utils/schema.d.ts +1 -1
- package/dist/runtime/utils/schema.js +70 -16
- package/dist/runtime/utils/temporal.d.ts +10 -0
- package/dist/runtime/utils/temporal.js +28 -0
- package/dist/runtime/utils/validation.d.ts +5 -0
- package/dist/runtime/utils/validation.js +36 -0
- package/dist/runtime/validation/zod.d.ts +5 -2
- package/dist/runtime/validation/zod.js +563 -54
- package/dist/types.d.mts +2 -0
- package/package.json +18 -14
|
@@ -1,95 +1,558 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
516
|
+
return applyArrayRules(z.array(buildScalarOptionSchema(messages), {
|
|
517
|
+
error: typeError(messages.required, messages.invalidSelection)
|
|
518
|
+
}), field.rules, messages);
|
|
85
519
|
}
|
|
86
|
-
if (field.type === "
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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 === "
|
|
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:
|
|
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:
|
|
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:
|
|
669
|
+
message: messages.required
|
|
158
670
|
});
|
|
159
671
|
}
|
|
160
|
-
if (field.type === "
|
|
672
|
+
if (field.type === "date" || field.type === "time" || field.type === "temporal") {
|
|
161
673
|
return schema.refine((value) => {
|
|
162
|
-
|
|
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:
|
|
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
|
}
|