@form-engine-ts/react 1.1.0 → 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 +30 -0
- package/dist/index.cjs +612 -141
- package/dist/index.d.cts +109 -6
- package/dist/index.d.ts +109 -6
- package/dist/index.js +589 -110
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4,6 +4,384 @@ import {
|
|
|
4
4
|
sanitizeSchema
|
|
5
5
|
} from "@form-engine-ts/core";
|
|
6
6
|
import { useState } from "react";
|
|
7
|
+
|
|
8
|
+
// src/hooks/useFormBuilder.ts
|
|
9
|
+
import {
|
|
10
|
+
validateFormSchema
|
|
11
|
+
} from "@form-engine-ts/core";
|
|
12
|
+
import { useCallback, useMemo } from "react";
|
|
13
|
+
var DEFAULT_PREFIXES = { field: "q", option: "opt", page: "page" };
|
|
14
|
+
function defaultIdFactory(kind, existingIds) {
|
|
15
|
+
const prefix = DEFAULT_PREFIXES[kind];
|
|
16
|
+
let id;
|
|
17
|
+
do {
|
|
18
|
+
id = `${prefix}_${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
19
|
+
} while (existingIds.has(id));
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
function newField(type, id, optionId) {
|
|
23
|
+
const base = { id, title: "New question", required: false };
|
|
24
|
+
if (type === "text" || type === "textarea") return { ...base, type };
|
|
25
|
+
if (type === "number") return { ...base, type };
|
|
26
|
+
if (type === "rating") return { ...base, type, min: 1, max: 5 };
|
|
27
|
+
if (type === "checkbox") return { ...base, type };
|
|
28
|
+
return optionId === void 0 ? void 0 : { ...base, type, options: [{ id: optionId, label: "Option 1" }] };
|
|
29
|
+
}
|
|
30
|
+
function move(items, sourceIndex, targetIndex) {
|
|
31
|
+
if (sourceIndex < 0 || targetIndex < 0 || targetIndex >= items.length || sourceIndex === targetIndex)
|
|
32
|
+
return void 0;
|
|
33
|
+
const result = [...items];
|
|
34
|
+
const [item] = result.splice(sourceIndex, 1);
|
|
35
|
+
if (item === void 0) return void 0;
|
|
36
|
+
result.splice(targetIndex, 0, item);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function addPolicyIssues(schema, policy, issues) {
|
|
40
|
+
if (policy?.maxFields !== void 0 && schema.fields.length > policy.maxFields) {
|
|
41
|
+
issues.push({
|
|
42
|
+
path: "fields",
|
|
43
|
+
code: "max_fields_exceeded",
|
|
44
|
+
message: `At most ${policy.maxFields} fields are allowed.`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
schema.fields.forEach((field, index) => {
|
|
48
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(field.type)) {
|
|
49
|
+
issues.push({
|
|
50
|
+
path: `fields[${index}].type`,
|
|
51
|
+
code: "disallowed_field_type",
|
|
52
|
+
message: `Field type ${field.type} is not allowed.`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (policy?.maxTextLength !== void 0) {
|
|
56
|
+
for (const [property, text] of [
|
|
57
|
+
["title", field.title],
|
|
58
|
+
["description", field.description]
|
|
59
|
+
]) {
|
|
60
|
+
if (text !== void 0 && text.length > policy.maxTextLength) {
|
|
61
|
+
issues.push({
|
|
62
|
+
path: `fields[${index}].${property}`,
|
|
63
|
+
code: "max_text_length_exceeded",
|
|
64
|
+
message: `Text must be at most ${policy.maxTextLength} characters.`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (policy?.maxOptionsPerField !== void 0 && "options" in field && field.options.length > policy.maxOptionsPerField) {
|
|
70
|
+
issues.push({
|
|
71
|
+
path: `fields[${index}].options`,
|
|
72
|
+
code: "max_options_exceeded",
|
|
73
|
+
message: `At most ${policy.maxOptionsPerField} options are allowed.`
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
for (const locale of policy?.requiredLocales ?? []) {
|
|
78
|
+
if (!(schema.supportedLocales ?? []).includes(locale)) {
|
|
79
|
+
issues.push({
|
|
80
|
+
path: "supportedLocales",
|
|
81
|
+
code: "required_locale_missing",
|
|
82
|
+
message: `Required locale ${locale} is missing.`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (locale === schema.defaultLocale) continue;
|
|
86
|
+
const requiredTranslations = [
|
|
87
|
+
{ path: `translations.${locale}.title`, value: schema.translations?.[locale]?.title },
|
|
88
|
+
...schema.fields.map((field, index) => ({
|
|
89
|
+
path: `fields[${index}].translations.${locale}.title`,
|
|
90
|
+
value: field.translations?.[locale]?.title
|
|
91
|
+
})),
|
|
92
|
+
...schema.fields.flatMap(
|
|
93
|
+
(field, fieldIndex) => "options" in field ? field.options.map((option, optionIndex) => ({
|
|
94
|
+
path: `fields[${fieldIndex}].options[${optionIndex}].translations.${locale}`,
|
|
95
|
+
value: option.translations?.[locale]
|
|
96
|
+
})) : []
|
|
97
|
+
),
|
|
98
|
+
...schema.pages?.flatMap(
|
|
99
|
+
(page, pageIndex) => page.title === void 0 ? [] : [{ path: `pages[${pageIndex}].translations.${locale}.title`, value: page.translations?.[locale]?.title }]
|
|
100
|
+
) ?? []
|
|
101
|
+
];
|
|
102
|
+
for (const translation of requiredTranslations) {
|
|
103
|
+
if (translation.value === void 0 || translation.value.trim().length === 0) {
|
|
104
|
+
issues.push({
|
|
105
|
+
path: translation.path,
|
|
106
|
+
code: "required_translation_missing",
|
|
107
|
+
message: `A translation for required locale ${locale} is missing.`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function useFormBuilder({
|
|
114
|
+
schema,
|
|
115
|
+
onChange,
|
|
116
|
+
policy,
|
|
117
|
+
idFactory = defaultIdFactory
|
|
118
|
+
}) {
|
|
119
|
+
const createId = useCallback(
|
|
120
|
+
(kind, existingIds) => {
|
|
121
|
+
const id = idFactory(kind, existingIds).trim();
|
|
122
|
+
return id.length > 0 && !existingIds.has(id) ? id : void 0;
|
|
123
|
+
},
|
|
124
|
+
[idFactory]
|
|
125
|
+
);
|
|
126
|
+
const addField = useCallback(
|
|
127
|
+
(type, pageId) => {
|
|
128
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type)) {
|
|
129
|
+
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
130
|
+
}
|
|
131
|
+
if (policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields) {
|
|
132
|
+
return { success: false, error: { type: "max_fields_exceeded", max: policy.maxFields } };
|
|
133
|
+
}
|
|
134
|
+
const fieldId = createId("field", new Set(schema.fields.map((field2) => field2.id)));
|
|
135
|
+
const needsOption = ["select", "radio", "multi-select"].includes(type);
|
|
136
|
+
const optionId = needsOption ? createId(
|
|
137
|
+
"option",
|
|
138
|
+
new Set(
|
|
139
|
+
schema.fields.flatMap((field2) => "options" in field2 ? field2.options.map((option) => option.id) : [])
|
|
140
|
+
)
|
|
141
|
+
) : void 0;
|
|
142
|
+
if (fieldId === void 0 || needsOption && optionId === void 0) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: { type: "invalid_operation", message: "idFactory returned a duplicate or empty ID." }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const field = newField(type, fieldId, optionId);
|
|
149
|
+
if (field === void 0) {
|
|
150
|
+
return { success: false, error: { type: "invalid_operation", message: "Could not create the field." } };
|
|
151
|
+
}
|
|
152
|
+
const pages = schema.pages?.map((page, index) => ({
|
|
153
|
+
...page,
|
|
154
|
+
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds, fieldId] : page.questionIds
|
|
155
|
+
}));
|
|
156
|
+
if (schema.pages !== void 0 && !schema.pages.some((page) => page.id === pageId) && pageId !== void 0) {
|
|
157
|
+
return { success: false, error: { type: "invalid_operation", message: `Unknown page: ${pageId}` } };
|
|
158
|
+
}
|
|
159
|
+
onChange({ ...schema, fields: [...schema.fields, field], ...pages === void 0 ? {} : { pages } });
|
|
160
|
+
return { success: true };
|
|
161
|
+
},
|
|
162
|
+
[createId, onChange, policy, schema]
|
|
163
|
+
);
|
|
164
|
+
const removeField = useCallback(
|
|
165
|
+
(fieldId) => {
|
|
166
|
+
if (schema.fields.length <= 1 || !schema.fields.some((field) => field.id === fieldId)) return;
|
|
167
|
+
const fields = schema.fields.filter((field) => field.id !== fieldId).map(
|
|
168
|
+
(field) => field.displayCondition?.questionId === fieldId ? (({ displayCondition: _condition, ...candidate }) => candidate)(field) : field
|
|
169
|
+
);
|
|
170
|
+
const remainingPages = schema.pages?.map((page) => ({ ...page, questionIds: page.questionIds.filter((id) => id !== fieldId) })).filter((page) => page.questionIds.length > 0);
|
|
171
|
+
if (schema.pages !== void 0 && remainingPages?.length === 0) {
|
|
172
|
+
const { pages: _pages, ...singlePageSchema } = schema;
|
|
173
|
+
onChange({ ...singlePageSchema, fields });
|
|
174
|
+
} else {
|
|
175
|
+
onChange({ ...schema, fields, ...remainingPages === void 0 ? {} : { pages: remainingPages } });
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
[onChange, schema]
|
|
179
|
+
);
|
|
180
|
+
const moveField = useCallback(
|
|
181
|
+
(fieldId, targetIndex) => {
|
|
182
|
+
const fields = move(
|
|
183
|
+
schema.fields,
|
|
184
|
+
schema.fields.findIndex((field) => field.id === fieldId),
|
|
185
|
+
targetIndex
|
|
186
|
+
);
|
|
187
|
+
if (fields !== void 0) {
|
|
188
|
+
const indexById = new Map(fields.map((field, index) => [field.id, index]));
|
|
189
|
+
const safeFields = fields.map((field, index) => {
|
|
190
|
+
const sourceIndex = field.displayCondition === void 0 ? void 0 : indexById.get(field.displayCondition.questionId);
|
|
191
|
+
if (field.displayCondition === void 0 || sourceIndex !== void 0 && sourceIndex < index) return field;
|
|
192
|
+
const { displayCondition: _condition, ...withoutCondition } = field;
|
|
193
|
+
return withoutCondition;
|
|
194
|
+
});
|
|
195
|
+
onChange({ ...schema, fields: safeFields });
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
[onChange, schema]
|
|
199
|
+
);
|
|
200
|
+
const updateField = useCallback(
|
|
201
|
+
(fieldId, updater) => {
|
|
202
|
+
const current = schema.fields.find((field) => field.id === fieldId);
|
|
203
|
+
if (current === void 0) return;
|
|
204
|
+
const updated = updater(current);
|
|
205
|
+
if (updated.id !== fieldId) return;
|
|
206
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(updated.type)) return;
|
|
207
|
+
const maxTextLength = policy?.maxTextLength;
|
|
208
|
+
if (maxTextLength !== void 0 && [updated.title, updated.description].some((text) => text !== void 0 && text.length > maxTextLength)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
onChange({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? updated : field) });
|
|
212
|
+
},
|
|
213
|
+
[onChange, policy, schema]
|
|
214
|
+
);
|
|
215
|
+
const addOption = useCallback(
|
|
216
|
+
(fieldId) => {
|
|
217
|
+
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
218
|
+
if (field === void 0 || !("options" in field)) {
|
|
219
|
+
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
220
|
+
}
|
|
221
|
+
if (policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField) {
|
|
222
|
+
return { success: false, error: { type: "max_options_exceeded", max: policy.maxOptionsPerField } };
|
|
223
|
+
}
|
|
224
|
+
const existingIds = new Set(
|
|
225
|
+
schema.fields.flatMap(
|
|
226
|
+
(candidate) => "options" in candidate ? candidate.options.map((option2) => option2.id) : []
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
const optionId = createId("option", existingIds);
|
|
230
|
+
if (optionId === void 0) {
|
|
231
|
+
return {
|
|
232
|
+
success: false,
|
|
233
|
+
error: { type: "invalid_operation", message: "idFactory returned a duplicate or empty ID." }
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const option = { id: optionId, label: `Option ${field.options.length + 1}` };
|
|
237
|
+
onChange({
|
|
238
|
+
...schema,
|
|
239
|
+
fields: schema.fields.map(
|
|
240
|
+
(candidate) => candidate.id === fieldId && "options" in candidate ? { ...candidate, options: [...candidate.options, option] } : candidate
|
|
241
|
+
)
|
|
242
|
+
});
|
|
243
|
+
return { success: true };
|
|
244
|
+
},
|
|
245
|
+
[createId, onChange, policy, schema]
|
|
246
|
+
);
|
|
247
|
+
const removeOption = useCallback(
|
|
248
|
+
(fieldId, optionId) => {
|
|
249
|
+
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
250
|
+
if (field === void 0 || !("options" in field) || field.options.length <= 1) return;
|
|
251
|
+
onChange({
|
|
252
|
+
...schema,
|
|
253
|
+
fields: schema.fields.map(
|
|
254
|
+
(candidate) => candidate.id === fieldId && "options" in candidate ? { ...candidate, options: candidate.options.filter((option) => option.id !== optionId) } : candidate
|
|
255
|
+
)
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
[onChange, schema]
|
|
259
|
+
);
|
|
260
|
+
const moveOption = useCallback(
|
|
261
|
+
(fieldId, optionId, targetIndex) => {
|
|
262
|
+
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
263
|
+
if (field === void 0 || !("options" in field)) return;
|
|
264
|
+
const options = move(
|
|
265
|
+
field.options,
|
|
266
|
+
field.options.findIndex((option) => option.id === optionId),
|
|
267
|
+
targetIndex
|
|
268
|
+
);
|
|
269
|
+
if (options === void 0) return;
|
|
270
|
+
onChange({
|
|
271
|
+
...schema,
|
|
272
|
+
fields: schema.fields.map(
|
|
273
|
+
(candidate) => candidate.id === fieldId && "options" in candidate ? { ...candidate, options } : candidate
|
|
274
|
+
)
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
[onChange, schema]
|
|
278
|
+
);
|
|
279
|
+
const addPage = useCallback(
|
|
280
|
+
(questionId) => {
|
|
281
|
+
const existingIds = new Set(schema.pages?.map((page) => page.id) ?? []);
|
|
282
|
+
const pageId = createId("page", existingIds);
|
|
283
|
+
if (pageId === void 0) return;
|
|
284
|
+
if (schema.pages === void 0) {
|
|
285
|
+
onChange({ ...schema, pages: [{ id: pageId, questionIds: schema.fields.map((field) => field.id) }] });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const movableQuestionId = questionId ?? schema.pages.find((page) => page.questionIds.length > 1)?.questionIds.at(-1);
|
|
289
|
+
if (movableQuestionId === void 0) return;
|
|
290
|
+
const sourcePage = schema.pages.find((page) => page.questionIds.includes(movableQuestionId));
|
|
291
|
+
if (sourcePage === void 0 || sourcePage.questionIds.length <= 1) return;
|
|
292
|
+
const pages = [
|
|
293
|
+
...schema.pages.map((page) => ({
|
|
294
|
+
...page,
|
|
295
|
+
questionIds: page.questionIds.filter((id) => id !== movableQuestionId)
|
|
296
|
+
})),
|
|
297
|
+
{ id: pageId, questionIds: [movableQuestionId] }
|
|
298
|
+
];
|
|
299
|
+
onChange({ ...schema, pages });
|
|
300
|
+
},
|
|
301
|
+
[createId, onChange, schema]
|
|
302
|
+
);
|
|
303
|
+
const removePage = useCallback(
|
|
304
|
+
(pageId) => {
|
|
305
|
+
if (schema.pages === void 0) return;
|
|
306
|
+
const index = schema.pages.findIndex((page) => page.id === pageId);
|
|
307
|
+
const removed = schema.pages[index];
|
|
308
|
+
if (removed === void 0) return;
|
|
309
|
+
if (schema.pages.length === 1) {
|
|
310
|
+
const { pages: _pages, ...singlePage } = schema;
|
|
311
|
+
onChange(singlePage);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const targetIndex = index === 0 ? 1 : index - 1;
|
|
315
|
+
onChange({
|
|
316
|
+
...schema,
|
|
317
|
+
pages: schema.pages.map(
|
|
318
|
+
(page, pageIndex) => pageIndex === targetIndex ? { ...page, questionIds: [...page.questionIds, ...removed.questionIds] } : page
|
|
319
|
+
).filter((page) => page.id !== pageId)
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
[onChange, schema]
|
|
323
|
+
);
|
|
324
|
+
const setLocaleTranslation = useCallback(
|
|
325
|
+
(locale, target, property, text) => {
|
|
326
|
+
if (locale.trim().length === 0 || policy?.maxTextLength !== void 0 && text.length > policy.maxTextLength)
|
|
327
|
+
return;
|
|
328
|
+
const supportedLocales = [.../* @__PURE__ */ new Set([...schema.supportedLocales ?? [], locale])];
|
|
329
|
+
if (target === "form" && ["title", "description", "completionMessage"].includes(property)) {
|
|
330
|
+
onChange({
|
|
331
|
+
...schema,
|
|
332
|
+
supportedLocales,
|
|
333
|
+
translations: { ...schema.translations, [locale]: { ...schema.translations?.[locale], [property]: text } }
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const fields = schema.fields.map((field) => {
|
|
338
|
+
if (field.id === target && ["title", "description"].includes(property)) {
|
|
339
|
+
return {
|
|
340
|
+
...field,
|
|
341
|
+
translations: { ...field.translations, [locale]: { ...field.translations?.[locale], [property]: text } }
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (!("options" in field) || property !== "label") return field;
|
|
345
|
+
return {
|
|
346
|
+
...field,
|
|
347
|
+
options: field.options.map(
|
|
348
|
+
(option) => option.id === target ? { ...option, translations: { ...option.translations, [locale]: text } } : option
|
|
349
|
+
)
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
const pages = schema.pages?.map(
|
|
353
|
+
(page) => page.id === target && ["title", "description"].includes(property) ? {
|
|
354
|
+
...page,
|
|
355
|
+
translations: { ...page.translations, [locale]: { ...page.translations?.[locale], [property]: text } }
|
|
356
|
+
} : page
|
|
357
|
+
);
|
|
358
|
+
onChange({ ...schema, supportedLocales, fields, ...pages === void 0 ? {} : { pages } });
|
|
359
|
+
},
|
|
360
|
+
[onChange, policy, schema]
|
|
361
|
+
);
|
|
362
|
+
const validationIssues = useMemo(() => {
|
|
363
|
+
const result = validateFormSchema(schema);
|
|
364
|
+
const issues = result.valid ? [] : [...result.issues];
|
|
365
|
+
addPolicyIssues(schema, policy, issues);
|
|
366
|
+
return issues;
|
|
367
|
+
}, [policy, schema]);
|
|
368
|
+
return {
|
|
369
|
+
schema,
|
|
370
|
+
addField,
|
|
371
|
+
removeField,
|
|
372
|
+
moveField,
|
|
373
|
+
updateField,
|
|
374
|
+
addOption,
|
|
375
|
+
removeOption,
|
|
376
|
+
moveOption,
|
|
377
|
+
addPage,
|
|
378
|
+
removePage,
|
|
379
|
+
setLocaleTranslation,
|
|
380
|
+
validationIssues
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/builder.tsx
|
|
7
385
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
386
|
var FIELD_TYPES = [
|
|
9
387
|
"text",
|
|
@@ -187,7 +565,21 @@ function ConditionValueEditor({
|
|
|
187
565
|
}
|
|
188
566
|
);
|
|
189
567
|
}
|
|
190
|
-
function FormBuilder({
|
|
568
|
+
function FormBuilder({
|
|
569
|
+
schema,
|
|
570
|
+
onChange,
|
|
571
|
+
locale = "en",
|
|
572
|
+
translator,
|
|
573
|
+
translationAdapter,
|
|
574
|
+
policy,
|
|
575
|
+
idFactory
|
|
576
|
+
}) {
|
|
577
|
+
const headless = useFormBuilder({
|
|
578
|
+
schema,
|
|
579
|
+
onChange,
|
|
580
|
+
...policy === void 0 ? {} : { policy },
|
|
581
|
+
...idFactory === void 0 ? {} : { idFactory }
|
|
582
|
+
});
|
|
191
583
|
const [newPageQuestionId, setNewPageQuestionId] = useState("");
|
|
192
584
|
const [newLocale, setNewLocale] = useState("");
|
|
193
585
|
const [editingLocale, setEditingLocale] = useState("");
|
|
@@ -198,9 +590,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
198
590
|
return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
|
|
199
591
|
};
|
|
200
592
|
const emitSchema = (candidate) => onChange(sanitizeBuilderSchema(candidate));
|
|
201
|
-
const updateField =
|
|
202
|
-
emitSchema({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? update(field) : field) });
|
|
203
|
-
};
|
|
593
|
+
const updateField = headless.updateField;
|
|
204
594
|
const changeType = (fieldId, type) => {
|
|
205
595
|
emitSchema({
|
|
206
596
|
...schema,
|
|
@@ -216,10 +606,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
216
606
|
})
|
|
217
607
|
});
|
|
218
608
|
};
|
|
219
|
-
const removeField =
|
|
220
|
-
if (schema.fields.length === 1) return;
|
|
221
|
-
emitSchema({ ...schema, fields: schema.fields.filter((field) => field.id !== fieldId) });
|
|
222
|
-
};
|
|
609
|
+
const removeField = headless.removeField;
|
|
223
610
|
const moveField = (index, offset) => {
|
|
224
611
|
const target = index + offset;
|
|
225
612
|
if (target < 0 || target >= schema.fields.length) return;
|
|
@@ -231,21 +618,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
231
618
|
fields[target] = current;
|
|
232
619
|
emitSchema({ ...schema, fields });
|
|
233
620
|
};
|
|
234
|
-
const addField = () =>
|
|
235
|
-
const id = createUniqueId("q", new Set(schema.fields.map((field) => field.id)));
|
|
236
|
-
const nextSchema = {
|
|
237
|
-
...schema,
|
|
238
|
-
fields: [...schema.fields, { id, type: "text", title: translate("builder.newQuestionTitle"), required: false }]
|
|
239
|
-
};
|
|
240
|
-
emitSchema(
|
|
241
|
-
schema.pages === void 0 ? nextSchema : {
|
|
242
|
-
...nextSchema,
|
|
243
|
-
pages: schema.pages.map(
|
|
244
|
-
(page, index) => index === (schema.pages?.length ?? 0) - 1 ? { ...page, questionIds: [...page.questionIds, id] } : page
|
|
245
|
-
)
|
|
246
|
-
}
|
|
247
|
-
);
|
|
248
|
-
};
|
|
621
|
+
const addField = () => headless.addField("text");
|
|
249
622
|
const enablePages = () => {
|
|
250
623
|
if (schema.pages !== void 0) return;
|
|
251
624
|
emitSchema({
|
|
@@ -343,7 +716,10 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
343
716
|
setIsTranslating(true);
|
|
344
717
|
setTranslationError(null);
|
|
345
718
|
try {
|
|
346
|
-
|
|
719
|
+
const populated = await populateSchemaTranslations(schema, [editingLocale], translationAdapter, {
|
|
720
|
+
overwrite: "all"
|
|
721
|
+
});
|
|
722
|
+
onChange(populated.schema);
|
|
347
723
|
} catch (cause) {
|
|
348
724
|
setTranslationError(cause instanceof Error ? cause.message : String(cause));
|
|
349
725
|
} finally {
|
|
@@ -692,7 +1068,9 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
692
1068
|
{
|
|
693
1069
|
value: field.type,
|
|
694
1070
|
onChange: (event) => changeType(field.id, event.currentTarget.value),
|
|
695
|
-
children: FIELD_TYPES.
|
|
1071
|
+
children: FIELD_TYPES.filter(
|
|
1072
|
+
(type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
|
|
1073
|
+
).map((type) => /* @__PURE__ */ jsx("option", { value: type, children: translate(fieldTypeKey(type)) }, type))
|
|
696
1074
|
}
|
|
697
1075
|
)
|
|
698
1076
|
] }),
|
|
@@ -865,13 +1243,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
865
1243
|
{
|
|
866
1244
|
type: "button",
|
|
867
1245
|
disabled: field.options.length === 1,
|
|
868
|
-
onClick: () =>
|
|
869
|
-
field.id,
|
|
870
|
-
(current) => "options" in current ? {
|
|
871
|
-
...current,
|
|
872
|
-
options: current.options.filter((_item, itemIndex) => itemIndex !== optionIndex)
|
|
873
|
-
} : current
|
|
874
|
-
),
|
|
1246
|
+
onClick: () => headless.removeOption(field.id, option.id),
|
|
875
1247
|
children: translate("builder.remove")
|
|
876
1248
|
}
|
|
877
1249
|
)
|
|
@@ -880,22 +1252,8 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
880
1252
|
"button",
|
|
881
1253
|
{
|
|
882
1254
|
type: "button",
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
(current) => "options" in current ? (() => {
|
|
886
|
-
const id = createUniqueId("opt", new Set(current.options.map((option) => option.id)));
|
|
887
|
-
return {
|
|
888
|
-
...current,
|
|
889
|
-
options: [
|
|
890
|
-
...current.options,
|
|
891
|
-
{
|
|
892
|
-
id,
|
|
893
|
-
label: translate("builder.newOptionLabel", { index: current.options.length + 1 })
|
|
894
|
-
}
|
|
895
|
-
]
|
|
896
|
-
};
|
|
897
|
-
})() : current
|
|
898
|
-
),
|
|
1255
|
+
disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
|
|
1256
|
+
onClick: () => headless.addOption(field.id),
|
|
899
1257
|
children: translate("builder.addOption")
|
|
900
1258
|
}
|
|
901
1259
|
)
|
|
@@ -957,7 +1315,16 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
957
1315
|
] })
|
|
958
1316
|
] }, field.id);
|
|
959
1317
|
}) }),
|
|
960
|
-
/* @__PURE__ */ jsx(
|
|
1318
|
+
/* @__PURE__ */ jsx(
|
|
1319
|
+
"button",
|
|
1320
|
+
{
|
|
1321
|
+
className: "form-engine-builder__add",
|
|
1322
|
+
type: "button",
|
|
1323
|
+
disabled: policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields,
|
|
1324
|
+
onClick: addField,
|
|
1325
|
+
children: translate("builder.addQuestion")
|
|
1326
|
+
}
|
|
1327
|
+
)
|
|
961
1328
|
] });
|
|
962
1329
|
}
|
|
963
1330
|
|
|
@@ -971,7 +1338,7 @@ import {
|
|
|
971
1338
|
validateAnswers,
|
|
972
1339
|
validatePageAnswers
|
|
973
1340
|
} from "@form-engine-ts/core";
|
|
974
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useState as useState2 } from "react";
|
|
1341
|
+
import { createContext, useCallback as useCallback2, useContext, useEffect, useMemo as useMemo2, useState as useState2 } from "react";
|
|
975
1342
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
976
1343
|
var FormContext = createContext(null);
|
|
977
1344
|
function issuesByField(issues) {
|
|
@@ -988,7 +1355,7 @@ function FormProvider({
|
|
|
988
1355
|
onSubmit,
|
|
989
1356
|
children
|
|
990
1357
|
}) {
|
|
991
|
-
const validSchema =
|
|
1358
|
+
const validSchema = useMemo2(() => {
|
|
992
1359
|
assertValidFormSchema(schema);
|
|
993
1360
|
const localized = resolveLocalizedSchema(schema, locale);
|
|
994
1361
|
assertValidFormSchema(localized);
|
|
@@ -999,13 +1366,13 @@ function FormProvider({
|
|
|
999
1366
|
const [submitStatus, setSubmitStatus] = useState2("idle");
|
|
1000
1367
|
const [submitError, setSubmitError] = useState2(null);
|
|
1001
1368
|
const [validationPageIndex, setValidationPageIndex] = useState2(null);
|
|
1002
|
-
const visibility =
|
|
1003
|
-
const pageVisibility =
|
|
1369
|
+
const visibility = useMemo2(() => calculateFieldVisibility(validSchema, values), [validSchema, values]);
|
|
1370
|
+
const pageVisibility = useMemo2(() => calculatePageVisibility(validSchema, values), [validSchema, values]);
|
|
1004
1371
|
useEffect(() => {
|
|
1005
1372
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
1006
1373
|
setValues((current) => Object.fromEntries(Object.entries(current).filter(([fieldId]) => fieldIds.has(fieldId))));
|
|
1007
1374
|
}, [validSchema]);
|
|
1008
|
-
const setValue =
|
|
1375
|
+
const setValue = useCallback2(
|
|
1009
1376
|
(fieldId, value) => {
|
|
1010
1377
|
setValues((current) => {
|
|
1011
1378
|
const next = { ...current, [fieldId]: value };
|
|
@@ -1021,7 +1388,7 @@ function FormProvider({
|
|
|
1021
1388
|
},
|
|
1022
1389
|
[validSchema, validationPageIndex]
|
|
1023
1390
|
);
|
|
1024
|
-
const restoreValues =
|
|
1391
|
+
const restoreValues = useCallback2(
|
|
1025
1392
|
(restoredValues) => {
|
|
1026
1393
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
1027
1394
|
setValues(Object.fromEntries(Object.entries(restoredValues).filter(([fieldId]) => fieldIds.has(fieldId))));
|
|
@@ -1032,7 +1399,7 @@ function FormProvider({
|
|
|
1032
1399
|
},
|
|
1033
1400
|
[validSchema]
|
|
1034
1401
|
);
|
|
1035
|
-
const validatePage =
|
|
1402
|
+
const validatePage = useCallback2(
|
|
1036
1403
|
(pageIndex) => {
|
|
1037
1404
|
const result = validatePageAnswers(validSchema, pageIndex, values);
|
|
1038
1405
|
setErrors(issuesByField(result.issues));
|
|
@@ -1043,42 +1410,51 @@ function FormProvider({
|
|
|
1043
1410
|
},
|
|
1044
1411
|
[validSchema, values]
|
|
1045
1412
|
);
|
|
1046
|
-
const reset =
|
|
1413
|
+
const reset = useCallback2(() => {
|
|
1047
1414
|
setValues({ ...initialValues });
|
|
1048
1415
|
setErrors({});
|
|
1049
1416
|
setValidationPageIndex(null);
|
|
1050
1417
|
setSubmitStatus("idle");
|
|
1051
1418
|
setSubmitError(null);
|
|
1052
1419
|
}, [initialValues]);
|
|
1053
|
-
const submit =
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1420
|
+
const submit = useCallback2(
|
|
1421
|
+
async (beforeSubmit) => {
|
|
1422
|
+
const validation = validateAnswers(validSchema, values);
|
|
1423
|
+
if (!validation.valid) {
|
|
1424
|
+
setErrors(issuesByField(validation.issues));
|
|
1425
|
+
setValidationPageIndex(null);
|
|
1426
|
+
setSubmitStatus("error");
|
|
1427
|
+
setSubmitError(null);
|
|
1428
|
+
return { status: "invalid", issues: validation.issues };
|
|
1429
|
+
}
|
|
1430
|
+
setErrors({});
|
|
1057
1431
|
setValidationPageIndex(null);
|
|
1058
|
-
setSubmitStatus("error");
|
|
1059
1432
|
setSubmitError(null);
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1433
|
+
const visibleValues = selectVisibleAnswers(validSchema, values);
|
|
1434
|
+
try {
|
|
1435
|
+
if (beforeSubmit !== void 0 && await beforeSubmit(visibleValues) === "cancel") {
|
|
1436
|
+
setSubmitStatus("idle");
|
|
1437
|
+
return { status: "cancelled" };
|
|
1438
|
+
}
|
|
1439
|
+
setSubmitStatus("submitting");
|
|
1440
|
+
await onSubmit(visibleValues);
|
|
1441
|
+
if (resetOnSuccess) setValues({ ...initialValues });
|
|
1442
|
+
setSubmitStatus("success");
|
|
1443
|
+
return { status: "success" };
|
|
1444
|
+
} catch (cause) {
|
|
1445
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1446
|
+
setSubmitError(error);
|
|
1447
|
+
setSubmitStatus("error");
|
|
1448
|
+
return { status: "error", error };
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
[initialValues, onSubmit, resetOnSuccess, validSchema, values]
|
|
1452
|
+
);
|
|
1453
|
+
const translate = useCallback2(
|
|
1078
1454
|
(key, params) => translator.translate(key, locale, params),
|
|
1079
1455
|
[locale, translator]
|
|
1080
1456
|
);
|
|
1081
|
-
const contextValue =
|
|
1457
|
+
const contextValue = useMemo2(
|
|
1082
1458
|
() => ({
|
|
1083
1459
|
schema: validSchema,
|
|
1084
1460
|
locale,
|
|
@@ -1126,7 +1502,7 @@ function useField(fieldId) {
|
|
|
1126
1502
|
const form = useForm();
|
|
1127
1503
|
const field = form.schema.fields.find((item) => item.id === fieldId);
|
|
1128
1504
|
if (field === void 0) throw new Error(`Unknown form field: ${fieldId}`);
|
|
1129
|
-
const setValue =
|
|
1505
|
+
const setValue = useCallback2((value) => form.setValue(fieldId, value), [fieldId, form]);
|
|
1130
1506
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
1131
1507
|
}
|
|
1132
1508
|
|
|
@@ -1134,8 +1510,15 @@ function useField(fieldId) {
|
|
|
1134
1510
|
import {
|
|
1135
1511
|
validateAnswers as validateAnswers2
|
|
1136
1512
|
} from "@form-engine-ts/core";
|
|
1137
|
-
import {
|
|
1138
|
-
|
|
1513
|
+
import {
|
|
1514
|
+
Fragment as Fragment2,
|
|
1515
|
+
useEffect as useEffect2,
|
|
1516
|
+
useId,
|
|
1517
|
+
useMemo as useMemo3,
|
|
1518
|
+
useRef,
|
|
1519
|
+
useState as useState3
|
|
1520
|
+
} from "react";
|
|
1521
|
+
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1139
1522
|
function describedBy(field, error, helpId, errorId) {
|
|
1140
1523
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
1141
1524
|
Boolean
|
|
@@ -1149,7 +1532,7 @@ function RequiredMark({ required }) {
|
|
|
1149
1532
|
] }) : null;
|
|
1150
1533
|
}
|
|
1151
1534
|
function FieldMessage({ props }) {
|
|
1152
|
-
return /* @__PURE__ */ jsxs2(
|
|
1535
|
+
return /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
1153
1536
|
props.field.description === void 0 ? null : /* @__PURE__ */ jsx3("div", { id: props.helpId, className: "fe-help", children: props.field.description }),
|
|
1154
1537
|
props.error === void 0 ? null : /* @__PURE__ */ jsx3("div", { id: props.errorId, className: "fe-error", children: props.translate(props.error.messageKey, props.error.params) })
|
|
1155
1538
|
] });
|
|
@@ -1337,12 +1720,15 @@ function parseDraft(serialized) {
|
|
|
1337
1720
|
return null;
|
|
1338
1721
|
}
|
|
1339
1722
|
}
|
|
1340
|
-
function
|
|
1723
|
+
function ContextFormRenderer({
|
|
1341
1724
|
components = {},
|
|
1342
1725
|
className = "",
|
|
1343
1726
|
successMessageKey,
|
|
1344
1727
|
errorMessageKey,
|
|
1345
|
-
autoSaveKey
|
|
1728
|
+
autoSaveKey,
|
|
1729
|
+
beforeSubmit,
|
|
1730
|
+
onDraftSave,
|
|
1731
|
+
slots = {}
|
|
1346
1732
|
}) {
|
|
1347
1733
|
const form = useForm();
|
|
1348
1734
|
const prefix = useId().replace(/:/g, "");
|
|
@@ -1352,7 +1738,7 @@ function FormRenderer({
|
|
|
1352
1738
|
const [currentPageIndex, setCurrentPageIndex] = useState3(0);
|
|
1353
1739
|
const [focusFieldId, setFocusFieldId] = useState3(null);
|
|
1354
1740
|
const pages = form.schema.pages;
|
|
1355
|
-
const visiblePageIndexes =
|
|
1741
|
+
const visiblePageIndexes = useMemo3(
|
|
1356
1742
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
1357
1743
|
[form.pageVisibility, pages]
|
|
1358
1744
|
);
|
|
@@ -1390,9 +1776,10 @@ function FormRenderer({
|
|
|
1390
1776
|
setDraftRestored(true);
|
|
1391
1777
|
}, [autoSaveKey, form.restoreValues, form.schema.id, form.schema.version]);
|
|
1392
1778
|
useEffect2(() => {
|
|
1393
|
-
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1394
1779
|
if (form.submitStatus === "success") return;
|
|
1395
1780
|
const timeout = globalThis.setTimeout(() => {
|
|
1781
|
+
onDraftSave?.(form.values);
|
|
1782
|
+
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1396
1783
|
const draft = {
|
|
1397
1784
|
formId: form.schema.id,
|
|
1398
1785
|
formVersion: form.schema.version,
|
|
@@ -1402,7 +1789,7 @@ function FormRenderer({
|
|
|
1402
1789
|
globalThis.localStorage.setItem(autoSaveKey, JSON.stringify(draft));
|
|
1403
1790
|
}, 500);
|
|
1404
1791
|
return () => globalThis.clearTimeout(timeout);
|
|
1405
|
-
}, [autoSaveKey, form.schema.id, form.schema.version, form.submitStatus, form.values]);
|
|
1792
|
+
}, [autoSaveKey, form.schema.id, form.schema.version, form.submitStatus, form.values, onDraftSave]);
|
|
1406
1793
|
const focusFirstIssue = (fieldId) => {
|
|
1407
1794
|
if (fieldId !== void 0) setFocusFieldId(fieldId);
|
|
1408
1795
|
};
|
|
@@ -1415,27 +1802,39 @@ function FormRenderer({
|
|
|
1415
1802
|
const nextPageIndex = visiblePageIndexes[activeVisibleIndex + 1];
|
|
1416
1803
|
if (nextPageIndex !== void 0) setCurrentPageIndex(nextPageIndex);
|
|
1417
1804
|
};
|
|
1418
|
-
const
|
|
1419
|
-
event.preventDefault();
|
|
1805
|
+
const submitValues = async () => {
|
|
1420
1806
|
const validation = validateAnswers2(form.schema, form.values);
|
|
1421
1807
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
1422
|
-
const
|
|
1423
|
-
if (
|
|
1808
|
+
const result = await form.submit(beforeSubmit);
|
|
1809
|
+
if (result.status === "invalid") {
|
|
1424
1810
|
const invalidPageIndex = pages?.findIndex(
|
|
1425
1811
|
(page) => firstInvalidFieldId === void 0 ? false : page.questionIds.includes(firstInvalidFieldId)
|
|
1426
1812
|
);
|
|
1427
1813
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
1428
1814
|
focusFirstIssue(firstInvalidFieldId);
|
|
1429
|
-
return;
|
|
1815
|
+
return result;
|
|
1430
1816
|
}
|
|
1817
|
+
if (result.status !== "success") return result;
|
|
1431
1818
|
if (autoSaveKey !== void 0 && typeof globalThis.localStorage !== "undefined") {
|
|
1432
1819
|
globalThis.localStorage.removeItem(autoSaveKey);
|
|
1433
1820
|
setDraftRestored(false);
|
|
1434
1821
|
}
|
|
1435
1822
|
setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
1823
|
+
return result;
|
|
1824
|
+
};
|
|
1825
|
+
const handleSubmit = (event) => {
|
|
1826
|
+
event.preventDefault();
|
|
1827
|
+
void submitValues();
|
|
1436
1828
|
};
|
|
1829
|
+
const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
|
|
1830
|
+
const canPrev = pages !== void 0 && activeVisibleIndex > 0;
|
|
1831
|
+
const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
|
|
1832
|
+
const renderSubmitButton = () => slots.renderSubmitButton?.({ isSubmitting: form.isSubmitting, onSubmit: () => void submitValues() }) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
|
|
1437
1833
|
return /* @__PURE__ */ jsxs2("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
|
|
1438
|
-
|
|
1834
|
+
slots.renderHeader?.({
|
|
1835
|
+
title: form.schema.title,
|
|
1836
|
+
...form.schema.description === void 0 ? {} : { description: form.schema.description }
|
|
1837
|
+
}) ?? /* @__PURE__ */ jsxs2("header", { className: "fe-header", children: [
|
|
1439
1838
|
/* @__PURE__ */ jsx3("h1", { children: form.schema.title }),
|
|
1440
1839
|
form.schema.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { children: form.schema.description }),
|
|
1441
1840
|
pages === void 0 ? null : /* @__PURE__ */ jsxs2("div", { className: "fe-progress", children: [
|
|
@@ -1463,41 +1862,121 @@ function FormRenderer({
|
|
|
1463
1862
|
activePage?.title === void 0 ? null : /* @__PURE__ */ jsx3("h2", { className: "fe-page-title", children: activePage.title }),
|
|
1464
1863
|
activePage?.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { className: "fe-page-description", children: activePage.description }),
|
|
1465
1864
|
/* @__PURE__ */ jsx3("div", { className: "fe-fields", children: form.schema.fields.filter((field) => form.visibility[field.id] === true && (fieldIds === void 0 || fieldIds.has(field.id))).map((field) => {
|
|
1865
|
+
const error = form.errors[field.id];
|
|
1466
1866
|
const props = {
|
|
1467
1867
|
field,
|
|
1468
1868
|
value: form.values[field.id],
|
|
1469
|
-
error
|
|
1869
|
+
error,
|
|
1470
1870
|
setValue: (value) => form.setValue(field.id, value),
|
|
1471
1871
|
translate: form.translate,
|
|
1472
1872
|
inputId: `${prefix}-${field.id}`,
|
|
1473
1873
|
errorId: `${prefix}-${field.id}-error`,
|
|
1474
1874
|
helpId: `${prefix}-${field.id}-help`
|
|
1475
1875
|
};
|
|
1876
|
+
if (slots.renderField !== void 0) {
|
|
1877
|
+
return /* @__PURE__ */ jsx3(Fragment2, { children: slots.renderField({
|
|
1878
|
+
question: field,
|
|
1879
|
+
value: form.values[field.id],
|
|
1880
|
+
onChange: (value) => {
|
|
1881
|
+
if (isFormValue(value)) form.setValue(field.id, value);
|
|
1882
|
+
},
|
|
1883
|
+
...error === void 0 ? {} : { error }
|
|
1884
|
+
}) }, field.id);
|
|
1885
|
+
}
|
|
1476
1886
|
const Component = components[field.type];
|
|
1477
1887
|
return Component === void 0 ? /* @__PURE__ */ jsx3(DefaultField, { ...props }, field.id) : /* @__PURE__ */ jsx3(Component, { ...props }, field.id);
|
|
1478
1888
|
}) }),
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1889
|
+
validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-validation-summary", role: "alert", children: [
|
|
1890
|
+
validationIssues.length,
|
|
1891
|
+
" validation error",
|
|
1892
|
+
validationIssues.length === 1 ? "" : "s",
|
|
1893
|
+
"."
|
|
1894
|
+
] }),
|
|
1895
|
+
pages === void 0 ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
1896
|
+
slots.renderNavigation?.({
|
|
1897
|
+
currentPage: 0,
|
|
1898
|
+
totalPages: 1,
|
|
1899
|
+
canPrev: false,
|
|
1900
|
+
canNext: false,
|
|
1901
|
+
onPrev: () => void 0,
|
|
1902
|
+
onNext: () => void 0
|
|
1903
|
+
}),
|
|
1904
|
+
renderSubmitButton()
|
|
1905
|
+
] }) : /* @__PURE__ */ jsxs2("div", { className: "form-step-navigation", children: [
|
|
1906
|
+
slots.renderNavigation?.({
|
|
1907
|
+
currentPage: activeVisibleIndex,
|
|
1908
|
+
totalPages: visiblePageIndexes.length,
|
|
1909
|
+
canPrev,
|
|
1910
|
+
canNext,
|
|
1911
|
+
onPrev: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
1912
|
+
onNext: handleNext
|
|
1913
|
+
}) ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
1914
|
+
canPrev ? /* @__PURE__ */ jsx3(
|
|
1915
|
+
"button",
|
|
1916
|
+
{
|
|
1917
|
+
className: "btn-prev",
|
|
1918
|
+
type: "button",
|
|
1919
|
+
onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
1920
|
+
children: form.translate("form.back")
|
|
1921
|
+
}
|
|
1922
|
+
) : null,
|
|
1923
|
+
canNext ? /* @__PURE__ */ jsx3("button", { className: "btn-next", type: "button", onClick: handleNext, children: form.translate("form.next") }) : null
|
|
1924
|
+
] }),
|
|
1925
|
+
canNext ? null : renderSubmitButton()
|
|
1490
1926
|
] }),
|
|
1491
1927
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
1492
|
-
form.submitStatus === "success"
|
|
1928
|
+
form.submitStatus === "success" ? slots.renderCompletion?.({
|
|
1929
|
+
message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
|
|
1930
|
+
}) ?? /* @__PURE__ */ jsx3("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
|
|
1493
1931
|
form.submitStatus === "error" && form.submitError !== null && errorMessageKey !== void 0 ? /* @__PURE__ */ jsx3("div", { role: "alert", children: form.translate(errorMessageKey) }) : null
|
|
1494
1932
|
] })
|
|
1495
1933
|
] });
|
|
1496
1934
|
}
|
|
1935
|
+
var RENDERER_MESSAGES = {
|
|
1936
|
+
"form.submit": "Submit",
|
|
1937
|
+
"form.back": "Back",
|
|
1938
|
+
"form.next": "Next",
|
|
1939
|
+
"form.step": "Step {{current}} / {{total}}",
|
|
1940
|
+
"form.draftRestored": "Draft restored",
|
|
1941
|
+
"validation.required": "This field is required."
|
|
1942
|
+
};
|
|
1943
|
+
var defaultRendererTranslator = {
|
|
1944
|
+
translate(key, _locale, params = {}) {
|
|
1945
|
+
return (RENDERER_MESSAGES[key] ?? key).replace(
|
|
1946
|
+
/\{\{(\w+)\}\}/g,
|
|
1947
|
+
(token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
function FormRenderer(props) {
|
|
1952
|
+
if (!("schema" in props)) return /* @__PURE__ */ jsx3(ContextFormRenderer, { ...props });
|
|
1953
|
+
const {
|
|
1954
|
+
schema,
|
|
1955
|
+
locale = schema.defaultLocale ?? "en",
|
|
1956
|
+
translator = defaultRendererTranslator,
|
|
1957
|
+
initialValues,
|
|
1958
|
+
resetOnSuccess,
|
|
1959
|
+
onSubmit,
|
|
1960
|
+
...rendererProps
|
|
1961
|
+
} = props;
|
|
1962
|
+
return /* @__PURE__ */ jsx3(
|
|
1963
|
+
FormProvider,
|
|
1964
|
+
{
|
|
1965
|
+
schema,
|
|
1966
|
+
locale,
|
|
1967
|
+
translator,
|
|
1968
|
+
onSubmit,
|
|
1969
|
+
...initialValues === void 0 ? {} : { initialValues },
|
|
1970
|
+
...resetOnSuccess === void 0 ? {} : { resetOnSuccess },
|
|
1971
|
+
children: /* @__PURE__ */ jsx3(ContextFormRenderer, { ...rendererProps })
|
|
1972
|
+
}
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1497
1975
|
export {
|
|
1498
1976
|
FormBuilder,
|
|
1499
1977
|
FormProvider,
|
|
1500
1978
|
FormRenderer,
|
|
1501
1979
|
useField,
|
|
1502
|
-
useForm
|
|
1980
|
+
useForm,
|
|
1981
|
+
useFormBuilder
|
|
1503
1982
|
};
|