@form-engine-ts/react 1.0.0 → 1.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 +12 -0
- package/dist/index.cjs +723 -42
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +719 -32
- package/dist/styles.css +77 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// src/builder.tsx
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
populateSchemaTranslations,
|
|
4
|
+
sanitizeSchema
|
|
5
|
+
} from "@form-engine-ts/core";
|
|
6
|
+
import { useState } from "react";
|
|
3
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
8
|
var FIELD_TYPES = [
|
|
5
9
|
"text",
|
|
@@ -37,6 +41,22 @@ var BUILDER_DEFAULTS = {
|
|
|
37
41
|
"builder.conditionTrue": "true",
|
|
38
42
|
"builder.conditionFalse": "false",
|
|
39
43
|
"builder.addQuestion": "Add question",
|
|
44
|
+
"builder.pages": "Page manager",
|
|
45
|
+
"builder.enablePages": "Enable multi-step pages",
|
|
46
|
+
"builder.addPage": "Add page",
|
|
47
|
+
"builder.newPage": "New page",
|
|
48
|
+
"builder.pageTitle": "Page title",
|
|
49
|
+
"builder.pageDescription": "Page description",
|
|
50
|
+
"builder.pageQuestion": "Question to move to the new page",
|
|
51
|
+
"builder.questionPage": "Page",
|
|
52
|
+
"builder.pageCondition": "Page display condition",
|
|
53
|
+
"builder.localization": "Localization",
|
|
54
|
+
"builder.defaultLocale": "Default locale",
|
|
55
|
+
"builder.supportedLocales": "Supported locales",
|
|
56
|
+
"builder.addLocale": "Add locale",
|
|
57
|
+
"builder.editLocale": "Edit locale",
|
|
58
|
+
"builder.autoTranslate": "Translate all text",
|
|
59
|
+
"builder.translationUnavailable": "Provide an async translation adapter to enable automatic translation.",
|
|
40
60
|
"builder.fieldType.text": "Text",
|
|
41
61
|
"builder.fieldType.textarea": "Textarea",
|
|
42
62
|
"builder.fieldType.number": "Number",
|
|
@@ -109,14 +129,21 @@ function withoutDisplayCondition(field) {
|
|
|
109
129
|
function sanitizeBuilderSchema(schema) {
|
|
110
130
|
const sanitized = sanitizeSchema(schema);
|
|
111
131
|
const indexById = new Map(sanitized.fields.map((field, index) => [field.id, index]));
|
|
132
|
+
const fields = sanitized.fields.map((field, index) => {
|
|
133
|
+
const sourceId = field.displayCondition?.questionId;
|
|
134
|
+
if (sourceId === void 0) return field;
|
|
135
|
+
const sourceIndex = indexById.get(sourceId);
|
|
136
|
+
return sourceIndex !== void 0 && sourceIndex < index ? field : withoutDisplayCondition(field);
|
|
137
|
+
});
|
|
112
138
|
return {
|
|
113
139
|
...sanitized,
|
|
114
|
-
fields
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
fields,
|
|
141
|
+
...sanitized.pages === void 0 ? {} : {
|
|
142
|
+
pages: sanitized.pages.map((page) => ({
|
|
143
|
+
...page,
|
|
144
|
+
questionIds: fields.filter((field) => page.questionIds.includes(field.id)).map((field) => field.id)
|
|
145
|
+
}))
|
|
146
|
+
}
|
|
120
147
|
};
|
|
121
148
|
}
|
|
122
149
|
function conditionWithValue(questionId, operator, value) {
|
|
@@ -160,7 +187,12 @@ function ConditionValueEditor({
|
|
|
160
187
|
}
|
|
161
188
|
);
|
|
162
189
|
}
|
|
163
|
-
function FormBuilder({ schema, onChange, locale = "en", translator }) {
|
|
190
|
+
function FormBuilder({ schema, onChange, locale = "en", translator, translationAdapter }) {
|
|
191
|
+
const [newPageQuestionId, setNewPageQuestionId] = useState("");
|
|
192
|
+
const [newLocale, setNewLocale] = useState("");
|
|
193
|
+
const [editingLocale, setEditingLocale] = useState("");
|
|
194
|
+
const [isTranslating, setIsTranslating] = useState(false);
|
|
195
|
+
const [translationError, setTranslationError] = useState(null);
|
|
164
196
|
const translate = (key, params = {}) => {
|
|
165
197
|
const translated = translator?.translate(key, locale, params);
|
|
166
198
|
return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
|
|
@@ -201,12 +233,405 @@ function FormBuilder({ schema, onChange, locale = "en", translator }) {
|
|
|
201
233
|
};
|
|
202
234
|
const addField = () => {
|
|
203
235
|
const id = createUniqueId("q", new Set(schema.fields.map((field) => field.id)));
|
|
204
|
-
|
|
236
|
+
const nextSchema = {
|
|
205
237
|
...schema,
|
|
206
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
|
+
};
|
|
249
|
+
const enablePages = () => {
|
|
250
|
+
if (schema.pages !== void 0) return;
|
|
251
|
+
emitSchema({
|
|
252
|
+
...schema,
|
|
253
|
+
pages: [
|
|
254
|
+
{
|
|
255
|
+
id: createUniqueId("page", /* @__PURE__ */ new Set()),
|
|
256
|
+
title: translate("builder.newPage"),
|
|
257
|
+
questionIds: schema.fields.map((field) => field.id)
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
const pageForField = (fieldId) => schema.pages?.find((page) => page.questionIds.includes(fieldId));
|
|
263
|
+
const movablePageQuestions = schema.pages?.flatMap((page) => page.questionIds.length > 1 ? page.questionIds : []) ?? [];
|
|
264
|
+
const addPage = () => {
|
|
265
|
+
if (schema.pages === void 0) {
|
|
266
|
+
enablePages();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const questionId = movablePageQuestions.includes(newPageQuestionId) ? newPageQuestionId : movablePageQuestions[0];
|
|
270
|
+
if (questionId === void 0) return;
|
|
271
|
+
const pageId = createUniqueId("page", new Set(schema.pages.map((page) => page.id)));
|
|
272
|
+
emitSchema({
|
|
273
|
+
...schema,
|
|
274
|
+
pages: [
|
|
275
|
+
...schema.pages.map((page) => ({
|
|
276
|
+
...page,
|
|
277
|
+
questionIds: page.questionIds.filter((id) => id !== questionId)
|
|
278
|
+
})),
|
|
279
|
+
{ id: pageId, title: translate("builder.newPage"), questionIds: [questionId] }
|
|
280
|
+
]
|
|
281
|
+
});
|
|
282
|
+
setNewPageQuestionId("");
|
|
283
|
+
};
|
|
284
|
+
const removePage = (pageIndex) => {
|
|
285
|
+
if (schema.pages === void 0) return;
|
|
286
|
+
const removed = schema.pages[pageIndex];
|
|
287
|
+
if (removed === void 0) return;
|
|
288
|
+
if (schema.pages.length === 1) {
|
|
289
|
+
const { pages: _pages, ...singlePage } = schema;
|
|
290
|
+
emitSchema(singlePage);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const targetIndex = pageIndex === 0 ? 1 : pageIndex - 1;
|
|
294
|
+
emitSchema({
|
|
295
|
+
...schema,
|
|
296
|
+
pages: schema.pages.map(
|
|
297
|
+
(page, index) => index === targetIndex ? { ...page, questionIds: [...page.questionIds, ...removed.questionIds] } : page
|
|
298
|
+
).filter((_page, index) => index !== pageIndex)
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
const movePage = (pageIndex, offset) => {
|
|
302
|
+
if (schema.pages === void 0) return;
|
|
303
|
+
const target = pageIndex + offset;
|
|
304
|
+
if (target < 0 || target >= schema.pages.length) return;
|
|
305
|
+
const pages = [...schema.pages];
|
|
306
|
+
const current = pages[pageIndex];
|
|
307
|
+
const other = pages[target];
|
|
308
|
+
if (current === void 0 || other === void 0) return;
|
|
309
|
+
pages[pageIndex] = other;
|
|
310
|
+
pages[target] = current;
|
|
311
|
+
emitSchema({ ...schema, pages });
|
|
312
|
+
};
|
|
313
|
+
const updatePage = (pageId, update) => {
|
|
314
|
+
if (schema.pages === void 0) return;
|
|
315
|
+
emitSchema({ ...schema, pages: schema.pages.map((page) => page.id === pageId ? update(page) : page) });
|
|
316
|
+
};
|
|
317
|
+
const assignFieldToPage = (fieldId, pageId) => {
|
|
318
|
+
if (schema.pages === void 0) return;
|
|
319
|
+
emitSchema({
|
|
320
|
+
...schema,
|
|
321
|
+
pages: schema.pages.map((page) => ({
|
|
322
|
+
...page,
|
|
323
|
+
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)
|
|
324
|
+
})).filter((page) => page.questionIds.length > 0)
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
const addLocale = () => {
|
|
328
|
+
const normalized = newLocale.trim();
|
|
329
|
+
if (normalized.length === 0) return;
|
|
330
|
+
const supportedLocales = [
|
|
331
|
+
.../* @__PURE__ */ new Set([
|
|
332
|
+
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
333
|
+
...schema.supportedLocales ?? [],
|
|
334
|
+
normalized
|
|
335
|
+
])
|
|
336
|
+
];
|
|
337
|
+
emitSchema({ ...schema, supportedLocales });
|
|
338
|
+
setEditingLocale(normalized);
|
|
339
|
+
setNewLocale("");
|
|
340
|
+
};
|
|
341
|
+
const translateAll = async () => {
|
|
342
|
+
if (translationAdapter === void 0 || editingLocale.length === 0) return;
|
|
343
|
+
setIsTranslating(true);
|
|
344
|
+
setTranslationError(null);
|
|
345
|
+
try {
|
|
346
|
+
onChange(await populateSchemaTranslations(schema, [editingLocale], translationAdapter));
|
|
347
|
+
} catch (cause) {
|
|
348
|
+
setTranslationError(cause instanceof Error ? cause.message : String(cause));
|
|
349
|
+
} finally {
|
|
350
|
+
setIsTranslating(false);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const updateFormTranslation = (key, value) => {
|
|
354
|
+
if (editingLocale.length === 0) return;
|
|
355
|
+
const current = schema.translations?.[editingLocale];
|
|
356
|
+
const next = key === "title" ? value.length === 0 ? { description: current?.description } : { ...current, title: value } : value.length === 0 ? { title: current?.title } : { ...current, description: value };
|
|
357
|
+
emitSchema({
|
|
358
|
+
...schema,
|
|
359
|
+
translations: {
|
|
360
|
+
...schema.translations,
|
|
361
|
+
[editingLocale]: {
|
|
362
|
+
...next.title === void 0 ? {} : { title: next.title },
|
|
363
|
+
...next.description === void 0 ? {} : { description: next.description }
|
|
364
|
+
}
|
|
365
|
+
}
|
|
207
366
|
});
|
|
208
367
|
};
|
|
209
368
|
return /* @__PURE__ */ jsxs("section", { className: "form-engine-builder", "aria-label": translate("builder.formBuilder"), children: [
|
|
369
|
+
/* @__PURE__ */ jsxs("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
|
|
370
|
+
/* @__PURE__ */ jsx("h2", { id: "builder-pages-heading", children: translate("builder.pages") }),
|
|
371
|
+
schema.pages === void 0 ? /* @__PURE__ */ jsx("button", { type: "button", onClick: enablePages, children: translate("builder.enablePages") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
372
|
+
schema.pages.map((page, pageIndex) => {
|
|
373
|
+
const priorQuestionIds = new Set(schema.pages?.slice(0, pageIndex).flatMap((item) => item.questionIds));
|
|
374
|
+
const availableSources = schema.fields.filter((field) => priorQuestionIds.has(field.id));
|
|
375
|
+
const source = schema.fields.find((field) => field.id === page.displayCondition?.questionId);
|
|
376
|
+
return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__page", children: [
|
|
377
|
+
/* @__PURE__ */ jsx("legend", { children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }),
|
|
378
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__toolbar", children: [
|
|
379
|
+
/* @__PURE__ */ jsx(
|
|
380
|
+
"button",
|
|
381
|
+
{
|
|
382
|
+
type: "button",
|
|
383
|
+
disabled: pageIndex === 0,
|
|
384
|
+
"aria-label": translate("builder.moveUp", { title: page.title ?? page.id }),
|
|
385
|
+
onClick: () => movePage(pageIndex, -1),
|
|
386
|
+
children: "\u2191"
|
|
387
|
+
}
|
|
388
|
+
),
|
|
389
|
+
/* @__PURE__ */ jsx(
|
|
390
|
+
"button",
|
|
391
|
+
{
|
|
392
|
+
type: "button",
|
|
393
|
+
disabled: pageIndex === (schema.pages?.length ?? 0) - 1,
|
|
394
|
+
"aria-label": translate("builder.moveDown", { title: page.title ?? page.id }),
|
|
395
|
+
onClick: () => movePage(pageIndex, 1),
|
|
396
|
+
children: "\u2193"
|
|
397
|
+
}
|
|
398
|
+
),
|
|
399
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => removePage(pageIndex), children: translate("builder.deleteAction") })
|
|
400
|
+
] }),
|
|
401
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
402
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
403
|
+
translate("builder.pageTitle"),
|
|
404
|
+
/* @__PURE__ */ jsx(
|
|
405
|
+
"input",
|
|
406
|
+
{
|
|
407
|
+
value: page.title ?? "",
|
|
408
|
+
onChange: (event) => {
|
|
409
|
+
const value = event.currentTarget.value;
|
|
410
|
+
updatePage(page.id, (current) => {
|
|
411
|
+
if (value.length > 0) return { ...current, title: value };
|
|
412
|
+
const { title: _title, ...withoutTitle } = current;
|
|
413
|
+
return withoutTitle;
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
] }),
|
|
419
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
420
|
+
translate("builder.pageDescription"),
|
|
421
|
+
/* @__PURE__ */ jsx(
|
|
422
|
+
"input",
|
|
423
|
+
{
|
|
424
|
+
value: page.description ?? "",
|
|
425
|
+
onChange: (event) => {
|
|
426
|
+
const value = event.currentTarget.value;
|
|
427
|
+
updatePage(page.id, (current) => {
|
|
428
|
+
if (value.length > 0) return { ...current, description: value };
|
|
429
|
+
const { description: _description, ...withoutDescription } = current;
|
|
430
|
+
return withoutDescription;
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
] })
|
|
436
|
+
] }),
|
|
437
|
+
editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
|
|
438
|
+
/* @__PURE__ */ jsx("strong", { children: editingLocale }),
|
|
439
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
440
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
441
|
+
translate("builder.pageTitle"),
|
|
442
|
+
/* @__PURE__ */ jsx(
|
|
443
|
+
"input",
|
|
444
|
+
{
|
|
445
|
+
value: page.translations?.[editingLocale]?.title ?? "",
|
|
446
|
+
onChange: (event) => {
|
|
447
|
+
const value = event.currentTarget.value;
|
|
448
|
+
updatePage(page.id, (current) => ({
|
|
449
|
+
...current,
|
|
450
|
+
translations: {
|
|
451
|
+
...current.translations,
|
|
452
|
+
[editingLocale]: {
|
|
453
|
+
...value.length === 0 ? {} : { title: value },
|
|
454
|
+
...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
] }),
|
|
462
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
463
|
+
translate("builder.pageDescription"),
|
|
464
|
+
/* @__PURE__ */ jsx(
|
|
465
|
+
"input",
|
|
466
|
+
{
|
|
467
|
+
value: page.translations?.[editingLocale]?.description ?? "",
|
|
468
|
+
onChange: (event) => {
|
|
469
|
+
const value = event.currentTarget.value;
|
|
470
|
+
updatePage(page.id, (current) => ({
|
|
471
|
+
...current,
|
|
472
|
+
translations: {
|
|
473
|
+
...current.translations,
|
|
474
|
+
[editingLocale]: {
|
|
475
|
+
...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
|
|
476
|
+
...value.length === 0 ? {} : { description: value }
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
] })
|
|
484
|
+
] })
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
|
|
487
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
488
|
+
translate("builder.pageCondition"),
|
|
489
|
+
/* @__PURE__ */ jsxs(
|
|
490
|
+
"select",
|
|
491
|
+
{
|
|
492
|
+
value: page.displayCondition?.questionId ?? "",
|
|
493
|
+
onChange: (event) => {
|
|
494
|
+
const selected = schema.fields.find((field) => field.id === event.currentTarget.value);
|
|
495
|
+
updatePage(page.id, (current) => {
|
|
496
|
+
if (selected === void 0) {
|
|
497
|
+
const { displayCondition: _condition, ...withoutCondition } = current;
|
|
498
|
+
return withoutCondition;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
...current,
|
|
502
|
+
displayCondition: conditionWithValue(
|
|
503
|
+
selected.id,
|
|
504
|
+
conditionOperators(selected)[0] ?? "not_empty",
|
|
505
|
+
defaultConditionValue(selected)
|
|
506
|
+
)
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
},
|
|
510
|
+
children: [
|
|
511
|
+
/* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
|
|
512
|
+
availableSources.map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
)
|
|
516
|
+
] }),
|
|
517
|
+
page.displayCondition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
518
|
+
/* @__PURE__ */ jsx(
|
|
519
|
+
"select",
|
|
520
|
+
{
|
|
521
|
+
"aria-label": translate("builder.conditionOperator"),
|
|
522
|
+
value: page.displayCondition.operator,
|
|
523
|
+
onChange: (event) => {
|
|
524
|
+
const operator = event.currentTarget.value;
|
|
525
|
+
updatePage(page.id, (current) => ({
|
|
526
|
+
...current,
|
|
527
|
+
displayCondition: conditionWithValue(source.id, operator, defaultConditionValue(source))
|
|
528
|
+
}));
|
|
529
|
+
},
|
|
530
|
+
children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
|
|
531
|
+
}
|
|
532
|
+
),
|
|
533
|
+
/* @__PURE__ */ jsx(
|
|
534
|
+
ConditionValueEditor,
|
|
535
|
+
{
|
|
536
|
+
source,
|
|
537
|
+
condition: page.displayCondition,
|
|
538
|
+
onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
|
|
539
|
+
translate
|
|
540
|
+
}
|
|
541
|
+
)
|
|
542
|
+
] }) : null
|
|
543
|
+
] })
|
|
544
|
+
] }, page.id);
|
|
545
|
+
}),
|
|
546
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__page-add", children: [
|
|
547
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
548
|
+
translate("builder.pageQuestion"),
|
|
549
|
+
/* @__PURE__ */ jsxs(
|
|
550
|
+
"select",
|
|
551
|
+
{
|
|
552
|
+
value: newPageQuestionId,
|
|
553
|
+
disabled: movablePageQuestions.length === 0,
|
|
554
|
+
onChange: (event) => setNewPageQuestionId(event.currentTarget.value),
|
|
555
|
+
children: [
|
|
556
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
|
|
557
|
+
schema.fields.filter((field) => movablePageQuestions.includes(field.id)).map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
)
|
|
561
|
+
] }),
|
|
562
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: movablePageQuestions.length === 0, onClick: addPage, children: translate("builder.addPage") })
|
|
563
|
+
] })
|
|
564
|
+
] })
|
|
565
|
+
] }),
|
|
566
|
+
/* @__PURE__ */ jsxs("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
|
|
567
|
+
/* @__PURE__ */ jsx("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
|
|
568
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
569
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
570
|
+
translate("builder.defaultLocale"),
|
|
571
|
+
/* @__PURE__ */ jsx(
|
|
572
|
+
"input",
|
|
573
|
+
{
|
|
574
|
+
value: schema.defaultLocale ?? "",
|
|
575
|
+
onChange: (event) => {
|
|
576
|
+
const value = event.currentTarget.value.trim();
|
|
577
|
+
emitSchema(
|
|
578
|
+
value.length === 0 ? schema : {
|
|
579
|
+
...schema,
|
|
580
|
+
defaultLocale: value,
|
|
581
|
+
supportedLocales: [.../* @__PURE__ */ new Set([value, ...schema.supportedLocales ?? []])]
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
)
|
|
587
|
+
] }),
|
|
588
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
589
|
+
translate("builder.addLocale"),
|
|
590
|
+
/* @__PURE__ */ jsx("input", { value: newLocale, onChange: (event) => setNewLocale(event.currentTarget.value) })
|
|
591
|
+
] }),
|
|
592
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: addLocale, children: translate("builder.addLocale") }),
|
|
593
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
594
|
+
translate("builder.editLocale"),
|
|
595
|
+
/* @__PURE__ */ jsxs("select", { value: editingLocale, onChange: (event) => setEditingLocale(event.currentTarget.value), children: [
|
|
596
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
|
|
597
|
+
(schema.supportedLocales ?? []).filter((item) => item !== schema.defaultLocale).map((item) => /* @__PURE__ */ jsx("option", { value: item, children: item }, item))
|
|
598
|
+
] })
|
|
599
|
+
] }),
|
|
600
|
+
/* @__PURE__ */ jsx(
|
|
601
|
+
"button",
|
|
602
|
+
{
|
|
603
|
+
type: "button",
|
|
604
|
+
disabled: translationAdapter === void 0 || editingLocale.length === 0 || isTranslating,
|
|
605
|
+
onClick: () => void translateAll(),
|
|
606
|
+
children: translate("builder.autoTranslate")
|
|
607
|
+
}
|
|
608
|
+
)
|
|
609
|
+
] }),
|
|
610
|
+
translationAdapter === void 0 ? /* @__PURE__ */ jsx("p", { children: translate("builder.translationUnavailable") }) : null,
|
|
611
|
+
translationError === null ? null : /* @__PURE__ */ jsx("p", { className: "form-engine-builder__error", children: translationError }),
|
|
612
|
+
editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
613
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
614
|
+
translate("builder.questionTitle"),
|
|
615
|
+
/* @__PURE__ */ jsx(
|
|
616
|
+
"input",
|
|
617
|
+
{
|
|
618
|
+
value: schema.translations?.[editingLocale]?.title ?? "",
|
|
619
|
+
onChange: (event) => updateFormTranslation("title", event.currentTarget.value)
|
|
620
|
+
}
|
|
621
|
+
)
|
|
622
|
+
] }),
|
|
623
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
624
|
+
translate("builder.pageDescription"),
|
|
625
|
+
/* @__PURE__ */ jsx(
|
|
626
|
+
"input",
|
|
627
|
+
{
|
|
628
|
+
value: schema.translations?.[editingLocale]?.description ?? "",
|
|
629
|
+
onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
] })
|
|
633
|
+
] })
|
|
634
|
+
] }),
|
|
210
635
|
/* @__PURE__ */ jsx("div", { className: "form-engine-builder__list", children: schema.fields.map((field, index) => {
|
|
211
636
|
const condition = field.displayCondition;
|
|
212
637
|
const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
|
|
@@ -283,6 +708,98 @@ function FormBuilder({ schema, onChange, locale = "en", translator }) {
|
|
|
283
708
|
translate("builder.required")
|
|
284
709
|
] })
|
|
285
710
|
] }),
|
|
711
|
+
schema.pages === void 0 ? null : /* @__PURE__ */ jsxs("label", { children: [
|
|
712
|
+
translate("builder.questionPage"),
|
|
713
|
+
/* @__PURE__ */ jsx(
|
|
714
|
+
"select",
|
|
715
|
+
{
|
|
716
|
+
value: pageForField(field.id)?.id ?? "",
|
|
717
|
+
onChange: (event) => assignFieldToPage(field.id, event.currentTarget.value),
|
|
718
|
+
children: schema.pages.map((page, pageIndex) => /* @__PURE__ */ jsx("option", { value: page.id, children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }, page.id))
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
] }),
|
|
722
|
+
editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
|
|
723
|
+
/* @__PURE__ */ jsx("strong", { children: editingLocale }),
|
|
724
|
+
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
725
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
726
|
+
translate("builder.questionTitle"),
|
|
727
|
+
/* @__PURE__ */ jsx(
|
|
728
|
+
"input",
|
|
729
|
+
{
|
|
730
|
+
value: field.translations?.[editingLocale]?.title ?? "",
|
|
731
|
+
onChange: (event) => {
|
|
732
|
+
const value = event.currentTarget.value;
|
|
733
|
+
updateField(field.id, (current) => ({
|
|
734
|
+
...current,
|
|
735
|
+
translations: {
|
|
736
|
+
...current.translations,
|
|
737
|
+
[editingLocale]: {
|
|
738
|
+
...value.length === 0 ? {} : { title: value },
|
|
739
|
+
...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
)
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
748
|
+
translate("builder.pageDescription"),
|
|
749
|
+
/* @__PURE__ */ jsx(
|
|
750
|
+
"input",
|
|
751
|
+
{
|
|
752
|
+
value: field.translations?.[editingLocale]?.description ?? "",
|
|
753
|
+
onChange: (event) => {
|
|
754
|
+
const value = event.currentTarget.value;
|
|
755
|
+
updateField(field.id, (current) => ({
|
|
756
|
+
...current,
|
|
757
|
+
translations: {
|
|
758
|
+
...current.translations,
|
|
759
|
+
[editingLocale]: {
|
|
760
|
+
...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
|
|
761
|
+
...value.length === 0 ? {} : { description: value }
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
)
|
|
768
|
+
] })
|
|
769
|
+
] }),
|
|
770
|
+
"options" in field ? field.options.map((option, optionIndex) => /* @__PURE__ */ jsxs("label", { children: [
|
|
771
|
+
translate("builder.optionLabel", { index: optionIndex + 1 }),
|
|
772
|
+
" (",
|
|
773
|
+
editingLocale,
|
|
774
|
+
")",
|
|
775
|
+
/* @__PURE__ */ jsx(
|
|
776
|
+
"input",
|
|
777
|
+
{
|
|
778
|
+
value: option.translations?.[editingLocale] ?? "",
|
|
779
|
+
onChange: (event) => {
|
|
780
|
+
const value = event.currentTarget.value;
|
|
781
|
+
updateField(field.id, (current) => {
|
|
782
|
+
if (!("options" in current)) return current;
|
|
783
|
+
return {
|
|
784
|
+
...current,
|
|
785
|
+
options: current.options.map(
|
|
786
|
+
(candidate) => candidate.id === option.id ? {
|
|
787
|
+
...candidate,
|
|
788
|
+
translations: Object.fromEntries([
|
|
789
|
+
...Object.entries(candidate.translations ?? {}).filter(
|
|
790
|
+
([localeKey]) => localeKey !== editingLocale
|
|
791
|
+
),
|
|
792
|
+
...value.length === 0 ? [] : [[editingLocale, value]]
|
|
793
|
+
])
|
|
794
|
+
} : candidate
|
|
795
|
+
)
|
|
796
|
+
};
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
)
|
|
801
|
+
] }, option.id)) : null
|
|
802
|
+
] }),
|
|
286
803
|
field.type === "rating" ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
287
804
|
/* @__PURE__ */ jsxs("label", { children: [
|
|
288
805
|
translate("builder.minimum"),
|
|
@@ -448,10 +965,13 @@ function FormBuilder({ schema, onChange, locale = "en", translator }) {
|
|
|
448
965
|
import {
|
|
449
966
|
assertValidFormSchema,
|
|
450
967
|
calculateFieldVisibility,
|
|
968
|
+
calculatePageVisibility,
|
|
969
|
+
resolveLocalizedSchema,
|
|
451
970
|
selectVisibleAnswers,
|
|
452
|
-
validateAnswers
|
|
971
|
+
validateAnswers,
|
|
972
|
+
validatePageAnswers
|
|
453
973
|
} from "@form-engine-ts/core";
|
|
454
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
974
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState as useState2 } from "react";
|
|
455
975
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
456
976
|
var FormContext = createContext(null);
|
|
457
977
|
function issuesByField(issues) {
|
|
@@ -470,13 +990,17 @@ function FormProvider({
|
|
|
470
990
|
}) {
|
|
471
991
|
const validSchema = useMemo(() => {
|
|
472
992
|
assertValidFormSchema(schema);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const [
|
|
478
|
-
const [
|
|
993
|
+
const localized = resolveLocalizedSchema(schema, locale);
|
|
994
|
+
assertValidFormSchema(localized);
|
|
995
|
+
return localized;
|
|
996
|
+
}, [locale, schema]);
|
|
997
|
+
const [values, setValues] = useState2(() => ({ ...initialValues }));
|
|
998
|
+
const [errors, setErrors] = useState2({});
|
|
999
|
+
const [submitStatus, setSubmitStatus] = useState2("idle");
|
|
1000
|
+
const [submitError, setSubmitError] = useState2(null);
|
|
1001
|
+
const [validationPageIndex, setValidationPageIndex] = useState2(null);
|
|
479
1002
|
const visibility = useMemo(() => calculateFieldVisibility(validSchema, values), [validSchema, values]);
|
|
1003
|
+
const pageVisibility = useMemo(() => calculatePageVisibility(validSchema, values), [validSchema, values]);
|
|
480
1004
|
useEffect(() => {
|
|
481
1005
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
482
1006
|
setValues((current) => Object.fromEntries(Object.entries(current).filter(([fieldId]) => fieldIds.has(fieldId))));
|
|
@@ -487,7 +1011,7 @@ function FormProvider({
|
|
|
487
1011
|
const next = { ...current, [fieldId]: value };
|
|
488
1012
|
setErrors((currentErrors) => {
|
|
489
1013
|
if (Object.keys(currentErrors).length === 0) return currentErrors;
|
|
490
|
-
const result = validateAnswers(validSchema, next);
|
|
1014
|
+
const result = validationPageIndex === null ? validateAnswers(validSchema, next) : validatePageAnswers(validSchema, validationPageIndex, next);
|
|
491
1015
|
return issuesByField(result.issues);
|
|
492
1016
|
});
|
|
493
1017
|
return next;
|
|
@@ -495,11 +1019,34 @@ function FormProvider({
|
|
|
495
1019
|
setSubmitStatus((current) => current === "success" || current === "error" ? "idle" : current);
|
|
496
1020
|
setSubmitError(null);
|
|
497
1021
|
},
|
|
1022
|
+
[validSchema, validationPageIndex]
|
|
1023
|
+
);
|
|
1024
|
+
const restoreValues = useCallback(
|
|
1025
|
+
(restoredValues) => {
|
|
1026
|
+
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
1027
|
+
setValues(Object.fromEntries(Object.entries(restoredValues).filter(([fieldId]) => fieldIds.has(fieldId))));
|
|
1028
|
+
setErrors({});
|
|
1029
|
+
setValidationPageIndex(null);
|
|
1030
|
+
setSubmitStatus("idle");
|
|
1031
|
+
setSubmitError(null);
|
|
1032
|
+
},
|
|
498
1033
|
[validSchema]
|
|
499
1034
|
);
|
|
1035
|
+
const validatePage = useCallback(
|
|
1036
|
+
(pageIndex) => {
|
|
1037
|
+
const result = validatePageAnswers(validSchema, pageIndex, values);
|
|
1038
|
+
setErrors(issuesByField(result.issues));
|
|
1039
|
+
setValidationPageIndex(result.valid ? null : pageIndex);
|
|
1040
|
+
setSubmitStatus("idle");
|
|
1041
|
+
setSubmitError(null);
|
|
1042
|
+
return result;
|
|
1043
|
+
},
|
|
1044
|
+
[validSchema, values]
|
|
1045
|
+
);
|
|
500
1046
|
const reset = useCallback(() => {
|
|
501
1047
|
setValues({ ...initialValues });
|
|
502
1048
|
setErrors({});
|
|
1049
|
+
setValidationPageIndex(null);
|
|
503
1050
|
setSubmitStatus("idle");
|
|
504
1051
|
setSubmitError(null);
|
|
505
1052
|
}, [initialValues]);
|
|
@@ -507,11 +1054,13 @@ function FormProvider({
|
|
|
507
1054
|
const validation = validateAnswers(validSchema, values);
|
|
508
1055
|
if (!validation.valid) {
|
|
509
1056
|
setErrors(issuesByField(validation.issues));
|
|
1057
|
+
setValidationPageIndex(null);
|
|
510
1058
|
setSubmitStatus("error");
|
|
511
1059
|
setSubmitError(null);
|
|
512
1060
|
return false;
|
|
513
1061
|
}
|
|
514
1062
|
setErrors({});
|
|
1063
|
+
setValidationPageIndex(null);
|
|
515
1064
|
setSubmitStatus("submitting");
|
|
516
1065
|
setSubmitError(null);
|
|
517
1066
|
try {
|
|
@@ -536,11 +1085,14 @@ function FormProvider({
|
|
|
536
1085
|
translator,
|
|
537
1086
|
values,
|
|
538
1087
|
visibility,
|
|
1088
|
+
pageVisibility,
|
|
539
1089
|
errors,
|
|
540
1090
|
submitStatus,
|
|
541
1091
|
submitError,
|
|
542
1092
|
isSubmitting: submitStatus === "submitting",
|
|
543
1093
|
setValue,
|
|
1094
|
+
restoreValues,
|
|
1095
|
+
validatePage,
|
|
544
1096
|
reset,
|
|
545
1097
|
submit,
|
|
546
1098
|
translate
|
|
@@ -548,13 +1100,16 @@ function FormProvider({
|
|
|
548
1100
|
[
|
|
549
1101
|
errors,
|
|
550
1102
|
locale,
|
|
1103
|
+
pageVisibility,
|
|
551
1104
|
reset,
|
|
1105
|
+
restoreValues,
|
|
552
1106
|
setValue,
|
|
553
1107
|
submit,
|
|
554
1108
|
submitError,
|
|
555
1109
|
submitStatus,
|
|
556
1110
|
translate,
|
|
557
1111
|
translator,
|
|
1112
|
+
validatePage,
|
|
558
1113
|
validSchema,
|
|
559
1114
|
values,
|
|
560
1115
|
visibility
|
|
@@ -579,7 +1134,7 @@ function useField(fieldId) {
|
|
|
579
1134
|
import {
|
|
580
1135
|
validateAnswers as validateAnswers2
|
|
581
1136
|
} from "@form-engine-ts/core";
|
|
582
|
-
import { useId } from "react";
|
|
1137
|
+
import { useEffect as useEffect2, useId, useMemo as useMemo2, useRef, useState as useState3 } from "react";
|
|
583
1138
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
584
1139
|
function describedBy(field, error, helpId, errorId) {
|
|
585
1140
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
@@ -758,35 +1313,156 @@ function DefaultField(props) {
|
|
|
758
1313
|
/* @__PURE__ */ jsx3(FieldMessage, { props })
|
|
759
1314
|
] });
|
|
760
1315
|
}
|
|
1316
|
+
function isRecord(value) {
|
|
1317
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1318
|
+
}
|
|
1319
|
+
function isFormValue(value) {
|
|
1320
|
+
return value === void 0 || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1321
|
+
}
|
|
1322
|
+
function parseDraft(serialized) {
|
|
1323
|
+
try {
|
|
1324
|
+
const value = JSON.parse(serialized);
|
|
1325
|
+
if (!isRecord(value) || typeof value.formId !== "string" || typeof value.formVersion !== "number" || !Number.isInteger(value.formVersion) || typeof value.savedAt !== "string" || !isRecord(value.values) || !Object.values(value.values).every(isFormValue)) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
formId: value.formId,
|
|
1330
|
+
formVersion: value.formVersion,
|
|
1331
|
+
savedAt: value.savedAt,
|
|
1332
|
+
values: Object.fromEntries(
|
|
1333
|
+
Object.entries(value.values).filter((entry) => isFormValue(entry[1]))
|
|
1334
|
+
)
|
|
1335
|
+
};
|
|
1336
|
+
} catch {
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
761
1340
|
function FormRenderer({
|
|
762
1341
|
components = {},
|
|
763
1342
|
className = "",
|
|
764
1343
|
successMessageKey,
|
|
765
|
-
errorMessageKey
|
|
1344
|
+
errorMessageKey,
|
|
1345
|
+
autoSaveKey
|
|
766
1346
|
}) {
|
|
767
1347
|
const form = useForm();
|
|
768
1348
|
const prefix = useId().replace(/:/g, "");
|
|
1349
|
+
const formRef = useRef(null);
|
|
1350
|
+
const loadedDraftKey = useRef(null);
|
|
1351
|
+
const [draftRestored, setDraftRestored] = useState3(false);
|
|
1352
|
+
const [currentPageIndex, setCurrentPageIndex] = useState3(0);
|
|
1353
|
+
const [focusFieldId, setFocusFieldId] = useState3(null);
|
|
1354
|
+
const pages = form.schema.pages;
|
|
1355
|
+
const visiblePageIndexes = useMemo2(
|
|
1356
|
+
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
1357
|
+
[form.pageVisibility, pages]
|
|
1358
|
+
);
|
|
1359
|
+
const activePage = pages?.[currentPageIndex];
|
|
1360
|
+
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
1361
|
+
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
1362
|
+
useEffect2(() => {
|
|
1363
|
+
if (pages === void 0 || visiblePageIndexes.length === 0) {
|
|
1364
|
+
setCurrentPageIndex(0);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (!visiblePageIndexes.includes(currentPageIndex)) setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
1368
|
+
}, [currentPageIndex, pages, visiblePageIndexes]);
|
|
1369
|
+
useEffect2(() => {
|
|
1370
|
+
if (focusFieldId === null) return;
|
|
1371
|
+
const fieldContainer = [...formRef.current?.querySelectorAll("[data-field-id]") ?? []].find(
|
|
1372
|
+
(element) => element.dataset.fieldId === focusFieldId
|
|
1373
|
+
);
|
|
1374
|
+
const control = fieldContainer?.querySelector("input, select, textarea");
|
|
1375
|
+
if (control !== void 0 && control !== null) {
|
|
1376
|
+
control.focus();
|
|
1377
|
+
setFocusFieldId(null);
|
|
1378
|
+
}
|
|
1379
|
+
}, [focusFieldId]);
|
|
1380
|
+
useEffect2(() => {
|
|
1381
|
+
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1382
|
+
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
1383
|
+
if (loadedDraftKey.current === loadIdentity) return;
|
|
1384
|
+
loadedDraftKey.current = loadIdentity;
|
|
1385
|
+
const serialized = globalThis.localStorage.getItem(autoSaveKey);
|
|
1386
|
+
if (serialized === null) return;
|
|
1387
|
+
const draft = parseDraft(serialized);
|
|
1388
|
+
if (draft === null || draft.formId !== form.schema.id || draft.formVersion !== form.schema.version) return;
|
|
1389
|
+
form.restoreValues(draft.values);
|
|
1390
|
+
setDraftRestored(true);
|
|
1391
|
+
}, [autoSaveKey, form.restoreValues, form.schema.id, form.schema.version]);
|
|
1392
|
+
useEffect2(() => {
|
|
1393
|
+
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
1394
|
+
if (form.submitStatus === "success") return;
|
|
1395
|
+
const timeout = globalThis.setTimeout(() => {
|
|
1396
|
+
const draft = {
|
|
1397
|
+
formId: form.schema.id,
|
|
1398
|
+
formVersion: form.schema.version,
|
|
1399
|
+
values: form.values,
|
|
1400
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1401
|
+
};
|
|
1402
|
+
globalThis.localStorage.setItem(autoSaveKey, JSON.stringify(draft));
|
|
1403
|
+
}, 500);
|
|
1404
|
+
return () => globalThis.clearTimeout(timeout);
|
|
1405
|
+
}, [autoSaveKey, form.schema.id, form.schema.version, form.submitStatus, form.values]);
|
|
1406
|
+
const focusFirstIssue = (fieldId) => {
|
|
1407
|
+
if (fieldId !== void 0) setFocusFieldId(fieldId);
|
|
1408
|
+
};
|
|
1409
|
+
const handleNext = () => {
|
|
1410
|
+
const result = form.validatePage(currentPageIndex);
|
|
1411
|
+
if (!result.valid) {
|
|
1412
|
+
focusFirstIssue(result.issues[0]?.fieldId);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
const nextPageIndex = visiblePageIndexes[activeVisibleIndex + 1];
|
|
1416
|
+
if (nextPageIndex !== void 0) setCurrentPageIndex(nextPageIndex);
|
|
1417
|
+
};
|
|
769
1418
|
const handleSubmit = async (event) => {
|
|
770
1419
|
event.preventDefault();
|
|
771
|
-
const formElement = event.currentTarget;
|
|
772
1420
|
const validation = validateAnswers2(form.schema, form.values);
|
|
773
1421
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
774
1422
|
const valid = await form.submit();
|
|
775
|
-
if (!valid
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1423
|
+
if (!valid) {
|
|
1424
|
+
const invalidPageIndex = pages?.findIndex(
|
|
1425
|
+
(page) => firstInvalidFieldId === void 0 ? false : page.questionIds.includes(firstInvalidFieldId)
|
|
1426
|
+
);
|
|
1427
|
+
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
1428
|
+
focusFirstIssue(firstInvalidFieldId);
|
|
1429
|
+
return;
|
|
782
1430
|
}
|
|
1431
|
+
if (autoSaveKey !== void 0 && typeof globalThis.localStorage !== "undefined") {
|
|
1432
|
+
globalThis.localStorage.removeItem(autoSaveKey);
|
|
1433
|
+
setDraftRestored(false);
|
|
1434
|
+
}
|
|
1435
|
+
setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
783
1436
|
};
|
|
784
|
-
return /* @__PURE__ */ jsxs2("form", { className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
|
|
1437
|
+
return /* @__PURE__ */ jsxs2("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
|
|
785
1438
|
/* @__PURE__ */ jsxs2("header", { className: "fe-header", children: [
|
|
786
1439
|
/* @__PURE__ */ jsx3("h1", { children: form.schema.title }),
|
|
787
|
-
form.schema.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { children: form.schema.description })
|
|
1440
|
+
form.schema.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { children: form.schema.description }),
|
|
1441
|
+
pages === void 0 ? null : /* @__PURE__ */ jsxs2("div", { className: "fe-progress", children: [
|
|
1442
|
+
/* @__PURE__ */ jsx3(
|
|
1443
|
+
"div",
|
|
1444
|
+
{
|
|
1445
|
+
className: "form-progress-bar",
|
|
1446
|
+
role: "progressbar",
|
|
1447
|
+
"aria-valuemin": 1,
|
|
1448
|
+
"aria-valuemax": visiblePageIndexes.length,
|
|
1449
|
+
"aria-valuenow": activeVisibleIndex + 1,
|
|
1450
|
+
children: /* @__PURE__ */ jsx3(
|
|
1451
|
+
"div",
|
|
1452
|
+
{
|
|
1453
|
+
className: "form-progress-fill",
|
|
1454
|
+
style: { width: `${(activeVisibleIndex + 1) / visiblePageIndexes.length * 100}%` }
|
|
1455
|
+
}
|
|
1456
|
+
)
|
|
1457
|
+
}
|
|
1458
|
+
),
|
|
1459
|
+
/* @__PURE__ */ jsx3("span", { children: form.translate("form.step", { current: activeVisibleIndex + 1, total: visiblePageIndexes.length }) })
|
|
1460
|
+
] }),
|
|
1461
|
+
draftRestored ? /* @__PURE__ */ jsx3("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
|
|
788
1462
|
] }),
|
|
789
|
-
/* @__PURE__ */ jsx3("
|
|
1463
|
+
activePage?.title === void 0 ? null : /* @__PURE__ */ jsx3("h2", { className: "fe-page-title", children: activePage.title }),
|
|
1464
|
+
activePage?.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { className: "fe-page-description", children: activePage.description }),
|
|
1465
|
+
/* @__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) => {
|
|
790
1466
|
const props = {
|
|
791
1467
|
field,
|
|
792
1468
|
value: form.values[field.id],
|
|
@@ -800,7 +1476,18 @@ function FormRenderer({
|
|
|
800
1476
|
const Component = components[field.type];
|
|
801
1477
|
return Component === void 0 ? /* @__PURE__ */ jsx3(DefaultField, { ...props }, field.id) : /* @__PURE__ */ jsx3(Component, { ...props }, field.id);
|
|
802
1478
|
}) }),
|
|
803
|
-
/* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") }),
|
|
1479
|
+
pages === void 0 ? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") }) : /* @__PURE__ */ jsxs2("div", { className: "form-step-navigation", children: [
|
|
1480
|
+
activeVisibleIndex > 0 ? /* @__PURE__ */ jsx3(
|
|
1481
|
+
"button",
|
|
1482
|
+
{
|
|
1483
|
+
className: "btn-prev",
|
|
1484
|
+
type: "button",
|
|
1485
|
+
onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
1486
|
+
children: form.translate("form.back")
|
|
1487
|
+
}
|
|
1488
|
+
) : null,
|
|
1489
|
+
activeVisibleIndex < visiblePageIndexes.length - 1 ? /* @__PURE__ */ jsx3("button", { className: "btn-next", type: "button", onClick: handleNext, children: form.translate("form.next") }) : /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") })
|
|
1490
|
+
] }),
|
|
804
1491
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
805
1492
|
form.submitStatus === "success" && successMessageKey !== void 0 ? /* @__PURE__ */ jsx3("div", { role: "status", children: form.translate(successMessageKey) }) : null,
|
|
806
1493
|
form.submitStatus === "error" && form.submitError !== null && errorMessageKey !== void 0 ? /* @__PURE__ */ jsx3("div", { role: "alert", children: form.translate(errorMessageKey) }) : null
|