@form-engine-ts/react 2.9.4 → 2.9.6

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.js CHANGED
@@ -1058,7 +1058,7 @@ function BuilderButton(props) {
1058
1058
  ...props,
1059
1059
  className: primitiveClassName(props.className, "feb-button", context.unstyled),
1060
1060
  disabled: context.readOnly || props.disabled === true,
1061
- readOnly: context.readOnly
1061
+ readOnly: context.readOnly || props.readOnly === true
1062
1062
  }
1063
1063
  );
1064
1064
  }
@@ -1073,7 +1073,7 @@ function BuilderIconButton(props) {
1073
1073
  icon,
1074
1074
  className: primitiveClassName(props.className, "feb-icon-button", context.unstyled),
1075
1075
  disabled: context.readOnly || props.disabled === true,
1076
- readOnly: context.readOnly
1076
+ readOnly: context.readOnly || props.readOnly === true
1077
1077
  }
1078
1078
  );
1079
1079
  }
@@ -1086,7 +1086,7 @@ function BuilderTextInput(props) {
1086
1086
  ...props,
1087
1087
  className: primitiveClassName(props.className, "feb-input", context.unstyled),
1088
1088
  disabled: context.readOnly || props.disabled === true,
1089
- readOnly: context.readOnly
1089
+ readOnly: context.readOnly || props.readOnly === true
1090
1090
  }
1091
1091
  );
1092
1092
  }
@@ -1099,7 +1099,7 @@ function BuilderTextArea(props) {
1099
1099
  ...props,
1100
1100
  className: primitiveClassName(props.className, "feb-textarea", context.unstyled),
1101
1101
  disabled: context.readOnly || props.disabled === true,
1102
- readOnly: context.readOnly
1102
+ readOnly: context.readOnly || props.readOnly === true
1103
1103
  }
1104
1104
  );
1105
1105
  }
@@ -1112,7 +1112,7 @@ function BuilderSelect(props) {
1112
1112
  ...props,
1113
1113
  className: primitiveClassName(props.className, "feb-select", context.unstyled),
1114
1114
  disabled: context.readOnly || props.disabled === true,
1115
- readOnly: context.readOnly
1115
+ readOnly: context.readOnly || props.readOnly === true
1116
1116
  }
1117
1117
  );
1118
1118
  }
@@ -1125,7 +1125,7 @@ function BuilderCheckbox(props) {
1125
1125
  ...props,
1126
1126
  className: primitiveClassName(props.className, "feb-checkbox", context.unstyled),
1127
1127
  disabled: context.readOnly || props.disabled === true,
1128
- readOnly: context.readOnly
1128
+ readOnly: context.readOnly || props.readOnly === true
1129
1129
  }
1130
1130
  );
1131
1131
  }
@@ -1271,6 +1271,59 @@ function fieldTypeKey(type) {
1271
1271
  function operatorKey(operator) {
1272
1272
  return `builder.operator.${operator}`;
1273
1273
  }
1274
+ function manualTranslationContext(schema, locale, target, property, translatedText) {
1275
+ if (target.kind === "form") {
1276
+ if (property !== "title" && property !== "description" && property !== "completionMessage") return void 0;
1277
+ return {
1278
+ locale,
1279
+ kind: "form",
1280
+ nodeId: schema.id,
1281
+ property,
1282
+ sourceText: schema[property] ?? "",
1283
+ translatedText,
1284
+ ...schema.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[locale]?.[property] }
1285
+ };
1286
+ }
1287
+ if (target.id === void 0) return void 0;
1288
+ if (target.kind === "field") {
1289
+ const field = schema.fields.find((candidate) => candidate.id === target.id);
1290
+ if (field === void 0 || property !== "title" && property !== "description") return void 0;
1291
+ return {
1292
+ locale,
1293
+ kind: "field",
1294
+ nodeId: field.id,
1295
+ property,
1296
+ sourceText: field[property] ?? "",
1297
+ translatedText,
1298
+ ...field.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: field.translationMetadata[locale]?.[property] }
1299
+ };
1300
+ }
1301
+ if (target.kind === "page") {
1302
+ const page = schema.pages?.find((candidate) => candidate.id === target.id);
1303
+ if (page === void 0 || property !== "title" && property !== "description") return void 0;
1304
+ return {
1305
+ locale,
1306
+ kind: "page",
1307
+ nodeId: page.id,
1308
+ property,
1309
+ sourceText: page[property] ?? "",
1310
+ translatedText,
1311
+ ...page.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: page.translationMetadata[locale]?.[property] }
1312
+ };
1313
+ }
1314
+ if (property !== "label") return void 0;
1315
+ const option = schema.fields.filter((field) => "options" in field).flatMap((field) => field.options).find((candidate) => candidate.id === target.id);
1316
+ if (option === void 0) return void 0;
1317
+ return {
1318
+ locale,
1319
+ kind: "option",
1320
+ nodeId: option.id,
1321
+ property,
1322
+ sourceText: option.label,
1323
+ translatedText,
1324
+ ...option.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: option.translationMetadata[locale]?.[property] }
1325
+ };
1326
+ }
1274
1327
  function defaultConditionValue(field) {
1275
1328
  if (field.type === "checkbox") return true;
1276
1329
  if (field.type === "number" || field.type === "rating") return field.min ?? 1;
@@ -1529,25 +1582,30 @@ function FormBuilder({
1529
1582
  setIsTranslating(false);
1530
1583
  }
1531
1584
  };
1532
- const updateManualTranslation = (context) => {
1533
- if (readOnly) return;
1534
- const metadata = createManualTranslationMetadata?.(context);
1535
- const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
1536
- executeAction(
1585
+ const setManualTranslation = (targetLocale, target, property, text) => {
1586
+ if (readOnly) return { success: true };
1587
+ const normalizedLocale = targetLocale.trim();
1588
+ const context = manualTranslationContext(schema, normalizedLocale, target, property, text);
1589
+ const metadata = context === void 0 ? void 0 : createManualTranslationMetadata?.(context);
1590
+ return executeAction(
1537
1591
  () => headless.setLocaleTranslation(
1538
- context.locale,
1592
+ normalizedLocale,
1539
1593
  target,
1540
- context.property,
1541
- context.translatedText,
1594
+ property,
1595
+ text,
1542
1596
  metadata === void 0 ? void 0 : { metadata }
1543
1597
  ),
1544
1598
  {
1545
- action: "setLocaleTranslation",
1546
- targetId: context.nodeId,
1547
- params: { locale: context.locale, kind: context.kind, property: context.property }
1599
+ action: "setManualTranslation",
1600
+ ...target.id === void 0 ? {} : { targetId: target.id },
1601
+ params: { locale: normalizedLocale, kind: target.kind, property }
1548
1602
  }
1549
1603
  );
1550
1604
  };
1605
+ const updateManualTranslation = (context) => {
1606
+ const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
1607
+ setManualTranslation(context.locale, target, context.property, context.translatedText);
1608
+ };
1551
1609
  const updateFormTranslation = (property, translatedText) => {
1552
1610
  if (editingLocale.length === 0) return;
1553
1611
  updateManualTranslation({
@@ -1656,6 +1714,7 @@ function FormBuilder({
1656
1714
  ...target.id === void 0 ? {} : { targetId: target.id },
1657
1715
  params: { locale: targetLocale, property }
1658
1716
  }),
1717
+ setManualTranslation,
1659
1718
  addLocale: (targetLocale) => executeAction(() => headless.addLocale(targetLocale), {
1660
1719
  action: "addLocale",
1661
1720
  params: { locale: targetLocale }
@@ -2576,6 +2635,17 @@ function FormProvider({
2576
2635
  },
2577
2636
  [validSchema, validationPageIndex]
2578
2637
  );
2638
+ const setServerErrors = useCallback2((fieldErrors) => {
2639
+ setErrors(
2640
+ Object.fromEntries(
2641
+ Object.entries(fieldErrors).map(([fieldId, message]) => [
2642
+ fieldId,
2643
+ { fieldId, code: "invalid_type", messageKey: message, params: {} }
2644
+ ])
2645
+ )
2646
+ );
2647
+ setValidationPageIndex(null);
2648
+ }, []);
2579
2649
  const restoreValues = useCallback2(
2580
2650
  (restoredValues) => {
2581
2651
  const fieldIds = new Set(validSchema.fields.map((field) => field.id));
@@ -2660,6 +2730,7 @@ function FormProvider({
2660
2730
  submitError,
2661
2731
  isSubmitting: submitStatus === "submitting",
2662
2732
  setValue,
2733
+ setServerErrors,
2663
2734
  restoreValues,
2664
2735
  validatePage,
2665
2736
  reset,
@@ -2673,6 +2744,7 @@ function FormProvider({
2673
2744
  reset,
2674
2745
  restoreValues,
2675
2746
  setValue,
2747
+ setServerErrors,
2676
2748
  submit,
2677
2749
  submitError,
2678
2750
  submitStatus,
@@ -2823,12 +2895,25 @@ import {
2823
2895
  } from "@form-engine-ts/core";
2824
2896
  import {
2825
2897
  Fragment as Fragment2,
2898
+ useCallback as useCallback3,
2826
2899
  useEffect as useEffect3,
2827
2900
  useId,
2828
2901
  useMemo as useMemo4,
2829
2902
  useRef as useRef2,
2830
2903
  useState as useState4
2831
2904
  } from "react";
2905
+
2906
+ // src/types.ts
2907
+ var FormSubmissionError = class extends Error {
2908
+ payload;
2909
+ constructor(message, payload) {
2910
+ super(message);
2911
+ this.name = "FormSubmissionError";
2912
+ this.payload = payload ?? { formError: message };
2913
+ }
2914
+ };
2915
+
2916
+ // src/renderer.tsx
2832
2917
  import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2833
2918
  function describedBy(field, error, helpId, errorId) {
2834
2919
  const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
@@ -3025,6 +3110,30 @@ function isRecord(value) {
3025
3110
  function isFormValue(value) {
3026
3111
  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");
3027
3112
  }
3113
+ function displaySubmittedValue(field, value, translate) {
3114
+ if (value === void 0 || value === null) return "";
3115
+ if (field.type === "checkbox") return value === true ? translate("form.yes") : translate("form.no");
3116
+ if (field.type === "multi-select" && Array.isArray(value)) {
3117
+ const labels = new Map(field.options.map((option) => [option.id, option.label]));
3118
+ return value.map((item) => labels.get(item) ?? item).join(", ");
3119
+ }
3120
+ if ((field.type === "radio" || field.type === "select") && typeof value === "string") {
3121
+ return field.options.find((option) => option.id === value)?.label ?? value;
3122
+ }
3123
+ if (Array.isArray(value)) return value.join(", ");
3124
+ return String(value);
3125
+ }
3126
+ function buildSubmittedItems(schema, answers, visibility, translate, showHiddenFields) {
3127
+ return schema.fields.filter((field) => showHiddenFields || visibility[field.id] === true).map((field) => ({
3128
+ fieldId: field.id,
3129
+ title: field.title,
3130
+ type: field.type,
3131
+ rawValue: answers[field.id],
3132
+ displayValue: displaySubmittedValue(field, answers[field.id], translate),
3133
+ visible: visibility[field.id] === true,
3134
+ ...field.metadata === void 0 ? {} : { metadata: field.metadata }
3135
+ }));
3136
+ }
3028
3137
  function parseDraft(serialized) {
3029
3138
  try {
3030
3139
  const value = JSON.parse(serialized);
@@ -3051,6 +3160,11 @@ function ContextFormRenderer({
3051
3160
  autoSaveKey,
3052
3161
  beforeSubmit,
3053
3162
  onDraftSave,
3163
+ successRenderMode = "append",
3164
+ submissionConfirmationRenderMode = "inline",
3165
+ showHiddenFieldsInSummary = false,
3166
+ fieldsClassName,
3167
+ hideFormOnSuccess = false,
3054
3168
  submissionGuards = [],
3055
3169
  receiptStore,
3056
3170
  attemptStore,
@@ -3066,9 +3180,13 @@ function ContextFormRenderer({
3066
3180
  const [focusFieldId, setFocusFieldId] = useState4(null);
3067
3181
  const [confirmation, setConfirmation] = useState4(null);
3068
3182
  const [guardMessage, setGuardMessage] = useState4(null);
3183
+ const [guardsPending, setGuardsPending] = useState4(false);
3069
3184
  const [receipt, setReceipt] = useState4(null);
3185
+ const [completionData, setCompletionData] = useState4(null);
3070
3186
  const [receiptLoaded, setReceiptLoaded] = useState4(receiptStore === void 0);
3071
3187
  const rendererSubmissionInFlight = useRef2(false);
3188
+ const completionRef = useRef2(null);
3189
+ const confirmationRef = useRef2(null);
3072
3190
  const pages = form.schema.pages;
3073
3191
  const visiblePageIndexes = useMemo4(
3074
3192
  () => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
@@ -3078,8 +3196,13 @@ function ContextFormRenderer({
3078
3196
  const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
3079
3197
  const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
3080
3198
  const visibleValues = useMemo4(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
3081
- const submitState = confirmation === null ? form.submitStatus : "confirming";
3199
+ const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
3082
3200
  const interactionLocked = submitState === "confirming" || submitState === "submitting";
3201
+ const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
3202
+ const focusSubmitButton = useCallback3(() => {
3203
+ const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
3204
+ button?.focus();
3205
+ }, []);
3083
3206
  useEffect3(() => {
3084
3207
  let active = true;
3085
3208
  if (receiptStore === void 0) {
@@ -3119,6 +3242,25 @@ function ContextFormRenderer({
3119
3242
  setFocusFieldId(null);
3120
3243
  }
3121
3244
  }, [focusFieldId]);
3245
+ useEffect3(() => {
3246
+ if (!isReplaceMode || form.submitStatus !== "success") return;
3247
+ completionRef.current?.focus();
3248
+ }, [form.submitStatus, isReplaceMode]);
3249
+ useEffect3(() => {
3250
+ if (confirmation === null) return;
3251
+ const confirmButton = confirmationRef.current?.querySelector("[data-fe-confirm], button");
3252
+ confirmButton?.focus();
3253
+ if (submissionConfirmationRenderMode !== "dialog") return;
3254
+ const onKeyDown = (event) => {
3255
+ if (event.key === "Escape") {
3256
+ event.preventDefault();
3257
+ setConfirmation(null);
3258
+ globalThis.setTimeout(focusSubmitButton, 0);
3259
+ }
3260
+ };
3261
+ globalThis.addEventListener("keydown", onKeyDown);
3262
+ return () => globalThis.removeEventListener("keydown", onKeyDown);
3263
+ }, [confirmation, focusSubmitButton, submissionConfirmationRenderMode]);
3122
3264
  useEffect3(() => {
3123
3265
  if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
3124
3266
  const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
@@ -3184,13 +3326,14 @@ function ContextFormRenderer({
3184
3326
  };
3185
3327
  };
3186
3328
  const submitValues = async (guardsConfirmed = false) => {
3187
- if (rendererSubmissionInFlight.current || confirmation !== null && !guardsConfirmed) {
3329
+ if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
3188
3330
  return { status: "cancelled" };
3189
3331
  }
3190
3332
  const validation = validateAnswers2(form.schema, form.values);
3191
3333
  const firstInvalidFieldId = validation.issues[0]?.fieldId;
3192
3334
  if (validation.valid && !guardsConfirmed && submissionGuards.length > 0) {
3193
3335
  rendererSubmissionInFlight.current = true;
3336
+ setGuardsPending(true);
3194
3337
  try {
3195
3338
  const guardResult = await runSubmissionGuards(submissionGuards);
3196
3339
  if (guardResult.status === "block") {
@@ -3207,6 +3350,7 @@ function ContextFormRenderer({
3207
3350
  }
3208
3351
  } finally {
3209
3352
  rendererSubmissionInFlight.current = false;
3353
+ setGuardsPending(false);
3210
3354
  }
3211
3355
  }
3212
3356
  setGuardMessage(null);
@@ -3232,7 +3376,33 @@ function ContextFormRenderer({
3232
3376
  focusFirstIssue(firstInvalidFieldId);
3233
3377
  return result;
3234
3378
  }
3379
+ if (result.status === "error") {
3380
+ if (result.error instanceof FormSubmissionError) {
3381
+ const fieldErrors = result.error.payload.fieldErrors ?? {};
3382
+ form.setServerErrors?.(fieldErrors);
3383
+ const firstServerFieldId = Object.keys(fieldErrors)[0];
3384
+ if (firstServerFieldId !== void 0) {
3385
+ const invalidPageIndex = pages?.findIndex((page) => page.questionIds.includes(firstServerFieldId));
3386
+ if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
3387
+ focusFirstIssue(firstServerFieldId);
3388
+ }
3389
+ }
3390
+ return result;
3391
+ }
3235
3392
  if (result.status !== "success") return result;
3393
+ const submittedAnswers = { ...form.values };
3394
+ const submittedItems = buildSubmittedItems(
3395
+ form.schema,
3396
+ submittedAnswers,
3397
+ form.visibility,
3398
+ (key) => form.translate(key),
3399
+ showHiddenFieldsInSummary
3400
+ );
3401
+ setCompletionData({
3402
+ answers: submittedAnswers,
3403
+ submittedItems,
3404
+ ...result.response === void 0 ? {} : { response: result.response }
3405
+ });
3236
3406
  if (receiptStore !== void 0) {
3237
3407
  const response = result.response;
3238
3408
  const submissionId = response?.submissionId ?? submissionAttempt?.attemptId;
@@ -3244,7 +3414,6 @@ function ContextFormRenderer({
3244
3414
  };
3245
3415
  try {
3246
3416
  await receiptStore.save(storedReceipt);
3247
- setReceipt(storedReceipt);
3248
3417
  } catch (cause) {
3249
3418
  const error = cause instanceof Error ? cause : new Error(String(cause));
3250
3419
  try {
@@ -3271,7 +3440,7 @@ function ContextFormRenderer({
3271
3440
  };
3272
3441
  const handleSubmit = (event) => {
3273
3442
  event.preventDefault();
3274
- if (interactionLocked) return;
3443
+ if (submitState === "submitting" || submitState === "confirming" || submitState === "success") return;
3275
3444
  void submitValues();
3276
3445
  };
3277
3446
  const confirmSubmission = () => {
@@ -3280,6 +3449,7 @@ function ContextFormRenderer({
3280
3449
  };
3281
3450
  const cancelSubmission = () => {
3282
3451
  setConfirmation(null);
3452
+ globalThis.setTimeout(focusSubmitButton, 0);
3283
3453
  };
3284
3454
  const resetReceipt = async () => {
3285
3455
  if (receiptStore === void 0) return;
@@ -3290,7 +3460,58 @@ function ContextFormRenderer({
3290
3460
  const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
3291
3461
  const canPrev = pages !== void 0 && activeVisibleIndex > 0;
3292
3462
  const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
3293
- const renderSubmitButton = () => slots.renderSubmitButton?.({ isSubmitting: interactionLocked, onSubmit: () => void submitValues() }) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: interactionLocked, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
3463
+ const renderSubmitButton = () => {
3464
+ const submitButtonProps = {
3465
+ isSubmitting: submitState === "submitting",
3466
+ submitStatus: submitState,
3467
+ disabled: interactionLocked || submitState === "success",
3468
+ onSubmit: () => void submitValues()
3469
+ };
3470
+ return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: submitButtonProps.disabled, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
3471
+ };
3472
+ const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
3473
+ const activeCompletionData = completionData ?? {
3474
+ answers: { ...form.values },
3475
+ submittedItems: buildSubmittedItems(
3476
+ form.schema,
3477
+ form.values,
3478
+ form.visibility,
3479
+ (key) => form.translate(key),
3480
+ showHiddenFieldsInSummary
3481
+ )
3482
+ };
3483
+ const completionProps = {
3484
+ message: completionMessage,
3485
+ schema: form.schema,
3486
+ answers: activeCompletionData.answers,
3487
+ submittedItems: activeCompletionData.submittedItems,
3488
+ ...activeCompletionData.response === void 0 ? {} : { response: activeCompletionData.response },
3489
+ onReset: form.reset
3490
+ };
3491
+ const completionRegion = /* @__PURE__ */ jsxs2("div", { ref: completionRef, className: "fe-completion", role: "status", "aria-live": "polite", tabIndex: -1, children: [
3492
+ slots.renderCompletion?.(completionProps) ?? /* @__PURE__ */ jsx3("div", { children: completionMessage }),
3493
+ slots.renderSubmittedValues?.({ items: activeCompletionData.submittedItems, schema: form.schema })
3494
+ ] });
3495
+ const confirmationContent = /* @__PURE__ */ jsx3(
3496
+ "div",
3497
+ {
3498
+ ref: confirmationRef,
3499
+ className: "fe-submission-confirmation",
3500
+ role: submissionConfirmationRenderMode === "dialog" ? void 0 : "dialog",
3501
+ children: slots.renderSubmissionConfirmation?.({
3502
+ findings: confirmation?.findings ?? [],
3503
+ message: confirmation?.message ?? form.translate("form.confirmSensitiveData"),
3504
+ schema: form.schema,
3505
+ visibleValues,
3506
+ onConfirm: confirmSubmission,
3507
+ onCancel: cancelSubmission
3508
+ }) ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
3509
+ /* @__PURE__ */ jsx3("p", { children: confirmation?.message ?? form.translate("form.confirmSensitiveData") }),
3510
+ /* @__PURE__ */ jsx3("button", { type: "button", "data-fe-confirm": "true", onClick: confirmSubmission, children: form.translate("form.confirmSubmission") }),
3511
+ /* @__PURE__ */ jsx3("button", { type: "button", onClick: cancelSubmission, children: form.translate("form.cancelSubmission") })
3512
+ ] })
3513
+ }
3514
+ );
3294
3515
  if (!receiptLoaded) return null;
3295
3516
  if (receipt !== null) {
3296
3517
  return /* @__PURE__ */ jsx3("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
@@ -3301,129 +3522,139 @@ function ContextFormRenderer({
3301
3522
  receiptStore === void 0 ? null : /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => void resetReceipt(), children: form.translate("form.submitAnother") })
3302
3523
  ] }) });
3303
3524
  }
3304
- return /* @__PURE__ */ jsxs2("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
3305
- slots.renderHeader?.({
3306
- title: form.schema.title,
3307
- ...form.schema.description === void 0 ? {} : { description: form.schema.description }
3308
- }) ?? /* @__PURE__ */ jsxs2("header", { className: "fe-header", children: [
3309
- /* @__PURE__ */ jsx3("h1", { children: form.schema.title }),
3310
- form.schema.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { children: form.schema.description }),
3311
- pages === void 0 ? null : /* @__PURE__ */ jsxs2("div", { className: "fe-progress", children: [
3312
- /* @__PURE__ */ jsx3(
3313
- "div",
3314
- {
3315
- className: "form-progress-bar",
3316
- role: "progressbar",
3317
- "aria-valuemin": 1,
3318
- "aria-valuemax": visiblePageIndexes.length,
3319
- "aria-valuenow": activeVisibleIndex + 1,
3320
- children: /* @__PURE__ */ jsx3(
3321
- "div",
3322
- {
3323
- className: "form-progress-fill",
3324
- style: { width: `${(activeVisibleIndex + 1) / visiblePageIndexes.length * 100}%` }
3525
+ if (form.submitStatus === "success" && isReplaceMode) {
3526
+ return /* @__PURE__ */ jsx3("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
3527
+ }
3528
+ if (confirmation !== null && submissionConfirmationRenderMode === "replace") {
3529
+ return /* @__PURE__ */ jsx3("div", { className: `fe-form ${className}`.trim(), children: confirmationContent });
3530
+ }
3531
+ return /* @__PURE__ */ jsxs2(Fragment3, { children: [
3532
+ /* @__PURE__ */ jsxs2(
3533
+ "form",
3534
+ {
3535
+ ref: formRef,
3536
+ className: `fe-form ${className}`.trim(),
3537
+ noValidate: true,
3538
+ onSubmit: handleSubmit,
3539
+ "aria-hidden": confirmation !== null && submissionConfirmationRenderMode === "dialog" ? true : void 0,
3540
+ children: [
3541
+ slots.renderHeader?.({
3542
+ title: form.schema.title,
3543
+ ...form.schema.description === void 0 ? {} : { description: form.schema.description }
3544
+ }) ?? /* @__PURE__ */ jsxs2("header", { className: "fe-header", children: [
3545
+ /* @__PURE__ */ jsx3("h1", { children: form.schema.title }),
3546
+ form.schema.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { children: form.schema.description }),
3547
+ pages === void 0 ? null : /* @__PURE__ */ jsxs2("div", { className: "fe-progress", children: [
3548
+ /* @__PURE__ */ jsx3(
3549
+ "div",
3550
+ {
3551
+ className: "form-progress-bar",
3552
+ role: "progressbar",
3553
+ "aria-valuemin": 1,
3554
+ "aria-valuemax": visiblePageIndexes.length,
3555
+ "aria-valuenow": activeVisibleIndex + 1,
3556
+ children: /* @__PURE__ */ jsx3(
3557
+ "div",
3558
+ {
3559
+ className: "form-progress-fill",
3560
+ style: { width: `${(activeVisibleIndex + 1) / visiblePageIndexes.length * 100}%` }
3561
+ }
3562
+ )
3563
+ }
3564
+ ),
3565
+ /* @__PURE__ */ jsx3("span", { children: form.translate("form.step", { current: activeVisibleIndex + 1, total: visiblePageIndexes.length }) })
3566
+ ] }),
3567
+ draftRestored ? /* @__PURE__ */ jsx3("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
3568
+ ] }),
3569
+ activePage === void 0 ? null : slots.renderPageHeader?.({
3570
+ page: activePage,
3571
+ pageIndex: activeVisibleIndex,
3572
+ totalPages: visiblePageIndexes.length
3573
+ }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-page-header", children: [
3574
+ activePage.title === void 0 ? null : /* @__PURE__ */ jsx3("h2", { className: "fe-page-title", children: activePage.title }),
3575
+ activePage.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { className: "fe-page-description", children: activePage.description })
3576
+ ] }),
3577
+ (() => {
3578
+ const fieldChildren = form.schema.fields.filter((field) => form.visibility[field.id] === true && (fieldIds === void 0 || fieldIds.has(field.id))).map((field) => {
3579
+ const error = form.errors[field.id];
3580
+ const props = {
3581
+ field,
3582
+ value: form.values[field.id],
3583
+ error,
3584
+ setValue: (value) => form.setValue(field.id, value),
3585
+ translate: form.translate,
3586
+ inputId: `${prefix}-${field.id}`,
3587
+ errorId: `${prefix}-${field.id}-error`,
3588
+ helpId: `${prefix}-${field.id}-help`,
3589
+ ...slots.renderCharacterCount === void 0 ? {} : { renderCharacterCount: slots.renderCharacterCount }
3590
+ };
3591
+ if (slots.renderField !== void 0) {
3592
+ return /* @__PURE__ */ jsx3(Fragment2, { children: slots.renderField({
3593
+ question: field,
3594
+ value: form.values[field.id],
3595
+ onChange: (value) => {
3596
+ if (isFormValue(value)) form.setValue(field.id, value);
3597
+ },
3598
+ ...error === void 0 ? {} : { error }
3599
+ }) }, field.id);
3325
3600
  }
3326
- )
3327
- }
3328
- ),
3329
- /* @__PURE__ */ jsx3("span", { children: form.translate("form.step", { current: activeVisibleIndex + 1, total: visiblePageIndexes.length }) })
3330
- ] }),
3331
- draftRestored ? /* @__PURE__ */ jsx3("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
3332
- ] }),
3333
- activePage === void 0 ? null : slots.renderPageHeader?.({
3334
- page: activePage,
3335
- pageIndex: activeVisibleIndex,
3336
- totalPages: visiblePageIndexes.length
3337
- }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-page-header", children: [
3338
- activePage.title === void 0 ? null : /* @__PURE__ */ jsx3("h2", { className: "fe-page-title", children: activePage.title }),
3339
- activePage.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { className: "fe-page-description", children: activePage.description })
3340
- ] }),
3341
- /* @__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) => {
3342
- const error = form.errors[field.id];
3343
- const props = {
3344
- field,
3345
- value: form.values[field.id],
3346
- error,
3347
- setValue: (value) => form.setValue(field.id, value),
3348
- translate: form.translate,
3349
- inputId: `${prefix}-${field.id}`,
3350
- errorId: `${prefix}-${field.id}-error`,
3351
- helpId: `${prefix}-${field.id}-help`,
3352
- ...slots.renderCharacterCount === void 0 ? {} : { renderCharacterCount: slots.renderCharacterCount }
3353
- };
3354
- if (slots.renderField !== void 0) {
3355
- return /* @__PURE__ */ jsx3(Fragment2, { children: slots.renderField({
3356
- question: field,
3357
- value: form.values[field.id],
3358
- onChange: (value) => {
3359
- if (isFormValue(value)) form.setValue(field.id, value);
3360
- },
3361
- ...error === void 0 ? {} : { error }
3362
- }) }, field.id);
3601
+ const Component = components[field.type];
3602
+ return Component === void 0 ? /* @__PURE__ */ jsx3(DefaultField, { ...props }, field.id) : /* @__PURE__ */ jsx3(Component, { ...props }, field.id);
3603
+ });
3604
+ const fieldClassName = `fe-fields${fieldsClassName === void 0 ? "" : ` ${fieldsClassName}`}`;
3605
+ return slots.renderFields?.({ children: fieldChildren, className: fieldClassName }) ?? /* @__PURE__ */ jsx3("div", { className: fieldClassName, children: fieldChildren });
3606
+ })(),
3607
+ guardMessage === null ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: guardMessage }),
3608
+ confirmation !== null && submissionConfirmationRenderMode === "inline" ? confirmationContent : null,
3609
+ validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-validation-summary", role: "alert", children: [
3610
+ validationIssues.length,
3611
+ " validation error",
3612
+ validationIssues.length === 1 ? "" : "s",
3613
+ "."
3614
+ ] }),
3615
+ pages === void 0 ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
3616
+ slots.renderNavigation?.({
3617
+ currentPage: 0,
3618
+ totalPages: 1,
3619
+ canPrev: false,
3620
+ canNext: false,
3621
+ onPrev: () => void 0,
3622
+ onNext: () => void 0
3623
+ }),
3624
+ renderSubmitButton()
3625
+ ] }) : /* @__PURE__ */ jsxs2("div", { className: "form-step-navigation", children: [
3626
+ slots.renderNavigation?.({
3627
+ currentPage: activeVisibleIndex,
3628
+ totalPages: visiblePageIndexes.length,
3629
+ canPrev,
3630
+ canNext,
3631
+ onPrev: () => {
3632
+ if (!interactionLocked) setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0);
3633
+ },
3634
+ onNext: handleNext
3635
+ }) ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
3636
+ canPrev ? /* @__PURE__ */ jsx3(
3637
+ "button",
3638
+ {
3639
+ className: "btn-prev",
3640
+ type: "button",
3641
+ disabled: interactionLocked,
3642
+ onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
3643
+ children: form.translate("form.back")
3644
+ }
3645
+ ) : null,
3646
+ canNext ? /* @__PURE__ */ jsx3("button", { className: "btn-next", type: "button", disabled: interactionLocked, onClick: handleNext, children: form.translate("form.next") }) : null
3647
+ ] }),
3648
+ canNext ? null : renderSubmitButton()
3649
+ ] }),
3650
+ /* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
3651
+ form.submitStatus === "success" ? completionRegion : null,
3652
+ form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof FormSubmissionError && form.submitError.payload.formError !== void 0 ? /* @__PURE__ */ jsx3("div", { role: "alert", children: form.submitError.payload.formError }) : errorMessageKey === void 0 ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
3653
+ ] })
3654
+ ]
3363
3655
  }
3364
- const Component = components[field.type];
3365
- return Component === void 0 ? /* @__PURE__ */ jsx3(DefaultField, { ...props }, field.id) : /* @__PURE__ */ jsx3(Component, { ...props }, field.id);
3366
- }) }),
3367
- guardMessage === null ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: guardMessage }),
3368
- confirmation === null ? null : slots.renderSubmissionConfirmation?.({
3369
- findings: confirmation.findings,
3370
- message: confirmation.message ?? form.translate("form.confirmSensitiveData"),
3371
- schema: form.schema,
3372
- visibleValues,
3373
- onConfirm: confirmSubmission,
3374
- onCancel: cancelSubmission
3375
- }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-submission-confirmation", role: "dialog", "aria-modal": "true", children: [
3376
- /* @__PURE__ */ jsx3("p", { children: confirmation.message ?? form.translate("form.confirmSensitiveData") }),
3377
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: confirmSubmission, children: form.translate("form.confirmSubmission") }),
3378
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: cancelSubmission, children: form.translate("form.cancelSubmission") })
3379
- ] }),
3380
- validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-validation-summary", role: "alert", children: [
3381
- validationIssues.length,
3382
- " validation error",
3383
- validationIssues.length === 1 ? "" : "s",
3384
- "."
3385
- ] }),
3386
- pages === void 0 ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
3387
- slots.renderNavigation?.({
3388
- currentPage: 0,
3389
- totalPages: 1,
3390
- canPrev: false,
3391
- canNext: false,
3392
- onPrev: () => void 0,
3393
- onNext: () => void 0
3394
- }),
3395
- renderSubmitButton()
3396
- ] }) : /* @__PURE__ */ jsxs2("div", { className: "form-step-navigation", children: [
3397
- slots.renderNavigation?.({
3398
- currentPage: activeVisibleIndex,
3399
- totalPages: visiblePageIndexes.length,
3400
- canPrev,
3401
- canNext,
3402
- onPrev: () => {
3403
- if (!interactionLocked) setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0);
3404
- },
3405
- onNext: handleNext
3406
- }) ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
3407
- canPrev ? /* @__PURE__ */ jsx3(
3408
- "button",
3409
- {
3410
- className: "btn-prev",
3411
- type: "button",
3412
- disabled: interactionLocked,
3413
- onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
3414
- children: form.translate("form.back")
3415
- }
3416
- ) : null,
3417
- canNext ? /* @__PURE__ */ jsx3("button", { className: "btn-next", type: "button", disabled: interactionLocked, onClick: handleNext, children: form.translate("form.next") }) : null
3418
- ] }),
3419
- canNext ? null : renderSubmitButton()
3420
- ] }),
3421
- /* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
3422
- form.submitStatus === "success" ? slots.renderCompletion?.({
3423
- message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
3424
- }) ?? /* @__PURE__ */ jsx3("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
3425
- form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (errorMessageKey === void 0 ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
3426
- ] })
3656
+ ),
3657
+ confirmation !== null && submissionConfirmationRenderMode === "dialog" ? /* @__PURE__ */ jsx3("div", { className: "fe-confirmation-dialog-backdrop", role: "dialog", "aria-modal": "true", children: confirmationContent }) : null
3427
3658
  ] });
3428
3659
  }
3429
3660
  var RENDERER_MESSAGES = {
@@ -3436,6 +3667,8 @@ var RENDERER_MESSAGES = {
3436
3667
  "form.confirmSensitiveData": "Sensitive data may be included. Confirm before submitting.",
3437
3668
  "form.confirmSubmission": "Confirm submission",
3438
3669
  "form.cancelSubmission": "Cancel",
3670
+ "form.yes": "Yes",
3671
+ "form.no": "No",
3439
3672
  "form.alreadySubmitted": "Already submitted.",
3440
3673
  "form.submitAnother": "Submit another response",
3441
3674
  "validation.required": "This field is required."
@@ -3476,6 +3709,7 @@ export {
3476
3709
  FormBuilder,
3477
3710
  FormProvider,
3478
3711
  FormRenderer,
3712
+ FormSubmissionError,
3479
3713
  createLocalStorageSubmissionAttemptStore,
3480
3714
  createLocalStorageSubmissionReceiptStore,
3481
3715
  resolveInitialFieldType,