@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/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ module.exports = __toCommonJS(index_exports);
30
30
 
31
31
  // src/builder.tsx
32
32
  var import_core = require("@form-engine-ts/core");
33
+ var import_react = require("react");
33
34
  var import_jsx_runtime = require("react/jsx-runtime");
34
35
  var FIELD_TYPES = [
35
36
  "text",
@@ -67,6 +68,22 @@ var BUILDER_DEFAULTS = {
67
68
  "builder.conditionTrue": "true",
68
69
  "builder.conditionFalse": "false",
69
70
  "builder.addQuestion": "Add question",
71
+ "builder.pages": "Page manager",
72
+ "builder.enablePages": "Enable multi-step pages",
73
+ "builder.addPage": "Add page",
74
+ "builder.newPage": "New page",
75
+ "builder.pageTitle": "Page title",
76
+ "builder.pageDescription": "Page description",
77
+ "builder.pageQuestion": "Question to move to the new page",
78
+ "builder.questionPage": "Page",
79
+ "builder.pageCondition": "Page display condition",
80
+ "builder.localization": "Localization",
81
+ "builder.defaultLocale": "Default locale",
82
+ "builder.supportedLocales": "Supported locales",
83
+ "builder.addLocale": "Add locale",
84
+ "builder.editLocale": "Edit locale",
85
+ "builder.autoTranslate": "Translate all text",
86
+ "builder.translationUnavailable": "Provide an async translation adapter to enable automatic translation.",
70
87
  "builder.fieldType.text": "Text",
71
88
  "builder.fieldType.textarea": "Textarea",
72
89
  "builder.fieldType.number": "Number",
@@ -139,14 +156,21 @@ function withoutDisplayCondition(field) {
139
156
  function sanitizeBuilderSchema(schema) {
140
157
  const sanitized = (0, import_core.sanitizeSchema)(schema);
141
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
+ });
142
165
  return {
143
166
  ...sanitized,
144
- fields: sanitized.fields.map((field, index) => {
145
- const sourceId = field.displayCondition?.questionId;
146
- if (sourceId === void 0) return field;
147
- const sourceIndex = indexById.get(sourceId);
148
- return sourceIndex !== void 0 && sourceIndex < index ? field : withoutDisplayCondition(field);
149
- })
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
+ }
150
174
  };
151
175
  }
152
176
  function conditionWithValue(questionId, operator, value) {
@@ -190,7 +214,12 @@ function ConditionValueEditor({
190
214
  }
191
215
  );
192
216
  }
193
- function FormBuilder({ schema, onChange, locale = "en", translator }) {
217
+ function FormBuilder({ schema, onChange, locale = "en", translator, translationAdapter }) {
218
+ const [newPageQuestionId, setNewPageQuestionId] = (0, import_react.useState)("");
219
+ const [newLocale, setNewLocale] = (0, import_react.useState)("");
220
+ const [editingLocale, setEditingLocale] = (0, import_react.useState)("");
221
+ const [isTranslating, setIsTranslating] = (0, import_react.useState)(false);
222
+ const [translationError, setTranslationError] = (0, import_react.useState)(null);
194
223
  const translate = (key, params = {}) => {
195
224
  const translated = translator?.translate(key, locale, params);
196
225
  return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
@@ -231,12 +260,405 @@ function FormBuilder({ schema, onChange, locale = "en", translator }) {
231
260
  };
232
261
  const addField = () => {
233
262
  const id = createUniqueId("q", new Set(schema.fields.map((field) => field.id)));
234
- emitSchema({
263
+ const nextSchema = {
235
264
  ...schema,
236
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
+ );
275
+ };
276
+ const enablePages = () => {
277
+ if (schema.pages !== void 0) return;
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
+ });
288
+ };
289
+ const pageForField = (fieldId) => schema.pages?.find((page) => page.questionIds.includes(fieldId));
290
+ const movablePageQuestions = schema.pages?.flatMap((page) => page.questionIds.length > 1 ? page.questionIds : []) ?? [];
291
+ const addPage = () => {
292
+ if (schema.pages === void 0) {
293
+ enablePages();
294
+ return;
295
+ }
296
+ const questionId = movablePageQuestions.includes(newPageQuestionId) ? newPageQuestionId : movablePageQuestions[0];
297
+ if (questionId === void 0) return;
298
+ const pageId = createUniqueId("page", new Set(schema.pages.map((page) => page.id)));
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
+ });
309
+ setNewPageQuestionId("");
310
+ };
311
+ const removePage = (pageIndex) => {
312
+ if (schema.pages === void 0) return;
313
+ const removed = schema.pages[pageIndex];
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
+ });
327
+ };
328
+ const movePage = (pageIndex, offset) => {
329
+ if (schema.pages === void 0) return;
330
+ const target = pageIndex + offset;
331
+ if (target < 0 || target >= schema.pages.length) return;
332
+ const pages = [...schema.pages];
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 });
339
+ };
340
+ const updatePage = (pageId, update) => {
341
+ if (schema.pages === void 0) return;
342
+ emitSchema({ ...schema, pages: schema.pages.map((page) => page.id === pageId ? update(page) : page) });
343
+ };
344
+ const assignFieldToPage = (fieldId, pageId) => {
345
+ if (schema.pages === void 0) return;
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
+ });
353
+ };
354
+ const addLocale = () => {
355
+ const normalized = newLocale.trim();
356
+ if (normalized.length === 0) return;
357
+ const supportedLocales = [
358
+ .../* @__PURE__ */ new Set([
359
+ ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
360
+ ...schema.supportedLocales ?? [],
361
+ normalized
362
+ ])
363
+ ];
364
+ emitSchema({ ...schema, supportedLocales });
365
+ setEditingLocale(normalized);
366
+ setNewLocale("");
367
+ };
368
+ const translateAll = async () => {
369
+ if (translationAdapter === void 0 || editingLocale.length === 0) return;
370
+ setIsTranslating(true);
371
+ setTranslationError(null);
372
+ try {
373
+ onChange(await (0, import_core.populateSchemaTranslations)(schema, [editingLocale], translationAdapter));
374
+ } catch (cause) {
375
+ setTranslationError(cause instanceof Error ? cause.message : String(cause));
376
+ } finally {
377
+ setIsTranslating(false);
378
+ }
379
+ };
380
+ const updateFormTranslation = (key, value) => {
381
+ if (editingLocale.length === 0) return;
382
+ const current = schema.translations?.[editingLocale];
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
+ }
237
393
  });
238
394
  };
239
395
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "form-engine-builder", "aria-label": translate("builder.formBuilder"), children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "builder-pages-heading", children: translate("builder.pages") }),
398
+ schema.pages === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: enablePages, children: translate("builder.enablePages") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
399
+ schema.pages.map((page, pageIndex) => {
400
+ const priorQuestionIds = new Set(schema.pages?.slice(0, pageIndex).flatMap((item) => item.questionIds));
401
+ const availableSources = schema.fields.filter((field) => priorQuestionIds.has(field.id));
402
+ const source = schema.fields.find((field) => field.id === page.displayCondition?.questionId);
403
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { className: "form-engine-builder__page", children: [
404
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }),
405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__toolbar", children: [
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
407
+ "button",
408
+ {
409
+ type: "button",
410
+ disabled: pageIndex === 0,
411
+ "aria-label": translate("builder.moveUp", { title: page.title ?? page.id }),
412
+ onClick: () => movePage(pageIndex, -1),
413
+ children: "\u2191"
414
+ }
415
+ ),
416
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
417
+ "button",
418
+ {
419
+ type: "button",
420
+ disabled: pageIndex === (schema.pages?.length ?? 0) - 1,
421
+ "aria-label": translate("builder.moveDown", { title: page.title ?? page.id }),
422
+ onClick: () => movePage(pageIndex, 1),
423
+ children: "\u2193"
424
+ }
425
+ ),
426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => removePage(pageIndex), children: translate("builder.deleteAction") })
427
+ ] }),
428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
430
+ translate("builder.pageTitle"),
431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
432
+ "input",
433
+ {
434
+ value: page.title ?? "",
435
+ onChange: (event) => {
436
+ const value = event.currentTarget.value;
437
+ updatePage(page.id, (current) => {
438
+ if (value.length > 0) return { ...current, title: value };
439
+ const { title: _title, ...withoutTitle } = current;
440
+ return withoutTitle;
441
+ });
442
+ }
443
+ }
444
+ )
445
+ ] }),
446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
447
+ translate("builder.pageDescription"),
448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
449
+ "input",
450
+ {
451
+ value: page.description ?? "",
452
+ onChange: (event) => {
453
+ const value = event.currentTarget.value;
454
+ updatePage(page.id, (current) => {
455
+ if (value.length > 0) return { ...current, description: value };
456
+ const { description: _description, ...withoutDescription } = current;
457
+ return withoutDescription;
458
+ });
459
+ }
460
+ }
461
+ )
462
+ ] })
463
+ ] }),
464
+ editingLocale.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__translation-editor", children: [
465
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: editingLocale }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
468
+ translate("builder.pageTitle"),
469
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
470
+ "input",
471
+ {
472
+ value: page.translations?.[editingLocale]?.title ?? "",
473
+ onChange: (event) => {
474
+ const value = event.currentTarget.value;
475
+ updatePage(page.id, (current) => ({
476
+ ...current,
477
+ translations: {
478
+ ...current.translations,
479
+ [editingLocale]: {
480
+ ...value.length === 0 ? {} : { title: value },
481
+ ...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
482
+ }
483
+ }
484
+ }));
485
+ }
486
+ }
487
+ )
488
+ ] }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
490
+ translate("builder.pageDescription"),
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
492
+ "input",
493
+ {
494
+ value: page.translations?.[editingLocale]?.description ?? "",
495
+ onChange: (event) => {
496
+ const value = event.currentTarget.value;
497
+ updatePage(page.id, (current) => ({
498
+ ...current,
499
+ translations: {
500
+ ...current.translations,
501
+ [editingLocale]: {
502
+ ...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
503
+ ...value.length === 0 ? {} : { description: value }
504
+ }
505
+ }
506
+ }));
507
+ }
508
+ }
509
+ )
510
+ ] })
511
+ ] })
512
+ ] }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__condition", children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
515
+ translate("builder.pageCondition"),
516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
517
+ "select",
518
+ {
519
+ value: page.displayCondition?.questionId ?? "",
520
+ onChange: (event) => {
521
+ const selected = schema.fields.find((field) => field.id === event.currentTarget.value);
522
+ updatePage(page.id, (current) => {
523
+ if (selected === void 0) {
524
+ const { displayCondition: _condition, ...withoutCondition } = current;
525
+ return withoutCondition;
526
+ }
527
+ return {
528
+ ...current,
529
+ displayCondition: conditionWithValue(
530
+ selected.id,
531
+ conditionOperators(selected)[0] ?? "not_empty",
532
+ defaultConditionValue(selected)
533
+ )
534
+ };
535
+ });
536
+ },
537
+ children: [
538
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: translate("builder.alwaysVisible") }),
539
+ availableSources.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: field.id, children: field.title }, field.id))
540
+ ]
541
+ }
542
+ )
543
+ ] }),
544
+ page.displayCondition !== void 0 && source !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
546
+ "select",
547
+ {
548
+ "aria-label": translate("builder.conditionOperator"),
549
+ value: page.displayCondition.operator,
550
+ onChange: (event) => {
551
+ const operator = event.currentTarget.value;
552
+ updatePage(page.id, (current) => ({
553
+ ...current,
554
+ displayCondition: conditionWithValue(source.id, operator, defaultConditionValue(source))
555
+ }));
556
+ },
557
+ children: conditionOperators(source).map((operator) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
558
+ }
559
+ ),
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
561
+ ConditionValueEditor,
562
+ {
563
+ source,
564
+ condition: page.displayCondition,
565
+ onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
566
+ translate
567
+ }
568
+ )
569
+ ] }) : null
570
+ ] })
571
+ ] }, page.id);
572
+ }),
573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__page-add", children: [
574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
575
+ translate("builder.pageQuestion"),
576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
577
+ "select",
578
+ {
579
+ value: newPageQuestionId,
580
+ disabled: movablePageQuestions.length === 0,
581
+ onChange: (event) => setNewPageQuestionId(event.currentTarget.value),
582
+ children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u2014" }),
584
+ schema.fields.filter((field) => movablePageQuestions.includes(field.id)).map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: field.id, children: field.title }, field.id))
585
+ ]
586
+ }
587
+ )
588
+ ] }),
589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: movablePageQuestions.length === 0, onClick: addPage, children: translate("builder.addPage") })
590
+ ] })
591
+ ] })
592
+ ] }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
597
+ translate("builder.defaultLocale"),
598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
599
+ "input",
600
+ {
601
+ 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
+ }
612
+ }
613
+ )
614
+ ] }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
616
+ translate("builder.addLocale"),
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { value: newLocale, onChange: (event) => setNewLocale(event.currentTarget.value) })
618
+ ] }),
619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: addLocale, children: translate("builder.addLocale") }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
621
+ translate("builder.editLocale"),
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: editingLocale, onChange: (event) => setEditingLocale(event.currentTarget.value), children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u2014" }),
624
+ (schema.supportedLocales ?? []).filter((item) => item !== schema.defaultLocale).map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: item, children: item }, item))
625
+ ] })
626
+ ] }),
627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
628
+ "button",
629
+ {
630
+ type: "button",
631
+ disabled: translationAdapter === void 0 || editingLocale.length === 0 || isTranslating,
632
+ onClick: () => void translateAll(),
633
+ children: translate("builder.autoTranslate")
634
+ }
635
+ )
636
+ ] }),
637
+ translationAdapter === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: translate("builder.translationUnavailable") }) : null,
638
+ translationError === null ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "form-engine-builder__error", children: translationError }),
639
+ editingLocale.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
640
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
641
+ translate("builder.questionTitle"),
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
643
+ "input",
644
+ {
645
+ value: schema.translations?.[editingLocale]?.title ?? "",
646
+ onChange: (event) => updateFormTranslation("title", event.currentTarget.value)
647
+ }
648
+ )
649
+ ] }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
651
+ translate("builder.pageDescription"),
652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
653
+ "input",
654
+ {
655
+ value: schema.translations?.[editingLocale]?.description ?? "",
656
+ onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
657
+ }
658
+ )
659
+ ] })
660
+ ] })
661
+ ] }),
240
662
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "form-engine-builder__list", children: schema.fields.map((field, index) => {
241
663
  const condition = field.displayCondition;
242
664
  const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
@@ -313,6 +735,98 @@ function FormBuilder({ schema, onChange, locale = "en", translator }) {
313
735
  translate("builder.required")
314
736
  ] })
315
737
  ] }),
738
+ schema.pages === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
739
+ translate("builder.questionPage"),
740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
741
+ "select",
742
+ {
743
+ value: pageForField(field.id)?.id ?? "",
744
+ onChange: (event) => assignFieldToPage(field.id, event.currentTarget.value),
745
+ children: schema.pages.map((page, pageIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: page.id, children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }, page.id))
746
+ }
747
+ )
748
+ ] }),
749
+ editingLocale.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__translation-editor", children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: editingLocale }),
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
753
+ translate("builder.questionTitle"),
754
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
755
+ "input",
756
+ {
757
+ value: field.translations?.[editingLocale]?.title ?? "",
758
+ onChange: (event) => {
759
+ const value = event.currentTarget.value;
760
+ updateField(field.id, (current) => ({
761
+ ...current,
762
+ translations: {
763
+ ...current.translations,
764
+ [editingLocale]: {
765
+ ...value.length === 0 ? {} : { title: value },
766
+ ...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
767
+ }
768
+ }
769
+ }));
770
+ }
771
+ }
772
+ )
773
+ ] }),
774
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
775
+ translate("builder.pageDescription"),
776
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
777
+ "input",
778
+ {
779
+ value: field.translations?.[editingLocale]?.description ?? "",
780
+ onChange: (event) => {
781
+ const value = event.currentTarget.value;
782
+ updateField(field.id, (current) => ({
783
+ ...current,
784
+ translations: {
785
+ ...current.translations,
786
+ [editingLocale]: {
787
+ ...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
788
+ ...value.length === 0 ? {} : { description: value }
789
+ }
790
+ }
791
+ }));
792
+ }
793
+ }
794
+ )
795
+ ] })
796
+ ] }),
797
+ "options" in field ? field.options.map((option, optionIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
798
+ translate("builder.optionLabel", { index: optionIndex + 1 }),
799
+ " (",
800
+ editingLocale,
801
+ ")",
802
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
803
+ "input",
804
+ {
805
+ value: option.translations?.[editingLocale] ?? "",
806
+ onChange: (event) => {
807
+ const value = event.currentTarget.value;
808
+ updateField(field.id, (current) => {
809
+ if (!("options" in current)) return current;
810
+ return {
811
+ ...current,
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
+ }
826
+ }
827
+ )
828
+ ] }, option.id)) : null
829
+ ] }),
316
830
  field.type === "rating" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "form-engine-builder__grid", children: [
317
831
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
318
832
  translate("builder.minimum"),
@@ -476,9 +990,9 @@ function FormBuilder({ schema, onChange, locale = "en", translator }) {
476
990
 
477
991
  // src/context.tsx
478
992
  var import_core2 = require("@form-engine-ts/core");
479
- var import_react = require("react");
993
+ var import_react2 = require("react");
480
994
  var import_jsx_runtime2 = require("react/jsx-runtime");
481
- var FormContext = (0, import_react.createContext)(null);
995
+ var FormContext = (0, import_react2.createContext)(null);
482
996
  function issuesByField(issues) {
483
997
  const result = {};
484
998
  for (const issue of issues) result[issue.fieldId] ??= issue;
@@ -493,26 +1007,30 @@ function FormProvider({
493
1007
  onSubmit,
494
1008
  children
495
1009
  }) {
496
- const validSchema = (0, import_react.useMemo)(() => {
1010
+ const validSchema = (0, import_react2.useMemo)(() => {
497
1011
  (0, import_core2.assertValidFormSchema)(schema);
498
- return schema;
499
- }, [schema]);
500
- const [values, setValues] = (0, import_react.useState)(() => ({ ...initialValues }));
501
- const [errors, setErrors] = (0, import_react.useState)({});
502
- const [submitStatus, setSubmitStatus] = (0, import_react.useState)("idle");
503
- const [submitError, setSubmitError] = (0, import_react.useState)(null);
504
- const visibility = (0, import_react.useMemo)(() => (0, import_core2.calculateFieldVisibility)(validSchema, values), [validSchema, values]);
505
- (0, import_react.useEffect)(() => {
1012
+ const localized = (0, import_core2.resolveLocalizedSchema)(schema, locale);
1013
+ (0, import_core2.assertValidFormSchema)(localized);
1014
+ return localized;
1015
+ }, [locale, schema]);
1016
+ const [values, setValues] = (0, import_react2.useState)(() => ({ ...initialValues }));
1017
+ const [errors, setErrors] = (0, import_react2.useState)({});
1018
+ const [submitStatus, setSubmitStatus] = (0, import_react2.useState)("idle");
1019
+ const [submitError, setSubmitError] = (0, import_react2.useState)(null);
1020
+ const [validationPageIndex, setValidationPageIndex] = (0, import_react2.useState)(null);
1021
+ const visibility = (0, import_react2.useMemo)(() => (0, import_core2.calculateFieldVisibility)(validSchema, values), [validSchema, values]);
1022
+ const pageVisibility = (0, import_react2.useMemo)(() => (0, import_core2.calculatePageVisibility)(validSchema, values), [validSchema, values]);
1023
+ (0, import_react2.useEffect)(() => {
506
1024
  const fieldIds = new Set(validSchema.fields.map((field) => field.id));
507
1025
  setValues((current) => Object.fromEntries(Object.entries(current).filter(([fieldId]) => fieldIds.has(fieldId))));
508
1026
  }, [validSchema]);
509
- const setValue = (0, import_react.useCallback)(
1027
+ const setValue = (0, import_react2.useCallback)(
510
1028
  (fieldId, value) => {
511
1029
  setValues((current) => {
512
1030
  const next = { ...current, [fieldId]: value };
513
1031
  setErrors((currentErrors) => {
514
1032
  if (Object.keys(currentErrors).length === 0) return currentErrors;
515
- const result = (0, import_core2.validateAnswers)(validSchema, next);
1033
+ const result = validationPageIndex === null ? (0, import_core2.validateAnswers)(validSchema, next) : (0, import_core2.validatePageAnswers)(validSchema, validationPageIndex, next);
516
1034
  return issuesByField(result.issues);
517
1035
  });
518
1036
  return next;
@@ -520,23 +1038,48 @@ function FormProvider({
520
1038
  setSubmitStatus((current) => current === "success" || current === "error" ? "idle" : current);
521
1039
  setSubmitError(null);
522
1040
  },
1041
+ [validSchema, validationPageIndex]
1042
+ );
1043
+ const restoreValues = (0, import_react2.useCallback)(
1044
+ (restoredValues) => {
1045
+ const fieldIds = new Set(validSchema.fields.map((field) => field.id));
1046
+ setValues(Object.fromEntries(Object.entries(restoredValues).filter(([fieldId]) => fieldIds.has(fieldId))));
1047
+ setErrors({});
1048
+ setValidationPageIndex(null);
1049
+ setSubmitStatus("idle");
1050
+ setSubmitError(null);
1051
+ },
523
1052
  [validSchema]
524
1053
  );
525
- const reset = (0, import_react.useCallback)(() => {
1054
+ const validatePage = (0, import_react2.useCallback)(
1055
+ (pageIndex) => {
1056
+ const result = (0, import_core2.validatePageAnswers)(validSchema, pageIndex, values);
1057
+ setErrors(issuesByField(result.issues));
1058
+ setValidationPageIndex(result.valid ? null : pageIndex);
1059
+ setSubmitStatus("idle");
1060
+ setSubmitError(null);
1061
+ return result;
1062
+ },
1063
+ [validSchema, values]
1064
+ );
1065
+ const reset = (0, import_react2.useCallback)(() => {
526
1066
  setValues({ ...initialValues });
527
1067
  setErrors({});
1068
+ setValidationPageIndex(null);
528
1069
  setSubmitStatus("idle");
529
1070
  setSubmitError(null);
530
1071
  }, [initialValues]);
531
- const submit = (0, import_react.useCallback)(async () => {
1072
+ const submit = (0, import_react2.useCallback)(async () => {
532
1073
  const validation = (0, import_core2.validateAnswers)(validSchema, values);
533
1074
  if (!validation.valid) {
534
1075
  setErrors(issuesByField(validation.issues));
1076
+ setValidationPageIndex(null);
535
1077
  setSubmitStatus("error");
536
1078
  setSubmitError(null);
537
1079
  return false;
538
1080
  }
539
1081
  setErrors({});
1082
+ setValidationPageIndex(null);
540
1083
  setSubmitStatus("submitting");
541
1084
  setSubmitError(null);
542
1085
  try {
@@ -550,22 +1093,25 @@ function FormProvider({
550
1093
  return false;
551
1094
  }
552
1095
  }, [initialValues, onSubmit, resetOnSuccess, validSchema, values]);
553
- const translate = (0, import_react.useCallback)(
1096
+ const translate = (0, import_react2.useCallback)(
554
1097
  (key, params) => translator.translate(key, locale, params),
555
1098
  [locale, translator]
556
1099
  );
557
- const contextValue = (0, import_react.useMemo)(
1100
+ const contextValue = (0, import_react2.useMemo)(
558
1101
  () => ({
559
1102
  schema: validSchema,
560
1103
  locale,
561
1104
  translator,
562
1105
  values,
563
1106
  visibility,
1107
+ pageVisibility,
564
1108
  errors,
565
1109
  submitStatus,
566
1110
  submitError,
567
1111
  isSubmitting: submitStatus === "submitting",
568
1112
  setValue,
1113
+ restoreValues,
1114
+ validatePage,
569
1115
  reset,
570
1116
  submit,
571
1117
  translate
@@ -573,13 +1119,16 @@ function FormProvider({
573
1119
  [
574
1120
  errors,
575
1121
  locale,
1122
+ pageVisibility,
576
1123
  reset,
1124
+ restoreValues,
577
1125
  setValue,
578
1126
  submit,
579
1127
  submitError,
580
1128
  submitStatus,
581
1129
  translate,
582
1130
  translator,
1131
+ validatePage,
583
1132
  validSchema,
584
1133
  values,
585
1134
  visibility
@@ -588,7 +1137,7 @@ function FormProvider({
588
1137
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FormContext.Provider, { value: contextValue, children });
589
1138
  }
590
1139
  function useForm() {
591
- const context = (0, import_react.useContext)(FormContext);
1140
+ const context = (0, import_react2.useContext)(FormContext);
592
1141
  if (context === null) throw new Error("useForm must be called inside a FormProvider.");
593
1142
  return context;
594
1143
  }
@@ -596,13 +1145,13 @@ function useField(fieldId) {
596
1145
  const form = useForm();
597
1146
  const field = form.schema.fields.find((item) => item.id === fieldId);
598
1147
  if (field === void 0) throw new Error(`Unknown form field: ${fieldId}`);
599
- const setValue = (0, import_react.useCallback)((value) => form.setValue(fieldId, value), [fieldId, form]);
1148
+ const setValue = (0, import_react2.useCallback)((value) => form.setValue(fieldId, value), [fieldId, form]);
600
1149
  return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
601
1150
  }
602
1151
 
603
1152
  // src/renderer.tsx
604
1153
  var import_core3 = require("@form-engine-ts/core");
605
- var import_react2 = require("react");
1154
+ var import_react3 = require("react");
606
1155
  var import_jsx_runtime3 = require("react/jsx-runtime");
607
1156
  function describedBy(field, error, helpId, errorId) {
608
1157
  const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
@@ -781,35 +1330,156 @@ function DefaultField(props) {
781
1330
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FieldMessage, { props })
782
1331
  ] });
783
1332
  }
1333
+ function isRecord(value) {
1334
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1335
+ }
1336
+ function isFormValue(value) {
1337
+ 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");
1338
+ }
1339
+ function parseDraft(serialized) {
1340
+ try {
1341
+ const value = JSON.parse(serialized);
1342
+ 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)) {
1343
+ return null;
1344
+ }
1345
+ return {
1346
+ formId: value.formId,
1347
+ formVersion: value.formVersion,
1348
+ savedAt: value.savedAt,
1349
+ values: Object.fromEntries(
1350
+ Object.entries(value.values).filter((entry) => isFormValue(entry[1]))
1351
+ )
1352
+ };
1353
+ } catch {
1354
+ return null;
1355
+ }
1356
+ }
784
1357
  function FormRenderer({
785
1358
  components = {},
786
1359
  className = "",
787
1360
  successMessageKey,
788
- errorMessageKey
1361
+ errorMessageKey,
1362
+ autoSaveKey
789
1363
  }) {
790
1364
  const form = useForm();
791
- const prefix = (0, import_react2.useId)().replace(/:/g, "");
1365
+ const prefix = (0, import_react3.useId)().replace(/:/g, "");
1366
+ const formRef = (0, import_react3.useRef)(null);
1367
+ const loadedDraftKey = (0, import_react3.useRef)(null);
1368
+ const [draftRestored, setDraftRestored] = (0, import_react3.useState)(false);
1369
+ const [currentPageIndex, setCurrentPageIndex] = (0, import_react3.useState)(0);
1370
+ const [focusFieldId, setFocusFieldId] = (0, import_react3.useState)(null);
1371
+ const pages = form.schema.pages;
1372
+ const visiblePageIndexes = (0, import_react3.useMemo)(
1373
+ () => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
1374
+ [form.pageVisibility, pages]
1375
+ );
1376
+ const activePage = pages?.[currentPageIndex];
1377
+ const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
1378
+ const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
1379
+ (0, import_react3.useEffect)(() => {
1380
+ if (pages === void 0 || visiblePageIndexes.length === 0) {
1381
+ setCurrentPageIndex(0);
1382
+ return;
1383
+ }
1384
+ if (!visiblePageIndexes.includes(currentPageIndex)) setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
1385
+ }, [currentPageIndex, pages, visiblePageIndexes]);
1386
+ (0, import_react3.useEffect)(() => {
1387
+ if (focusFieldId === null) return;
1388
+ const fieldContainer = [...formRef.current?.querySelectorAll("[data-field-id]") ?? []].find(
1389
+ (element) => element.dataset.fieldId === focusFieldId
1390
+ );
1391
+ const control = fieldContainer?.querySelector("input, select, textarea");
1392
+ if (control !== void 0 && control !== null) {
1393
+ control.focus();
1394
+ setFocusFieldId(null);
1395
+ }
1396
+ }, [focusFieldId]);
1397
+ (0, import_react3.useEffect)(() => {
1398
+ if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
1399
+ const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
1400
+ if (loadedDraftKey.current === loadIdentity) return;
1401
+ loadedDraftKey.current = loadIdentity;
1402
+ const serialized = globalThis.localStorage.getItem(autoSaveKey);
1403
+ if (serialized === null) return;
1404
+ const draft = parseDraft(serialized);
1405
+ if (draft === null || draft.formId !== form.schema.id || draft.formVersion !== form.schema.version) return;
1406
+ form.restoreValues(draft.values);
1407
+ setDraftRestored(true);
1408
+ }, [autoSaveKey, form.restoreValues, form.schema.id, form.schema.version]);
1409
+ (0, import_react3.useEffect)(() => {
1410
+ if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
1411
+ if (form.submitStatus === "success") return;
1412
+ const timeout = globalThis.setTimeout(() => {
1413
+ const draft = {
1414
+ formId: form.schema.id,
1415
+ formVersion: form.schema.version,
1416
+ values: form.values,
1417
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1418
+ };
1419
+ globalThis.localStorage.setItem(autoSaveKey, JSON.stringify(draft));
1420
+ }, 500);
1421
+ return () => globalThis.clearTimeout(timeout);
1422
+ }, [autoSaveKey, form.schema.id, form.schema.version, form.submitStatus, form.values]);
1423
+ const focusFirstIssue = (fieldId) => {
1424
+ if (fieldId !== void 0) setFocusFieldId(fieldId);
1425
+ };
1426
+ const handleNext = () => {
1427
+ const result = form.validatePage(currentPageIndex);
1428
+ if (!result.valid) {
1429
+ focusFirstIssue(result.issues[0]?.fieldId);
1430
+ return;
1431
+ }
1432
+ const nextPageIndex = visiblePageIndexes[activeVisibleIndex + 1];
1433
+ if (nextPageIndex !== void 0) setCurrentPageIndex(nextPageIndex);
1434
+ };
792
1435
  const handleSubmit = async (event) => {
793
1436
  event.preventDefault();
794
- const formElement = event.currentTarget;
795
1437
  const validation = (0, import_core3.validateAnswers)(form.schema, form.values);
796
1438
  const firstInvalidFieldId = validation.issues[0]?.fieldId;
797
1439
  const valid = await form.submit();
798
- if (!valid && firstInvalidFieldId !== void 0) {
799
- queueMicrotask(() => {
800
- const fieldContainer = [...formElement.querySelectorAll("[data-field-id]")].find(
801
- (element) => element.dataset.fieldId === firstInvalidFieldId
802
- );
803
- fieldContainer?.querySelector("input, select, textarea")?.focus();
804
- });
1440
+ if (!valid) {
1441
+ const invalidPageIndex = pages?.findIndex(
1442
+ (page) => firstInvalidFieldId === void 0 ? false : page.questionIds.includes(firstInvalidFieldId)
1443
+ );
1444
+ if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
1445
+ focusFirstIssue(firstInvalidFieldId);
1446
+ return;
805
1447
  }
1448
+ if (autoSaveKey !== void 0 && typeof globalThis.localStorage !== "undefined") {
1449
+ globalThis.localStorage.removeItem(autoSaveKey);
1450
+ setDraftRestored(false);
1451
+ }
1452
+ setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
806
1453
  };
807
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
1454
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
808
1455
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "fe-header", children: [
809
1456
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { children: form.schema.title }),
810
- form.schema.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: form.schema.description })
1457
+ form.schema.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: form.schema.description }),
1458
+ pages === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-progress", children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1460
+ "div",
1461
+ {
1462
+ className: "form-progress-bar",
1463
+ role: "progressbar",
1464
+ "aria-valuemin": 1,
1465
+ "aria-valuemax": visiblePageIndexes.length,
1466
+ "aria-valuenow": activeVisibleIndex + 1,
1467
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1468
+ "div",
1469
+ {
1470
+ className: "form-progress-fill",
1471
+ style: { width: `${(activeVisibleIndex + 1) / visiblePageIndexes.length * 100}%` }
1472
+ }
1473
+ )
1474
+ }
1475
+ ),
1476
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: form.translate("form.step", { current: activeVisibleIndex + 1, total: visiblePageIndexes.length }) })
1477
+ ] }),
1478
+ draftRestored ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
811
1479
  ] }),
812
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fe-fields", children: form.schema.fields.filter((field) => form.visibility[field.id] === true).map((field) => {
1480
+ activePage?.title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "fe-page-title", children: activePage.title }),
1481
+ activePage?.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fe-page-description", children: activePage.description }),
1482
+ /* @__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) => {
813
1483
  const props = {
814
1484
  field,
815
1485
  value: form.values[field.id],
@@ -823,7 +1493,18 @@ function FormRenderer({
823
1493
  const Component = components[field.type];
824
1494
  return Component === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultField, { ...props }, field.id) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props }, field.id);
825
1495
  }) }),
826
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") }),
1496
+ pages === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "form-step-navigation", children: [
1497
+ activeVisibleIndex > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1498
+ "button",
1499
+ {
1500
+ className: "btn-prev",
1501
+ type: "button",
1502
+ onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
1503
+ children: form.translate("form.back")
1504
+ }
1505
+ ) : null,
1506
+ activeVisibleIndex < visiblePageIndexes.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "btn-next", type: "button", onClick: handleNext, children: form.translate("form.next") }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "fe-submit", type: "submit", disabled: form.isSubmitting, children: form.translate(form.schema.submitLabelKey ?? "form.submit") })
1507
+ ] }),
827
1508
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
828
1509
  form.submitStatus === "success" && successMessageKey !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "status", children: form.translate(successMessageKey) }) : null,
829
1510
  form.submitStatus === "error" && form.submitError !== null && errorMessageKey !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: form.translate(errorMessageKey) }) : null