@case-framework/survey-items 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/choice.d.mts +36 -0
- package/dist/choice.d.mts.map +1 -0
- package/dist/choice.mjs +103 -0
- package/dist/choice.mjs.map +1 -0
- package/dist/form.d.mts +187 -0
- package/dist/form.d.mts.map +1 -0
- package/dist/form.mjs +766 -0
- package/dist/form.mjs.map +1 -0
- package/package.json +39 -0
package/dist/form.mjs
ADDED
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import { ContentType, ValueType, generateCodingKey, generateId, getContentPlainText } from "@case-framework/survey-core";
|
|
2
|
+
//#region src/form.ts
|
|
3
|
+
const FORM_DATEPICKER_FIELD_MODES = [
|
|
4
|
+
"year",
|
|
5
|
+
"year-month",
|
|
6
|
+
"year-month-day"
|
|
7
|
+
];
|
|
8
|
+
function getFormFieldValueType(field) {
|
|
9
|
+
switch (field.type) {
|
|
10
|
+
case "number":
|
|
11
|
+
case "slider": return ValueType.number;
|
|
12
|
+
case "datepicker": return ValueType.date;
|
|
13
|
+
case "switch": return ValueType.boolean;
|
|
14
|
+
default: return ValueType.string;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function getFormFieldEffectiveValidations(field) {
|
|
18
|
+
const effectiveValidations = field.validations.map((validation) => ({
|
|
19
|
+
...validation,
|
|
20
|
+
source: "validation"
|
|
21
|
+
}));
|
|
22
|
+
if (field.required !== true || effectiveValidations.some((validation) => validation.type === "required")) return effectiveValidations;
|
|
23
|
+
return [{
|
|
24
|
+
id: `${field.id}-required`,
|
|
25
|
+
type: "required",
|
|
26
|
+
source: "required"
|
|
27
|
+
}, ...effectiveValidations];
|
|
28
|
+
}
|
|
29
|
+
function createFormItemConfig(itemId) {
|
|
30
|
+
return {
|
|
31
|
+
id: itemId,
|
|
32
|
+
authoringMode: "custom",
|
|
33
|
+
fieldGroups: []
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createPlainTextContent(value) {
|
|
37
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
38
|
+
return normalizedValue ? {
|
|
39
|
+
type: ContentType.plain,
|
|
40
|
+
content: normalizedValue
|
|
41
|
+
} : void 0;
|
|
42
|
+
}
|
|
43
|
+
function getTranslatedPlainText(itemTranslations, locale, contentKey, fallbackLocale) {
|
|
44
|
+
const content = itemTranslations?.getContent(locale, contentKey, fallbackLocale);
|
|
45
|
+
return getContentPlainText(content).trim() || void 0;
|
|
46
|
+
}
|
|
47
|
+
const FORM_FIELD_TRANSLATION_PREFIX = "fields";
|
|
48
|
+
const FORM_FIELD_GROUP_TRANSLATION_PREFIX = "fieldGroups";
|
|
49
|
+
function withFormTranslationNamespace(prefix, translationNamespace) {
|
|
50
|
+
return translationNamespace?.trim() ? `${translationNamespace}.${prefix}` : prefix;
|
|
51
|
+
}
|
|
52
|
+
function getFormFieldGroupTranslationPrefix(groupId, translationNamespace) {
|
|
53
|
+
return withFormTranslationNamespace(`${FORM_FIELD_GROUP_TRANSLATION_PREFIX}.${groupId}`, translationNamespace);
|
|
54
|
+
}
|
|
55
|
+
function getFormFieldGroupTitleContentKey(groupId, translationNamespace) {
|
|
56
|
+
return `${getFormFieldGroupTranslationPrefix(groupId, translationNamespace)}.title`;
|
|
57
|
+
}
|
|
58
|
+
function getFormFieldGroupDescriptionContentKey(groupId, translationNamespace) {
|
|
59
|
+
return `${getFormFieldGroupTranslationPrefix(groupId, translationNamespace)}.description`;
|
|
60
|
+
}
|
|
61
|
+
function getFormFieldTranslationPrefix(fieldId, translationNamespace) {
|
|
62
|
+
return withFormTranslationNamespace(`${FORM_FIELD_TRANSLATION_PREFIX}.${fieldId}`, translationNamespace);
|
|
63
|
+
}
|
|
64
|
+
function getFormFieldLabelContentKey(fieldId, translationNamespace) {
|
|
65
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.label`;
|
|
66
|
+
}
|
|
67
|
+
function getFormFieldDescriptionContentKey(fieldId, translationNamespace) {
|
|
68
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.description`;
|
|
69
|
+
}
|
|
70
|
+
function getFormFieldErrorMessageContentKey(fieldId, translationNamespace) {
|
|
71
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.error`;
|
|
72
|
+
}
|
|
73
|
+
function getFormFieldPlaceholderContentKey(fieldId, translationNamespace) {
|
|
74
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.placeholder`;
|
|
75
|
+
}
|
|
76
|
+
function getFormFieldUnitContentKey(fieldId, translationNamespace) {
|
|
77
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.unit`;
|
|
78
|
+
}
|
|
79
|
+
function getFormFieldCheckedLabelContentKey(fieldId, translationNamespace) {
|
|
80
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.checkedLabel`;
|
|
81
|
+
}
|
|
82
|
+
function getFormFieldUncheckedLabelContentKey(fieldId, translationNamespace) {
|
|
83
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.uncheckedLabel`;
|
|
84
|
+
}
|
|
85
|
+
function getFormFieldOptionLabelContentKey(fieldId, optionId, translationNamespace) {
|
|
86
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.options.${optionId}.label`;
|
|
87
|
+
}
|
|
88
|
+
function getFormFieldValidationMessageContentKey(fieldId, validationId, translationNamespace) {
|
|
89
|
+
return `${getFormFieldTranslationPrefix(fieldId, translationNamespace)}.validations.${validationId}.message`;
|
|
90
|
+
}
|
|
91
|
+
function getFormFieldTranslationKeys(field, translationNamespace) {
|
|
92
|
+
const keys = [
|
|
93
|
+
getFormFieldLabelContentKey(field.id, translationNamespace),
|
|
94
|
+
getFormFieldDescriptionContentKey(field.id, translationNamespace),
|
|
95
|
+
getFormFieldErrorMessageContentKey(field.id, translationNamespace),
|
|
96
|
+
...field.validations.map((validation) => getFormFieldValidationMessageContentKey(field.id, validation.id, translationNamespace))
|
|
97
|
+
];
|
|
98
|
+
switch (field.type) {
|
|
99
|
+
case "textarea":
|
|
100
|
+
case "datepicker":
|
|
101
|
+
case "input":
|
|
102
|
+
keys.push(getFormFieldPlaceholderContentKey(field.id, translationNamespace));
|
|
103
|
+
break;
|
|
104
|
+
case "number":
|
|
105
|
+
keys.push(getFormFieldPlaceholderContentKey(field.id, translationNamespace));
|
|
106
|
+
keys.push(getFormFieldUnitContentKey(field.id, translationNamespace));
|
|
107
|
+
break;
|
|
108
|
+
case "switch":
|
|
109
|
+
keys.push(getFormFieldCheckedLabelContentKey(field.id, translationNamespace));
|
|
110
|
+
keys.push(getFormFieldUncheckedLabelContentKey(field.id, translationNamespace));
|
|
111
|
+
break;
|
|
112
|
+
case "select": {
|
|
113
|
+
keys.push(getFormFieldPlaceholderContentKey(field.id, translationNamespace));
|
|
114
|
+
const controller = field.controller;
|
|
115
|
+
keys.push(...controller.options.map((option) => getFormFieldOptionLabelContentKey(field.id, option.id, translationNamespace)));
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "slider": break;
|
|
119
|
+
}
|
|
120
|
+
return keys;
|
|
121
|
+
}
|
|
122
|
+
function getFormFieldGroupTranslationKeys(group, translationNamespace) {
|
|
123
|
+
return [getFormFieldGroupTitleContentKey(group.id, translationNamespace), getFormFieldGroupDescriptionContentKey(group.id, translationNamespace)];
|
|
124
|
+
}
|
|
125
|
+
function writeFormFieldGroupTranslations(itemTranslations, locale, group, translationNamespace) {
|
|
126
|
+
itemTranslations.setContent(locale, getFormFieldGroupTitleContentKey(group.id, translationNamespace), createPlainTextContent(group.title));
|
|
127
|
+
itemTranslations.setContent(locale, getFormFieldGroupDescriptionContentKey(group.id, translationNamespace), createPlainTextContent(group.description));
|
|
128
|
+
}
|
|
129
|
+
function writeFormFieldTranslations(itemTranslations, locale, field, translationNamespace) {
|
|
130
|
+
itemTranslations.setContent(locale, getFormFieldLabelContentKey(field.id, translationNamespace), createPlainTextContent(field.label));
|
|
131
|
+
itemTranslations.setContent(locale, getFormFieldDescriptionContentKey(field.id, translationNamespace), createPlainTextContent(field.description));
|
|
132
|
+
itemTranslations.setContent(locale, getFormFieldErrorMessageContentKey(field.id, translationNamespace), createPlainTextContent(field.errorMessage));
|
|
133
|
+
for (const validation of field.validations) itemTranslations.setContent(locale, getFormFieldValidationMessageContentKey(field.id, validation.id, translationNamespace), createPlainTextContent(validation.message));
|
|
134
|
+
switch (field.type) {
|
|
135
|
+
case "textarea":
|
|
136
|
+
case "datepicker":
|
|
137
|
+
case "input": {
|
|
138
|
+
const controller = field.controller;
|
|
139
|
+
itemTranslations.setContent(locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), createPlainTextContent(controller.placeholder));
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "number": {
|
|
143
|
+
const controller = field.controller;
|
|
144
|
+
itemTranslations.setContent(locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), createPlainTextContent(controller.placeholder));
|
|
145
|
+
itemTranslations.setContent(locale, getFormFieldUnitContentKey(field.id, translationNamespace), createPlainTextContent(controller.unit));
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "switch": {
|
|
149
|
+
const controller = field.controller;
|
|
150
|
+
itemTranslations.setContent(locale, getFormFieldCheckedLabelContentKey(field.id, translationNamespace), createPlainTextContent(controller.checkedLabel));
|
|
151
|
+
itemTranslations.setContent(locale, getFormFieldUncheckedLabelContentKey(field.id, translationNamespace), createPlainTextContent(controller.uncheckedLabel));
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "select": {
|
|
155
|
+
const controller = field.controller;
|
|
156
|
+
itemTranslations.setContent(locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), createPlainTextContent(controller.placeholder));
|
|
157
|
+
for (const option of controller.options) itemTranslations.setContent(locale, getFormFieldOptionLabelContentKey(field.id, option.id, translationNamespace), createPlainTextContent(option.label));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "slider": break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function withTranslatedFieldContent(field, itemTranslations, locale, fallbackLocale, translationNamespace) {
|
|
164
|
+
const translatedField = {
|
|
165
|
+
...field,
|
|
166
|
+
label: getTranslatedPlainText(itemTranslations, locale, getFormFieldLabelContentKey(field.id, translationNamespace), fallbackLocale) ?? field.label,
|
|
167
|
+
description: getTranslatedPlainText(itemTranslations, locale, getFormFieldDescriptionContentKey(field.id, translationNamespace), fallbackLocale) ?? field.description,
|
|
168
|
+
errorMessage: getTranslatedPlainText(itemTranslations, locale, getFormFieldErrorMessageContentKey(field.id, translationNamespace), fallbackLocale) ?? field.errorMessage,
|
|
169
|
+
validations: field.validations.map((validation) => ({
|
|
170
|
+
...validation,
|
|
171
|
+
message: getTranslatedPlainText(itemTranslations, locale, getFormFieldValidationMessageContentKey(field.id, validation.id, translationNamespace), fallbackLocale) ?? validation.message
|
|
172
|
+
}))
|
|
173
|
+
};
|
|
174
|
+
switch (field.type) {
|
|
175
|
+
case "textarea": {
|
|
176
|
+
const controller = field.controller;
|
|
177
|
+
translatedField.controller = {
|
|
178
|
+
...controller,
|
|
179
|
+
placeholder: getTranslatedPlainText(itemTranslations, locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.placeholder
|
|
180
|
+
};
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "number": {
|
|
184
|
+
const controller = field.controller;
|
|
185
|
+
translatedField.controller = {
|
|
186
|
+
...controller,
|
|
187
|
+
placeholder: getTranslatedPlainText(itemTranslations, locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.placeholder,
|
|
188
|
+
unit: getTranslatedPlainText(itemTranslations, locale, getFormFieldUnitContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.unit
|
|
189
|
+
};
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case "datepicker": {
|
|
193
|
+
const controller = field.controller;
|
|
194
|
+
translatedField.controller = {
|
|
195
|
+
...controller,
|
|
196
|
+
placeholder: getTranslatedPlainText(itemTranslations, locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.placeholder
|
|
197
|
+
};
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "switch": {
|
|
201
|
+
const controller = field.controller;
|
|
202
|
+
translatedField.controller = {
|
|
203
|
+
...controller,
|
|
204
|
+
checkedLabel: getTranslatedPlainText(itemTranslations, locale, getFormFieldCheckedLabelContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.checkedLabel,
|
|
205
|
+
uncheckedLabel: getTranslatedPlainText(itemTranslations, locale, getFormFieldUncheckedLabelContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.uncheckedLabel
|
|
206
|
+
};
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "select": {
|
|
210
|
+
const controller = field.controller;
|
|
211
|
+
translatedField.controller = {
|
|
212
|
+
...controller,
|
|
213
|
+
placeholder: getTranslatedPlainText(itemTranslations, locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.placeholder,
|
|
214
|
+
options: controller.options.map((option) => ({
|
|
215
|
+
...option,
|
|
216
|
+
label: getTranslatedPlainText(itemTranslations, locale, getFormFieldOptionLabelContentKey(field.id, option.id, translationNamespace), fallbackLocale) ?? option.label
|
|
217
|
+
}))
|
|
218
|
+
};
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "slider": break;
|
|
222
|
+
default: {
|
|
223
|
+
const controller = field.controller;
|
|
224
|
+
translatedField.controller = {
|
|
225
|
+
...controller,
|
|
226
|
+
placeholder: getTranslatedPlainText(itemTranslations, locale, getFormFieldPlaceholderContentKey(field.id, translationNamespace), fallbackLocale) ?? controller.placeholder
|
|
227
|
+
};
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return translatedField;
|
|
232
|
+
}
|
|
233
|
+
function withTranslatedFieldGroupContent(group, itemTranslations, locale, fallbackLocale, translationNamespace) {
|
|
234
|
+
return {
|
|
235
|
+
...group,
|
|
236
|
+
title: getTranslatedPlainText(itemTranslations, locale, getFormFieldGroupTitleContentKey(group.id, translationNamespace), fallbackLocale) ?? group.title,
|
|
237
|
+
description: getTranslatedPlainText(itemTranslations, locale, getFormFieldGroupDescriptionContentKey(group.id, translationNamespace), fallbackLocale) ?? group.description,
|
|
238
|
+
fields: group.fields.map((field) => withTranslatedFieldContent(field, itemTranslations, locale, fallbackLocale, translationNamespace))
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function withFormItemTranslatedContent(config, itemTranslations, locale, options = {}) {
|
|
242
|
+
const { fallbackLocale, translationNamespace } = options;
|
|
243
|
+
return {
|
|
244
|
+
...config,
|
|
245
|
+
fieldGroups: config.fieldGroups.map((group) => withTranslatedFieldGroupContent(group, itemTranslations, locale, fallbackLocale, translationNamespace))
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function withoutTranslatedFieldContent(field) {
|
|
249
|
+
const serializedField = {
|
|
250
|
+
...field,
|
|
251
|
+
label: void 0,
|
|
252
|
+
description: void 0,
|
|
253
|
+
errorMessage: void 0,
|
|
254
|
+
required: void 0,
|
|
255
|
+
hasValidations: void 0,
|
|
256
|
+
isInvalid: void 0,
|
|
257
|
+
validations: field.validations.map((validation) => ({
|
|
258
|
+
...validation,
|
|
259
|
+
message: void 0
|
|
260
|
+
}))
|
|
261
|
+
};
|
|
262
|
+
switch (field.type) {
|
|
263
|
+
case "textarea":
|
|
264
|
+
serializedField.controller = {
|
|
265
|
+
...field.controller,
|
|
266
|
+
placeholder: void 0
|
|
267
|
+
};
|
|
268
|
+
break;
|
|
269
|
+
case "number":
|
|
270
|
+
serializedField.controller = {
|
|
271
|
+
...field.controller,
|
|
272
|
+
placeholder: void 0,
|
|
273
|
+
unit: void 0
|
|
274
|
+
};
|
|
275
|
+
break;
|
|
276
|
+
case "datepicker":
|
|
277
|
+
serializedField.controller = {
|
|
278
|
+
...field.controller,
|
|
279
|
+
placeholder: void 0
|
|
280
|
+
};
|
|
281
|
+
break;
|
|
282
|
+
case "switch":
|
|
283
|
+
serializedField.controller = {
|
|
284
|
+
...field.controller,
|
|
285
|
+
checkedLabel: void 0,
|
|
286
|
+
uncheckedLabel: void 0
|
|
287
|
+
};
|
|
288
|
+
break;
|
|
289
|
+
case "select": {
|
|
290
|
+
const controller = field.controller;
|
|
291
|
+
serializedField.controller = {
|
|
292
|
+
...controller,
|
|
293
|
+
placeholder: void 0,
|
|
294
|
+
options: controller.options.map((option) => ({
|
|
295
|
+
...option,
|
|
296
|
+
label: void 0
|
|
297
|
+
}))
|
|
298
|
+
};
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "slider": break;
|
|
302
|
+
default:
|
|
303
|
+
serializedField.controller = {
|
|
304
|
+
...field.controller,
|
|
305
|
+
placeholder: void 0
|
|
306
|
+
};
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
return serializedField;
|
|
310
|
+
}
|
|
311
|
+
function withoutTranslatedFieldGroupContent(group) {
|
|
312
|
+
return {
|
|
313
|
+
...group,
|
|
314
|
+
title: void 0,
|
|
315
|
+
description: void 0,
|
|
316
|
+
fields: group.fields.map((field) => withoutTranslatedFieldContent(field))
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function withoutFormItemTranslatedContent(config) {
|
|
320
|
+
return {
|
|
321
|
+
...config,
|
|
322
|
+
fieldGroups: config.fieldGroups.map((group) => withoutTranslatedFieldGroupContent(group))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function normalizeColumnCount(value) {
|
|
326
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 1;
|
|
327
|
+
return Math.min(4, Math.max(1, Math.round(value)));
|
|
328
|
+
}
|
|
329
|
+
function normalizeFormFieldGroupLayoutConfig(rawLayout) {
|
|
330
|
+
const layout = rawLayout && typeof rawLayout === "object" ? rawLayout : {};
|
|
331
|
+
const hasBaseKey = Object.prototype.hasOwnProperty.call(layout, "base");
|
|
332
|
+
const hasXsKey = Object.prototype.hasOwnProperty.call(layout, "xs");
|
|
333
|
+
if (!hasBaseKey && !hasXsKey && (layout.sm !== void 0 || layout.md !== void 0 || layout.lg !== void 0)) {
|
|
334
|
+
const narrow = normalizeColumnCount(layout.sm);
|
|
335
|
+
return {
|
|
336
|
+
base: narrow,
|
|
337
|
+
xs: narrow,
|
|
338
|
+
sm: normalizeColumnCount(layout.md ?? layout.sm),
|
|
339
|
+
md: normalizeColumnCount(layout.lg ?? layout.md ?? layout.sm),
|
|
340
|
+
xl: normalizeColumnCount(layout.lg ?? layout.md ?? layout.sm)
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const xs = normalizeColumnCount(layout.xs ?? layout.sm ?? 1);
|
|
344
|
+
const sm = normalizeColumnCount(layout.sm ?? 1);
|
|
345
|
+
const md = normalizeColumnCount(layout.md ?? 1);
|
|
346
|
+
const xl = normalizeColumnCount(layout.xl ?? layout.lg ?? 1);
|
|
347
|
+
return {
|
|
348
|
+
base: normalizeColumnCount(hasBaseKey ? layout.base : xs),
|
|
349
|
+
xs,
|
|
350
|
+
sm,
|
|
351
|
+
md,
|
|
352
|
+
xl
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/** CSS custom properties for `.form-field-group-layout-scope` + `.form-field-group-grid`. */
|
|
356
|
+
function formFieldGroupLayoutCssVars(layout) {
|
|
357
|
+
return {
|
|
358
|
+
"--form-group-cols-base": String(layout.base),
|
|
359
|
+
"--form-group-cols-xs": String(layout.xs),
|
|
360
|
+
"--form-group-cols-sm": String(layout.sm),
|
|
361
|
+
"--form-group-cols-md": String(layout.md),
|
|
362
|
+
"--form-group-cols-xl": String(layout.xl)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function normalizeOptionalString(value) {
|
|
366
|
+
if (typeof value !== "string") return;
|
|
367
|
+
return value.trim() || void 0;
|
|
368
|
+
}
|
|
369
|
+
function normalizeOptionalNumber(value) {
|
|
370
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
function normalizeOptionalBoolean(value) {
|
|
374
|
+
return value === true ? true : value === false ? false : void 0;
|
|
375
|
+
}
|
|
376
|
+
function normalizeFormFieldKey(id, key) {
|
|
377
|
+
return key?.trim() || id.replace(/[^a-zA-Z0-9]/g, "").slice(0, 4).toUpperCase() || generateCodingKey(4);
|
|
378
|
+
}
|
|
379
|
+
function createDefaultFormFieldControlConfig(type) {
|
|
380
|
+
switch (type) {
|
|
381
|
+
case "textarea": return {
|
|
382
|
+
type,
|
|
383
|
+
rows: 3
|
|
384
|
+
};
|
|
385
|
+
case "number": return {
|
|
386
|
+
type,
|
|
387
|
+
step: 1
|
|
388
|
+
};
|
|
389
|
+
case "datepicker": return {
|
|
390
|
+
type,
|
|
391
|
+
mode: "year-month-day"
|
|
392
|
+
};
|
|
393
|
+
case "switch": return {
|
|
394
|
+
type,
|
|
395
|
+
defaultChecked: false
|
|
396
|
+
};
|
|
397
|
+
case "select": return {
|
|
398
|
+
type,
|
|
399
|
+
options: []
|
|
400
|
+
};
|
|
401
|
+
case "slider": return {
|
|
402
|
+
type,
|
|
403
|
+
min: 0,
|
|
404
|
+
max: 100,
|
|
405
|
+
step: 1,
|
|
406
|
+
defaultValue: 50,
|
|
407
|
+
showValue: true
|
|
408
|
+
};
|
|
409
|
+
default: return {
|
|
410
|
+
type: "input",
|
|
411
|
+
inputMode: "text"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function normalizeFormFieldSelectOptionConfig(rawOption) {
|
|
416
|
+
if (!rawOption || typeof rawOption !== "object") return null;
|
|
417
|
+
const option = rawOption;
|
|
418
|
+
const id = normalizeOptionalString(option.id) ?? generateId();
|
|
419
|
+
const value = normalizeOptionalString(option.value);
|
|
420
|
+
const label = normalizeOptionalString(option.label);
|
|
421
|
+
if (!value && !label) return null;
|
|
422
|
+
return {
|
|
423
|
+
id,
|
|
424
|
+
value: value ?? label ?? id,
|
|
425
|
+
label
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function hasDuplicateSelectOptionValue(options) {
|
|
429
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
430
|
+
for (const option of options) {
|
|
431
|
+
const normalizedValue = option.value.trim().toLowerCase();
|
|
432
|
+
if (!normalizedValue) continue;
|
|
433
|
+
if (seenValues.has(normalizedValue)) return true;
|
|
434
|
+
seenValues.add(normalizedValue);
|
|
435
|
+
}
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
function normalizeFormFieldValidationConfig(rawValidation) {
|
|
439
|
+
if (!rawValidation || typeof rawValidation !== "object") return null;
|
|
440
|
+
const validation = rawValidation;
|
|
441
|
+
const id = normalizeOptionalString(validation.id) ?? generateId();
|
|
442
|
+
const message = normalizeOptionalString(validation.message);
|
|
443
|
+
switch (validation.type) {
|
|
444
|
+
case "minLength":
|
|
445
|
+
case "maxLength":
|
|
446
|
+
case "min":
|
|
447
|
+
case "max": {
|
|
448
|
+
const value = normalizeOptionalNumber(validation.value);
|
|
449
|
+
if (value === void 0) return null;
|
|
450
|
+
return {
|
|
451
|
+
id,
|
|
452
|
+
type: validation.type,
|
|
453
|
+
value,
|
|
454
|
+
message
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
case "pattern": {
|
|
458
|
+
const value = normalizeOptionalString(validation.value);
|
|
459
|
+
if (!value) return null;
|
|
460
|
+
return {
|
|
461
|
+
id,
|
|
462
|
+
type: "pattern",
|
|
463
|
+
value,
|
|
464
|
+
message
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
case "minDate":
|
|
468
|
+
case "maxDate": {
|
|
469
|
+
const value = normalizeOptionalString(validation.value);
|
|
470
|
+
if (!value) return null;
|
|
471
|
+
return {
|
|
472
|
+
id,
|
|
473
|
+
type: validation.type,
|
|
474
|
+
value,
|
|
475
|
+
message
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
case "email":
|
|
479
|
+
case "url":
|
|
480
|
+
case "integer":
|
|
481
|
+
case "required":
|
|
482
|
+
case "mustBeTrue": return {
|
|
483
|
+
id,
|
|
484
|
+
type: validation.type,
|
|
485
|
+
message
|
|
486
|
+
};
|
|
487
|
+
default: return null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function normalizeFormFieldControlConfig(rawController, type) {
|
|
491
|
+
const controller = rawController && typeof rawController === "object" ? rawController : {};
|
|
492
|
+
switch (type) {
|
|
493
|
+
case "textarea": {
|
|
494
|
+
const defaults = createDefaultFormFieldControlConfig(type);
|
|
495
|
+
return {
|
|
496
|
+
type,
|
|
497
|
+
placeholder: normalizeOptionalString(controller.placeholder),
|
|
498
|
+
rows: Math.min(12, Math.max(2, Math.round(normalizeOptionalNumber(controller.rows) ?? defaults.rows ?? 3)))
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
case "number": {
|
|
502
|
+
const defaults = createDefaultFormFieldControlConfig(type);
|
|
503
|
+
return {
|
|
504
|
+
type,
|
|
505
|
+
placeholder: normalizeOptionalString(controller.placeholder),
|
|
506
|
+
step: normalizeOptionalNumber(controller.step) ?? defaults.step,
|
|
507
|
+
unit: normalizeOptionalString(controller.unit),
|
|
508
|
+
defaultValue: normalizeOptionalNumber(controller.defaultValue)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
case "datepicker": {
|
|
512
|
+
const defaults = createDefaultFormFieldControlConfig(type);
|
|
513
|
+
return {
|
|
514
|
+
type,
|
|
515
|
+
placeholder: normalizeOptionalString(controller.placeholder),
|
|
516
|
+
mode: controller.mode === "year" || controller.mode === "year-month" ? controller.mode : defaults.mode
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
case "switch": return {
|
|
520
|
+
type,
|
|
521
|
+
checkedLabel: normalizeOptionalString(controller.checkedLabel),
|
|
522
|
+
uncheckedLabel: normalizeOptionalString(controller.uncheckedLabel),
|
|
523
|
+
defaultChecked: normalizeOptionalBoolean(controller.defaultChecked) ?? false
|
|
524
|
+
};
|
|
525
|
+
case "select": {
|
|
526
|
+
const defaults = createDefaultFormFieldControlConfig(type);
|
|
527
|
+
return {
|
|
528
|
+
type,
|
|
529
|
+
placeholder: normalizeOptionalString(controller.placeholder),
|
|
530
|
+
allowClear: normalizeOptionalBoolean(controller.allowClear),
|
|
531
|
+
options: Array.isArray(controller.options) ? controller.options.map((option) => normalizeFormFieldSelectOptionConfig(option)).filter((option) => option !== null) : defaults.options
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
case "slider": {
|
|
535
|
+
const defaults = createDefaultFormFieldControlConfig(type);
|
|
536
|
+
return {
|
|
537
|
+
type,
|
|
538
|
+
min: normalizeOptionalNumber(controller.min) ?? defaults.min,
|
|
539
|
+
max: normalizeOptionalNumber(controller.max) ?? defaults.max,
|
|
540
|
+
step: normalizeOptionalNumber(controller.step) ?? defaults.step,
|
|
541
|
+
defaultValue: normalizeOptionalNumber(controller.defaultValue) ?? defaults.defaultValue,
|
|
542
|
+
showValue: normalizeOptionalBoolean(controller.showValue) ?? defaults.showValue ?? true
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
default: return {
|
|
546
|
+
type: "input",
|
|
547
|
+
placeholder: normalizeOptionalString(controller.placeholder),
|
|
548
|
+
inputMode: controller.inputMode === "email" || controller.inputMode === "tel" || controller.inputMode === "url" || controller.inputMode === "password" ? controller.inputMode : "text",
|
|
549
|
+
autocomplete: normalizeOptionalString(controller.autocomplete)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function hasDuplicateValidationType(validations) {
|
|
554
|
+
const seenValidationTypes = /* @__PURE__ */ new Set();
|
|
555
|
+
for (const validation of validations) {
|
|
556
|
+
if (seenValidationTypes.has(validation.type)) return true;
|
|
557
|
+
seenValidationTypes.add(validation.type);
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
function isFormFieldConfigInvalid(field) {
|
|
562
|
+
if (!field.key.trim()) return true;
|
|
563
|
+
if (hasDuplicateValidationType(field.validations)) return true;
|
|
564
|
+
switch (field.type) {
|
|
565
|
+
case "number": {
|
|
566
|
+
const controller = field.controller;
|
|
567
|
+
if (controller.step !== void 0 && controller.step <= 0) return true;
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
case "datepicker": {
|
|
571
|
+
const minDate = field.validations.find((validation) => validation.type === "minDate")?.value;
|
|
572
|
+
const maxDate = field.validations.find((validation) => validation.type === "maxDate")?.value;
|
|
573
|
+
if (typeof minDate === "string" && typeof maxDate === "string" && minDate > maxDate) return true;
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
case "select": {
|
|
577
|
+
const controller = field.controller;
|
|
578
|
+
return controller.options.length === 0 || controller.options.some((option) => !option.value.trim()) || hasDuplicateSelectOptionValue(controller.options);
|
|
579
|
+
}
|
|
580
|
+
case "slider": {
|
|
581
|
+
const controller = field.controller;
|
|
582
|
+
if (controller.min === void 0 || controller.max === void 0 || controller.step === void 0 || controller.defaultValue === void 0) return true;
|
|
583
|
+
return controller.min >= controller.max || controller.step <= 0 || controller.defaultValue < controller.min || controller.defaultValue > controller.max;
|
|
584
|
+
}
|
|
585
|
+
default: return field.validations.some((validation) => {
|
|
586
|
+
if ((validation.type === "minLength" || validation.type === "maxLength" || validation.type === "min" || validation.type === "max") && typeof validation.value === "number") return validation.value < 0;
|
|
587
|
+
if ((validation.type === "pattern" || validation.type === "minDate" || validation.type === "maxDate") && typeof validation.value === "string") return validation.value.trim().length === 0;
|
|
588
|
+
return false;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function normalizeFormFieldConfig(rawField) {
|
|
593
|
+
if (!rawField || typeof rawField !== "object") return null;
|
|
594
|
+
const field = rawField;
|
|
595
|
+
const trimmedId = typeof field.id === "string" ? field.id.trim() : "";
|
|
596
|
+
const trimmedKey = typeof field.key === "string" ? field.key.trim() : "";
|
|
597
|
+
if (!trimmedId) return null;
|
|
598
|
+
const normalizedKey = normalizeFormFieldKey(trimmedId, trimmedKey);
|
|
599
|
+
const type = field.type === "textarea" || field.type === "number" || field.type === "datepicker" || field.type === "switch" || field.type === "select" || field.type === "slider" ? field.type : "input";
|
|
600
|
+
const normalizedRawValidations = Array.isArray(field.validations) ? field.validations.map((validation) => normalizeFormFieldValidationConfig(validation)).filter((validation) => validation !== null) : [];
|
|
601
|
+
const validations = getFormFieldEffectiveValidations({
|
|
602
|
+
id: trimmedId,
|
|
603
|
+
required: field.required === true ? true : void 0,
|
|
604
|
+
validations: normalizedRawValidations
|
|
605
|
+
}).map((validation) => ({
|
|
606
|
+
id: validation.id,
|
|
607
|
+
type: validation.type,
|
|
608
|
+
value: validation.value,
|
|
609
|
+
message: validation.message
|
|
610
|
+
}));
|
|
611
|
+
const controller = normalizeFormFieldControlConfig(field.controller, type);
|
|
612
|
+
const hasEffectiveValidations = getFormFieldEffectiveValidations({
|
|
613
|
+
id: trimmedId,
|
|
614
|
+
validations
|
|
615
|
+
}).length > 0;
|
|
616
|
+
return {
|
|
617
|
+
id: trimmedId,
|
|
618
|
+
key: normalizedKey,
|
|
619
|
+
type,
|
|
620
|
+
label: normalizeOptionalString(field.label),
|
|
621
|
+
description: normalizeOptionalString(field.description),
|
|
622
|
+
errorMessage: normalizeOptionalString(field.errorMessage),
|
|
623
|
+
required: validations.some((validation) => validation.type === "required") ? true : void 0,
|
|
624
|
+
controller,
|
|
625
|
+
validations,
|
|
626
|
+
hasValidations: hasEffectiveValidations ? true : void 0,
|
|
627
|
+
isInvalid: isFormFieldConfigInvalid({
|
|
628
|
+
key: normalizedKey,
|
|
629
|
+
type,
|
|
630
|
+
controller,
|
|
631
|
+
validations
|
|
632
|
+
}) ? true : void 0
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function normalizeFormFieldGroupConfig(rawGroup) {
|
|
636
|
+
if (!rawGroup || typeof rawGroup !== "object") return null;
|
|
637
|
+
const group = rawGroup;
|
|
638
|
+
const trimmedId = typeof group.id === "string" ? group.id.trim() : "";
|
|
639
|
+
const trimmedTitle = typeof group.title === "string" ? group.title.trim() : "";
|
|
640
|
+
const trimmedDescription = typeof group.description === "string" ? group.description.trim() : "";
|
|
641
|
+
if (!trimmedId) return null;
|
|
642
|
+
return {
|
|
643
|
+
id: trimmedId,
|
|
644
|
+
title: trimmedTitle || void 0,
|
|
645
|
+
description: trimmedDescription || void 0,
|
|
646
|
+
hasDisplayCondition: group.hasDisplayCondition === true ? true : void 0,
|
|
647
|
+
layout: normalizeFormFieldGroupLayoutConfig(group.layout),
|
|
648
|
+
fields: Array.isArray(group.fields) ? group.fields.map((field) => normalizeFormFieldConfig(field)).filter((field) => field !== null) : []
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function normalizeFormItemConfig(rawConfig, itemId) {
|
|
652
|
+
const config = rawConfig ?? {};
|
|
653
|
+
return {
|
|
654
|
+
id: typeof config.id === "string" && config.id.trim() ? config.id : itemId,
|
|
655
|
+
authoringMode: config.authoringMode === "simple" ? "simple" : "custom",
|
|
656
|
+
fieldGroups: Array.isArray(config.fieldGroups) ? config.fieldGroups.map((group) => normalizeFormFieldGroupConfig(group)).filter((group) => group !== null) : []
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function createFormFieldGroupConfig() {
|
|
660
|
+
return {
|
|
661
|
+
id: generateId(),
|
|
662
|
+
layout: {
|
|
663
|
+
base: 1,
|
|
664
|
+
xs: 1,
|
|
665
|
+
sm: 1,
|
|
666
|
+
md: 1,
|
|
667
|
+
xl: 1
|
|
668
|
+
},
|
|
669
|
+
fields: []
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function getNextAvailableFormFieldKey(fields) {
|
|
673
|
+
const usedKeys = new Set(fields.map((field) => field.key.trim().toLowerCase()).filter(Boolean));
|
|
674
|
+
let nextIndex = 1;
|
|
675
|
+
while (usedKeys.has(`f${nextIndex}`)) nextIndex += 1;
|
|
676
|
+
return `F${nextIndex}`;
|
|
677
|
+
}
|
|
678
|
+
function createFormFieldConfig(type, key = generateCodingKey(4)) {
|
|
679
|
+
return {
|
|
680
|
+
id: generateId(),
|
|
681
|
+
key,
|
|
682
|
+
type,
|
|
683
|
+
controller: createDefaultFormFieldControlConfig(type),
|
|
684
|
+
validations: []
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
688
|
+
const SIMPLE_FORM_FIELD_TYPES = new Set([
|
|
689
|
+
"input",
|
|
690
|
+
"textarea",
|
|
691
|
+
"number",
|
|
692
|
+
"slider",
|
|
693
|
+
"select",
|
|
694
|
+
"datepicker",
|
|
695
|
+
"switch"
|
|
696
|
+
]);
|
|
697
|
+
const getFormAuthoringMode = (config) => isRecord(config) && config.authoringMode === "simple" ? "simple" : "custom";
|
|
698
|
+
const hasSingleFormField = (config) => {
|
|
699
|
+
if (!isRecord(config) || !Array.isArray(config.fieldGroups) || config.fieldGroups.length !== 1) return false;
|
|
700
|
+
const [group] = config.fieldGroups;
|
|
701
|
+
if (!isRecord(group) || !Array.isArray(group.fields) || group.fields.length !== 1) return false;
|
|
702
|
+
const layout = isRecord(group.layout) ? group.layout : void 0;
|
|
703
|
+
if (group.hasDisplayCondition === true || layout && [
|
|
704
|
+
"base",
|
|
705
|
+
"xs",
|
|
706
|
+
"sm",
|
|
707
|
+
"md",
|
|
708
|
+
"xl"
|
|
709
|
+
].some((breakpoint) => layout[breakpoint] !== void 0 && layout[breakpoint] !== 1)) return false;
|
|
710
|
+
const [field] = group.fields;
|
|
711
|
+
return isRecord(field) && typeof field.id === "string" && field.id.length > 0 && typeof field.key === "string" && field.key.length > 0 && typeof field.type === "string" && SIMPLE_FORM_FIELD_TYPES.has(field.type) && isRecord(field.controller) && field.controller.type === field.type;
|
|
712
|
+
};
|
|
713
|
+
const isValidSimpleFormConfig = (config) => getFormAuthoringMode(config) !== "simple" || hasSingleFormField(config);
|
|
714
|
+
/**
|
|
715
|
+
* Canonicalizes the editor-facing mode without modifying groups, fields, or translations.
|
|
716
|
+
* Missing modes and malformed simple forms become custom; valid simple forms stay simple.
|
|
717
|
+
*/
|
|
718
|
+
const normalizeFormAuthoringConfig = (config) => {
|
|
719
|
+
if (!isRecord(config)) return;
|
|
720
|
+
return {
|
|
721
|
+
...config,
|
|
722
|
+
authoringMode: config.authoringMode === "simple" && hasSingleFormField(config) ? "simple" : "custom"
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
/**
|
|
726
|
+
* Returns true only for an in-place edit of the existing sole field that the simple editor can
|
|
727
|
+
* still represent. Adding, moving, retyping, or exposing hidden field configuration is structural.
|
|
728
|
+
*/
|
|
729
|
+
const isCompatibleSimpleFieldUpsert = ({ config, field, groupId, index }) => {
|
|
730
|
+
if (getFormAuthoringMode(config) !== "simple" || !isRecord(config) || !Array.isArray(config.fieldGroups) || config.fieldGroups.length !== 1 || (index ?? 0) > 0) return false;
|
|
731
|
+
const [group] = config.fieldGroups;
|
|
732
|
+
if (!isRecord(group) || group.id !== groupId || !Array.isArray(group.fields)) return false;
|
|
733
|
+
const [existingField] = group.fields;
|
|
734
|
+
if (!isRecord(existingField) || group.fields.length !== 1) return false;
|
|
735
|
+
const controller = isRecord(field.controller) ? field.controller : void 0;
|
|
736
|
+
const validationsUnchanged = field.validations === void 0 || JSON.stringify(field.validations) === JSON.stringify(existingField.validations);
|
|
737
|
+
const simpleEditableKeys = new Set([
|
|
738
|
+
"id",
|
|
739
|
+
"key",
|
|
740
|
+
"type",
|
|
741
|
+
"controller",
|
|
742
|
+
"required",
|
|
743
|
+
"validations"
|
|
744
|
+
]);
|
|
745
|
+
const hiddenConfigUnchanged = Object.keys(field).filter((key) => !simpleEditableKeys.has(key)).every((key) => JSON.stringify(field[key]) === JSON.stringify(existingField[key]));
|
|
746
|
+
return existingField.id === field.id && existingField.key === field.key && existingField.type === field.type && controller?.type === field.type && validationsUnchanged && hiddenConfigUnchanged;
|
|
747
|
+
};
|
|
748
|
+
/** Structural form edits leave simple mode unless the sole field remains representable there. */
|
|
749
|
+
const transitionFormAuthoringMode = ({ config, fieldGroups, preserveSimpleMode = false }) => {
|
|
750
|
+
const nextConfig = {
|
|
751
|
+
...config,
|
|
752
|
+
fieldGroups
|
|
753
|
+
};
|
|
754
|
+
if (getFormAuthoringMode(config) !== "simple") return {
|
|
755
|
+
...nextConfig,
|
|
756
|
+
authoringMode: "custom"
|
|
757
|
+
};
|
|
758
|
+
return {
|
|
759
|
+
...nextConfig,
|
|
760
|
+
authoringMode: preserveSimpleMode && hasSingleFormField(nextConfig) ? "simple" : "custom"
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
//#endregion
|
|
764
|
+
export { FORM_DATEPICKER_FIELD_MODES, createFormFieldConfig, createFormFieldGroupConfig, createFormItemConfig, formFieldGroupLayoutCssVars, getFormAuthoringMode, getFormFieldCheckedLabelContentKey, getFormFieldDescriptionContentKey, getFormFieldEffectiveValidations, getFormFieldErrorMessageContentKey, getFormFieldGroupDescriptionContentKey, getFormFieldGroupTitleContentKey, getFormFieldGroupTranslationKeys, getFormFieldGroupTranslationPrefix, getFormFieldLabelContentKey, getFormFieldOptionLabelContentKey, getFormFieldPlaceholderContentKey, getFormFieldTranslationKeys, getFormFieldTranslationPrefix, getFormFieldUncheckedLabelContentKey, getFormFieldUnitContentKey, getFormFieldValidationMessageContentKey, getFormFieldValueType, getNextAvailableFormFieldKey, hasSingleFormField, isCompatibleSimpleFieldUpsert, isValidSimpleFormConfig, normalizeFormAuthoringConfig, normalizeFormItemConfig, transitionFormAuthoringMode, withFormItemTranslatedContent, withoutFormItemTranslatedContent, writeFormFieldGroupTranslations, writeFormFieldTranslations };
|
|
765
|
+
|
|
766
|
+
//# sourceMappingURL=form.mjs.map
|