@form-engine-ts/react 1.1.0 → 2.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 +35 -0
- package/dist/index.cjs +943 -403
- package/dist/index.d.cts +151 -6
- package/dist/index.d.ts +151 -6
- package/dist/index.js +923 -375
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -24,13 +24,623 @@ __export(index_exports, {
|
|
|
24
24
|
FormProvider: () => FormProvider,
|
|
25
25
|
FormRenderer: () => FormRenderer,
|
|
26
26
|
useField: () => useField,
|
|
27
|
-
useForm: () => useForm
|
|
27
|
+
useForm: () => useForm,
|
|
28
|
+
useFormBuilder: () => useFormBuilder
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
30
31
|
|
|
31
32
|
// src/builder.tsx
|
|
33
|
+
var import_core2 = require("@form-engine-ts/core");
|
|
34
|
+
var import_react2 = require("react");
|
|
35
|
+
|
|
36
|
+
// src/hooks/useFormBuilder.ts
|
|
32
37
|
var import_core = require("@form-engine-ts/core");
|
|
33
38
|
var import_react = require("react");
|
|
39
|
+
var DEFAULT_PREFIXES = { field: "q", option: "opt", page: "page" };
|
|
40
|
+
var CHOICE_TYPES = ["select", "radio", "multi-select"];
|
|
41
|
+
function defaultIdFactory(kind, existingIds) {
|
|
42
|
+
const prefix = DEFAULT_PREFIXES[kind];
|
|
43
|
+
let id;
|
|
44
|
+
do
|
|
45
|
+
id = `${prefix}_${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
46
|
+
while (existingIds.has(id));
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
function defaultCreateField(type, id) {
|
|
50
|
+
const base = { id, title: "New question", required: false };
|
|
51
|
+
if (type === "text" || type === "textarea" || type === "number" || type === "checkbox") return { ...base, type };
|
|
52
|
+
if (type === "rating") return { ...base, type, min: 1, max: 5 };
|
|
53
|
+
return { ...base, type, options: [] };
|
|
54
|
+
}
|
|
55
|
+
function defaultCreateOption(field, id) {
|
|
56
|
+
return { id, label: `Option ${"options" in field ? field.options.length + 1 : 1}` };
|
|
57
|
+
}
|
|
58
|
+
function defaultCreatePage(id, questionIds) {
|
|
59
|
+
return { id, title: "New page", questionIds };
|
|
60
|
+
}
|
|
61
|
+
function move(items, sourceIndex, targetIndex) {
|
|
62
|
+
if (sourceIndex < 0 || targetIndex < 0 || targetIndex >= items.length || sourceIndex === targetIndex)
|
|
63
|
+
return void 0;
|
|
64
|
+
const result = [...items];
|
|
65
|
+
const [item] = result.splice(sourceIndex, 1);
|
|
66
|
+
if (item === void 0) return void 0;
|
|
67
|
+
result.splice(targetIndex, 0, item);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
function withoutDisplayCondition(field) {
|
|
71
|
+
const { displayCondition: _displayCondition, ...rest } = field;
|
|
72
|
+
return rest;
|
|
73
|
+
}
|
|
74
|
+
function removeLocalizedProperty(translations, locale, property) {
|
|
75
|
+
const current = translations?.[locale];
|
|
76
|
+
if (current === void 0) return translations;
|
|
77
|
+
const { [property]: _removed, ...remaining } = current;
|
|
78
|
+
return { ...translations, [locale]: remaining };
|
|
79
|
+
}
|
|
80
|
+
function setTranslationMetadata(node, locale, property, metadata, remove) {
|
|
81
|
+
const localeMetadata = node.translationMetadata?.[locale];
|
|
82
|
+
if (metadata === void 0 && !remove) return node;
|
|
83
|
+
const nextLocaleMetadata = remove ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== property)) : { ...localeMetadata, [property]: metadata ?? {} };
|
|
84
|
+
return { ...node, translationMetadata: { ...node.translationMetadata, [locale]: nextLocaleMetadata } };
|
|
85
|
+
}
|
|
86
|
+
function failedId(kind, result) {
|
|
87
|
+
return { success: false, error: result.error ?? { type: "invalid_id", kind, id: "" } };
|
|
88
|
+
}
|
|
89
|
+
function useFormBuilder({
|
|
90
|
+
schema,
|
|
91
|
+
onChange,
|
|
92
|
+
policy,
|
|
93
|
+
idFactory = defaultIdFactory,
|
|
94
|
+
factories = {}
|
|
95
|
+
}) {
|
|
96
|
+
const createId = (0, import_react.useCallback)(
|
|
97
|
+
(kind, existingIds) => {
|
|
98
|
+
const rawId = idFactory(kind, existingIds);
|
|
99
|
+
const id = rawId.trim();
|
|
100
|
+
return id.length > 0 && !existingIds.has(id) ? { id } : { error: { type: "invalid_id", kind, id: rawId } };
|
|
101
|
+
},
|
|
102
|
+
[idFactory]
|
|
103
|
+
);
|
|
104
|
+
const textPolicyError = (0, import_react.useCallback)(
|
|
105
|
+
(text) => policy?.maxTextLength !== void 0 && text.length > policy.maxTextLength ? { success: false, error: { type: "max_text_length_exceeded", max: policy.maxTextLength } } : void 0,
|
|
106
|
+
[policy?.maxTextLength]
|
|
107
|
+
);
|
|
108
|
+
const updateField = (0, import_react.useCallback)(
|
|
109
|
+
(fieldId, updater) => {
|
|
110
|
+
const current = schema.fields.find((field) => field.id === fieldId);
|
|
111
|
+
if (current === void 0)
|
|
112
|
+
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
113
|
+
const updated = updater(current);
|
|
114
|
+
if (updated.id !== fieldId)
|
|
115
|
+
return { success: false, error: { type: "invalid_id", kind: "field", id: updated.id } };
|
|
116
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(updated.type))
|
|
117
|
+
return { success: false, error: { type: "disallowed_field_type", fieldType: updated.type } };
|
|
118
|
+
for (const text of [updated.title, updated.description]) {
|
|
119
|
+
if (text !== void 0) {
|
|
120
|
+
const error = textPolicyError(text);
|
|
121
|
+
if (error !== void 0) return error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
onChange({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? updated : field) });
|
|
125
|
+
return { success: true };
|
|
126
|
+
},
|
|
127
|
+
[onChange, policy?.allowedFieldTypes, schema, textPolicyError]
|
|
128
|
+
);
|
|
129
|
+
const updateOption = (0, import_react.useCallback)(
|
|
130
|
+
(fieldId, optionId, updater) => {
|
|
131
|
+
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
132
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
133
|
+
if (!("options" in field))
|
|
134
|
+
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
135
|
+
const current = field.options.find((option) => option.id === optionId);
|
|
136
|
+
if (current === void 0)
|
|
137
|
+
return { success: false, error: { type: "node_not_found", kind: "option", id: optionId } };
|
|
138
|
+
const updated = updater(current);
|
|
139
|
+
if (updated.id !== optionId)
|
|
140
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: updated.id } };
|
|
141
|
+
const error = textPolicyError(updated.label);
|
|
142
|
+
if (error !== void 0) return error;
|
|
143
|
+
onChange({
|
|
144
|
+
...schema,
|
|
145
|
+
fields: schema.fields.map(
|
|
146
|
+
(candidate) => candidate.id === fieldId && "options" in candidate ? {
|
|
147
|
+
...candidate,
|
|
148
|
+
options: candidate.options.map((option) => option.id === optionId ? updated : option)
|
|
149
|
+
} : candidate
|
|
150
|
+
)
|
|
151
|
+
});
|
|
152
|
+
return { success: true };
|
|
153
|
+
},
|
|
154
|
+
[onChange, schema, textPolicyError]
|
|
155
|
+
);
|
|
156
|
+
const updatePage = (0, import_react.useCallback)(
|
|
157
|
+
(pageId, updater) => {
|
|
158
|
+
const page = schema.pages?.find((candidate) => candidate.id === pageId);
|
|
159
|
+
if (page === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
160
|
+
const pages = schema.pages;
|
|
161
|
+
if (pages === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
162
|
+
const updated = updater(page);
|
|
163
|
+
if (updated.id !== pageId) return { success: false, error: { type: "invalid_id", kind: "page", id: updated.id } };
|
|
164
|
+
onChange({ ...schema, pages: pages.map((candidate) => candidate.id === pageId ? updated : candidate) });
|
|
165
|
+
return { success: true };
|
|
166
|
+
},
|
|
167
|
+
[onChange, schema]
|
|
168
|
+
);
|
|
169
|
+
const addField = (0, import_react.useCallback)(
|
|
170
|
+
(type, pageId) => {
|
|
171
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
172
|
+
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
173
|
+
if (policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields)
|
|
174
|
+
return { success: false, error: { type: "max_fields_exceeded", max: policy.maxFields } };
|
|
175
|
+
if (pageId !== void 0 && !schema.pages?.some((page) => page.id === pageId))
|
|
176
|
+
return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
177
|
+
const fieldIds = new Set(schema.fields.map((field2) => field2.id));
|
|
178
|
+
const fieldId = createId("field", fieldIds);
|
|
179
|
+
if (fieldId.id === void 0) return failedId("field", fieldId);
|
|
180
|
+
let field = (factories.createField ?? defaultCreateField)(type, fieldId.id);
|
|
181
|
+
if (field.id !== fieldId.id || field.type !== type || fieldIds.has(field.id))
|
|
182
|
+
return { success: false, error: { type: "invalid_id", kind: "field", id: field.id } };
|
|
183
|
+
if (CHOICE_TYPES.includes(type) && "options" in field && field.options.length === 0) {
|
|
184
|
+
const optionIds = new Set(
|
|
185
|
+
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
186
|
+
);
|
|
187
|
+
const optionId = createId("option", optionIds);
|
|
188
|
+
if (optionId.id === void 0) return failedId("option", optionId);
|
|
189
|
+
const option = (factories.createOption ?? defaultCreateOption)(field, optionId.id);
|
|
190
|
+
if (option.id !== optionId.id || optionIds.has(option.id))
|
|
191
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
192
|
+
field = { ...field, options: [option] };
|
|
193
|
+
}
|
|
194
|
+
const pages = schema.pages?.map((page, index) => ({
|
|
195
|
+
...page,
|
|
196
|
+
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds, field.id] : page.questionIds
|
|
197
|
+
}));
|
|
198
|
+
onChange({ ...schema, fields: [...schema.fields, field], ...pages === void 0 ? {} : { pages } });
|
|
199
|
+
return { success: true };
|
|
200
|
+
},
|
|
201
|
+
[createId, factories, onChange, policy, schema]
|
|
202
|
+
);
|
|
203
|
+
const removeField = (0, import_react.useCallback)(
|
|
204
|
+
(fieldId) => {
|
|
205
|
+
if (!schema.fields.some((field) => field.id === fieldId))
|
|
206
|
+
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
207
|
+
if (schema.fields.length <= 1)
|
|
208
|
+
return { success: false, error: { type: "invalid_operation", message: "A form must contain one field." } };
|
|
209
|
+
const fields = schema.fields.filter((field) => field.id !== fieldId).map((field) => field.displayCondition?.questionId === fieldId ? withoutDisplayCondition(field) : field);
|
|
210
|
+
const pages = schema.pages?.map((page) => ({ ...page, questionIds: page.questionIds.filter((id) => id !== fieldId) })).filter((page) => page.questionIds.length > 0);
|
|
211
|
+
if (schema.pages !== void 0 && pages?.length === 0) {
|
|
212
|
+
const { pages: _pages, ...single } = schema;
|
|
213
|
+
onChange({ ...single, fields });
|
|
214
|
+
} else onChange({ ...schema, fields, ...pages === void 0 ? {} : { pages } });
|
|
215
|
+
return { success: true };
|
|
216
|
+
},
|
|
217
|
+
[onChange, schema]
|
|
218
|
+
);
|
|
219
|
+
const moveField = (0, import_react.useCallback)(
|
|
220
|
+
(fieldId, targetIndex) => {
|
|
221
|
+
const sourceIndex = schema.fields.findIndex((field) => field.id === fieldId);
|
|
222
|
+
if (sourceIndex < 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
223
|
+
const fields = move(schema.fields, sourceIndex, targetIndex);
|
|
224
|
+
if (fields === void 0)
|
|
225
|
+
return { success: false, error: { type: "invalid_operation", message: "Invalid field position." } };
|
|
226
|
+
const indexById = new Map(fields.map((field, index) => [field.id, index]));
|
|
227
|
+
onChange({
|
|
228
|
+
...schema,
|
|
229
|
+
fields: fields.map((field, index) => {
|
|
230
|
+
const source = field.displayCondition?.questionId;
|
|
231
|
+
return source === void 0 || (indexById.get(source) ?? index) < index ? field : withoutDisplayCondition(field);
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
return { success: true };
|
|
235
|
+
},
|
|
236
|
+
[onChange, schema]
|
|
237
|
+
);
|
|
238
|
+
const addOption = (0, import_react.useCallback)(
|
|
239
|
+
(fieldId) => {
|
|
240
|
+
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
241
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
242
|
+
if (!("options" in field))
|
|
243
|
+
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
244
|
+
if (policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField)
|
|
245
|
+
return { success: false, error: { type: "max_options_exceeded", max: policy.maxOptionsPerField } };
|
|
246
|
+
const ids = new Set(
|
|
247
|
+
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
248
|
+
);
|
|
249
|
+
const id = createId("option", ids);
|
|
250
|
+
if (id.id === void 0) return failedId("option", id);
|
|
251
|
+
const option = (factories.createOption ?? defaultCreateOption)(field, id.id);
|
|
252
|
+
if (option.id !== id.id || ids.has(option.id))
|
|
253
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
254
|
+
onChange({
|
|
255
|
+
...schema,
|
|
256
|
+
fields: schema.fields.map(
|
|
257
|
+
(item) => item.id === fieldId && "options" in item ? { ...item, options: [...item.options, option] } : item
|
|
258
|
+
)
|
|
259
|
+
});
|
|
260
|
+
return { success: true };
|
|
261
|
+
},
|
|
262
|
+
[createId, factories.createOption, onChange, policy?.maxOptionsPerField, schema]
|
|
263
|
+
);
|
|
264
|
+
const removeOption = (0, import_react.useCallback)(
|
|
265
|
+
(fieldId, optionId) => {
|
|
266
|
+
const field = schema.fields.find((item) => item.id === fieldId);
|
|
267
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
268
|
+
if (!("options" in field) || !field.options.some((option) => option.id === optionId))
|
|
269
|
+
return { success: false, error: { type: "node_not_found", kind: "option", id: optionId } };
|
|
270
|
+
if (field.options.length <= 1)
|
|
271
|
+
return { success: false, error: { type: "invalid_operation", message: "A choice field needs one option." } };
|
|
272
|
+
onChange({
|
|
273
|
+
...schema,
|
|
274
|
+
fields: schema.fields.map(
|
|
275
|
+
(item) => item.id === fieldId && "options" in item ? { ...item, options: item.options.filter((option) => option.id !== optionId) } : item
|
|
276
|
+
)
|
|
277
|
+
});
|
|
278
|
+
return { success: true };
|
|
279
|
+
},
|
|
280
|
+
[onChange, schema]
|
|
281
|
+
);
|
|
282
|
+
const moveOption = (0, import_react.useCallback)(
|
|
283
|
+
(fieldId, optionId, targetIndex) => {
|
|
284
|
+
const field = schema.fields.find((item) => item.id === fieldId);
|
|
285
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
286
|
+
if (!("options" in field))
|
|
287
|
+
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
288
|
+
const options = move(
|
|
289
|
+
field.options,
|
|
290
|
+
field.options.findIndex((option) => option.id === optionId),
|
|
291
|
+
targetIndex
|
|
292
|
+
);
|
|
293
|
+
if (options === void 0)
|
|
294
|
+
return { success: false, error: { type: "invalid_operation", message: "Invalid option position." } };
|
|
295
|
+
onChange({
|
|
296
|
+
...schema,
|
|
297
|
+
fields: schema.fields.map(
|
|
298
|
+
(item) => item.id === fieldId && "options" in item ? { ...item, options } : item
|
|
299
|
+
)
|
|
300
|
+
});
|
|
301
|
+
return { success: true };
|
|
302
|
+
},
|
|
303
|
+
[onChange, schema]
|
|
304
|
+
);
|
|
305
|
+
const changeFieldType = (0, import_react.useCallback)(
|
|
306
|
+
(fieldId, type) => {
|
|
307
|
+
const field = schema.fields.find((item) => item.id === fieldId);
|
|
308
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
309
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
310
|
+
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
311
|
+
let transformed = (0, import_core.transformFieldType)(field, type);
|
|
312
|
+
if (CHOICE_TYPES.includes(type) && !("options" in field) && "options" in transformed) {
|
|
313
|
+
const ids = new Set(
|
|
314
|
+
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
315
|
+
);
|
|
316
|
+
const id = createId("option", ids);
|
|
317
|
+
if (id.id === void 0) return failedId("option", id);
|
|
318
|
+
const option = (factories.createOption ?? defaultCreateOption)(transformed, id.id);
|
|
319
|
+
if (option.id !== id.id || ids.has(option.id))
|
|
320
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
321
|
+
transformed = { ...transformed, options: [option] };
|
|
322
|
+
}
|
|
323
|
+
onChange({ ...schema, fields: schema.fields.map((item) => item.id === fieldId ? transformed : item) });
|
|
324
|
+
return { success: true };
|
|
325
|
+
},
|
|
326
|
+
[createId, factories.createOption, onChange, policy?.allowedFieldTypes, schema]
|
|
327
|
+
);
|
|
328
|
+
const addPage = (0, import_react.useCallback)(
|
|
329
|
+
(questionId) => {
|
|
330
|
+
const ids = new Set(schema.pages?.map((page2) => page2.id) ?? []);
|
|
331
|
+
const id = createId("page", ids);
|
|
332
|
+
if (id.id === void 0) return failedId("page", id);
|
|
333
|
+
const questionIds = schema.pages === void 0 ? schema.fields.map((field) => field.id) : [questionId ?? schema.pages.find((page2) => page2.questionIds.length > 1)?.questionIds.at(-1)].filter(
|
|
334
|
+
(value) => value !== void 0
|
|
335
|
+
);
|
|
336
|
+
if (questionIds.length === 0)
|
|
337
|
+
return { success: false, error: { type: "invalid_operation", message: "No question can be moved." } };
|
|
338
|
+
const source = schema.pages?.find((page2) => page2.questionIds.includes(questionIds[0] ?? ""));
|
|
339
|
+
if (schema.pages !== void 0 && (source === void 0 || source.questionIds.length <= 1))
|
|
340
|
+
return { success: false, error: { type: "invalid_operation", message: "A page cannot be left empty." } };
|
|
341
|
+
const page = (factories.createPage ?? defaultCreatePage)(id.id, [...questionIds]);
|
|
342
|
+
if (page.id !== id.id || ids.has(page.id))
|
|
343
|
+
return { success: false, error: { type: "invalid_id", kind: "page", id: page.id } };
|
|
344
|
+
const pages = schema.pages === void 0 ? [page] : [
|
|
345
|
+
...schema.pages.map((item) => ({
|
|
346
|
+
...item,
|
|
347
|
+
questionIds: item.questionIds.filter((fieldId) => !questionIds.includes(fieldId))
|
|
348
|
+
})),
|
|
349
|
+
page
|
|
350
|
+
];
|
|
351
|
+
onChange({ ...schema, pages });
|
|
352
|
+
return { success: true };
|
|
353
|
+
},
|
|
354
|
+
[createId, factories.createPage, onChange, schema]
|
|
355
|
+
);
|
|
356
|
+
const removePage = (0, import_react.useCallback)(
|
|
357
|
+
(pageId) => {
|
|
358
|
+
const index = schema.pages?.findIndex((page) => page.id === pageId) ?? -1;
|
|
359
|
+
const removed = schema.pages?.[index];
|
|
360
|
+
if (removed === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
361
|
+
const currentPages = schema.pages;
|
|
362
|
+
if (currentPages === void 0)
|
|
363
|
+
return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
364
|
+
if ((schema.pages?.length ?? 0) === 1) {
|
|
365
|
+
const { pages: _pages, ...single } = schema;
|
|
366
|
+
onChange(single);
|
|
367
|
+
return { success: true };
|
|
368
|
+
}
|
|
369
|
+
const targetIndex = index === 0 ? 1 : index - 1;
|
|
370
|
+
onChange({
|
|
371
|
+
...schema,
|
|
372
|
+
pages: currentPages.map(
|
|
373
|
+
(page, pageIndex) => pageIndex === targetIndex ? { ...page, questionIds: [...page.questionIds, ...removed.questionIds] } : page
|
|
374
|
+
).filter((page) => page.id !== pageId)
|
|
375
|
+
});
|
|
376
|
+
return { success: true };
|
|
377
|
+
},
|
|
378
|
+
[onChange, schema]
|
|
379
|
+
);
|
|
380
|
+
const movePage = (0, import_react.useCallback)(
|
|
381
|
+
(pageId, targetIndex) => {
|
|
382
|
+
const sourceIndex = schema.pages?.findIndex((page) => page.id === pageId) ?? -1;
|
|
383
|
+
if (sourceIndex < 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
384
|
+
const pages = move(schema.pages ?? [], sourceIndex, targetIndex);
|
|
385
|
+
if (pages === void 0)
|
|
386
|
+
return { success: false, error: { type: "invalid_operation", message: "Invalid page position." } };
|
|
387
|
+
const assignment = new Map(pages.flatMap((page, index) => page.questionIds.map((id) => [id, index])));
|
|
388
|
+
onChange({
|
|
389
|
+
...schema,
|
|
390
|
+
pages: pages.map((page, index) => {
|
|
391
|
+
const source = page.displayCondition?.questionId;
|
|
392
|
+
if (source === void 0 || (assignment.get(source) ?? index) < index) return page;
|
|
393
|
+
const { displayCondition: _condition, ...rest } = page;
|
|
394
|
+
return rest;
|
|
395
|
+
})
|
|
396
|
+
});
|
|
397
|
+
return { success: true };
|
|
398
|
+
},
|
|
399
|
+
[onChange, schema]
|
|
400
|
+
);
|
|
401
|
+
const assignFieldToPage = (0, import_react.useCallback)(
|
|
402
|
+
(fieldId, pageId) => {
|
|
403
|
+
if (!schema.fields.some((field) => field.id === fieldId))
|
|
404
|
+
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
405
|
+
if (pageId === null) {
|
|
406
|
+
if (schema.pages !== void 0) {
|
|
407
|
+
const { pages: _pages, ...single } = schema;
|
|
408
|
+
onChange(single);
|
|
409
|
+
}
|
|
410
|
+
return { success: true };
|
|
411
|
+
}
|
|
412
|
+
if (!schema.pages?.some((page) => page.id === pageId))
|
|
413
|
+
return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
414
|
+
const pages = schema.pages.map((page) => ({
|
|
415
|
+
...page,
|
|
416
|
+
questionIds: page.id === pageId ? schema.fields.filter((field) => page.questionIds.includes(field.id) || field.id === fieldId).map((field) => field.id) : page.questionIds.filter((id) => id !== fieldId)
|
|
417
|
+
})).filter((page) => page.questionIds.length > 0);
|
|
418
|
+
onChange({ ...schema, pages });
|
|
419
|
+
return { success: true };
|
|
420
|
+
},
|
|
421
|
+
[onChange, schema]
|
|
422
|
+
);
|
|
423
|
+
const setDisplayCondition = (0, import_react.useCallback)(
|
|
424
|
+
(fieldId, condition) => {
|
|
425
|
+
const index = schema.fields.findIndex((field) => field.id === fieldId);
|
|
426
|
+
if (index < 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
427
|
+
if (condition !== void 0) {
|
|
428
|
+
const sourceIndex = schema.fields.findIndex((field) => field.id === condition.questionId);
|
|
429
|
+
if (sourceIndex < 0 || sourceIndex >= index)
|
|
430
|
+
return {
|
|
431
|
+
success: false,
|
|
432
|
+
error: { type: "invalid_operation", message: "Conditions require an earlier field." }
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
onChange({
|
|
436
|
+
...schema,
|
|
437
|
+
fields: schema.fields.map(
|
|
438
|
+
(field) => field.id !== fieldId ? field : condition === void 0 ? withoutDisplayCondition(field) : { ...field, displayCondition: condition }
|
|
439
|
+
)
|
|
440
|
+
});
|
|
441
|
+
return { success: true };
|
|
442
|
+
},
|
|
443
|
+
[onChange, schema]
|
|
444
|
+
);
|
|
445
|
+
const setSourceText = (0, import_react.useCallback)(
|
|
446
|
+
(target, property, text) => {
|
|
447
|
+
const error = textPolicyError(text);
|
|
448
|
+
if (error !== void 0) return error;
|
|
449
|
+
if (target.kind === "form") {
|
|
450
|
+
if (!["title", "description", "completionMessage"].includes(property))
|
|
451
|
+
return {
|
|
452
|
+
success: false,
|
|
453
|
+
error: { type: "invalid_operation", message: `Unsupported form property: ${property}` }
|
|
454
|
+
};
|
|
455
|
+
if (property === "title") onChange({ ...schema, title: text });
|
|
456
|
+
else if (property === "description") {
|
|
457
|
+
const { description: _description, ...withoutDescription } = schema;
|
|
458
|
+
onChange(text.length === 0 ? withoutDescription : { ...schema, description: text });
|
|
459
|
+
} else {
|
|
460
|
+
const { completionMessage: _completionMessage, ...withoutCompletionMessage } = schema;
|
|
461
|
+
onChange(text.length === 0 ? withoutCompletionMessage : { ...schema, completionMessage: text });
|
|
462
|
+
}
|
|
463
|
+
return { success: true };
|
|
464
|
+
}
|
|
465
|
+
if (target.id === void 0)
|
|
466
|
+
return { success: false, error: { type: "invalid_operation", message: "A target ID is required." } };
|
|
467
|
+
if (target.kind === "field") {
|
|
468
|
+
if (!["title", "description"].includes(property))
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
error: { type: "invalid_operation", message: `Unsupported field property: ${property}` }
|
|
472
|
+
};
|
|
473
|
+
return updateField(target.id, (field) => {
|
|
474
|
+
if (property === "title") return { ...field, title: text };
|
|
475
|
+
const { description: _description, ...withoutDescription } = field;
|
|
476
|
+
return text.length === 0 ? withoutDescription : { ...field, description: text };
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (target.kind === "option") {
|
|
480
|
+
if (property !== "label")
|
|
481
|
+
return {
|
|
482
|
+
success: false,
|
|
483
|
+
error: { type: "invalid_operation", message: `Unsupported option property: ${property}` }
|
|
484
|
+
};
|
|
485
|
+
for (const field of schema.fields)
|
|
486
|
+
if ("options" in field && field.options.some((option) => option.id === target.id))
|
|
487
|
+
return updateOption(field.id, target.id, (option) => ({ ...option, label: text }));
|
|
488
|
+
return { success: false, error: { type: "node_not_found", kind: "option", id: target.id } };
|
|
489
|
+
}
|
|
490
|
+
if (!["title", "description"].includes(property))
|
|
491
|
+
return {
|
|
492
|
+
success: false,
|
|
493
|
+
error: { type: "invalid_operation", message: `Unsupported page property: ${property}` }
|
|
494
|
+
};
|
|
495
|
+
return updatePage(target.id, (page) => {
|
|
496
|
+
if (property === "title") {
|
|
497
|
+
const { title: _title, ...withoutTitle } = page;
|
|
498
|
+
return text.length === 0 ? withoutTitle : { ...page, title: text };
|
|
499
|
+
}
|
|
500
|
+
const { description: _description, ...withoutDescription } = page;
|
|
501
|
+
return text.length === 0 ? withoutDescription : { ...page, description: text };
|
|
502
|
+
});
|
|
503
|
+
},
|
|
504
|
+
[onChange, schema, textPolicyError, updateField, updateOption, updatePage]
|
|
505
|
+
);
|
|
506
|
+
const setLocaleTranslation = (0, import_react.useCallback)(
|
|
507
|
+
(locale, target, property, text, options = {}) => {
|
|
508
|
+
const normalized = locale.trim();
|
|
509
|
+
if (normalized.length === 0)
|
|
510
|
+
return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
|
|
511
|
+
const error = textPolicyError(text);
|
|
512
|
+
if (error !== void 0) return error;
|
|
513
|
+
const supportedLocales = [.../* @__PURE__ */ new Set([...schema.supportedLocales ?? [], normalized])];
|
|
514
|
+
const remove = text.length === 0;
|
|
515
|
+
if (target.kind === "form") {
|
|
516
|
+
if (!["title", "description", "completionMessage"].includes(property))
|
|
517
|
+
return {
|
|
518
|
+
success: false,
|
|
519
|
+
error: { type: "invalid_operation", message: `Unsupported form property: ${property}` }
|
|
520
|
+
};
|
|
521
|
+
const key = property;
|
|
522
|
+
const translations = remove ? removeLocalizedProperty(schema.translations, normalized, key) : { ...schema.translations, [normalized]: { ...schema.translations?.[normalized], [key]: text } };
|
|
523
|
+
onChange(
|
|
524
|
+
setTranslationMetadata(
|
|
525
|
+
{ ...schema, supportedLocales, ...translations === void 0 ? {} : { translations } },
|
|
526
|
+
normalized,
|
|
527
|
+
property,
|
|
528
|
+
options.metadata,
|
|
529
|
+
remove
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
return { success: true };
|
|
533
|
+
}
|
|
534
|
+
if (target.id === void 0)
|
|
535
|
+
return { success: false, error: { type: "invalid_operation", message: "A target ID is required." } };
|
|
536
|
+
let found = false;
|
|
537
|
+
const fields = schema.fields.map((field) => {
|
|
538
|
+
if (target.kind === "field" && field.id === target.id && ["title", "description"].includes(property)) {
|
|
539
|
+
found = true;
|
|
540
|
+
const key = property;
|
|
541
|
+
const translations = remove ? removeLocalizedProperty(field.translations, normalized, key) : { ...field.translations, [normalized]: { ...field.translations?.[normalized], [key]: text } };
|
|
542
|
+
return setTranslationMetadata(
|
|
543
|
+
{ ...field, ...translations === void 0 ? {} : { translations } },
|
|
544
|
+
normalized,
|
|
545
|
+
property,
|
|
546
|
+
options.metadata,
|
|
547
|
+
remove
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
if (target.kind !== "option" || property !== "label" || !("options" in field)) return field;
|
|
551
|
+
return {
|
|
552
|
+
...field,
|
|
553
|
+
options: field.options.map((option) => {
|
|
554
|
+
if (option.id !== target.id) return option;
|
|
555
|
+
found = true;
|
|
556
|
+
const translations = remove ? Object.fromEntries(Object.entries(option.translations ?? {}).filter(([key]) => key !== normalized)) : { ...option.translations, [normalized]: text };
|
|
557
|
+
return setTranslationMetadata({ ...option, translations }, normalized, property, options.metadata, remove);
|
|
558
|
+
})
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
const pages = schema.pages?.map((page) => {
|
|
562
|
+
if (target.kind !== "page" || page.id !== target.id || !["title", "description"].includes(property))
|
|
563
|
+
return page;
|
|
564
|
+
found = true;
|
|
565
|
+
const key = property;
|
|
566
|
+
const translations = remove ? removeLocalizedProperty(page.translations, normalized, key) : { ...page.translations, [normalized]: { ...page.translations?.[normalized], [key]: text } };
|
|
567
|
+
return setTranslationMetadata(
|
|
568
|
+
{ ...page, ...translations === void 0 ? {} : { translations } },
|
|
569
|
+
normalized,
|
|
570
|
+
property,
|
|
571
|
+
options.metadata,
|
|
572
|
+
remove
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
if (!found) return { success: false, error: { type: "node_not_found", kind: target.kind, id: target.id } };
|
|
576
|
+
onChange({ ...schema, supportedLocales, fields, ...pages === void 0 ? {} : { pages } });
|
|
577
|
+
return { success: true };
|
|
578
|
+
},
|
|
579
|
+
[onChange, schema, textPolicyError]
|
|
580
|
+
);
|
|
581
|
+
const addLocale = (0, import_react.useCallback)(
|
|
582
|
+
(locale) => {
|
|
583
|
+
const normalized = locale.trim();
|
|
584
|
+
if (normalized.length === 0)
|
|
585
|
+
return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
|
|
586
|
+
onChange({
|
|
587
|
+
...schema,
|
|
588
|
+
supportedLocales: [
|
|
589
|
+
.../* @__PURE__ */ new Set([
|
|
590
|
+
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
591
|
+
...schema.supportedLocales ?? [],
|
|
592
|
+
normalized
|
|
593
|
+
])
|
|
594
|
+
]
|
|
595
|
+
});
|
|
596
|
+
return { success: true };
|
|
597
|
+
},
|
|
598
|
+
[onChange, schema]
|
|
599
|
+
);
|
|
600
|
+
const setDefaultLocale = (0, import_react.useCallback)(
|
|
601
|
+
(locale) => {
|
|
602
|
+
const normalized = locale.trim();
|
|
603
|
+
if (normalized.length === 0)
|
|
604
|
+
return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
|
|
605
|
+
onChange({
|
|
606
|
+
...schema,
|
|
607
|
+
defaultLocale: normalized,
|
|
608
|
+
supportedLocales: [.../* @__PURE__ */ new Set([normalized, ...schema.supportedLocales ?? []])]
|
|
609
|
+
});
|
|
610
|
+
return { success: true };
|
|
611
|
+
},
|
|
612
|
+
[onChange, schema]
|
|
613
|
+
);
|
|
614
|
+
const validationIssues = (0, import_react.useMemo)(() => {
|
|
615
|
+
const result = (0, import_core.validateFormSchema)(schema, policy === void 0 ? {} : { policy });
|
|
616
|
+
return result.valid ? [] : result.issues;
|
|
617
|
+
}, [policy, schema]);
|
|
618
|
+
return {
|
|
619
|
+
schema,
|
|
620
|
+
addField,
|
|
621
|
+
removeField,
|
|
622
|
+
moveField,
|
|
623
|
+
updateField,
|
|
624
|
+
changeFieldType,
|
|
625
|
+
addOption,
|
|
626
|
+
updateOption,
|
|
627
|
+
removeOption,
|
|
628
|
+
moveOption,
|
|
629
|
+
addPage,
|
|
630
|
+
updatePage,
|
|
631
|
+
removePage,
|
|
632
|
+
movePage,
|
|
633
|
+
assignFieldToPage,
|
|
634
|
+
setDisplayCondition,
|
|
635
|
+
setSourceText,
|
|
636
|
+
setLocaleTranslation,
|
|
637
|
+
addLocale,
|
|
638
|
+
setDefaultLocale,
|
|
639
|
+
validationIssues
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/builder.tsx
|
|
34
644
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
645
|
var FIELD_TYPES = [
|
|
36
646
|
"text",
|
|
@@ -51,6 +661,7 @@ var BUILDER_DEFAULTS = {
|
|
|
51
661
|
"builder.questionTitle": "\u8CEA\u554F\u6587 / Question Title",
|
|
52
662
|
"builder.questionTitlePlaceholder": "Example: Tell us what we could improve",
|
|
53
663
|
"builder.newQuestionTitle": "New question",
|
|
664
|
+
"builder.completionMessage": "Completion message",
|
|
54
665
|
"builder.type": "Type",
|
|
55
666
|
"builder.required": "Required",
|
|
56
667
|
"builder.minimum": "Minimum",
|
|
@@ -109,33 +720,6 @@ function fieldTypeKey(type) {
|
|
|
109
720
|
function operatorKey(operator) {
|
|
110
721
|
return `builder.operator.${operator}`;
|
|
111
722
|
}
|
|
112
|
-
function createUniqueId(prefix, existingIds) {
|
|
113
|
-
let id;
|
|
114
|
-
do {
|
|
115
|
-
id = `${prefix}_${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
116
|
-
} while (existingIds.has(id));
|
|
117
|
-
return id;
|
|
118
|
-
}
|
|
119
|
-
function baseField(field, type) {
|
|
120
|
-
return {
|
|
121
|
-
id: field.id,
|
|
122
|
-
type,
|
|
123
|
-
title: field.title,
|
|
124
|
-
...field.description === void 0 ? {} : { description: field.description },
|
|
125
|
-
...field.translationKey === void 0 ? {} : { translationKey: field.translationKey },
|
|
126
|
-
required: field.required,
|
|
127
|
-
...field.displayCondition === void 0 ? {} : { displayCondition: field.displayCondition }
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
function normalizeField(field, type, newOptionLabel) {
|
|
131
|
-
const base = baseField(field, type);
|
|
132
|
-
if (type === "text" || type === "textarea") return { ...base, type };
|
|
133
|
-
if (type === "number") return { ...base, type };
|
|
134
|
-
if (type === "rating") return { ...base, type, min: 1, max: 5 };
|
|
135
|
-
if (type === "checkbox") return { ...base, type };
|
|
136
|
-
const options = "options" in field && field.options.length > 0 ? field.options : [{ id: createUniqueId("opt", /* @__PURE__ */ new Set()), label: newOptionLabel }];
|
|
137
|
-
return { ...base, type, options };
|
|
138
|
-
}
|
|
139
723
|
function defaultConditionValue(field) {
|
|
140
724
|
if (field.type === "checkbox") return true;
|
|
141
725
|
if (field.type === "number" || field.type === "rating") return field.min ?? 1;
|
|
@@ -149,30 +733,6 @@ function conditionOperators(field) {
|
|
|
149
733
|
}
|
|
150
734
|
return ["equals", "not_equals", "not_empty"];
|
|
151
735
|
}
|
|
152
|
-
function withoutDisplayCondition(field) {
|
|
153
|
-
const { displayCondition: _displayCondition, ...rest } = field;
|
|
154
|
-
return rest;
|
|
155
|
-
}
|
|
156
|
-
function sanitizeBuilderSchema(schema) {
|
|
157
|
-
const sanitized = (0, import_core.sanitizeSchema)(schema);
|
|
158
|
-
const indexById = new Map(sanitized.fields.map((field, index) => [field.id, index]));
|
|
159
|
-
const fields = sanitized.fields.map((field, index) => {
|
|
160
|
-
const sourceId = field.displayCondition?.questionId;
|
|
161
|
-
if (sourceId === void 0) return field;
|
|
162
|
-
const sourceIndex = indexById.get(sourceId);
|
|
163
|
-
return sourceIndex !== void 0 && sourceIndex < index ? field : withoutDisplayCondition(field);
|
|
164
|
-
});
|
|
165
|
-
return {
|
|
166
|
-
...sanitized,
|
|
167
|
-
fields,
|
|
168
|
-
...sanitized.pages === void 0 ? {} : {
|
|
169
|
-
pages: sanitized.pages.map((page) => ({
|
|
170
|
-
...page,
|
|
171
|
-
questionIds: fields.filter((field) => page.questionIds.includes(field.id)).map((field) => field.id)
|
|
172
|
-
}))
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
736
|
function conditionWithValue(questionId, operator, value) {
|
|
177
737
|
return operator === "not_empty" ? { questionId, operator } : { questionId, operator, value };
|
|
178
738
|
}
|
|
@@ -214,77 +774,45 @@ function ConditionValueEditor({
|
|
|
214
774
|
}
|
|
215
775
|
);
|
|
216
776
|
}
|
|
217
|
-
function FormBuilder({
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
777
|
+
function FormBuilder({
|
|
778
|
+
schema,
|
|
779
|
+
onChange,
|
|
780
|
+
locale = "en",
|
|
781
|
+
translator,
|
|
782
|
+
translationAdapter,
|
|
783
|
+
translationOptions,
|
|
784
|
+
onTranslationReport,
|
|
785
|
+
policy,
|
|
786
|
+
idFactory,
|
|
787
|
+
factories
|
|
788
|
+
}) {
|
|
789
|
+
const headless = useFormBuilder({
|
|
790
|
+
schema,
|
|
791
|
+
onChange,
|
|
792
|
+
...policy === void 0 ? {} : { policy },
|
|
793
|
+
...idFactory === void 0 ? {} : { idFactory },
|
|
794
|
+
...factories === void 0 ? {} : { factories }
|
|
795
|
+
});
|
|
796
|
+
const [newPageQuestionId, setNewPageQuestionId] = (0, import_react2.useState)("");
|
|
797
|
+
const [newLocale, setNewLocale] = (0, import_react2.useState)("");
|
|
798
|
+
const [editingLocale, setEditingLocale] = (0, import_react2.useState)("");
|
|
799
|
+
const [isTranslating, setIsTranslating] = (0, import_react2.useState)(false);
|
|
800
|
+
const [translationError, setTranslationError] = (0, import_react2.useState)(null);
|
|
223
801
|
const translate = (key, params = {}) => {
|
|
224
802
|
const translated = translator?.translate(key, locale, params);
|
|
225
803
|
return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
|
|
226
804
|
};
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
};
|
|
231
|
-
const changeType = (fieldId, type) => {
|
|
232
|
-
emitSchema({
|
|
233
|
-
...schema,
|
|
234
|
-
fields: schema.fields.map((field) => {
|
|
235
|
-
if (field.id === fieldId) {
|
|
236
|
-
return normalizeField(field, type, translate("builder.newOptionLabel", { index: 1 }));
|
|
237
|
-
}
|
|
238
|
-
if (field.displayCondition?.questionId === fieldId) {
|
|
239
|
-
const { displayCondition: _condition, ...withoutCondition } = field;
|
|
240
|
-
return withoutCondition;
|
|
241
|
-
}
|
|
242
|
-
return field;
|
|
243
|
-
})
|
|
244
|
-
});
|
|
245
|
-
};
|
|
246
|
-
const removeField = (fieldId) => {
|
|
247
|
-
if (schema.fields.length === 1) return;
|
|
248
|
-
emitSchema({ ...schema, fields: schema.fields.filter((field) => field.id !== fieldId) });
|
|
249
|
-
};
|
|
805
|
+
const updateField = headless.updateField;
|
|
806
|
+
const changeType = headless.changeFieldType;
|
|
807
|
+
const removeField = headless.removeField;
|
|
250
808
|
const moveField = (index, offset) => {
|
|
251
809
|
const target = index + offset;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const current = fields[index];
|
|
255
|
-
const other = fields[target];
|
|
256
|
-
if (current === void 0 || other === void 0) return;
|
|
257
|
-
fields[index] = other;
|
|
258
|
-
fields[target] = current;
|
|
259
|
-
emitSchema({ ...schema, fields });
|
|
260
|
-
};
|
|
261
|
-
const addField = () => {
|
|
262
|
-
const id = createUniqueId("q", new Set(schema.fields.map((field) => field.id)));
|
|
263
|
-
const nextSchema = {
|
|
264
|
-
...schema,
|
|
265
|
-
fields: [...schema.fields, { id, type: "text", title: translate("builder.newQuestionTitle"), required: false }]
|
|
266
|
-
};
|
|
267
|
-
emitSchema(
|
|
268
|
-
schema.pages === void 0 ? nextSchema : {
|
|
269
|
-
...nextSchema,
|
|
270
|
-
pages: schema.pages.map(
|
|
271
|
-
(page, index) => index === (schema.pages?.length ?? 0) - 1 ? { ...page, questionIds: [...page.questionIds, id] } : page
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
);
|
|
810
|
+
const field = schema.fields[index];
|
|
811
|
+
if (field !== void 0) headless.moveField(field.id, target);
|
|
275
812
|
};
|
|
813
|
+
const addField = () => headless.addField("text");
|
|
276
814
|
const enablePages = () => {
|
|
277
|
-
if (schema.pages
|
|
278
|
-
emitSchema({
|
|
279
|
-
...schema,
|
|
280
|
-
pages: [
|
|
281
|
-
{
|
|
282
|
-
id: createUniqueId("page", /* @__PURE__ */ new Set()),
|
|
283
|
-
title: translate("builder.newPage"),
|
|
284
|
-
questionIds: schema.fields.map((field) => field.id)
|
|
285
|
-
}
|
|
286
|
-
]
|
|
287
|
-
});
|
|
815
|
+
if (schema.pages === void 0) headless.addPage();
|
|
288
816
|
};
|
|
289
817
|
const pageForField = (fieldId) => schema.pages?.find((page) => page.questionIds.includes(fieldId));
|
|
290
818
|
const movablePageQuestions = schema.pages?.flatMap((page) => page.questionIds.length > 1 ? page.questionIds : []) ?? [];
|
|
@@ -295,73 +823,28 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
295
823
|
}
|
|
296
824
|
const questionId = movablePageQuestions.includes(newPageQuestionId) ? newPageQuestionId : movablePageQuestions[0];
|
|
297
825
|
if (questionId === void 0) return;
|
|
298
|
-
|
|
299
|
-
emitSchema({
|
|
300
|
-
...schema,
|
|
301
|
-
pages: [
|
|
302
|
-
...schema.pages.map((page) => ({
|
|
303
|
-
...page,
|
|
304
|
-
questionIds: page.questionIds.filter((id) => id !== questionId)
|
|
305
|
-
})),
|
|
306
|
-
{ id: pageId, title: translate("builder.newPage"), questionIds: [questionId] }
|
|
307
|
-
]
|
|
308
|
-
});
|
|
826
|
+
headless.addPage(questionId);
|
|
309
827
|
setNewPageQuestionId("");
|
|
310
828
|
};
|
|
311
829
|
const removePage = (pageIndex) => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (removed === void 0) return;
|
|
315
|
-
if (schema.pages.length === 1) {
|
|
316
|
-
const { pages: _pages, ...singlePage } = schema;
|
|
317
|
-
emitSchema(singlePage);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
const targetIndex = pageIndex === 0 ? 1 : pageIndex - 1;
|
|
321
|
-
emitSchema({
|
|
322
|
-
...schema,
|
|
323
|
-
pages: schema.pages.map(
|
|
324
|
-
(page, index) => index === targetIndex ? { ...page, questionIds: [...page.questionIds, ...removed.questionIds] } : page
|
|
325
|
-
).filter((_page, index) => index !== pageIndex)
|
|
326
|
-
});
|
|
830
|
+
const page = schema.pages?.[pageIndex];
|
|
831
|
+
if (page !== void 0) headless.removePage(page.id);
|
|
327
832
|
};
|
|
328
833
|
const movePage = (pageIndex, offset) => {
|
|
329
|
-
if (schema.pages === void 0) return;
|
|
330
834
|
const target = pageIndex + offset;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const current = pages[pageIndex];
|
|
334
|
-
const other = pages[target];
|
|
335
|
-
if (current === void 0 || other === void 0) return;
|
|
336
|
-
pages[pageIndex] = other;
|
|
337
|
-
pages[target] = current;
|
|
338
|
-
emitSchema({ ...schema, pages });
|
|
835
|
+
const page = schema.pages?.[pageIndex];
|
|
836
|
+
if (page !== void 0) headless.movePage(page.id, target);
|
|
339
837
|
};
|
|
340
838
|
const updatePage = (pageId, update) => {
|
|
341
|
-
|
|
342
|
-
emitSchema({ ...schema, pages: schema.pages.map((page) => page.id === pageId ? update(page) : page) });
|
|
839
|
+
headless.updatePage(pageId, update);
|
|
343
840
|
};
|
|
344
841
|
const assignFieldToPage = (fieldId, pageId) => {
|
|
345
|
-
|
|
346
|
-
emitSchema({
|
|
347
|
-
...schema,
|
|
348
|
-
pages: schema.pages.map((page) => ({
|
|
349
|
-
...page,
|
|
350
|
-
questionIds: page.id === pageId ? schema.fields.filter((field) => page.questionIds.includes(field.id) || field.id === fieldId).map((field) => field.id) : page.questionIds.filter((id) => id !== fieldId)
|
|
351
|
-
})).filter((page) => page.questionIds.length > 0)
|
|
352
|
-
});
|
|
842
|
+
headless.assignFieldToPage(fieldId, pageId);
|
|
353
843
|
};
|
|
354
844
|
const addLocale = () => {
|
|
355
845
|
const normalized = newLocale.trim();
|
|
356
846
|
if (normalized.length === 0) return;
|
|
357
|
-
|
|
358
|
-
.../* @__PURE__ */ new Set([
|
|
359
|
-
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
360
|
-
...schema.supportedLocales ?? [],
|
|
361
|
-
normalized
|
|
362
|
-
])
|
|
363
|
-
];
|
|
364
|
-
emitSchema({ ...schema, supportedLocales });
|
|
847
|
+
headless.addLocale(normalized);
|
|
365
848
|
setEditingLocale(normalized);
|
|
366
849
|
setNewLocale("");
|
|
367
850
|
};
|
|
@@ -370,7 +853,14 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
370
853
|
setIsTranslating(true);
|
|
371
854
|
setTranslationError(null);
|
|
372
855
|
try {
|
|
373
|
-
|
|
856
|
+
const populated = await (0, import_core2.populateSchemaTranslations)(
|
|
857
|
+
schema,
|
|
858
|
+
[editingLocale],
|
|
859
|
+
translationAdapter,
|
|
860
|
+
translationOptions ?? { overwrite: "all" }
|
|
861
|
+
);
|
|
862
|
+
onChange(populated.schema);
|
|
863
|
+
onTranslationReport?.(populated.report);
|
|
374
864
|
} catch (cause) {
|
|
375
865
|
setTranslationError(cause instanceof Error ? cause.message : String(cause));
|
|
376
866
|
} finally {
|
|
@@ -379,18 +869,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
379
869
|
};
|
|
380
870
|
const updateFormTranslation = (key, value) => {
|
|
381
871
|
if (editingLocale.length === 0) return;
|
|
382
|
-
|
|
383
|
-
const next = key === "title" ? value.length === 0 ? { description: current?.description } : { ...current, title: value } : value.length === 0 ? { title: current?.title } : { ...current, description: value };
|
|
384
|
-
emitSchema({
|
|
385
|
-
...schema,
|
|
386
|
-
translations: {
|
|
387
|
-
...schema.translations,
|
|
388
|
-
[editingLocale]: {
|
|
389
|
-
...next.title === void 0 ? {} : { title: next.title },
|
|
390
|
-
...next.description === void 0 ? {} : { description: next.description }
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
});
|
|
872
|
+
headless.setLocaleTranslation(editingLocale, { kind: "form" }, key, value);
|
|
394
873
|
};
|
|
395
874
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "form-engine-builder", "aria-label": translate("builder.formBuilder"), children: [
|
|
396
875
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
|
|
@@ -470,19 +949,12 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
470
949
|
"input",
|
|
471
950
|
{
|
|
472
951
|
value: page.translations?.[editingLocale]?.title ?? "",
|
|
473
|
-
onChange: (event) =>
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
[editingLocale]: {
|
|
480
|
-
...value.length === 0 ? {} : { title: value },
|
|
481
|
-
...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}));
|
|
485
|
-
}
|
|
952
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
953
|
+
editingLocale,
|
|
954
|
+
{ kind: "page", id: page.id },
|
|
955
|
+
"title",
|
|
956
|
+
event.currentTarget.value
|
|
957
|
+
)
|
|
486
958
|
}
|
|
487
959
|
)
|
|
488
960
|
] }),
|
|
@@ -492,19 +964,12 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
492
964
|
"input",
|
|
493
965
|
{
|
|
494
966
|
value: page.translations?.[editingLocale]?.description ?? "",
|
|
495
|
-
onChange: (event) =>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
[editingLocale]: {
|
|
502
|
-
...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
|
|
503
|
-
...value.length === 0 ? {} : { description: value }
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}));
|
|
507
|
-
}
|
|
967
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
968
|
+
editingLocale,
|
|
969
|
+
{ kind: "page", id: page.id },
|
|
970
|
+
"description",
|
|
971
|
+
event.currentTarget.value
|
|
972
|
+
)
|
|
508
973
|
}
|
|
509
974
|
)
|
|
510
975
|
] })
|
|
@@ -592,6 +1057,16 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
592
1057
|
] }),
|
|
593
1058
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
|
|
594
1059
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
|
|
1060
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
1061
|
+
translate("builder.completionMessage"),
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1063
|
+
"input",
|
|
1064
|
+
{
|
|
1065
|
+
value: schema.completionMessage ?? "",
|
|
1066
|
+
onChange: (event) => headless.setSourceText({ kind: "form" }, "completionMessage", event.currentTarget.value)
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
1069
|
+
] }),
|
|
595
1070
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
|
|
596
1071
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
597
1072
|
translate("builder.defaultLocale"),
|
|
@@ -599,16 +1074,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
599
1074
|
"input",
|
|
600
1075
|
{
|
|
601
1076
|
value: schema.defaultLocale ?? "",
|
|
602
|
-
onChange: (event) =>
|
|
603
|
-
const value = event.currentTarget.value.trim();
|
|
604
|
-
emitSchema(
|
|
605
|
-
value.length === 0 ? schema : {
|
|
606
|
-
...schema,
|
|
607
|
-
defaultLocale: value,
|
|
608
|
-
supportedLocales: [.../* @__PURE__ */ new Set([value, ...schema.supportedLocales ?? []])]
|
|
609
|
-
}
|
|
610
|
-
);
|
|
611
|
-
}
|
|
1077
|
+
onChange: (event) => headless.setDefaultLocale(event.currentTarget.value)
|
|
612
1078
|
}
|
|
613
1079
|
)
|
|
614
1080
|
] }),
|
|
@@ -656,6 +1122,16 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
656
1122
|
onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
|
|
657
1123
|
}
|
|
658
1124
|
)
|
|
1125
|
+
] }),
|
|
1126
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
1127
|
+
translate("builder.completionMessage"),
|
|
1128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1129
|
+
"input",
|
|
1130
|
+
{
|
|
1131
|
+
value: schema.translations?.[editingLocale]?.completionMessage ?? "",
|
|
1132
|
+
onChange: (event) => updateFormTranslation("completionMessage", event.currentTarget.value)
|
|
1133
|
+
}
|
|
1134
|
+
)
|
|
659
1135
|
] })
|
|
660
1136
|
] })
|
|
661
1137
|
] }),
|
|
@@ -719,7 +1195,9 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
719
1195
|
{
|
|
720
1196
|
value: field.type,
|
|
721
1197
|
onChange: (event) => changeType(field.id, event.currentTarget.value),
|
|
722
|
-
children: FIELD_TYPES.
|
|
1198
|
+
children: FIELD_TYPES.filter(
|
|
1199
|
+
(type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
|
|
1200
|
+
).map((type) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: type, children: translate(fieldTypeKey(type)) }, type))
|
|
723
1201
|
}
|
|
724
1202
|
)
|
|
725
1203
|
] }),
|
|
@@ -755,19 +1233,12 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
755
1233
|
"input",
|
|
756
1234
|
{
|
|
757
1235
|
value: field.translations?.[editingLocale]?.title ?? "",
|
|
758
|
-
onChange: (event) =>
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
[editingLocale]: {
|
|
765
|
-
...value.length === 0 ? {} : { title: value },
|
|
766
|
-
...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
}));
|
|
770
|
-
}
|
|
1236
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
1237
|
+
editingLocale,
|
|
1238
|
+
{ kind: "field", id: field.id },
|
|
1239
|
+
"title",
|
|
1240
|
+
event.currentTarget.value
|
|
1241
|
+
)
|
|
771
1242
|
}
|
|
772
1243
|
)
|
|
773
1244
|
] }),
|
|
@@ -777,19 +1248,12 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
777
1248
|
"input",
|
|
778
1249
|
{
|
|
779
1250
|
value: field.translations?.[editingLocale]?.description ?? "",
|
|
780
|
-
onChange: (event) =>
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
[editingLocale]: {
|
|
787
|
-
...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
|
|
788
|
-
...value.length === 0 ? {} : { description: value }
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}));
|
|
792
|
-
}
|
|
1251
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
1252
|
+
editingLocale,
|
|
1253
|
+
{ kind: "field", id: field.id },
|
|
1254
|
+
"description",
|
|
1255
|
+
event.currentTarget.value
|
|
1256
|
+
)
|
|
793
1257
|
}
|
|
794
1258
|
)
|
|
795
1259
|
] })
|
|
@@ -803,26 +1267,12 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
803
1267
|
"input",
|
|
804
1268
|
{
|
|
805
1269
|
value: option.translations?.[editingLocale] ?? "",
|
|
806
|
-
onChange: (event) =>
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
options: current.options.map(
|
|
813
|
-
(candidate) => candidate.id === option.id ? {
|
|
814
|
-
...candidate,
|
|
815
|
-
translations: Object.fromEntries([
|
|
816
|
-
...Object.entries(candidate.translations ?? {}).filter(
|
|
817
|
-
([localeKey]) => localeKey !== editingLocale
|
|
818
|
-
),
|
|
819
|
-
...value.length === 0 ? [] : [[editingLocale, value]]
|
|
820
|
-
])
|
|
821
|
-
} : candidate
|
|
822
|
-
)
|
|
823
|
-
};
|
|
824
|
-
});
|
|
825
|
-
}
|
|
1270
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
1271
|
+
editingLocale,
|
|
1272
|
+
{ kind: "option", id: option.id },
|
|
1273
|
+
"label",
|
|
1274
|
+
event.currentTarget.value
|
|
1275
|
+
)
|
|
826
1276
|
}
|
|
827
1277
|
)
|
|
828
1278
|
] }, option.id)) : null
|
|
@@ -874,17 +1324,10 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
874
1324
|
"aria-label": translate("builder.optionLabel", { index: optionIndex + 1 }),
|
|
875
1325
|
value: option.label,
|
|
876
1326
|
placeholder: translate("builder.optionLabelPlaceholder"),
|
|
877
|
-
onChange: (event) =>
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
return {
|
|
882
|
-
...current,
|
|
883
|
-
options: current.options.map(
|
|
884
|
-
(item, itemIndex) => itemIndex === optionIndex ? { ...item, label } : item
|
|
885
|
-
)
|
|
886
|
-
};
|
|
887
|
-
})
|
|
1327
|
+
onChange: (event) => event.currentTarget.value.trim().length === 0 ? void 0 : headless.updateOption(field.id, option.id, (item) => ({
|
|
1328
|
+
...item,
|
|
1329
|
+
label: event.currentTarget.value
|
|
1330
|
+
}))
|
|
888
1331
|
}
|
|
889
1332
|
),
|
|
890
1333
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -892,13 +1335,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
892
1335
|
{
|
|
893
1336
|
type: "button",
|
|
894
1337
|
disabled: field.options.length === 1,
|
|
895
|
-
onClick: () =>
|
|
896
|
-
field.id,
|
|
897
|
-
(current) => "options" in current ? {
|
|
898
|
-
...current,
|
|
899
|
-
options: current.options.filter((_item, itemIndex) => itemIndex !== optionIndex)
|
|
900
|
-
} : current
|
|
901
|
-
),
|
|
1338
|
+
onClick: () => headless.removeOption(field.id, option.id),
|
|
902
1339
|
children: translate("builder.remove")
|
|
903
1340
|
}
|
|
904
1341
|
)
|
|
@@ -907,22 +1344,8 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
907
1344
|
"button",
|
|
908
1345
|
{
|
|
909
1346
|
type: "button",
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
(current) => "options" in current ? (() => {
|
|
913
|
-
const id = createUniqueId("opt", new Set(current.options.map((option) => option.id)));
|
|
914
|
-
return {
|
|
915
|
-
...current,
|
|
916
|
-
options: [
|
|
917
|
-
...current.options,
|
|
918
|
-
{
|
|
919
|
-
id,
|
|
920
|
-
label: translate("builder.newOptionLabel", { index: current.options.length + 1 })
|
|
921
|
-
}
|
|
922
|
-
]
|
|
923
|
-
};
|
|
924
|
-
})() : current
|
|
925
|
-
),
|
|
1347
|
+
disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
|
|
1348
|
+
onClick: () => headless.addOption(field.id),
|
|
926
1349
|
children: translate("builder.addOption")
|
|
927
1350
|
}
|
|
928
1351
|
)
|
|
@@ -936,17 +1359,14 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
936
1359
|
value: condition?.questionId ?? "",
|
|
937
1360
|
onChange: (event) => {
|
|
938
1361
|
const selected = schema.fields.find((item) => item.id === event.currentTarget.value);
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
displayCondition: conditionWithValue(selected.id, operator, defaultConditionValue(selected))
|
|
948
|
-
};
|
|
949
|
-
});
|
|
1362
|
+
headless.setDisplayCondition(
|
|
1363
|
+
field.id,
|
|
1364
|
+
selected === void 0 ? void 0 : conditionWithValue(
|
|
1365
|
+
selected.id,
|
|
1366
|
+
conditionOperators(selected)[0] ?? "not_empty",
|
|
1367
|
+
defaultConditionValue(selected)
|
|
1368
|
+
)
|
|
1369
|
+
);
|
|
950
1370
|
},
|
|
951
1371
|
children: [
|
|
952
1372
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: translate("builder.alwaysVisible") }),
|
|
@@ -963,10 +1383,10 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
963
1383
|
value: condition.operator,
|
|
964
1384
|
onChange: (event) => {
|
|
965
1385
|
const operator = event.currentTarget.value;
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1386
|
+
headless.setDisplayCondition(
|
|
1387
|
+
field.id,
|
|
1388
|
+
conditionWithValue(source.id, operator, defaultConditionValue(source))
|
|
1389
|
+
);
|
|
970
1390
|
},
|
|
971
1391
|
children: conditionOperators(source).map((operator) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
|
|
972
1392
|
}
|
|
@@ -976,7 +1396,7 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
976
1396
|
{
|
|
977
1397
|
source,
|
|
978
1398
|
condition,
|
|
979
|
-
onChange: (next) =>
|
|
1399
|
+
onChange: (next) => headless.setDisplayCondition(field.id, next),
|
|
980
1400
|
translate
|
|
981
1401
|
}
|
|
982
1402
|
)
|
|
@@ -984,15 +1404,24 @@ function FormBuilder({ schema, onChange, locale = "en", translator, translationA
|
|
|
984
1404
|
] })
|
|
985
1405
|
] }, field.id);
|
|
986
1406
|
}) }),
|
|
987
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1407
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1408
|
+
"button",
|
|
1409
|
+
{
|
|
1410
|
+
className: "form-engine-builder__add",
|
|
1411
|
+
type: "button",
|
|
1412
|
+
disabled: policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields,
|
|
1413
|
+
onClick: addField,
|
|
1414
|
+
children: translate("builder.addQuestion")
|
|
1415
|
+
}
|
|
1416
|
+
)
|
|
988
1417
|
] });
|
|
989
1418
|
}
|
|
990
1419
|
|
|
991
1420
|
// src/context.tsx
|
|
992
|
-
var
|
|
993
|
-
var
|
|
1421
|
+
var import_core3 = require("@form-engine-ts/core");
|
|
1422
|
+
var import_react3 = require("react");
|
|
994
1423
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
995
|
-
var FormContext = (0,
|
|
1424
|
+
var FormContext = (0, import_react3.createContext)(null);
|
|
996
1425
|
function issuesByField(issues) {
|
|
997
1426
|
const result = {};
|
|
998
1427
|
for (const issue of issues) result[issue.fieldId] ??= issue;
|
|
@@ -1007,30 +1436,30 @@ function FormProvider({
|
|
|
1007
1436
|
onSubmit,
|
|
1008
1437
|
children
|
|
1009
1438
|
}) {
|
|
1010
|
-
const validSchema = (0,
|
|
1011
|
-
(0,
|
|
1012
|
-
const localized = (0,
|
|
1013
|
-
(0,
|
|
1439
|
+
const validSchema = (0, import_react3.useMemo)(() => {
|
|
1440
|
+
(0, import_core3.assertValidFormSchema)(schema);
|
|
1441
|
+
const localized = (0, import_core3.resolveLocalizedSchema)(schema, locale);
|
|
1442
|
+
(0, import_core3.assertValidFormSchema)(localized);
|
|
1014
1443
|
return localized;
|
|
1015
1444
|
}, [locale, schema]);
|
|
1016
|
-
const [values, setValues] = (0,
|
|
1017
|
-
const [errors, setErrors] = (0,
|
|
1018
|
-
const [submitStatus, setSubmitStatus] = (0,
|
|
1019
|
-
const [submitError, setSubmitError] = (0,
|
|
1020
|
-
const [validationPageIndex, setValidationPageIndex] = (0,
|
|
1021
|
-
const visibility = (0,
|
|
1022
|
-
const pageVisibility = (0,
|
|
1023
|
-
(0,
|
|
1445
|
+
const [values, setValues] = (0, import_react3.useState)(() => ({ ...initialValues }));
|
|
1446
|
+
const [errors, setErrors] = (0, import_react3.useState)({});
|
|
1447
|
+
const [submitStatus, setSubmitStatus] = (0, import_react3.useState)("idle");
|
|
1448
|
+
const [submitError, setSubmitError] = (0, import_react3.useState)(null);
|
|
1449
|
+
const [validationPageIndex, setValidationPageIndex] = (0, import_react3.useState)(null);
|
|
1450
|
+
const visibility = (0, import_react3.useMemo)(() => (0, import_core3.calculateFieldVisibility)(validSchema, values), [validSchema, values]);
|
|
1451
|
+
const pageVisibility = (0, import_react3.useMemo)(() => (0, import_core3.calculatePageVisibility)(validSchema, values), [validSchema, values]);
|
|
1452
|
+
(0, import_react3.useEffect)(() => {
|
|
1024
1453
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
1025
1454
|
setValues((current) => Object.fromEntries(Object.entries(current).filter(([fieldId]) => fieldIds.has(fieldId))));
|
|
1026
1455
|
}, [validSchema]);
|
|
1027
|
-
const setValue = (0,
|
|
1456
|
+
const setValue = (0, import_react3.useCallback)(
|
|
1028
1457
|
(fieldId, value) => {
|
|
1029
1458
|
setValues((current) => {
|
|
1030
1459
|
const next = { ...current, [fieldId]: value };
|
|
1031
1460
|
setErrors((currentErrors) => {
|
|
1032
1461
|
if (Object.keys(currentErrors).length === 0) return currentErrors;
|
|
1033
|
-
const result = validationPageIndex === null ? (0,
|
|
1462
|
+
const result = validationPageIndex === null ? (0, import_core3.validateAnswers)(validSchema, next) : (0, import_core3.validatePageAnswers)(validSchema, validationPageIndex, next);
|
|
1034
1463
|
return issuesByField(result.issues);
|
|
1035
1464
|
});
|
|
1036
1465
|
return next;
|
|
@@ -1040,7 +1469,7 @@ function FormProvider({
|
|
|
1040
1469
|
},
|
|
1041
1470
|
[validSchema, validationPageIndex]
|
|
1042
1471
|
);
|
|
1043
|
-
const restoreValues = (0,
|
|
1472
|
+
const restoreValues = (0, import_react3.useCallback)(
|
|
1044
1473
|
(restoredValues) => {
|
|
1045
1474
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
1046
1475
|
setValues(Object.fromEntries(Object.entries(restoredValues).filter(([fieldId]) => fieldIds.has(fieldId))));
|
|
@@ -1051,9 +1480,9 @@ function FormProvider({
|
|
|
1051
1480
|
},
|
|
1052
1481
|
[validSchema]
|
|
1053
1482
|
);
|
|
1054
|
-
const validatePage = (0,
|
|
1483
|
+
const validatePage = (0, import_react3.useCallback)(
|
|
1055
1484
|
(pageIndex) => {
|
|
1056
|
-
const result = (0,
|
|
1485
|
+
const result = (0, import_core3.validatePageAnswers)(validSchema, pageIndex, values);
|
|
1057
1486
|
setErrors(issuesByField(result.issues));
|
|
1058
1487
|
setValidationPageIndex(result.valid ? null : pageIndex);
|
|
1059
1488
|
setSubmitStatus("idle");
|
|
@@ -1062,42 +1491,51 @@ function FormProvider({
|
|
|
1062
1491
|
},
|
|
1063
1492
|
[validSchema, values]
|
|
1064
1493
|
);
|
|
1065
|
-
const reset = (0,
|
|
1494
|
+
const reset = (0, import_react3.useCallback)(() => {
|
|
1066
1495
|
setValues({ ...initialValues });
|
|
1067
1496
|
setErrors({});
|
|
1068
1497
|
setValidationPageIndex(null);
|
|
1069
1498
|
setSubmitStatus("idle");
|
|
1070
1499
|
setSubmitError(null);
|
|
1071
1500
|
}, [initialValues]);
|
|
1072
|
-
const submit = (0,
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1501
|
+
const submit = (0, import_react3.useCallback)(
|
|
1502
|
+
async (beforeSubmit) => {
|
|
1503
|
+
const validation = (0, import_core3.validateAnswers)(validSchema, values);
|
|
1504
|
+
if (!validation.valid) {
|
|
1505
|
+
setErrors(issuesByField(validation.issues));
|
|
1506
|
+
setValidationPageIndex(null);
|
|
1507
|
+
setSubmitStatus("error");
|
|
1508
|
+
setSubmitError(null);
|
|
1509
|
+
return { status: "invalid", issues: validation.issues };
|
|
1510
|
+
}
|
|
1511
|
+
setErrors({});
|
|
1076
1512
|
setValidationPageIndex(null);
|
|
1077
|
-
setSubmitStatus("error");
|
|
1078
1513
|
setSubmitError(null);
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1514
|
+
const visibleValues = (0, import_core3.selectVisibleAnswers)(validSchema, values);
|
|
1515
|
+
try {
|
|
1516
|
+
if (beforeSubmit !== void 0 && await beforeSubmit(visibleValues) === "cancel") {
|
|
1517
|
+
setSubmitStatus("idle");
|
|
1518
|
+
return { status: "cancelled" };
|
|
1519
|
+
}
|
|
1520
|
+
setSubmitStatus("submitting");
|
|
1521
|
+
await onSubmit(visibleValues);
|
|
1522
|
+
if (resetOnSuccess) setValues({ ...initialValues });
|
|
1523
|
+
setSubmitStatus("success");
|
|
1524
|
+
return { status: "success" };
|
|
1525
|
+
} catch (cause) {
|
|
1526
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1527
|
+
setSubmitError(error);
|
|
1528
|
+
setSubmitStatus("error");
|
|
1529
|
+
return { status: "error", error };
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1532
|
+
[initialValues, onSubmit, resetOnSuccess, validSchema, values]
|
|
1533
|
+
);
|
|
1534
|
+
const translate = (0, import_react3.useCallback)(
|
|
1097
1535
|
(key, params) => translator.translate(key, locale, params),
|
|
1098
1536
|
[locale, translator]
|
|
1099
1537
|
);
|
|
1100
|
-
const contextValue = (0,
|
|
1538
|
+
const contextValue = (0, import_react3.useMemo)(
|
|
1101
1539
|
() => ({
|
|
1102
1540
|
schema: validSchema,
|
|
1103
1541
|
locale,
|
|
@@ -1137,7 +1575,7 @@ function FormProvider({
|
|
|
1137
1575
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FormContext.Provider, { value: contextValue, children });
|
|
1138
1576
|
}
|
|
1139
1577
|
function useForm() {
|
|
1140
|
-
const context = (0,
|
|
1578
|
+
const context = (0, import_react3.useContext)(FormContext);
|
|
1141
1579
|
if (context === null) throw new Error("useForm must be called inside a FormProvider.");
|
|
1142
1580
|
return context;
|
|
1143
1581
|
}
|
|
@@ -1145,13 +1583,13 @@ function useField(fieldId) {
|
|
|
1145
1583
|
const form = useForm();
|
|
1146
1584
|
const field = form.schema.fields.find((item) => item.id === fieldId);
|
|
1147
1585
|
if (field === void 0) throw new Error(`Unknown form field: ${fieldId}`);
|
|
1148
|
-
const setValue = (0,
|
|
1586
|
+
const setValue = (0, import_react3.useCallback)((value) => form.setValue(fieldId, value), [fieldId, form]);
|
|
1149
1587
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
1150
1588
|
}
|
|
1151
1589
|
|
|
1152
1590
|
// src/renderer.tsx
|
|
1153
|
-
var
|
|
1154
|
-
var
|
|
1591
|
+
var import_core4 = require("@form-engine-ts/core");
|
|
1592
|
+
var import_react4 = require("react");
|
|
1155
1593
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1156
1594
|
function describedBy(field, error, helpId, errorId) {
|
|
1157
1595
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
@@ -1354,36 +1792,39 @@ function parseDraft(serialized) {
|
|
|
1354
1792
|
return null;
|
|
1355
1793
|
}
|
|
1356
1794
|
}
|
|
1357
|
-
function
|
|
1795
|
+
function ContextFormRenderer({
|
|
1358
1796
|
components = {},
|
|
1359
1797
|
className = "",
|
|
1360
1798
|
successMessageKey,
|
|
1361
1799
|
errorMessageKey,
|
|
1362
|
-
autoSaveKey
|
|
1800
|
+
autoSaveKey,
|
|
1801
|
+
beforeSubmit,
|
|
1802
|
+
onDraftSave,
|
|
1803
|
+
slots = {}
|
|
1363
1804
|
}) {
|
|
1364
1805
|
const form = useForm();
|
|
1365
|
-
const prefix = (0,
|
|
1366
|
-
const formRef = (0,
|
|
1367
|
-
const loadedDraftKey = (0,
|
|
1368
|
-
const [draftRestored, setDraftRestored] = (0,
|
|
1369
|
-
const [currentPageIndex, setCurrentPageIndex] = (0,
|
|
1370
|
-
const [focusFieldId, setFocusFieldId] = (0,
|
|
1806
|
+
const prefix = (0, import_react4.useId)().replace(/:/g, "");
|
|
1807
|
+
const formRef = (0, import_react4.useRef)(null);
|
|
1808
|
+
const loadedDraftKey = (0, import_react4.useRef)(null);
|
|
1809
|
+
const [draftRestored, setDraftRestored] = (0, import_react4.useState)(false);
|
|
1810
|
+
const [currentPageIndex, setCurrentPageIndex] = (0, import_react4.useState)(0);
|
|
1811
|
+
const [focusFieldId, setFocusFieldId] = (0, import_react4.useState)(null);
|
|
1371
1812
|
const pages = form.schema.pages;
|
|
1372
|
-
const visiblePageIndexes = (0,
|
|
1813
|
+
const visiblePageIndexes = (0, import_react4.useMemo)(
|
|
1373
1814
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
1374
1815
|
[form.pageVisibility, pages]
|
|
1375
1816
|
);
|
|
1376
1817
|
const activePage = pages?.[currentPageIndex];
|
|
1377
1818
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
1378
1819
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
1379
|
-
(0,
|
|
1820
|
+
(0, import_react4.useEffect)(() => {
|
|
1380
1821
|
if (pages === void 0 || visiblePageIndexes.length === 0) {
|
|
1381
1822
|
setCurrentPageIndex(0);
|
|
1382
1823
|
return;
|
|
1383
1824
|
}
|
|
1384
1825
|
if (!visiblePageIndexes.includes(currentPageIndex)) setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
1385
1826
|
}, [currentPageIndex, pages, visiblePageIndexes]);
|
|
1386
|
-
(0,
|
|
1827
|
+
(0, import_react4.useEffect)(() => {
|
|
1387
1828
|
if (focusFieldId === null) return;
|
|
1388
1829
|
const fieldContainer = [...formRef.current?.querySelectorAll("[data-field-id]") ?? []].find(
|
|
1389
1830
|
(element) => element.dataset.fieldId === focusFieldId
|
|
@@ -1394,7 +1835,7 @@ function FormRenderer({
|
|
|
1394
1835
|
setFocusFieldId(null);
|
|
1395
1836
|
}
|
|
1396
1837
|
}, [focusFieldId]);
|
|
1397
|
-
(0,
|
|
1838
|
+
(0, import_react4.useEffect)(() => {
|
|
1398
1839
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1399
1840
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
1400
1841
|
if (loadedDraftKey.current === loadIdentity) return;
|
|
@@ -1406,10 +1847,11 @@ function FormRenderer({
|
|
|
1406
1847
|
form.restoreValues(draft.values);
|
|
1407
1848
|
setDraftRestored(true);
|
|
1408
1849
|
}, [autoSaveKey, form.restoreValues, form.schema.id, form.schema.version]);
|
|
1409
|
-
(0,
|
|
1410
|
-
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1850
|
+
(0, import_react4.useEffect)(() => {
|
|
1411
1851
|
if (form.submitStatus === "success") return;
|
|
1412
1852
|
const timeout = globalThis.setTimeout(() => {
|
|
1853
|
+
onDraftSave?.(form.values);
|
|
1854
|
+
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1413
1855
|
const draft = {
|
|
1414
1856
|
formId: form.schema.id,
|
|
1415
1857
|
formVersion: form.schema.version,
|
|
@@ -1419,7 +1861,7 @@ function FormRenderer({
|
|
|
1419
1861
|
globalThis.localStorage.setItem(autoSaveKey, JSON.stringify(draft));
|
|
1420
1862
|
}, 500);
|
|
1421
1863
|
return () => globalThis.clearTimeout(timeout);
|
|
1422
|
-
}, [autoSaveKey, form.schema.id, form.schema.version, form.submitStatus, form.values]);
|
|
1864
|
+
}, [autoSaveKey, form.schema.id, form.schema.version, form.submitStatus, form.values, onDraftSave]);
|
|
1423
1865
|
const focusFirstIssue = (fieldId) => {
|
|
1424
1866
|
if (fieldId !== void 0) setFocusFieldId(fieldId);
|
|
1425
1867
|
};
|
|
@@ -1432,27 +1874,39 @@ function FormRenderer({
|
|
|
1432
1874
|
const nextPageIndex = visiblePageIndexes[activeVisibleIndex + 1];
|
|
1433
1875
|
if (nextPageIndex !== void 0) setCurrentPageIndex(nextPageIndex);
|
|
1434
1876
|
};
|
|
1435
|
-
const
|
|
1436
|
-
|
|
1437
|
-
const validation = (0, import_core3.validateAnswers)(form.schema, form.values);
|
|
1877
|
+
const submitValues = async () => {
|
|
1878
|
+
const validation = (0, import_core4.validateAnswers)(form.schema, form.values);
|
|
1438
1879
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
1439
|
-
const
|
|
1440
|
-
if (
|
|
1880
|
+
const result = await form.submit(beforeSubmit);
|
|
1881
|
+
if (result.status === "invalid") {
|
|
1441
1882
|
const invalidPageIndex = pages?.findIndex(
|
|
1442
1883
|
(page) => firstInvalidFieldId === void 0 ? false : page.questionIds.includes(firstInvalidFieldId)
|
|
1443
1884
|
);
|
|
1444
1885
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
1445
1886
|
focusFirstIssue(firstInvalidFieldId);
|
|
1446
|
-
return;
|
|
1887
|
+
return result;
|
|
1447
1888
|
}
|
|
1889
|
+
if (result.status !== "success") return result;
|
|
1448
1890
|
if (autoSaveKey !== void 0 && typeof globalThis.localStorage !== "undefined") {
|
|
1449
1891
|
globalThis.localStorage.removeItem(autoSaveKey);
|
|
1450
1892
|
setDraftRestored(false);
|
|
1451
1893
|
}
|
|
1452
1894
|
setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
1895
|
+
return result;
|
|
1896
|
+
};
|
|
1897
|
+
const handleSubmit = (event) => {
|
|
1898
|
+
event.preventDefault();
|
|
1899
|
+
void submitValues();
|
|
1453
1900
|
};
|
|
1901
|
+
const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
|
|
1902
|
+
const canPrev = pages !== void 0 && activeVisibleIndex > 0;
|
|
1903
|
+
const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
|
|
1904
|
+
const renderSubmitButton = () => slots.renderSubmitButton?.({ isSubmitting: form.isSubmitting, onSubmit: () => void submitValues() }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
|
|
1454
1905
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
|
|
1455
|
-
|
|
1906
|
+
slots.renderHeader?.({
|
|
1907
|
+
title: form.schema.title,
|
|
1908
|
+
...form.schema.description === void 0 ? {} : { description: form.schema.description }
|
|
1909
|
+
}) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "fe-header", children: [
|
|
1456
1910
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { children: form.schema.title }),
|
|
1457
1911
|
form.schema.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: form.schema.description }),
|
|
1458
1912
|
pages === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-progress", children: [
|
|
@@ -1477,45 +1931,131 @@ function FormRenderer({
|
|
|
1477
1931
|
] }),
|
|
1478
1932
|
draftRestored ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
|
|
1479
1933
|
] }),
|
|
1480
|
-
activePage
|
|
1481
|
-
|
|
1934
|
+
activePage === void 0 ? null : slots.renderPageHeader?.({
|
|
1935
|
+
page: activePage,
|
|
1936
|
+
pageIndex: activeVisibleIndex,
|
|
1937
|
+
totalPages: visiblePageIndexes.length
|
|
1938
|
+
}) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-page-header", children: [
|
|
1939
|
+
activePage.title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "fe-page-title", children: activePage.title }),
|
|
1940
|
+
activePage.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fe-page-description", children: activePage.description })
|
|
1941
|
+
] }),
|
|
1482
1942
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fe-fields", children: form.schema.fields.filter((field) => form.visibility[field.id] === true && (fieldIds === void 0 || fieldIds.has(field.id))).map((field) => {
|
|
1943
|
+
const error = form.errors[field.id];
|
|
1483
1944
|
const props = {
|
|
1484
1945
|
field,
|
|
1485
1946
|
value: form.values[field.id],
|
|
1486
|
-
error
|
|
1947
|
+
error,
|
|
1487
1948
|
setValue: (value) => form.setValue(field.id, value),
|
|
1488
1949
|
translate: form.translate,
|
|
1489
1950
|
inputId: `${prefix}-${field.id}`,
|
|
1490
1951
|
errorId: `${prefix}-${field.id}-error`,
|
|
1491
1952
|
helpId: `${prefix}-${field.id}-help`
|
|
1492
1953
|
};
|
|
1954
|
+
if (slots.renderField !== void 0) {
|
|
1955
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Fragment, { children: slots.renderField({
|
|
1956
|
+
question: field,
|
|
1957
|
+
value: form.values[field.id],
|
|
1958
|
+
onChange: (value) => {
|
|
1959
|
+
if (isFormValue(value)) form.setValue(field.id, value);
|
|
1960
|
+
},
|
|
1961
|
+
...error === void 0 ? {} : { error }
|
|
1962
|
+
}) }, field.id);
|
|
1963
|
+
}
|
|
1493
1964
|
const Component = components[field.type];
|
|
1494
1965
|
return Component === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultField, { ...props }, field.id) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props }, field.id);
|
|
1495
1966
|
}) }),
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1967
|
+
validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-validation-summary", role: "alert", children: [
|
|
1968
|
+
validationIssues.length,
|
|
1969
|
+
" validation error",
|
|
1970
|
+
validationIssues.length === 1 ? "" : "s",
|
|
1971
|
+
"."
|
|
1972
|
+
] }),
|
|
1973
|
+
pages === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1974
|
+
slots.renderNavigation?.({
|
|
1975
|
+
currentPage: 0,
|
|
1976
|
+
totalPages: 1,
|
|
1977
|
+
canPrev: false,
|
|
1978
|
+
canNext: false,
|
|
1979
|
+
onPrev: () => void 0,
|
|
1980
|
+
onNext: () => void 0
|
|
1981
|
+
}),
|
|
1982
|
+
renderSubmitButton()
|
|
1983
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "form-step-navigation", children: [
|
|
1984
|
+
slots.renderNavigation?.({
|
|
1985
|
+
currentPage: activeVisibleIndex,
|
|
1986
|
+
totalPages: visiblePageIndexes.length,
|
|
1987
|
+
canPrev,
|
|
1988
|
+
canNext,
|
|
1989
|
+
onPrev: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
1990
|
+
onNext: handleNext
|
|
1991
|
+
}) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1992
|
+
canPrev ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1993
|
+
"button",
|
|
1994
|
+
{
|
|
1995
|
+
className: "btn-prev",
|
|
1996
|
+
type: "button",
|
|
1997
|
+
onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
1998
|
+
children: form.translate("form.back")
|
|
1999
|
+
}
|
|
2000
|
+
) : null,
|
|
2001
|
+
canNext ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "btn-next", type: "button", onClick: handleNext, children: form.translate("form.next") }) : null
|
|
2002
|
+
] }),
|
|
2003
|
+
canNext ? null : renderSubmitButton()
|
|
1507
2004
|
] }),
|
|
1508
2005
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
1509
|
-
form.submitStatus === "success"
|
|
1510
|
-
|
|
2006
|
+
form.submitStatus === "success" ? slots.renderCompletion?.({
|
|
2007
|
+
message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
|
|
2008
|
+
}) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
|
|
2009
|
+
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (errorMessageKey === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
|
|
1511
2010
|
] })
|
|
1512
2011
|
] });
|
|
1513
2012
|
}
|
|
2013
|
+
var RENDERER_MESSAGES = {
|
|
2014
|
+
"form.submit": "Submit",
|
|
2015
|
+
"form.back": "Back",
|
|
2016
|
+
"form.next": "Next",
|
|
2017
|
+
"form.step": "Step {{current}} / {{total}}",
|
|
2018
|
+
"form.draftRestored": "Draft restored",
|
|
2019
|
+
"validation.required": "This field is required."
|
|
2020
|
+
};
|
|
2021
|
+
var defaultRendererTranslator = {
|
|
2022
|
+
translate(key, _locale, params = {}) {
|
|
2023
|
+
return (RENDERER_MESSAGES[key] ?? key).replace(
|
|
2024
|
+
/\{\{(\w+)\}\}/g,
|
|
2025
|
+
(token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
function FormRenderer(props) {
|
|
2030
|
+
if (!("schema" in props)) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ContextFormRenderer, { ...props });
|
|
2031
|
+
const {
|
|
2032
|
+
schema,
|
|
2033
|
+
locale = schema.defaultLocale ?? "en",
|
|
2034
|
+
translator = defaultRendererTranslator,
|
|
2035
|
+
initialValues,
|
|
2036
|
+
resetOnSuccess,
|
|
2037
|
+
onSubmit,
|
|
2038
|
+
...rendererProps
|
|
2039
|
+
} = props;
|
|
2040
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2041
|
+
FormProvider,
|
|
2042
|
+
{
|
|
2043
|
+
schema,
|
|
2044
|
+
locale,
|
|
2045
|
+
translator,
|
|
2046
|
+
onSubmit,
|
|
2047
|
+
...initialValues === void 0 ? {} : { initialValues },
|
|
2048
|
+
...resetOnSuccess === void 0 ? {} : { resetOnSuccess },
|
|
2049
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ContextFormRenderer, { ...rendererProps })
|
|
2050
|
+
}
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
1514
2053
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1515
2054
|
0 && (module.exports = {
|
|
1516
2055
|
FormBuilder,
|
|
1517
2056
|
FormProvider,
|
|
1518
2057
|
FormRenderer,
|
|
1519
2058
|
useField,
|
|
1520
|
-
useForm
|
|
2059
|
+
useForm,
|
|
2060
|
+
useFormBuilder
|
|
1521
2061
|
});
|