@form-engine-ts/react 2.9.5 → 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/README.md +8 -0
- package/dist/index.cjs +274 -120
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +274 -120
- package/dist/styles.css +17 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -152,3 +152,11 @@ preserved. Pass an SSR-safe `createLocalStorageSubmissionAttemptStore()` as `att
|
|
|
152
152
|
before submission. Renderer injects it as `attemptId` and `submissionId`, retains it after a failed request, promotes it
|
|
153
153
|
to the receipt after success, and then clears the attempt. Custom receipt stores may omit `getBatch`; the hook falls back
|
|
154
154
|
to concurrent `get` calls.
|
|
155
|
+
|
|
156
|
+
After success, completion rendering receives a snapshot of `answers`, `schema`, the optional response, and
|
|
157
|
+
`submittedItems`. Each summary item includes the field title, raw value, formatted display value, visibility, and field
|
|
158
|
+
metadata. Use `renderSubmittedValues` for a typed summary slot; hidden fields are omitted by default and can be included
|
|
159
|
+
with `showHiddenFieldsInSummary`. Server validation can be returned by throwing `FormSubmissionError` with `fieldErrors`
|
|
160
|
+
and `formError`; field messages are mapped back to the form and the first invalid control is focused. Use
|
|
161
|
+
`submissionConfirmationRenderMode="replace"` or `"dialog"` for alternate confirmation presentations, and
|
|
162
|
+
`fieldsClassName` or `renderFields` to control the fields wrapper.
|
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,
|
|
@@ -2657,6 +2658,17 @@ function FormProvider({
|
|
|
2657
2658
|
},
|
|
2658
2659
|
[validSchema, validationPageIndex]
|
|
2659
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
|
+
}, []);
|
|
2660
2672
|
const restoreValues = (0, import_react3.useCallback)(
|
|
2661
2673
|
(restoredValues) => {
|
|
2662
2674
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
@@ -2741,6 +2753,7 @@ function FormProvider({
|
|
|
2741
2753
|
submitError,
|
|
2742
2754
|
isSubmitting: submitStatus === "submitting",
|
|
2743
2755
|
setValue,
|
|
2756
|
+
setServerErrors,
|
|
2744
2757
|
restoreValues,
|
|
2745
2758
|
validatePage,
|
|
2746
2759
|
reset,
|
|
@@ -2754,6 +2767,7 @@ function FormProvider({
|
|
|
2754
2767
|
reset,
|
|
2755
2768
|
restoreValues,
|
|
2756
2769
|
setValue,
|
|
2770
|
+
setServerErrors,
|
|
2757
2771
|
submit,
|
|
2758
2772
|
submitError,
|
|
2759
2773
|
submitStatus,
|
|
@@ -2900,6 +2914,18 @@ function useSubmissionReceipts(store, queries) {
|
|
|
2900
2914
|
// src/renderer.tsx
|
|
2901
2915
|
var import_core4 = require("@form-engine-ts/core");
|
|
2902
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
|
|
2903
2929
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
2904
2930
|
function describedBy(field, error, helpId, errorId) {
|
|
2905
2931
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
@@ -3096,6 +3122,30 @@ function isRecord(value) {
|
|
|
3096
3122
|
function isFormValue(value) {
|
|
3097
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");
|
|
3098
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
|
+
}
|
|
3099
3149
|
function parseDraft(serialized) {
|
|
3100
3150
|
try {
|
|
3101
3151
|
const value = JSON.parse(serialized);
|
|
@@ -3123,6 +3173,9 @@ function ContextFormRenderer({
|
|
|
3123
3173
|
beforeSubmit,
|
|
3124
3174
|
onDraftSave,
|
|
3125
3175
|
successRenderMode = "append",
|
|
3176
|
+
submissionConfirmationRenderMode = "inline",
|
|
3177
|
+
showHiddenFieldsInSummary = false,
|
|
3178
|
+
fieldsClassName,
|
|
3126
3179
|
hideFormOnSuccess = false,
|
|
3127
3180
|
submissionGuards = [],
|
|
3128
3181
|
receiptStore,
|
|
@@ -3141,9 +3194,11 @@ function ContextFormRenderer({
|
|
|
3141
3194
|
const [guardMessage, setGuardMessage] = (0, import_react5.useState)(null);
|
|
3142
3195
|
const [guardsPending, setGuardsPending] = (0, import_react5.useState)(false);
|
|
3143
3196
|
const [receipt, setReceipt] = (0, import_react5.useState)(null);
|
|
3197
|
+
const [completionData, setCompletionData] = (0, import_react5.useState)(null);
|
|
3144
3198
|
const [receiptLoaded, setReceiptLoaded] = (0, import_react5.useState)(receiptStore === void 0);
|
|
3145
3199
|
const rendererSubmissionInFlight = (0, import_react5.useRef)(false);
|
|
3146
3200
|
const completionRef = (0, import_react5.useRef)(null);
|
|
3201
|
+
const confirmationRef = (0, import_react5.useRef)(null);
|
|
3147
3202
|
const pages = form.schema.pages;
|
|
3148
3203
|
const visiblePageIndexes = (0, import_react5.useMemo)(
|
|
3149
3204
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
@@ -3156,6 +3211,10 @@ function ContextFormRenderer({
|
|
|
3156
3211
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
3157
3212
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
3158
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
|
+
}, []);
|
|
3159
3218
|
(0, import_react5.useEffect)(() => {
|
|
3160
3219
|
let active = true;
|
|
3161
3220
|
if (receiptStore === void 0) {
|
|
@@ -3199,6 +3258,21 @@ function ContextFormRenderer({
|
|
|
3199
3258
|
if (!isReplaceMode || form.submitStatus !== "success") return;
|
|
3200
3259
|
completionRef.current?.focus();
|
|
3201
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]);
|
|
3202
3276
|
(0, import_react5.useEffect)(() => {
|
|
3203
3277
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
3204
3278
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
@@ -3314,7 +3388,33 @@ function ContextFormRenderer({
|
|
|
3314
3388
|
focusFirstIssue(firstInvalidFieldId);
|
|
3315
3389
|
return result;
|
|
3316
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
|
+
}
|
|
3317
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
|
+
});
|
|
3318
3418
|
if (receiptStore !== void 0) {
|
|
3319
3419
|
const response = result.response;
|
|
3320
3420
|
const submissionId = response?.submissionId ?? submissionAttempt?.attemptId;
|
|
@@ -3361,6 +3461,7 @@ function ContextFormRenderer({
|
|
|
3361
3461
|
};
|
|
3362
3462
|
const cancelSubmission = () => {
|
|
3363
3463
|
setConfirmation(null);
|
|
3464
|
+
globalThis.setTimeout(focusSubmitButton, 0);
|
|
3364
3465
|
};
|
|
3365
3466
|
const resetReceipt = async () => {
|
|
3366
3467
|
if (receiptStore === void 0) return;
|
|
@@ -3381,7 +3482,48 @@ function ContextFormRenderer({
|
|
|
3381
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") });
|
|
3382
3483
|
};
|
|
3383
3484
|
const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
|
|
3384
|
-
const
|
|
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
|
+
);
|
|
3385
3527
|
if (!receiptLoaded) return null;
|
|
3386
3528
|
if (receipt !== null) {
|
|
3387
3529
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
|
|
@@ -3395,127 +3537,136 @@ function ContextFormRenderer({
|
|
|
3395
3537
|
if (form.submitStatus === "success" && isReplaceMode) {
|
|
3396
3538
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
|
|
3397
3539
|
}
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
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);
|
|
3419
3612
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
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
|
+
]
|
|
3457
3667
|
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
}) }),
|
|
3461
|
-
guardMessage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", children: guardMessage }),
|
|
3462
|
-
confirmation === null ? null : slots.renderSubmissionConfirmation?.({
|
|
3463
|
-
findings: confirmation.findings,
|
|
3464
|
-
message: confirmation.message ?? form.translate("form.confirmSensitiveData"),
|
|
3465
|
-
schema: form.schema,
|
|
3466
|
-
visibleValues,
|
|
3467
|
-
onConfirm: confirmSubmission,
|
|
3468
|
-
onCancel: cancelSubmission
|
|
3469
|
-
}) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-submission-confirmation", role: "dialog", "aria-modal": "true", children: [
|
|
3470
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: confirmation.message ?? form.translate("form.confirmSensitiveData") }),
|
|
3471
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: confirmSubmission, children: form.translate("form.confirmSubmission") }),
|
|
3472
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: cancelSubmission, children: form.translate("form.cancelSubmission") })
|
|
3473
|
-
] }),
|
|
3474
|
-
validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-validation-summary", role: "alert", children: [
|
|
3475
|
-
validationIssues.length,
|
|
3476
|
-
" validation error",
|
|
3477
|
-
validationIssues.length === 1 ? "" : "s",
|
|
3478
|
-
"."
|
|
3479
|
-
] }),
|
|
3480
|
-
pages === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
3481
|
-
slots.renderNavigation?.({
|
|
3482
|
-
currentPage: 0,
|
|
3483
|
-
totalPages: 1,
|
|
3484
|
-
canPrev: false,
|
|
3485
|
-
canNext: false,
|
|
3486
|
-
onPrev: () => void 0,
|
|
3487
|
-
onNext: () => void 0
|
|
3488
|
-
}),
|
|
3489
|
-
renderSubmitButton()
|
|
3490
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "form-step-navigation", children: [
|
|
3491
|
-
slots.renderNavigation?.({
|
|
3492
|
-
currentPage: activeVisibleIndex,
|
|
3493
|
-
totalPages: visiblePageIndexes.length,
|
|
3494
|
-
canPrev,
|
|
3495
|
-
canNext,
|
|
3496
|
-
onPrev: () => {
|
|
3497
|
-
if (!interactionLocked) setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0);
|
|
3498
|
-
},
|
|
3499
|
-
onNext: handleNext
|
|
3500
|
-
}) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
3501
|
-
canPrev ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3502
|
-
"button",
|
|
3503
|
-
{
|
|
3504
|
-
className: "btn-prev",
|
|
3505
|
-
type: "button",
|
|
3506
|
-
disabled: interactionLocked,
|
|
3507
|
-
onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
3508
|
-
children: form.translate("form.back")
|
|
3509
|
-
}
|
|
3510
|
-
) : null,
|
|
3511
|
-
canNext ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "btn-next", type: "button", disabled: interactionLocked, onClick: handleNext, children: form.translate("form.next") }) : null
|
|
3512
|
-
] }),
|
|
3513
|
-
canNext ? null : renderSubmitButton()
|
|
3514
|
-
] }),
|
|
3515
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
3516
|
-
form.submitStatus === "success" ? completionRegion : null,
|
|
3517
|
-
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
|
|
3518
|
-
] })
|
|
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
|
|
3519
3670
|
] });
|
|
3520
3671
|
}
|
|
3521
3672
|
var RENDERER_MESSAGES = {
|
|
@@ -3528,6 +3679,8 @@ var RENDERER_MESSAGES = {
|
|
|
3528
3679
|
"form.confirmSensitiveData": "Sensitive data may be included. Confirm before submitting.",
|
|
3529
3680
|
"form.confirmSubmission": "Confirm submission",
|
|
3530
3681
|
"form.cancelSubmission": "Cancel",
|
|
3682
|
+
"form.yes": "Yes",
|
|
3683
|
+
"form.no": "No",
|
|
3531
3684
|
"form.alreadySubmitted": "Already submitted.",
|
|
3532
3685
|
"form.submitAnother": "Submit another response",
|
|
3533
3686
|
"validation.required": "This field is required."
|
|
@@ -3569,6 +3722,7 @@ function FormRenderer(props) {
|
|
|
3569
3722
|
FormBuilder,
|
|
3570
3723
|
FormProvider,
|
|
3571
3724
|
FormRenderer,
|
|
3725
|
+
FormSubmissionError,
|
|
3572
3726
|
createLocalStorageSubmissionAttemptStore,
|
|
3573
3727
|
createLocalStorageSubmissionReceiptStore,
|
|
3574
3728
|
resolveInitialFieldType,
|
package/dist/index.d.cts
CHANGED
|
@@ -338,6 +338,31 @@ interface SubmitResponse {
|
|
|
338
338
|
readonly submissionId?: string;
|
|
339
339
|
readonly submittedAt?: string;
|
|
340
340
|
}
|
|
341
|
+
interface FormSubmittedAnswerItem {
|
|
342
|
+
readonly fieldId: string;
|
|
343
|
+
readonly title: string;
|
|
344
|
+
readonly type: QuestionType;
|
|
345
|
+
readonly rawValue: unknown;
|
|
346
|
+
readonly displayValue: string;
|
|
347
|
+
readonly visible: boolean;
|
|
348
|
+
readonly metadata?: Readonly<Record<string, JsonValue>>;
|
|
349
|
+
}
|
|
350
|
+
interface FormCompletionSlotProps {
|
|
351
|
+
readonly message?: string;
|
|
352
|
+
readonly schema: FormSchema;
|
|
353
|
+
readonly answers: Record<string, unknown>;
|
|
354
|
+
readonly submittedItems: readonly FormSubmittedAnswerItem[];
|
|
355
|
+
readonly response?: SubmitResponse;
|
|
356
|
+
readonly onReset?: () => void;
|
|
357
|
+
}
|
|
358
|
+
interface FormServerErrorPayload {
|
|
359
|
+
readonly fieldErrors?: Readonly<Record<string, string>>;
|
|
360
|
+
readonly formError?: string;
|
|
361
|
+
}
|
|
362
|
+
declare class FormSubmissionError extends Error {
|
|
363
|
+
readonly payload: FormServerErrorPayload;
|
|
364
|
+
constructor(message: string, payload?: FormServerErrorPayload);
|
|
365
|
+
}
|
|
341
366
|
type FormSubmitHandler = (answers: FormValues) => SubmitResponse | void | Promise<SubmitResponse | undefined> | Promise<void>;
|
|
342
367
|
type SubmissionGuardResult = {
|
|
343
368
|
readonly status: "allow";
|
|
@@ -352,6 +377,7 @@ type SubmissionGuardResult = {
|
|
|
352
377
|
};
|
|
353
378
|
type SubmissionGuard = (schema: FormSchema, values: Record<string, unknown>) => SubmissionGuardResult | Promise<SubmissionGuardResult>;
|
|
354
379
|
type FormSuccessRenderMode = "append" | "replace";
|
|
380
|
+
type SubmissionConfirmationRenderMode = "inline" | "replace" | "dialog";
|
|
355
381
|
type FormSubmitStatus = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
356
382
|
/** @deprecated Use FormSubmitStatus instead. */
|
|
357
383
|
type FormSubmitState = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
@@ -369,6 +395,10 @@ interface SubmissionConfirmationSlotProps {
|
|
|
369
395
|
readonly onConfirm: () => void;
|
|
370
396
|
readonly onCancel: () => void;
|
|
371
397
|
}
|
|
398
|
+
interface FormFieldsSlotProps {
|
|
399
|
+
readonly children: ReactNode;
|
|
400
|
+
readonly className?: string;
|
|
401
|
+
}
|
|
372
402
|
interface FormRendererSlots {
|
|
373
403
|
readonly renderHeader?: (props: {
|
|
374
404
|
readonly title: string;
|
|
@@ -397,9 +427,15 @@ interface FormRendererSlots {
|
|
|
397
427
|
readonly renderValidationSummary?: (props: {
|
|
398
428
|
readonly issues: readonly ValidationError[];
|
|
399
429
|
}) => ReactNode;
|
|
430
|
+
/** Additional completion data is supplied at runtime; use renderSubmittedValues for typed summary rendering. */
|
|
400
431
|
readonly renderCompletion?: (props: {
|
|
401
432
|
readonly message: string;
|
|
402
433
|
}) => ReactNode;
|
|
434
|
+
readonly renderSubmittedValues?: (props: {
|
|
435
|
+
readonly items: readonly FormSubmittedAnswerItem[];
|
|
436
|
+
readonly schema: FormSchema;
|
|
437
|
+
}) => ReactNode;
|
|
438
|
+
readonly renderFields?: (props: FormFieldsSlotProps) => ReactNode;
|
|
403
439
|
readonly renderSubmitError?: (props: {
|
|
404
440
|
readonly error: Error;
|
|
405
441
|
readonly onRetry?: () => void;
|
|
@@ -467,6 +503,7 @@ interface FormContextValue {
|
|
|
467
503
|
readonly submitError: Error | null;
|
|
468
504
|
readonly isSubmitting: boolean;
|
|
469
505
|
readonly setValue: (fieldId: string, value: FormValue) => void;
|
|
506
|
+
readonly setServerErrors?: (fieldErrors: Readonly<Record<string, string>>) => void;
|
|
470
507
|
readonly restoreValues: (values: FormValues) => void;
|
|
471
508
|
readonly validatePage: (pageIndex: number) => AnswerValidationResult;
|
|
472
509
|
readonly reset: () => void;
|
|
@@ -512,6 +549,9 @@ interface FormRendererPresentationProps extends SubmissionProtectionProps {
|
|
|
512
549
|
* Defaults to "append" for backwards compatibility.
|
|
513
550
|
*/
|
|
514
551
|
readonly successRenderMode?: FormSuccessRenderMode;
|
|
552
|
+
readonly submissionConfirmationRenderMode?: SubmissionConfirmationRenderMode;
|
|
553
|
+
readonly showHiddenFieldsInSummary?: boolean;
|
|
554
|
+
readonly fieldsClassName?: string;
|
|
515
555
|
/** @deprecated Use successRenderMode="replace" instead. */
|
|
516
556
|
readonly hideFormOnSuccess?: boolean;
|
|
517
557
|
readonly successMessageKey?: string;
|
|
@@ -532,4 +572,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
532
572
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
533
573
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
534
574
|
|
|
535
|
-
export { type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldState, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormContextValue, FormProvider, type FormProviderProps, FormRenderer, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, resolveInitialFieldType, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
|
575
|
+
export { type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldState, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, resolveInitialFieldType, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
package/dist/index.d.ts
CHANGED
|
@@ -338,6 +338,31 @@ interface SubmitResponse {
|
|
|
338
338
|
readonly submissionId?: string;
|
|
339
339
|
readonly submittedAt?: string;
|
|
340
340
|
}
|
|
341
|
+
interface FormSubmittedAnswerItem {
|
|
342
|
+
readonly fieldId: string;
|
|
343
|
+
readonly title: string;
|
|
344
|
+
readonly type: QuestionType;
|
|
345
|
+
readonly rawValue: unknown;
|
|
346
|
+
readonly displayValue: string;
|
|
347
|
+
readonly visible: boolean;
|
|
348
|
+
readonly metadata?: Readonly<Record<string, JsonValue>>;
|
|
349
|
+
}
|
|
350
|
+
interface FormCompletionSlotProps {
|
|
351
|
+
readonly message?: string;
|
|
352
|
+
readonly schema: FormSchema;
|
|
353
|
+
readonly answers: Record<string, unknown>;
|
|
354
|
+
readonly submittedItems: readonly FormSubmittedAnswerItem[];
|
|
355
|
+
readonly response?: SubmitResponse;
|
|
356
|
+
readonly onReset?: () => void;
|
|
357
|
+
}
|
|
358
|
+
interface FormServerErrorPayload {
|
|
359
|
+
readonly fieldErrors?: Readonly<Record<string, string>>;
|
|
360
|
+
readonly formError?: string;
|
|
361
|
+
}
|
|
362
|
+
declare class FormSubmissionError extends Error {
|
|
363
|
+
readonly payload: FormServerErrorPayload;
|
|
364
|
+
constructor(message: string, payload?: FormServerErrorPayload);
|
|
365
|
+
}
|
|
341
366
|
type FormSubmitHandler = (answers: FormValues) => SubmitResponse | void | Promise<SubmitResponse | undefined> | Promise<void>;
|
|
342
367
|
type SubmissionGuardResult = {
|
|
343
368
|
readonly status: "allow";
|
|
@@ -352,6 +377,7 @@ type SubmissionGuardResult = {
|
|
|
352
377
|
};
|
|
353
378
|
type SubmissionGuard = (schema: FormSchema, values: Record<string, unknown>) => SubmissionGuardResult | Promise<SubmissionGuardResult>;
|
|
354
379
|
type FormSuccessRenderMode = "append" | "replace";
|
|
380
|
+
type SubmissionConfirmationRenderMode = "inline" | "replace" | "dialog";
|
|
355
381
|
type FormSubmitStatus = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
356
382
|
/** @deprecated Use FormSubmitStatus instead. */
|
|
357
383
|
type FormSubmitState = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
@@ -369,6 +395,10 @@ interface SubmissionConfirmationSlotProps {
|
|
|
369
395
|
readonly onConfirm: () => void;
|
|
370
396
|
readonly onCancel: () => void;
|
|
371
397
|
}
|
|
398
|
+
interface FormFieldsSlotProps {
|
|
399
|
+
readonly children: ReactNode;
|
|
400
|
+
readonly className?: string;
|
|
401
|
+
}
|
|
372
402
|
interface FormRendererSlots {
|
|
373
403
|
readonly renderHeader?: (props: {
|
|
374
404
|
readonly title: string;
|
|
@@ -397,9 +427,15 @@ interface FormRendererSlots {
|
|
|
397
427
|
readonly renderValidationSummary?: (props: {
|
|
398
428
|
readonly issues: readonly ValidationError[];
|
|
399
429
|
}) => ReactNode;
|
|
430
|
+
/** Additional completion data is supplied at runtime; use renderSubmittedValues for typed summary rendering. */
|
|
400
431
|
readonly renderCompletion?: (props: {
|
|
401
432
|
readonly message: string;
|
|
402
433
|
}) => ReactNode;
|
|
434
|
+
readonly renderSubmittedValues?: (props: {
|
|
435
|
+
readonly items: readonly FormSubmittedAnswerItem[];
|
|
436
|
+
readonly schema: FormSchema;
|
|
437
|
+
}) => ReactNode;
|
|
438
|
+
readonly renderFields?: (props: FormFieldsSlotProps) => ReactNode;
|
|
403
439
|
readonly renderSubmitError?: (props: {
|
|
404
440
|
readonly error: Error;
|
|
405
441
|
readonly onRetry?: () => void;
|
|
@@ -467,6 +503,7 @@ interface FormContextValue {
|
|
|
467
503
|
readonly submitError: Error | null;
|
|
468
504
|
readonly isSubmitting: boolean;
|
|
469
505
|
readonly setValue: (fieldId: string, value: FormValue) => void;
|
|
506
|
+
readonly setServerErrors?: (fieldErrors: Readonly<Record<string, string>>) => void;
|
|
470
507
|
readonly restoreValues: (values: FormValues) => void;
|
|
471
508
|
readonly validatePage: (pageIndex: number) => AnswerValidationResult;
|
|
472
509
|
readonly reset: () => void;
|
|
@@ -512,6 +549,9 @@ interface FormRendererPresentationProps extends SubmissionProtectionProps {
|
|
|
512
549
|
* Defaults to "append" for backwards compatibility.
|
|
513
550
|
*/
|
|
514
551
|
readonly successRenderMode?: FormSuccessRenderMode;
|
|
552
|
+
readonly submissionConfirmationRenderMode?: SubmissionConfirmationRenderMode;
|
|
553
|
+
readonly showHiddenFieldsInSummary?: boolean;
|
|
554
|
+
readonly fieldsClassName?: string;
|
|
515
555
|
/** @deprecated Use successRenderMode="replace" instead. */
|
|
516
556
|
readonly hideFormOnSuccess?: boolean;
|
|
517
557
|
readonly successMessageKey?: string;
|
|
@@ -532,4 +572,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
532
572
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
533
573
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
534
574
|
|
|
535
|
-
export { type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldState, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormContextValue, FormProvider, type FormProviderProps, FormRenderer, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, resolveInitialFieldType, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
|
575
|
+
export { type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldState, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, resolveInitialFieldType, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
package/dist/index.js
CHANGED
|
@@ -2635,6 +2635,17 @@ function FormProvider({
|
|
|
2635
2635
|
},
|
|
2636
2636
|
[validSchema, validationPageIndex]
|
|
2637
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
|
+
}, []);
|
|
2638
2649
|
const restoreValues = useCallback2(
|
|
2639
2650
|
(restoredValues) => {
|
|
2640
2651
|
const fieldIds = new Set(validSchema.fields.map((field) => field.id));
|
|
@@ -2719,6 +2730,7 @@ function FormProvider({
|
|
|
2719
2730
|
submitError,
|
|
2720
2731
|
isSubmitting: submitStatus === "submitting",
|
|
2721
2732
|
setValue,
|
|
2733
|
+
setServerErrors,
|
|
2722
2734
|
restoreValues,
|
|
2723
2735
|
validatePage,
|
|
2724
2736
|
reset,
|
|
@@ -2732,6 +2744,7 @@ function FormProvider({
|
|
|
2732
2744
|
reset,
|
|
2733
2745
|
restoreValues,
|
|
2734
2746
|
setValue,
|
|
2747
|
+
setServerErrors,
|
|
2735
2748
|
submit,
|
|
2736
2749
|
submitError,
|
|
2737
2750
|
submitStatus,
|
|
@@ -2882,12 +2895,25 @@ import {
|
|
|
2882
2895
|
} from "@form-engine-ts/core";
|
|
2883
2896
|
import {
|
|
2884
2897
|
Fragment as Fragment2,
|
|
2898
|
+
useCallback as useCallback3,
|
|
2885
2899
|
useEffect as useEffect3,
|
|
2886
2900
|
useId,
|
|
2887
2901
|
useMemo as useMemo4,
|
|
2888
2902
|
useRef as useRef2,
|
|
2889
2903
|
useState as useState4
|
|
2890
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
|
|
2891
2917
|
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2892
2918
|
function describedBy(field, error, helpId, errorId) {
|
|
2893
2919
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
@@ -3084,6 +3110,30 @@ function isRecord(value) {
|
|
|
3084
3110
|
function isFormValue(value) {
|
|
3085
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");
|
|
3086
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
|
+
}
|
|
3087
3137
|
function parseDraft(serialized) {
|
|
3088
3138
|
try {
|
|
3089
3139
|
const value = JSON.parse(serialized);
|
|
@@ -3111,6 +3161,9 @@ function ContextFormRenderer({
|
|
|
3111
3161
|
beforeSubmit,
|
|
3112
3162
|
onDraftSave,
|
|
3113
3163
|
successRenderMode = "append",
|
|
3164
|
+
submissionConfirmationRenderMode = "inline",
|
|
3165
|
+
showHiddenFieldsInSummary = false,
|
|
3166
|
+
fieldsClassName,
|
|
3114
3167
|
hideFormOnSuccess = false,
|
|
3115
3168
|
submissionGuards = [],
|
|
3116
3169
|
receiptStore,
|
|
@@ -3129,9 +3182,11 @@ function ContextFormRenderer({
|
|
|
3129
3182
|
const [guardMessage, setGuardMessage] = useState4(null);
|
|
3130
3183
|
const [guardsPending, setGuardsPending] = useState4(false);
|
|
3131
3184
|
const [receipt, setReceipt] = useState4(null);
|
|
3185
|
+
const [completionData, setCompletionData] = useState4(null);
|
|
3132
3186
|
const [receiptLoaded, setReceiptLoaded] = useState4(receiptStore === void 0);
|
|
3133
3187
|
const rendererSubmissionInFlight = useRef2(false);
|
|
3134
3188
|
const completionRef = useRef2(null);
|
|
3189
|
+
const confirmationRef = useRef2(null);
|
|
3135
3190
|
const pages = form.schema.pages;
|
|
3136
3191
|
const visiblePageIndexes = useMemo4(
|
|
3137
3192
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
@@ -3144,6 +3199,10 @@ function ContextFormRenderer({
|
|
|
3144
3199
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
3145
3200
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
3146
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
|
+
}, []);
|
|
3147
3206
|
useEffect3(() => {
|
|
3148
3207
|
let active = true;
|
|
3149
3208
|
if (receiptStore === void 0) {
|
|
@@ -3187,6 +3246,21 @@ function ContextFormRenderer({
|
|
|
3187
3246
|
if (!isReplaceMode || form.submitStatus !== "success") return;
|
|
3188
3247
|
completionRef.current?.focus();
|
|
3189
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]);
|
|
3190
3264
|
useEffect3(() => {
|
|
3191
3265
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
3192
3266
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
@@ -3302,7 +3376,33 @@ function ContextFormRenderer({
|
|
|
3302
3376
|
focusFirstIssue(firstInvalidFieldId);
|
|
3303
3377
|
return result;
|
|
3304
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
|
+
}
|
|
3305
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
|
+
});
|
|
3306
3406
|
if (receiptStore !== void 0) {
|
|
3307
3407
|
const response = result.response;
|
|
3308
3408
|
const submissionId = response?.submissionId ?? submissionAttempt?.attemptId;
|
|
@@ -3349,6 +3449,7 @@ function ContextFormRenderer({
|
|
|
3349
3449
|
};
|
|
3350
3450
|
const cancelSubmission = () => {
|
|
3351
3451
|
setConfirmation(null);
|
|
3452
|
+
globalThis.setTimeout(focusSubmitButton, 0);
|
|
3352
3453
|
};
|
|
3353
3454
|
const resetReceipt = async () => {
|
|
3354
3455
|
if (receiptStore === void 0) return;
|
|
@@ -3369,7 +3470,48 @@ function ContextFormRenderer({
|
|
|
3369
3470
|
return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: submitButtonProps.disabled, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
|
|
3370
3471
|
};
|
|
3371
3472
|
const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
|
|
3372
|
-
const
|
|
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
|
+
);
|
|
3373
3515
|
if (!receiptLoaded) return null;
|
|
3374
3516
|
if (receipt !== null) {
|
|
3375
3517
|
return /* @__PURE__ */ jsx3("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
|
|
@@ -3383,127 +3525,136 @@ function ContextFormRenderer({
|
|
|
3383
3525
|
if (form.submitStatus === "success" && isReplaceMode) {
|
|
3384
3526
|
return /* @__PURE__ */ jsx3("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
|
|
3385
3527
|
}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
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);
|
|
3407
3600
|
}
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
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
|
+
]
|
|
3445
3655
|
}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
}) }),
|
|
3449
|
-
guardMessage === null ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: guardMessage }),
|
|
3450
|
-
confirmation === null ? null : slots.renderSubmissionConfirmation?.({
|
|
3451
|
-
findings: confirmation.findings,
|
|
3452
|
-
message: confirmation.message ?? form.translate("form.confirmSensitiveData"),
|
|
3453
|
-
schema: form.schema,
|
|
3454
|
-
visibleValues,
|
|
3455
|
-
onConfirm: confirmSubmission,
|
|
3456
|
-
onCancel: cancelSubmission
|
|
3457
|
-
}) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-submission-confirmation", role: "dialog", "aria-modal": "true", children: [
|
|
3458
|
-
/* @__PURE__ */ jsx3("p", { children: confirmation.message ?? form.translate("form.confirmSensitiveData") }),
|
|
3459
|
-
/* @__PURE__ */ jsx3("button", { type: "button", onClick: confirmSubmission, children: form.translate("form.confirmSubmission") }),
|
|
3460
|
-
/* @__PURE__ */ jsx3("button", { type: "button", onClick: cancelSubmission, children: form.translate("form.cancelSubmission") })
|
|
3461
|
-
] }),
|
|
3462
|
-
validationIssues.length === 0 ? null : slots.renderValidationSummary?.({ issues: validationIssues }) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-validation-summary", role: "alert", children: [
|
|
3463
|
-
validationIssues.length,
|
|
3464
|
-
" validation error",
|
|
3465
|
-
validationIssues.length === 1 ? "" : "s",
|
|
3466
|
-
"."
|
|
3467
|
-
] }),
|
|
3468
|
-
pages === void 0 ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
3469
|
-
slots.renderNavigation?.({
|
|
3470
|
-
currentPage: 0,
|
|
3471
|
-
totalPages: 1,
|
|
3472
|
-
canPrev: false,
|
|
3473
|
-
canNext: false,
|
|
3474
|
-
onPrev: () => void 0,
|
|
3475
|
-
onNext: () => void 0
|
|
3476
|
-
}),
|
|
3477
|
-
renderSubmitButton()
|
|
3478
|
-
] }) : /* @__PURE__ */ jsxs2("div", { className: "form-step-navigation", children: [
|
|
3479
|
-
slots.renderNavigation?.({
|
|
3480
|
-
currentPage: activeVisibleIndex,
|
|
3481
|
-
totalPages: visiblePageIndexes.length,
|
|
3482
|
-
canPrev,
|
|
3483
|
-
canNext,
|
|
3484
|
-
onPrev: () => {
|
|
3485
|
-
if (!interactionLocked) setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0);
|
|
3486
|
-
},
|
|
3487
|
-
onNext: handleNext
|
|
3488
|
-
}) ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
3489
|
-
canPrev ? /* @__PURE__ */ jsx3(
|
|
3490
|
-
"button",
|
|
3491
|
-
{
|
|
3492
|
-
className: "btn-prev",
|
|
3493
|
-
type: "button",
|
|
3494
|
-
disabled: interactionLocked,
|
|
3495
|
-
onClick: () => setCurrentPageIndex(visiblePageIndexes[activeVisibleIndex - 1] ?? 0),
|
|
3496
|
-
children: form.translate("form.back")
|
|
3497
|
-
}
|
|
3498
|
-
) : null,
|
|
3499
|
-
canNext ? /* @__PURE__ */ jsx3("button", { className: "btn-next", type: "button", disabled: interactionLocked, onClick: handleNext, children: form.translate("form.next") }) : null
|
|
3500
|
-
] }),
|
|
3501
|
-
canNext ? null : renderSubmitButton()
|
|
3502
|
-
] }),
|
|
3503
|
-
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
3504
|
-
form.submitStatus === "success" ? completionRegion : null,
|
|
3505
|
-
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
|
|
3506
|
-
] })
|
|
3656
|
+
),
|
|
3657
|
+
confirmation !== null && submissionConfirmationRenderMode === "dialog" ? /* @__PURE__ */ jsx3("div", { className: "fe-confirmation-dialog-backdrop", role: "dialog", "aria-modal": "true", children: confirmationContent }) : null
|
|
3507
3658
|
] });
|
|
3508
3659
|
}
|
|
3509
3660
|
var RENDERER_MESSAGES = {
|
|
@@ -3516,6 +3667,8 @@ var RENDERER_MESSAGES = {
|
|
|
3516
3667
|
"form.confirmSensitiveData": "Sensitive data may be included. Confirm before submitting.",
|
|
3517
3668
|
"form.confirmSubmission": "Confirm submission",
|
|
3518
3669
|
"form.cancelSubmission": "Cancel",
|
|
3670
|
+
"form.yes": "Yes",
|
|
3671
|
+
"form.no": "No",
|
|
3519
3672
|
"form.alreadySubmitted": "Already submitted.",
|
|
3520
3673
|
"form.submitAnother": "Submit another response",
|
|
3521
3674
|
"validation.required": "This field is required."
|
|
@@ -3556,6 +3709,7 @@ export {
|
|
|
3556
3709
|
FormBuilder,
|
|
3557
3710
|
FormProvider,
|
|
3558
3711
|
FormRenderer,
|
|
3712
|
+
FormSubmissionError,
|
|
3559
3713
|
createLocalStorageSubmissionAttemptStore,
|
|
3560
3714
|
createLocalStorageSubmissionReceiptStore,
|
|
3561
3715
|
resolveInitialFieldType,
|
package/dist/styles.css
CHANGED
|
@@ -281,3 +281,20 @@
|
|
|
281
281
|
.form-engine-builder__add {
|
|
282
282
|
justify-self: start;
|
|
283
283
|
}
|
|
284
|
+
.fe-confirmation-dialog-backdrop {
|
|
285
|
+
position: fixed;
|
|
286
|
+
inset: 0;
|
|
287
|
+
z-index: 1000;
|
|
288
|
+
display: grid;
|
|
289
|
+
place-items: center;
|
|
290
|
+
padding: 1rem;
|
|
291
|
+
background: rgb(0 0 0 / 45%);
|
|
292
|
+
}
|
|
293
|
+
.fe-confirmation-dialog-backdrop .fe-submission-confirmation {
|
|
294
|
+
max-width: 32rem;
|
|
295
|
+
padding: 1.5rem;
|
|
296
|
+
background: Canvas;
|
|
297
|
+
color: CanvasText;
|
|
298
|
+
border-radius: 0.5rem;
|
|
299
|
+
box-shadow: 0 1rem 3rem rgb(0 0 0 / 25%);
|
|
300
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.6",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@form-engine-ts/core": "2.9.
|
|
46
|
-
"@form-engine-ts/privacy": "2.9.
|
|
45
|
+
"@form-engine-ts/core": "2.9.6",
|
|
46
|
+
"@form-engine-ts/privacy": "2.9.6"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|