@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.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  FormBuilder: () => FormBuilder,
24
24
  FormProvider: () => FormProvider,
25
25
  FormRenderer: () => FormRenderer,
26
+ FormSubmissionError: () => FormSubmissionError,
26
27
  createLocalStorageSubmissionAttemptStore: () => createLocalStorageSubmissionAttemptStore,
27
28
  createLocalStorageSubmissionReceiptStore: () => createLocalStorageSubmissionReceiptStore,
28
29
  resolveInitialFieldType: () => resolveInitialFieldType,
@@ -1088,7 +1089,7 @@ function BuilderButton(props) {
1088
1089
  ...props,
1089
1090
  className: primitiveClassName(props.className, "feb-button", context.unstyled),
1090
1091
  disabled: context.readOnly || props.disabled === true,
1091
- readOnly: context.readOnly
1092
+ readOnly: context.readOnly || props.readOnly === true
1092
1093
  }
1093
1094
  );
1094
1095
  }
@@ -1103,7 +1104,7 @@ function BuilderIconButton(props) {
1103
1104
  icon,
1104
1105
  className: primitiveClassName(props.className, "feb-icon-button", context.unstyled),
1105
1106
  disabled: context.readOnly || props.disabled === true,
1106
- readOnly: context.readOnly
1107
+ readOnly: context.readOnly || props.readOnly === true
1107
1108
  }
1108
1109
  );
1109
1110
  }
@@ -1116,7 +1117,7 @@ function BuilderTextInput(props) {
1116
1117
  ...props,
1117
1118
  className: primitiveClassName(props.className, "feb-input", context.unstyled),
1118
1119
  disabled: context.readOnly || props.disabled === true,
1119
- readOnly: context.readOnly
1120
+ readOnly: context.readOnly || props.readOnly === true
1120
1121
  }
1121
1122
  );
1122
1123
  }
@@ -1129,7 +1130,7 @@ function BuilderTextArea(props) {
1129
1130
  ...props,
1130
1131
  className: primitiveClassName(props.className, "feb-textarea", context.unstyled),
1131
1132
  disabled: context.readOnly || props.disabled === true,
1132
- readOnly: context.readOnly
1133
+ readOnly: context.readOnly || props.readOnly === true
1133
1134
  }
1134
1135
  );
1135
1136
  }
@@ -1142,7 +1143,7 @@ function BuilderSelect(props) {
1142
1143
  ...props,
1143
1144
  className: primitiveClassName(props.className, "feb-select", context.unstyled),
1144
1145
  disabled: context.readOnly || props.disabled === true,
1145
- readOnly: context.readOnly
1146
+ readOnly: context.readOnly || props.readOnly === true
1146
1147
  }
1147
1148
  );
1148
1149
  }
@@ -1155,7 +1156,7 @@ function BuilderCheckbox(props) {
1155
1156
  ...props,
1156
1157
  className: primitiveClassName(props.className, "feb-checkbox", context.unstyled),
1157
1158
  disabled: context.readOnly || props.disabled === true,
1158
- readOnly: context.readOnly
1159
+ readOnly: context.readOnly || props.readOnly === true
1159
1160
  }
1160
1161
  );
1161
1162
  }
@@ -1301,6 +1302,59 @@ function fieldTypeKey(type) {
1301
1302
  function operatorKey(operator) {
1302
1303
  return `builder.operator.${operator}`;
1303
1304
  }
1305
+ function manualTranslationContext(schema, locale, target, property, translatedText) {
1306
+ if (target.kind === "form") {
1307
+ if (property !== "title" && property !== "description" && property !== "completionMessage") return void 0;
1308
+ return {
1309
+ locale,
1310
+ kind: "form",
1311
+ nodeId: schema.id,
1312
+ property,
1313
+ sourceText: schema[property] ?? "",
1314
+ translatedText,
1315
+ ...schema.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[locale]?.[property] }
1316
+ };
1317
+ }
1318
+ if (target.id === void 0) return void 0;
1319
+ if (target.kind === "field") {
1320
+ const field = schema.fields.find((candidate) => candidate.id === target.id);
1321
+ if (field === void 0 || property !== "title" && property !== "description") return void 0;
1322
+ return {
1323
+ locale,
1324
+ kind: "field",
1325
+ nodeId: field.id,
1326
+ property,
1327
+ sourceText: field[property] ?? "",
1328
+ translatedText,
1329
+ ...field.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: field.translationMetadata[locale]?.[property] }
1330
+ };
1331
+ }
1332
+ if (target.kind === "page") {
1333
+ const page = schema.pages?.find((candidate) => candidate.id === target.id);
1334
+ if (page === void 0 || property !== "title" && property !== "description") return void 0;
1335
+ return {
1336
+ locale,
1337
+ kind: "page",
1338
+ nodeId: page.id,
1339
+ property,
1340
+ sourceText: page[property] ?? "",
1341
+ translatedText,
1342
+ ...page.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: page.translationMetadata[locale]?.[property] }
1343
+ };
1344
+ }
1345
+ if (property !== "label") return void 0;
1346
+ const option = schema.fields.filter((field) => "options" in field).flatMap((field) => field.options).find((candidate) => candidate.id === target.id);
1347
+ if (option === void 0) return void 0;
1348
+ return {
1349
+ locale,
1350
+ kind: "option",
1351
+ nodeId: option.id,
1352
+ property,
1353
+ sourceText: option.label,
1354
+ translatedText,
1355
+ ...option.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: option.translationMetadata[locale]?.[property] }
1356
+ };
1357
+ }
1304
1358
  function defaultConditionValue(field) {
1305
1359
  if (field.type === "checkbox") return true;
1306
1360
  if (field.type === "number" || field.type === "rating") return field.min ?? 1;
@@ -1559,25 +1613,30 @@ function FormBuilder({
1559
1613
  setIsTranslating(false);
1560
1614
  }
1561
1615
  };
1562
- const updateManualTranslation = (context) => {
1563
- if (readOnly) return;
1564
- const metadata = createManualTranslationMetadata?.(context);
1565
- const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
1566
- executeAction(
1616
+ const setManualTranslation = (targetLocale, target, property, text) => {
1617
+ if (readOnly) return { success: true };
1618
+ const normalizedLocale = targetLocale.trim();
1619
+ const context = manualTranslationContext(schema, normalizedLocale, target, property, text);
1620
+ const metadata = context === void 0 ? void 0 : createManualTranslationMetadata?.(context);
1621
+ return executeAction(
1567
1622
  () => headless.setLocaleTranslation(
1568
- context.locale,
1623
+ normalizedLocale,
1569
1624
  target,
1570
- context.property,
1571
- context.translatedText,
1625
+ property,
1626
+ text,
1572
1627
  metadata === void 0 ? void 0 : { metadata }
1573
1628
  ),
1574
1629
  {
1575
- action: "setLocaleTranslation",
1576
- targetId: context.nodeId,
1577
- params: { locale: context.locale, kind: context.kind, property: context.property }
1630
+ action: "setManualTranslation",
1631
+ ...target.id === void 0 ? {} : { targetId: target.id },
1632
+ params: { locale: normalizedLocale, kind: target.kind, property }
1578
1633
  }
1579
1634
  );
1580
1635
  };
1636
+ const updateManualTranslation = (context) => {
1637
+ const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
1638
+ setManualTranslation(context.locale, target, context.property, context.translatedText);
1639
+ };
1581
1640
  const updateFormTranslation = (property, translatedText) => {
1582
1641
  if (editingLocale.length === 0) return;
1583
1642
  updateManualTranslation({
@@ -1686,6 +1745,7 @@ function FormBuilder({
1686
1745
  ...target.id === void 0 ? {} : { targetId: target.id },
1687
1746
  params: { locale: targetLocale, property }
1688
1747
  }),
1748
+ setManualTranslation,
1689
1749
  addLocale: (targetLocale) => executeAction(() => headless.addLocale(targetLocale), {
1690
1750
  action: "addLocale",
1691
1751
  params: { locale: targetLocale }
@@ -2598,6 +2658,17 @@ function FormProvider({
2598
2658
  },
2599
2659
  [validSchema, validationPageIndex]
2600
2660
  );
2661
+ const setServerErrors = (0, import_react3.useCallback)((fieldErrors) => {
2662
+ setErrors(
2663
+ Object.fromEntries(
2664
+ Object.entries(fieldErrors).map(([fieldId, message]) => [
2665
+ fieldId,
2666
+ { fieldId, code: "invalid_type", messageKey: message, params: {} }
2667
+ ])
2668
+ )
2669
+ );
2670
+ setValidationPageIndex(null);
2671
+ }, []);
2601
2672
  const restoreValues = (0, import_react3.useCallback)(
2602
2673
  (restoredValues) => {
2603
2674
  const fieldIds = new Set(validSchema.fields.map((field) => field.id));
@@ -2682,6 +2753,7 @@ function FormProvider({
2682
2753
  submitError,
2683
2754
  isSubmitting: submitStatus === "submitting",
2684
2755
  setValue,
2756
+ setServerErrors,
2685
2757
  restoreValues,
2686
2758
  validatePage,
2687
2759
  reset,
@@ -2695,6 +2767,7 @@ function FormProvider({
2695
2767
  reset,
2696
2768
  restoreValues,
2697
2769
  setValue,
2770
+ setServerErrors,
2698
2771
  submit,
2699
2772
  submitError,
2700
2773
  submitStatus,
@@ -2841,6 +2914,18 @@ function useSubmissionReceipts(store, queries) {
2841
2914
  // src/renderer.tsx
2842
2915
  var import_core4 = require("@form-engine-ts/core");
2843
2916
  var import_react5 = require("react");
2917
+
2918
+ // src/types.ts
2919
+ var FormSubmissionError = class extends Error {
2920
+ payload;
2921
+ constructor(message, payload) {
2922
+ super(message);
2923
+ this.name = "FormSubmissionError";
2924
+ this.payload = payload ?? { formError: message };
2925
+ }
2926
+ };
2927
+
2928
+ // src/renderer.tsx
2844
2929
  var import_jsx_runtime3 = require("react/jsx-runtime");
2845
2930
  function describedBy(field, error, helpId, errorId) {
2846
2931
  const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
@@ -3037,6 +3122,30 @@ function isRecord(value) {
3037
3122
  function isFormValue(value) {
3038
3123
  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");
3039
3124
  }
3125
+ function displaySubmittedValue(field, value, translate) {
3126
+ if (value === void 0 || value === null) return "";
3127
+ if (field.type === "checkbox") return value === true ? translate("form.yes") : translate("form.no");
3128
+ if (field.type === "multi-select" && Array.isArray(value)) {
3129
+ const labels = new Map(field.options.map((option) => [option.id, option.label]));
3130
+ return value.map((item) => labels.get(item) ?? item).join(", ");
3131
+ }
3132
+ if ((field.type === "radio" || field.type === "select") && typeof value === "string") {
3133
+ return field.options.find((option) => option.id === value)?.label ?? value;
3134
+ }
3135
+ if (Array.isArray(value)) return value.join(", ");
3136
+ return String(value);
3137
+ }
3138
+ function buildSubmittedItems(schema, answers, visibility, translate, showHiddenFields) {
3139
+ return schema.fields.filter((field) => showHiddenFields || visibility[field.id] === true).map((field) => ({
3140
+ fieldId: field.id,
3141
+ title: field.title,
3142
+ type: field.type,
3143
+ rawValue: answers[field.id],
3144
+ displayValue: displaySubmittedValue(field, answers[field.id], translate),
3145
+ visible: visibility[field.id] === true,
3146
+ ...field.metadata === void 0 ? {} : { metadata: field.metadata }
3147
+ }));
3148
+ }
3040
3149
  function parseDraft(serialized) {
3041
3150
  try {
3042
3151
  const value = JSON.parse(serialized);
@@ -3063,6 +3172,11 @@ function ContextFormRenderer({
3063
3172
  autoSaveKey,
3064
3173
  beforeSubmit,
3065
3174
  onDraftSave,
3175
+ successRenderMode = "append",
3176
+ submissionConfirmationRenderMode = "inline",
3177
+ showHiddenFieldsInSummary = false,
3178
+ fieldsClassName,
3179
+ hideFormOnSuccess = false,
3066
3180
  submissionGuards = [],
3067
3181
  receiptStore,
3068
3182
  attemptStore,
@@ -3078,9 +3192,13 @@ function ContextFormRenderer({
3078
3192
  const [focusFieldId, setFocusFieldId] = (0, import_react5.useState)(null);
3079
3193
  const [confirmation, setConfirmation] = (0, import_react5.useState)(null);
3080
3194
  const [guardMessage, setGuardMessage] = (0, import_react5.useState)(null);
3195
+ const [guardsPending, setGuardsPending] = (0, import_react5.useState)(false);
3081
3196
  const [receipt, setReceipt] = (0, import_react5.useState)(null);
3197
+ const [completionData, setCompletionData] = (0, import_react5.useState)(null);
3082
3198
  const [receiptLoaded, setReceiptLoaded] = (0, import_react5.useState)(receiptStore === void 0);
3083
3199
  const rendererSubmissionInFlight = (0, import_react5.useRef)(false);
3200
+ const completionRef = (0, import_react5.useRef)(null);
3201
+ const confirmationRef = (0, import_react5.useRef)(null);
3084
3202
  const pages = form.schema.pages;
3085
3203
  const visiblePageIndexes = (0, import_react5.useMemo)(
3086
3204
  () => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
@@ -3090,8 +3208,13 @@ function ContextFormRenderer({
3090
3208
  const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
3091
3209
  const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
3092
3210
  const visibleValues = (0, import_react5.useMemo)(() => (0, import_core4.selectVisibleAnswers)(form.schema, form.values), [form.schema, form.values]);
3093
- const submitState = confirmation === null ? form.submitStatus : "confirming";
3211
+ const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
3094
3212
  const interactionLocked = submitState === "confirming" || submitState === "submitting";
3213
+ const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
3214
+ const focusSubmitButton = (0, import_react5.useCallback)(() => {
3215
+ const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
3216
+ button?.focus();
3217
+ }, []);
3095
3218
  (0, import_react5.useEffect)(() => {
3096
3219
  let active = true;
3097
3220
  if (receiptStore === void 0) {
@@ -3131,6 +3254,25 @@ function ContextFormRenderer({
3131
3254
  setFocusFieldId(null);
3132
3255
  }
3133
3256
  }, [focusFieldId]);
3257
+ (0, import_react5.useEffect)(() => {
3258
+ if (!isReplaceMode || form.submitStatus !== "success") return;
3259
+ completionRef.current?.focus();
3260
+ }, [form.submitStatus, isReplaceMode]);
3261
+ (0, import_react5.useEffect)(() => {
3262
+ if (confirmation === null) return;
3263
+ const confirmButton = confirmationRef.current?.querySelector("[data-fe-confirm], button");
3264
+ confirmButton?.focus();
3265
+ if (submissionConfirmationRenderMode !== "dialog") return;
3266
+ const onKeyDown = (event) => {
3267
+ if (event.key === "Escape") {
3268
+ event.preventDefault();
3269
+ setConfirmation(null);
3270
+ globalThis.setTimeout(focusSubmitButton, 0);
3271
+ }
3272
+ };
3273
+ globalThis.addEventListener("keydown", onKeyDown);
3274
+ return () => globalThis.removeEventListener("keydown", onKeyDown);
3275
+ }, [confirmation, focusSubmitButton, submissionConfirmationRenderMode]);
3134
3276
  (0, import_react5.useEffect)(() => {
3135
3277
  if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
3136
3278
  const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
@@ -3196,13 +3338,14 @@ function ContextFormRenderer({
3196
3338
  };
3197
3339
  };
3198
3340
  const submitValues = async (guardsConfirmed = false) => {
3199
- if (rendererSubmissionInFlight.current || confirmation !== null && !guardsConfirmed) {
3341
+ if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
3200
3342
  return { status: "cancelled" };
3201
3343
  }
3202
3344
  const validation = (0, import_core4.validateAnswers)(form.schema, form.values);
3203
3345
  const firstInvalidFieldId = validation.issues[0]?.fieldId;
3204
3346
  if (validation.valid && !guardsConfirmed && submissionGuards.length > 0) {
3205
3347
  rendererSubmissionInFlight.current = true;
3348
+ setGuardsPending(true);
3206
3349
  try {
3207
3350
  const guardResult = await runSubmissionGuards(submissionGuards);
3208
3351
  if (guardResult.status === "block") {
@@ -3219,6 +3362,7 @@ function ContextFormRenderer({
3219
3362
  }
3220
3363
  } finally {
3221
3364
  rendererSubmissionInFlight.current = false;
3365
+ setGuardsPending(false);
3222
3366
  }
3223
3367
  }
3224
3368
  setGuardMessage(null);
@@ -3244,7 +3388,33 @@ function ContextFormRenderer({
3244
3388
  focusFirstIssue(firstInvalidFieldId);
3245
3389
  return result;
3246
3390
  }
3391
+ if (result.status === "error") {
3392
+ if (result.error instanceof FormSubmissionError) {
3393
+ const fieldErrors = result.error.payload.fieldErrors ?? {};
3394
+ form.setServerErrors?.(fieldErrors);
3395
+ const firstServerFieldId = Object.keys(fieldErrors)[0];
3396
+ if (firstServerFieldId !== void 0) {
3397
+ const invalidPageIndex = pages?.findIndex((page) => page.questionIds.includes(firstServerFieldId));
3398
+ if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
3399
+ focusFirstIssue(firstServerFieldId);
3400
+ }
3401
+ }
3402
+ return result;
3403
+ }
3247
3404
  if (result.status !== "success") return result;
3405
+ const submittedAnswers = { ...form.values };
3406
+ const submittedItems = buildSubmittedItems(
3407
+ form.schema,
3408
+ submittedAnswers,
3409
+ form.visibility,
3410
+ (key) => form.translate(key),
3411
+ showHiddenFieldsInSummary
3412
+ );
3413
+ setCompletionData({
3414
+ answers: submittedAnswers,
3415
+ submittedItems,
3416
+ ...result.response === void 0 ? {} : { response: result.response }
3417
+ });
3248
3418
  if (receiptStore !== void 0) {
3249
3419
  const response = result.response;
3250
3420
  const submissionId = response?.submissionId ?? submissionAttempt?.attemptId;
@@ -3256,7 +3426,6 @@ function ContextFormRenderer({
3256
3426
  };
3257
3427
  try {
3258
3428
  await receiptStore.save(storedReceipt);
3259
- setReceipt(storedReceipt);
3260
3429
  } catch (cause) {
3261
3430
  const error = cause instanceof Error ? cause : new Error(String(cause));
3262
3431
  try {
@@ -3283,7 +3452,7 @@ function ContextFormRenderer({
3283
3452
  };
3284
3453
  const handleSubmit = (event) => {
3285
3454
  event.preventDefault();
3286
- if (interactionLocked) return;
3455
+ if (submitState === "submitting" || submitState === "confirming" || submitState === "success") return;
3287
3456
  void submitValues();
3288
3457
  };
3289
3458
  const confirmSubmission = () => {
@@ -3292,6 +3461,7 @@ function ContextFormRenderer({
3292
3461
  };
3293
3462
  const cancelSubmission = () => {
3294
3463
  setConfirmation(null);
3464
+ globalThis.setTimeout(focusSubmitButton, 0);
3295
3465
  };
3296
3466
  const resetReceipt = async () => {
3297
3467
  if (receiptStore === void 0) return;
@@ -3302,7 +3472,58 @@ function ContextFormRenderer({
3302
3472
  const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
3303
3473
  const canPrev = pages !== void 0 && activeVisibleIndex > 0;
3304
3474
  const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
3305
- const renderSubmitButton = () => slots.renderSubmitButton?.({ isSubmitting: interactionLocked, onSubmit: () => void submitValues() }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "fe-submit", type: "submit", disabled: interactionLocked, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
3475
+ const renderSubmitButton = () => {
3476
+ const submitButtonProps = {
3477
+ isSubmitting: submitState === "submitting",
3478
+ submitStatus: submitState,
3479
+ disabled: interactionLocked || submitState === "success",
3480
+ onSubmit: () => void submitValues()
3481
+ };
3482
+ return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "fe-submit", type: "submit", disabled: submitButtonProps.disabled, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
3483
+ };
3484
+ const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
3485
+ const activeCompletionData = completionData ?? {
3486
+ answers: { ...form.values },
3487
+ submittedItems: buildSubmittedItems(
3488
+ form.schema,
3489
+ form.values,
3490
+ form.visibility,
3491
+ (key) => form.translate(key),
3492
+ showHiddenFieldsInSummary
3493
+ )
3494
+ };
3495
+ const completionProps = {
3496
+ message: completionMessage,
3497
+ schema: form.schema,
3498
+ answers: activeCompletionData.answers,
3499
+ submittedItems: activeCompletionData.submittedItems,
3500
+ ...activeCompletionData.response === void 0 ? {} : { response: activeCompletionData.response },
3501
+ onReset: form.reset
3502
+ };
3503
+ const completionRegion = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: completionRef, className: "fe-completion", role: "status", "aria-live": "polite", tabIndex: -1, children: [
3504
+ slots.renderCompletion?.(completionProps) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: completionMessage }),
3505
+ slots.renderSubmittedValues?.({ items: activeCompletionData.submittedItems, schema: form.schema })
3506
+ ] });
3507
+ const confirmationContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3508
+ "div",
3509
+ {
3510
+ ref: confirmationRef,
3511
+ className: "fe-submission-confirmation",
3512
+ role: submissionConfirmationRenderMode === "dialog" ? void 0 : "dialog",
3513
+ children: slots.renderSubmissionConfirmation?.({
3514
+ findings: confirmation?.findings ?? [],
3515
+ message: confirmation?.message ?? form.translate("form.confirmSensitiveData"),
3516
+ schema: form.schema,
3517
+ visibleValues,
3518
+ onConfirm: confirmSubmission,
3519
+ onCancel: cancelSubmission
3520
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3521
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: confirmation?.message ?? form.translate("form.confirmSensitiveData") }),
3522
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "data-fe-confirm": "true", onClick: confirmSubmission, children: form.translate("form.confirmSubmission") }),
3523
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: cancelSubmission, children: form.translate("form.cancelSubmission") })
3524
+ ] })
3525
+ }
3526
+ );
3306
3527
  if (!receiptLoaded) return null;
3307
3528
  if (receipt !== null) {
3308
3529
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
@@ -3313,129 +3534,139 @@ function ContextFormRenderer({
3313
3534
  receiptStore === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => void resetReceipt(), children: form.translate("form.submitAnother") })
3314
3535
  ] }) });
3315
3536
  }
3316
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
3317
- slots.renderHeader?.({
3318
- title: form.schema.title,
3319
- ...form.schema.description === void 0 ? {} : { description: form.schema.description }
3320
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "fe-header", children: [
3321
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { children: form.schema.title }),
3322
- form.schema.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: form.schema.description }),
3323
- pages === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-progress", children: [
3324
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3325
- "div",
3326
- {
3327
- className: "form-progress-bar",
3328
- role: "progressbar",
3329
- "aria-valuemin": 1,
3330
- "aria-valuemax": visiblePageIndexes.length,
3331
- "aria-valuenow": activeVisibleIndex + 1,
3332
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3333
- "div",
3334
- {
3335
- className: "form-progress-fill",
3336
- style: { width: `${(activeVisibleIndex + 1) / visiblePageIndexes.length * 100}%` }
3537
+ if (form.submitStatus === "success" && isReplaceMode) {
3538
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
3539
+ }
3540
+ if (confirmation !== null && submissionConfirmationRenderMode === "replace") {
3541
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form ${className}`.trim(), children: confirmationContent });
3542
+ }
3543
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3544
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3545
+ "form",
3546
+ {
3547
+ ref: formRef,
3548
+ className: `fe-form ${className}`.trim(),
3549
+ noValidate: true,
3550
+ onSubmit: handleSubmit,
3551
+ "aria-hidden": confirmation !== null && submissionConfirmationRenderMode === "dialog" ? true : void 0,
3552
+ children: [
3553
+ slots.renderHeader?.({
3554
+ title: form.schema.title,
3555
+ ...form.schema.description === void 0 ? {} : { description: form.schema.description }
3556
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "fe-header", children: [
3557
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { children: form.schema.title }),
3558
+ form.schema.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: form.schema.description }),
3559
+ pages === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-progress", children: [
3560
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3561
+ "div",
3562
+ {
3563
+ className: "form-progress-bar",
3564
+ role: "progressbar",
3565
+ "aria-valuemin": 1,
3566
+ "aria-valuemax": visiblePageIndexes.length,
3567
+ "aria-valuenow": activeVisibleIndex + 1,
3568
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3569
+ "div",
3570
+ {
3571
+ className: "form-progress-fill",
3572
+ style: { width: `${(activeVisibleIndex + 1) / visiblePageIndexes.length * 100}%` }
3573
+ }
3574
+ )
3575
+ }
3576
+ ),
3577
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: form.translate("form.step", { current: activeVisibleIndex + 1, total: visiblePageIndexes.length }) })
3578
+ ] }),
3579
+ draftRestored ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
3580
+ ] }),
3581
+ activePage === void 0 ? null : slots.renderPageHeader?.({
3582
+ page: activePage,
3583
+ pageIndex: activeVisibleIndex,
3584
+ totalPages: visiblePageIndexes.length
3585
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-page-header", children: [
3586
+ activePage.title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "fe-page-title", children: activePage.title }),
3587
+ activePage.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fe-page-description", children: activePage.description })
3588
+ ] }),
3589
+ (() => {
3590
+ const fieldChildren = form.schema.fields.filter((field) => form.visibility[field.id] === true && (fieldIds === void 0 || fieldIds.has(field.id))).map((field) => {
3591
+ const error = form.errors[field.id];
3592
+ const props = {
3593
+ field,
3594
+ value: form.values[field.id],
3595
+ error,
3596
+ setValue: (value) => form.setValue(field.id, value),
3597
+ translate: form.translate,
3598
+ inputId: `${prefix}-${field.id}`,
3599
+ errorId: `${prefix}-${field.id}-error`,
3600
+ helpId: `${prefix}-${field.id}-help`,
3601
+ ...slots.renderCharacterCount === void 0 ? {} : { renderCharacterCount: slots.renderCharacterCount }
3602
+ };
3603
+ if (slots.renderField !== void 0) {
3604
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Fragment, { children: slots.renderField({
3605
+ question: field,
3606
+ value: form.values[field.id],
3607
+ onChange: (value) => {
3608
+ if (isFormValue(value)) form.setValue(field.id, value);
3609
+ },
3610
+ ...error === void 0 ? {} : { error }
3611
+ }) }, field.id);
3337
3612
  }
3338
- )
3339
- }
3340
- ),
3341
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: form.translate("form.step", { current: activeVisibleIndex + 1, total: visiblePageIndexes.length }) })
3342
- ] }),
3343
- draftRestored ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
3344
- ] }),
3345
- activePage === void 0 ? null : slots.renderPageHeader?.({
3346
- page: activePage,
3347
- pageIndex: activeVisibleIndex,
3348
- totalPages: visiblePageIndexes.length
3349
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-page-header", children: [
3350
- activePage.title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "fe-page-title", children: activePage.title }),
3351
- activePage.description === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fe-page-description", children: activePage.description })
3352
- ] }),
3353
- /* @__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) => {
3354
- const error = form.errors[field.id];
3355
- const props = {
3356
- field,
3357
- value: form.values[field.id],
3358
- error,
3359
- setValue: (value) => form.setValue(field.id, value),
3360
- translate: form.translate,
3361
- inputId: `${prefix}-${field.id}`,
3362
- errorId: `${prefix}-${field.id}-error`,
3363
- helpId: `${prefix}-${field.id}-help`,
3364
- ...slots.renderCharacterCount === void 0 ? {} : { renderCharacterCount: slots.renderCharacterCount }
3365
- };
3366
- if (slots.renderField !== void 0) {
3367
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Fragment, { children: slots.renderField({
3368
- question: field,
3369
- value: form.values[field.id],
3370
- onChange: (value) => {
3371
- if (isFormValue(value)) form.setValue(field.id, value);
3372
- },
3373
- ...error === void 0 ? {} : { error }
3374
- }) }, field.id);
3613
+ const Component = components[field.type];
3614
+ return Component === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultField, { ...props }, field.id) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props }, field.id);
3615
+ });
3616
+ const fieldClassName = `fe-fields${fieldsClassName === void 0 ? "" : ` ${fieldsClassName}`}`;
3617
+ return slots.renderFields?.({ children: fieldChildren, className: fieldClassName }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: fieldClassName, children: fieldChildren });
3618
+ })(),
3619
+ guardMessage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: guardMessage }),
3620
+ confirmation !== null && submissionConfirmationRenderMode === "inline" ? confirmationContent : null,
3621
+ validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-validation-summary", role: "alert", children: [
3622
+ validationIssues.length,
3623
+ " validation error",
3624
+ validationIssues.length === 1 ? "" : "s",
3625
+ "."
3626
+ ] }),
3627
+ pages === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3628
+ slots.renderNavigation?.({
3629
+ currentPage: 0,
3630
+ totalPages: 1,
3631
+ canPrev: false,
3632
+ canNext: false,
3633
+ onPrev: () => void 0,
3634
+ onNext: () => void 0
3635
+ }),
3636
+ renderSubmitButton()
3637
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "form-step-navigation", children: [
3638
+ slots.renderNavigation?.({
3639
+ currentPage: activeVisibleIndex,
3640
+ totalPages: visiblePageIndexes.length,
3641
+ canPrev,
3642
+ canNext,
3643
+ onPrev: () => {
3644
+ if (!interactionLocked) setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0);
3645
+ },
3646
+ onNext: handleNext
3647
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3648
+ canPrev ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3649
+ "button",
3650
+ {
3651
+ className: "btn-prev",
3652
+ type: "button",
3653
+ disabled: interactionLocked,
3654
+ onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
3655
+ children: form.translate("form.back")
3656
+ }
3657
+ ) : null,
3658
+ canNext ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "btn-next", type: "button", disabled: interactionLocked, onClick: handleNext, children: form.translate("form.next") }) : null
3659
+ ] }),
3660
+ canNext ? null : renderSubmitButton()
3661
+ ] }),
3662
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
3663
+ form.submitStatus === "success" ? completionRegion : null,
3664
+ 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__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: form.submitError.payload.formError }) : errorMessageKey === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
3665
+ ] })
3666
+ ]
3375
3667
  }
3376
- const Component = components[field.type];
3377
- return Component === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultField, { ...props }, field.id) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props }, field.id);
3378
- }) }),
3379
- guardMessage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: guardMessage }),
3380
- confirmation === null ? null : slots.renderSubmissionConfirmation?.({
3381
- findings: confirmation.findings,
3382
- message: confirmation.message ?? form.translate("form.confirmSensitiveData"),
3383
- schema: form.schema,
3384
- visibleValues,
3385
- onConfirm: confirmSubmission,
3386
- onCancel: cancelSubmission
3387
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-submission-confirmation", role: "dialog", "aria-modal": "true", children: [
3388
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: confirmation.message ?? form.translate("form.confirmSensitiveData") }),
3389
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: confirmSubmission, children: form.translate("form.confirmSubmission") }),
3390
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: cancelSubmission, children: form.translate("form.cancelSubmission") })
3391
- ] }),
3392
- validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-validation-summary", role: "alert", children: [
3393
- validationIssues.length,
3394
- " validation error",
3395
- validationIssues.length === 1 ? "" : "s",
3396
- "."
3397
- ] }),
3398
- pages === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3399
- slots.renderNavigation?.({
3400
- currentPage: 0,
3401
- totalPages: 1,
3402
- canPrev: false,
3403
- canNext: false,
3404
- onPrev: () => void 0,
3405
- onNext: () => void 0
3406
- }),
3407
- renderSubmitButton()
3408
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "form-step-navigation", children: [
3409
- slots.renderNavigation?.({
3410
- currentPage: activeVisibleIndex,
3411
- totalPages: visiblePageIndexes.length,
3412
- canPrev,
3413
- canNext,
3414
- onPrev: () => {
3415
- if (!interactionLocked) setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0);
3416
- },
3417
- onNext: handleNext
3418
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3419
- canPrev ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3420
- "button",
3421
- {
3422
- className: "btn-prev",
3423
- type: "button",
3424
- disabled: interactionLocked,
3425
- onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
3426
- children: form.translate("form.back")
3427
- }
3428
- ) : null,
3429
- canNext ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "btn-next", type: "button", disabled: interactionLocked, onClick: handleNext, children: form.translate("form.next") }) : null
3430
- ] }),
3431
- canNext ? null : renderSubmitButton()
3432
- ] }),
3433
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
3434
- form.submitStatus === "success" ? slots.renderCompletion?.({
3435
- message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
3436
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
3437
- form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (errorMessageKey === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
3438
- ] })
3668
+ ),
3669
+ confirmation !== null && submissionConfirmationRenderMode === "dialog" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fe-confirmation-dialog-backdrop", role: "dialog", "aria-modal": "true", children: confirmationContent }) : null
3439
3670
  ] });
3440
3671
  }
3441
3672
  var RENDERER_MESSAGES = {
@@ -3448,6 +3679,8 @@ var RENDERER_MESSAGES = {
3448
3679
  "form.confirmSensitiveData": "Sensitive data may be included. Confirm before submitting.",
3449
3680
  "form.confirmSubmission": "Confirm submission",
3450
3681
  "form.cancelSubmission": "Cancel",
3682
+ "form.yes": "Yes",
3683
+ "form.no": "No",
3451
3684
  "form.alreadySubmitted": "Already submitted.",
3452
3685
  "form.submitAnother": "Submit another response",
3453
3686
  "validation.required": "This field is required."
@@ -3489,6 +3722,7 @@ function FormRenderer(props) {
3489
3722
  FormBuilder,
3490
3723
  FormProvider,
3491
3724
  FormRenderer,
3725
+ FormSubmissionError,
3492
3726
  createLocalStorageSubmissionAttemptStore,
3493
3727
  createLocalStorageSubmissionReceiptStore,
3494
3728
  resolveInitialFieldType,