@form-engine-ts/react 2.9.4 → 2.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/index.cjs +105 -25
- package/dist/index.d.cts +27 -7
- package/dist/index.d.ts +27 -7
- package/dist/index.js +105 -25
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -81,6 +81,11 @@ relevant feature state, while localization slots receive policy and translation-
|
|
|
81
81
|
editors remain unchanged. A slot collection can set `sectionOrder` to arrange `basicSettings`, `completionMessage`,
|
|
82
82
|
`questions`, `addQuestion`, and `localization` as independent authoring sections.
|
|
83
83
|
|
|
84
|
+
Slot actions include `setManualTranslation(locale, target, property, text)`. It resolves the target's source text and
|
|
85
|
+
existing translation metadata, invokes `createManualTranslationMetadata`, and then stores the translated text and
|
|
86
|
+
generated metadata together. Custom field, page, option, and localization slots should use this action for user-edited
|
|
87
|
+
translations; `setLocaleTranslation` remains available for callers that already provide explicit metadata.
|
|
88
|
+
|
|
84
89
|
Input primitives receive design-system-friendly `name`, `label`, `required`, `error`, and `helperText` props in addition
|
|
85
90
|
to their normalized string-value callbacks. The `translationActions` slot also receives `translationError`, the latest
|
|
86
91
|
`translationReport`, and `onClearTranslationError`, allowing a custom MUI action surface to represent the complete
|
|
@@ -114,8 +119,8 @@ own labels. Builder action icons can be supplied with `renderIcon`, which resolv
|
|
|
114
119
|
renderHeader: ({ title }) => <MyHeader>{title}</MyHeader>,
|
|
115
120
|
renderPageHeader: ({ page }) => <MyPageHeader page={page} />,
|
|
116
121
|
renderField: (props) => <MyField {...props} />,
|
|
117
|
-
renderSubmitButton: ({ isSubmitting, onSubmit }) => (
|
|
118
|
-
<MyButton
|
|
122
|
+
renderSubmitButton: ({ isSubmitting, disabled, onSubmit }) => (
|
|
123
|
+
<MyButton loading={isSubmitting} disabled={disabled} onClick={onSubmit}>Save</MyButton>
|
|
119
124
|
),
|
|
120
125
|
renderSubmitError: ({ error, onRetry }) => <MyError error={error} onRetry={onRetry} />
|
|
121
126
|
}}
|
|
@@ -124,7 +129,11 @@ own labels. Builder action icons can be supplied with `renderIcon`, which resolv
|
|
|
124
129
|
|
|
125
130
|
Validation runs before `beforeSubmit`. A `"cancel"` result does not call `onSubmit` and preserves values and drafts.
|
|
126
131
|
Header, page-header, field, navigation, submit, validation-summary, completion, and submit-error slots can replace the
|
|
127
|
-
default UI. The localized `completionMessage` is displayed after a successful submission.
|
|
132
|
+
default UI. The localized `completionMessage` is displayed after a successful submission. Set
|
|
133
|
+
`successRenderMode="replace"` to remove the questions, navigation, and submit button after success and focus the
|
|
134
|
+
completion status region; the default `"append"` mode preserves the existing form UI. `hideFormOnSuccess` is retained
|
|
135
|
+
as a deprecated alias for `successRenderMode="replace"`. The submit-button slot receives `submitStatus` and `disabled`
|
|
136
|
+
in addition to `isSubmitting` and `onSubmit`.
|
|
128
137
|
|
|
129
138
|
Pass ordered `submissionGuards` to allow, block, or require confirmation before `onSubmit`. Confirmation receives all
|
|
130
139
|
guard findings through `renderSubmissionConfirmation`. A `receiptStore` prevents accidental repeat submissions and can
|
package/dist/index.cjs
CHANGED
|
@@ -1088,7 +1088,7 @@ function BuilderButton(props) {
|
|
|
1088
1088
|
...props,
|
|
1089
1089
|
className: primitiveClassName(props.className, "feb-button", context.unstyled),
|
|
1090
1090
|
disabled: context.readOnly || props.disabled === true,
|
|
1091
|
-
readOnly: context.readOnly
|
|
1091
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1092
1092
|
}
|
|
1093
1093
|
);
|
|
1094
1094
|
}
|
|
@@ -1103,7 +1103,7 @@ function BuilderIconButton(props) {
|
|
|
1103
1103
|
icon,
|
|
1104
1104
|
className: primitiveClassName(props.className, "feb-icon-button", context.unstyled),
|
|
1105
1105
|
disabled: context.readOnly || props.disabled === true,
|
|
1106
|
-
readOnly: context.readOnly
|
|
1106
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1107
1107
|
}
|
|
1108
1108
|
);
|
|
1109
1109
|
}
|
|
@@ -1116,7 +1116,7 @@ function BuilderTextInput(props) {
|
|
|
1116
1116
|
...props,
|
|
1117
1117
|
className: primitiveClassName(props.className, "feb-input", context.unstyled),
|
|
1118
1118
|
disabled: context.readOnly || props.disabled === true,
|
|
1119
|
-
readOnly: context.readOnly
|
|
1119
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1120
1120
|
}
|
|
1121
1121
|
);
|
|
1122
1122
|
}
|
|
@@ -1129,7 +1129,7 @@ function BuilderTextArea(props) {
|
|
|
1129
1129
|
...props,
|
|
1130
1130
|
className: primitiveClassName(props.className, "feb-textarea", context.unstyled),
|
|
1131
1131
|
disabled: context.readOnly || props.disabled === true,
|
|
1132
|
-
readOnly: context.readOnly
|
|
1132
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1133
1133
|
}
|
|
1134
1134
|
);
|
|
1135
1135
|
}
|
|
@@ -1142,7 +1142,7 @@ function BuilderSelect(props) {
|
|
|
1142
1142
|
...props,
|
|
1143
1143
|
className: primitiveClassName(props.className, "feb-select", context.unstyled),
|
|
1144
1144
|
disabled: context.readOnly || props.disabled === true,
|
|
1145
|
-
readOnly: context.readOnly
|
|
1145
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1146
1146
|
}
|
|
1147
1147
|
);
|
|
1148
1148
|
}
|
|
@@ -1155,7 +1155,7 @@ function BuilderCheckbox(props) {
|
|
|
1155
1155
|
...props,
|
|
1156
1156
|
className: primitiveClassName(props.className, "feb-checkbox", context.unstyled),
|
|
1157
1157
|
disabled: context.readOnly || props.disabled === true,
|
|
1158
|
-
readOnly: context.readOnly
|
|
1158
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1159
1159
|
}
|
|
1160
1160
|
);
|
|
1161
1161
|
}
|
|
@@ -1301,6 +1301,59 @@ function fieldTypeKey(type) {
|
|
|
1301
1301
|
function operatorKey(operator) {
|
|
1302
1302
|
return `builder.operator.${operator}`;
|
|
1303
1303
|
}
|
|
1304
|
+
function manualTranslationContext(schema, locale, target, property, translatedText) {
|
|
1305
|
+
if (target.kind === "form") {
|
|
1306
|
+
if (property !== "title" && property !== "description" && property !== "completionMessage") return void 0;
|
|
1307
|
+
return {
|
|
1308
|
+
locale,
|
|
1309
|
+
kind: "form",
|
|
1310
|
+
nodeId: schema.id,
|
|
1311
|
+
property,
|
|
1312
|
+
sourceText: schema[property] ?? "",
|
|
1313
|
+
translatedText,
|
|
1314
|
+
...schema.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[locale]?.[property] }
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
if (target.id === void 0) return void 0;
|
|
1318
|
+
if (target.kind === "field") {
|
|
1319
|
+
const field = schema.fields.find((candidate) => candidate.id === target.id);
|
|
1320
|
+
if (field === void 0 || property !== "title" && property !== "description") return void 0;
|
|
1321
|
+
return {
|
|
1322
|
+
locale,
|
|
1323
|
+
kind: "field",
|
|
1324
|
+
nodeId: field.id,
|
|
1325
|
+
property,
|
|
1326
|
+
sourceText: field[property] ?? "",
|
|
1327
|
+
translatedText,
|
|
1328
|
+
...field.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: field.translationMetadata[locale]?.[property] }
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
if (target.kind === "page") {
|
|
1332
|
+
const page = schema.pages?.find((candidate) => candidate.id === target.id);
|
|
1333
|
+
if (page === void 0 || property !== "title" && property !== "description") return void 0;
|
|
1334
|
+
return {
|
|
1335
|
+
locale,
|
|
1336
|
+
kind: "page",
|
|
1337
|
+
nodeId: page.id,
|
|
1338
|
+
property,
|
|
1339
|
+
sourceText: page[property] ?? "",
|
|
1340
|
+
translatedText,
|
|
1341
|
+
...page.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: page.translationMetadata[locale]?.[property] }
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
if (property !== "label") return void 0;
|
|
1345
|
+
const option = schema.fields.filter((field) => "options" in field).flatMap((field) => field.options).find((candidate) => candidate.id === target.id);
|
|
1346
|
+
if (option === void 0) return void 0;
|
|
1347
|
+
return {
|
|
1348
|
+
locale,
|
|
1349
|
+
kind: "option",
|
|
1350
|
+
nodeId: option.id,
|
|
1351
|
+
property,
|
|
1352
|
+
sourceText: option.label,
|
|
1353
|
+
translatedText,
|
|
1354
|
+
...option.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: option.translationMetadata[locale]?.[property] }
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1304
1357
|
function defaultConditionValue(field) {
|
|
1305
1358
|
if (field.type === "checkbox") return true;
|
|
1306
1359
|
if (field.type === "number" || field.type === "rating") return field.min ?? 1;
|
|
@@ -1559,25 +1612,30 @@ function FormBuilder({
|
|
|
1559
1612
|
setIsTranslating(false);
|
|
1560
1613
|
}
|
|
1561
1614
|
};
|
|
1562
|
-
const
|
|
1563
|
-
if (readOnly) return;
|
|
1564
|
-
const
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1615
|
+
const setManualTranslation = (targetLocale, target, property, text) => {
|
|
1616
|
+
if (readOnly) return { success: true };
|
|
1617
|
+
const normalizedLocale = targetLocale.trim();
|
|
1618
|
+
const context = manualTranslationContext(schema, normalizedLocale, target, property, text);
|
|
1619
|
+
const metadata = context === void 0 ? void 0 : createManualTranslationMetadata?.(context);
|
|
1620
|
+
return executeAction(
|
|
1567
1621
|
() => headless.setLocaleTranslation(
|
|
1568
|
-
|
|
1622
|
+
normalizedLocale,
|
|
1569
1623
|
target,
|
|
1570
|
-
|
|
1571
|
-
|
|
1624
|
+
property,
|
|
1625
|
+
text,
|
|
1572
1626
|
metadata === void 0 ? void 0 : { metadata }
|
|
1573
1627
|
),
|
|
1574
1628
|
{
|
|
1575
|
-
action: "
|
|
1576
|
-
targetId:
|
|
1577
|
-
params: { locale:
|
|
1629
|
+
action: "setManualTranslation",
|
|
1630
|
+
...target.id === void 0 ? {} : { targetId: target.id },
|
|
1631
|
+
params: { locale: normalizedLocale, kind: target.kind, property }
|
|
1578
1632
|
}
|
|
1579
1633
|
);
|
|
1580
1634
|
};
|
|
1635
|
+
const updateManualTranslation = (context) => {
|
|
1636
|
+
const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
|
|
1637
|
+
setManualTranslation(context.locale, target, context.property, context.translatedText);
|
|
1638
|
+
};
|
|
1581
1639
|
const updateFormTranslation = (property, translatedText) => {
|
|
1582
1640
|
if (editingLocale.length === 0) return;
|
|
1583
1641
|
updateManualTranslation({
|
|
@@ -1686,6 +1744,7 @@ function FormBuilder({
|
|
|
1686
1744
|
...target.id === void 0 ? {} : { targetId: target.id },
|
|
1687
1745
|
params: { locale: targetLocale, property }
|
|
1688
1746
|
}),
|
|
1747
|
+
setManualTranslation,
|
|
1689
1748
|
addLocale: (targetLocale) => executeAction(() => headless.addLocale(targetLocale), {
|
|
1690
1749
|
action: "addLocale",
|
|
1691
1750
|
params: { locale: targetLocale }
|
|
@@ -3063,6 +3122,8 @@ function ContextFormRenderer({
|
|
|
3063
3122
|
autoSaveKey,
|
|
3064
3123
|
beforeSubmit,
|
|
3065
3124
|
onDraftSave,
|
|
3125
|
+
successRenderMode = "append",
|
|
3126
|
+
hideFormOnSuccess = false,
|
|
3066
3127
|
submissionGuards = [],
|
|
3067
3128
|
receiptStore,
|
|
3068
3129
|
attemptStore,
|
|
@@ -3078,9 +3139,11 @@ function ContextFormRenderer({
|
|
|
3078
3139
|
const [focusFieldId, setFocusFieldId] = (0, import_react5.useState)(null);
|
|
3079
3140
|
const [confirmation, setConfirmation] = (0, import_react5.useState)(null);
|
|
3080
3141
|
const [guardMessage, setGuardMessage] = (0, import_react5.useState)(null);
|
|
3142
|
+
const [guardsPending, setGuardsPending] = (0, import_react5.useState)(false);
|
|
3081
3143
|
const [receipt, setReceipt] = (0, import_react5.useState)(null);
|
|
3082
3144
|
const [receiptLoaded, setReceiptLoaded] = (0, import_react5.useState)(receiptStore === void 0);
|
|
3083
3145
|
const rendererSubmissionInFlight = (0, import_react5.useRef)(false);
|
|
3146
|
+
const completionRef = (0, import_react5.useRef)(null);
|
|
3084
3147
|
const pages = form.schema.pages;
|
|
3085
3148
|
const visiblePageIndexes = (0, import_react5.useMemo)(
|
|
3086
3149
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
@@ -3090,8 +3153,9 @@ function ContextFormRenderer({
|
|
|
3090
3153
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
3091
3154
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
3092
3155
|
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";
|
|
3156
|
+
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
3094
3157
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
3158
|
+
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
3095
3159
|
(0, import_react5.useEffect)(() => {
|
|
3096
3160
|
let active = true;
|
|
3097
3161
|
if (receiptStore === void 0) {
|
|
@@ -3131,6 +3195,10 @@ function ContextFormRenderer({
|
|
|
3131
3195
|
setFocusFieldId(null);
|
|
3132
3196
|
}
|
|
3133
3197
|
}, [focusFieldId]);
|
|
3198
|
+
(0, import_react5.useEffect)(() => {
|
|
3199
|
+
if (!isReplaceMode || form.submitStatus !== "success") return;
|
|
3200
|
+
completionRef.current?.focus();
|
|
3201
|
+
}, [form.submitStatus, isReplaceMode]);
|
|
3134
3202
|
(0, import_react5.useEffect)(() => {
|
|
3135
3203
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
3136
3204
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
@@ -3196,13 +3264,14 @@ function ContextFormRenderer({
|
|
|
3196
3264
|
};
|
|
3197
3265
|
};
|
|
3198
3266
|
const submitValues = async (guardsConfirmed = false) => {
|
|
3199
|
-
if (rendererSubmissionInFlight.current || confirmation !== null && !guardsConfirmed) {
|
|
3267
|
+
if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
|
|
3200
3268
|
return { status: "cancelled" };
|
|
3201
3269
|
}
|
|
3202
3270
|
const validation = (0, import_core4.validateAnswers)(form.schema, form.values);
|
|
3203
3271
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
3204
3272
|
if (validation.valid && !guardsConfirmed && submissionGuards.length > 0) {
|
|
3205
3273
|
rendererSubmissionInFlight.current = true;
|
|
3274
|
+
setGuardsPending(true);
|
|
3206
3275
|
try {
|
|
3207
3276
|
const guardResult = await runSubmissionGuards(submissionGuards);
|
|
3208
3277
|
if (guardResult.status === "block") {
|
|
@@ -3219,6 +3288,7 @@ function ContextFormRenderer({
|
|
|
3219
3288
|
}
|
|
3220
3289
|
} finally {
|
|
3221
3290
|
rendererSubmissionInFlight.current = false;
|
|
3291
|
+
setGuardsPending(false);
|
|
3222
3292
|
}
|
|
3223
3293
|
}
|
|
3224
3294
|
setGuardMessage(null);
|
|
@@ -3256,7 +3326,6 @@ function ContextFormRenderer({
|
|
|
3256
3326
|
};
|
|
3257
3327
|
try {
|
|
3258
3328
|
await receiptStore.save(storedReceipt);
|
|
3259
|
-
setReceipt(storedReceipt);
|
|
3260
3329
|
} catch (cause) {
|
|
3261
3330
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
3262
3331
|
try {
|
|
@@ -3283,7 +3352,7 @@ function ContextFormRenderer({
|
|
|
3283
3352
|
};
|
|
3284
3353
|
const handleSubmit = (event) => {
|
|
3285
3354
|
event.preventDefault();
|
|
3286
|
-
if (
|
|
3355
|
+
if (submitState === "submitting" || submitState === "confirming" || submitState === "success") return;
|
|
3287
3356
|
void submitValues();
|
|
3288
3357
|
};
|
|
3289
3358
|
const confirmSubmission = () => {
|
|
@@ -3302,7 +3371,17 @@ function ContextFormRenderer({
|
|
|
3302
3371
|
const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
|
|
3303
3372
|
const canPrev = pages !== void 0 && activeVisibleIndex > 0;
|
|
3304
3373
|
const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
|
|
3305
|
-
const renderSubmitButton = () =>
|
|
3374
|
+
const renderSubmitButton = () => {
|
|
3375
|
+
const submitButtonProps = {
|
|
3376
|
+
isSubmitting: submitState === "submitting",
|
|
3377
|
+
submitStatus: submitState,
|
|
3378
|
+
disabled: interactionLocked || submitState === "success",
|
|
3379
|
+
onSubmit: () => void submitValues()
|
|
3380
|
+
};
|
|
3381
|
+
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
|
+
};
|
|
3383
|
+
const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
|
|
3384
|
+
const completionRegion = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: completionRef, className: "fe-completion", role: "status", "aria-live": "polite", tabIndex: -1, children: slots.renderCompletion?.({ message: completionMessage }) ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: completionMessage }) });
|
|
3306
3385
|
if (!receiptLoaded) return null;
|
|
3307
3386
|
if (receipt !== null) {
|
|
3308
3387
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
|
|
@@ -3313,6 +3392,9 @@ function ContextFormRenderer({
|
|
|
3313
3392
|
receiptStore === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => void resetReceipt(), children: form.translate("form.submitAnother") })
|
|
3314
3393
|
] }) });
|
|
3315
3394
|
}
|
|
3395
|
+
if (form.submitStatus === "success" && isReplaceMode) {
|
|
3396
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
|
|
3397
|
+
}
|
|
3316
3398
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
|
|
3317
3399
|
slots.renderHeader?.({
|
|
3318
3400
|
title: form.schema.title,
|
|
@@ -3431,9 +3513,7 @@ function ContextFormRenderer({
|
|
|
3431
3513
|
canNext ? null : renderSubmitButton()
|
|
3432
3514
|
] }),
|
|
3433
3515
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
3434
|
-
form.submitStatus === "success" ?
|
|
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,
|
|
3516
|
+
form.submitStatus === "success" ? completionRegion : null,
|
|
3437
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
|
|
3438
3518
|
] })
|
|
3439
3519
|
] });
|
package/dist/index.d.cts
CHANGED
|
@@ -229,10 +229,17 @@ interface FormBuilderComponents {
|
|
|
229
229
|
readonly renderIcon?: (actionType: BuilderActionIconType) => ReactNode;
|
|
230
230
|
}
|
|
231
231
|
type FormBuilderActions = Omit<FormBuilderResult, "schema" | "validationIssues">;
|
|
232
|
+
interface ManualTranslationTarget {
|
|
233
|
+
readonly kind: "form" | "page" | "field" | "option";
|
|
234
|
+
readonly id?: string;
|
|
235
|
+
}
|
|
236
|
+
interface BuilderSlotActions extends FormBuilderActions {
|
|
237
|
+
readonly setManualTranslation: (locale: string, target: ManualTranslationTarget, property: "title" | "description" | "label" | "completionMessage", text: string) => BuilderActionResult;
|
|
238
|
+
}
|
|
232
239
|
interface BuilderSlotBaseProps {
|
|
233
240
|
readonly schema: FormSchema;
|
|
234
241
|
readonly readOnly: boolean;
|
|
235
|
-
readonly actions:
|
|
242
|
+
readonly actions: BuilderSlotActions;
|
|
236
243
|
readonly components: Required<FormBuilderComponents>;
|
|
237
244
|
readonly translate: (key: string, params?: Record<string, unknown>) => string;
|
|
238
245
|
}
|
|
@@ -302,7 +309,7 @@ interface FormBuilderSlots {
|
|
|
302
309
|
readonly sectionOrder?: readonly FormBuilderSectionName[];
|
|
303
310
|
}
|
|
304
311
|
interface BuilderActionContext {
|
|
305
|
-
readonly action: "addField" | "removeField" | "moveField" | "changeFieldType" | "updateField" | "addOption" | "removeOption" | "moveOption" | "updateOption" | "addPage" | "removePage" | "movePage" | "updatePage" | "assignFieldToPage" | "addLocale" | "setDefaultLocale" | "setDisplayCondition" | "setSourceText" | "setLocaleTranslation";
|
|
312
|
+
readonly action: "addField" | "removeField" | "moveField" | "changeFieldType" | "updateField" | "addOption" | "removeOption" | "moveOption" | "updateOption" | "addPage" | "removePage" | "movePage" | "updatePage" | "assignFieldToPage" | "addLocale" | "setDefaultLocale" | "setDisplayCondition" | "setSourceText" | "setLocaleTranslation" | "setManualTranslation";
|
|
306
313
|
readonly targetId?: string;
|
|
307
314
|
readonly params?: Record<string, unknown>;
|
|
308
315
|
}
|
|
@@ -344,7 +351,16 @@ type SubmissionGuardResult = {
|
|
|
344
351
|
readonly message?: string;
|
|
345
352
|
};
|
|
346
353
|
type SubmissionGuard = (schema: FormSchema, values: Record<string, unknown>) => SubmissionGuardResult | Promise<SubmissionGuardResult>;
|
|
354
|
+
type FormSuccessRenderMode = "append" | "replace";
|
|
355
|
+
type FormSubmitStatus = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
356
|
+
/** @deprecated Use FormSubmitStatus instead. */
|
|
347
357
|
type FormSubmitState = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
358
|
+
interface RenderSubmitButtonProps {
|
|
359
|
+
readonly isSubmitting: boolean;
|
|
360
|
+
readonly submitStatus: FormSubmitStatus;
|
|
361
|
+
readonly disabled: boolean;
|
|
362
|
+
readonly onSubmit: () => void;
|
|
363
|
+
}
|
|
348
364
|
interface SubmissionConfirmationSlotProps {
|
|
349
365
|
readonly findings: readonly SensitiveDataFinding[];
|
|
350
366
|
readonly message?: string;
|
|
@@ -377,10 +393,7 @@ interface FormRendererSlots {
|
|
|
377
393
|
readonly onPrev: () => void;
|
|
378
394
|
readonly onNext: () => void;
|
|
379
395
|
}) => ReactNode;
|
|
380
|
-
readonly renderSubmitButton?: (props:
|
|
381
|
-
readonly isSubmitting: boolean;
|
|
382
|
-
readonly onSubmit: () => void;
|
|
383
|
-
}) => ReactNode;
|
|
396
|
+
readonly renderSubmitButton?: (props: RenderSubmitButtonProps) => ReactNode;
|
|
384
397
|
readonly renderValidationSummary?: (props: {
|
|
385
398
|
readonly issues: readonly ValidationError[];
|
|
386
399
|
}) => ReactNode;
|
|
@@ -494,6 +507,13 @@ type FieldComponents = Partial<Record<FieldType, ComponentType<FieldComponentPro
|
|
|
494
507
|
interface FormRendererPresentationProps extends SubmissionProtectionProps {
|
|
495
508
|
readonly components?: FieldComponents;
|
|
496
509
|
readonly className?: string;
|
|
510
|
+
/**
|
|
511
|
+
* Controls where the completion message is rendered after a successful submission.
|
|
512
|
+
* Defaults to "append" for backwards compatibility.
|
|
513
|
+
*/
|
|
514
|
+
readonly successRenderMode?: FormSuccessRenderMode;
|
|
515
|
+
/** @deprecated Use successRenderMode="replace" instead. */
|
|
516
|
+
readonly hideFormOnSuccess?: boolean;
|
|
497
517
|
readonly successMessageKey?: string;
|
|
498
518
|
readonly errorMessageKey?: string;
|
|
499
519
|
readonly autoSaveKey?: string;
|
|
@@ -512,4 +532,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
512
532
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
513
533
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
514
534
|
|
|
515
|
-
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 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 IconButtonProps, type InputComponentProps, type ManualTranslationContext, 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 };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -229,10 +229,17 @@ interface FormBuilderComponents {
|
|
|
229
229
|
readonly renderIcon?: (actionType: BuilderActionIconType) => ReactNode;
|
|
230
230
|
}
|
|
231
231
|
type FormBuilderActions = Omit<FormBuilderResult, "schema" | "validationIssues">;
|
|
232
|
+
interface ManualTranslationTarget {
|
|
233
|
+
readonly kind: "form" | "page" | "field" | "option";
|
|
234
|
+
readonly id?: string;
|
|
235
|
+
}
|
|
236
|
+
interface BuilderSlotActions extends FormBuilderActions {
|
|
237
|
+
readonly setManualTranslation: (locale: string, target: ManualTranslationTarget, property: "title" | "description" | "label" | "completionMessage", text: string) => BuilderActionResult;
|
|
238
|
+
}
|
|
232
239
|
interface BuilderSlotBaseProps {
|
|
233
240
|
readonly schema: FormSchema;
|
|
234
241
|
readonly readOnly: boolean;
|
|
235
|
-
readonly actions:
|
|
242
|
+
readonly actions: BuilderSlotActions;
|
|
236
243
|
readonly components: Required<FormBuilderComponents>;
|
|
237
244
|
readonly translate: (key: string, params?: Record<string, unknown>) => string;
|
|
238
245
|
}
|
|
@@ -302,7 +309,7 @@ interface FormBuilderSlots {
|
|
|
302
309
|
readonly sectionOrder?: readonly FormBuilderSectionName[];
|
|
303
310
|
}
|
|
304
311
|
interface BuilderActionContext {
|
|
305
|
-
readonly action: "addField" | "removeField" | "moveField" | "changeFieldType" | "updateField" | "addOption" | "removeOption" | "moveOption" | "updateOption" | "addPage" | "removePage" | "movePage" | "updatePage" | "assignFieldToPage" | "addLocale" | "setDefaultLocale" | "setDisplayCondition" | "setSourceText" | "setLocaleTranslation";
|
|
312
|
+
readonly action: "addField" | "removeField" | "moveField" | "changeFieldType" | "updateField" | "addOption" | "removeOption" | "moveOption" | "updateOption" | "addPage" | "removePage" | "movePage" | "updatePage" | "assignFieldToPage" | "addLocale" | "setDefaultLocale" | "setDisplayCondition" | "setSourceText" | "setLocaleTranslation" | "setManualTranslation";
|
|
306
313
|
readonly targetId?: string;
|
|
307
314
|
readonly params?: Record<string, unknown>;
|
|
308
315
|
}
|
|
@@ -344,7 +351,16 @@ type SubmissionGuardResult = {
|
|
|
344
351
|
readonly message?: string;
|
|
345
352
|
};
|
|
346
353
|
type SubmissionGuard = (schema: FormSchema, values: Record<string, unknown>) => SubmissionGuardResult | Promise<SubmissionGuardResult>;
|
|
354
|
+
type FormSuccessRenderMode = "append" | "replace";
|
|
355
|
+
type FormSubmitStatus = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
356
|
+
/** @deprecated Use FormSubmitStatus instead. */
|
|
347
357
|
type FormSubmitState = "idle" | "submitting" | "confirming" | "success" | "error";
|
|
358
|
+
interface RenderSubmitButtonProps {
|
|
359
|
+
readonly isSubmitting: boolean;
|
|
360
|
+
readonly submitStatus: FormSubmitStatus;
|
|
361
|
+
readonly disabled: boolean;
|
|
362
|
+
readonly onSubmit: () => void;
|
|
363
|
+
}
|
|
348
364
|
interface SubmissionConfirmationSlotProps {
|
|
349
365
|
readonly findings: readonly SensitiveDataFinding[];
|
|
350
366
|
readonly message?: string;
|
|
@@ -377,10 +393,7 @@ interface FormRendererSlots {
|
|
|
377
393
|
readonly onPrev: () => void;
|
|
378
394
|
readonly onNext: () => void;
|
|
379
395
|
}) => ReactNode;
|
|
380
|
-
readonly renderSubmitButton?: (props:
|
|
381
|
-
readonly isSubmitting: boolean;
|
|
382
|
-
readonly onSubmit: () => void;
|
|
383
|
-
}) => ReactNode;
|
|
396
|
+
readonly renderSubmitButton?: (props: RenderSubmitButtonProps) => ReactNode;
|
|
384
397
|
readonly renderValidationSummary?: (props: {
|
|
385
398
|
readonly issues: readonly ValidationError[];
|
|
386
399
|
}) => ReactNode;
|
|
@@ -494,6 +507,13 @@ type FieldComponents = Partial<Record<FieldType, ComponentType<FieldComponentPro
|
|
|
494
507
|
interface FormRendererPresentationProps extends SubmissionProtectionProps {
|
|
495
508
|
readonly components?: FieldComponents;
|
|
496
509
|
readonly className?: string;
|
|
510
|
+
/**
|
|
511
|
+
* Controls where the completion message is rendered after a successful submission.
|
|
512
|
+
* Defaults to "append" for backwards compatibility.
|
|
513
|
+
*/
|
|
514
|
+
readonly successRenderMode?: FormSuccessRenderMode;
|
|
515
|
+
/** @deprecated Use successRenderMode="replace" instead. */
|
|
516
|
+
readonly hideFormOnSuccess?: boolean;
|
|
497
517
|
readonly successMessageKey?: string;
|
|
498
518
|
readonly errorMessageKey?: string;
|
|
499
519
|
readonly autoSaveKey?: string;
|
|
@@ -512,4 +532,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
512
532
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
513
533
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
514
534
|
|
|
515
|
-
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 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 IconButtonProps, type InputComponentProps, type ManualTranslationContext, 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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1058,7 +1058,7 @@ function BuilderButton(props) {
|
|
|
1058
1058
|
...props,
|
|
1059
1059
|
className: primitiveClassName(props.className, "feb-button", context.unstyled),
|
|
1060
1060
|
disabled: context.readOnly || props.disabled === true,
|
|
1061
|
-
readOnly: context.readOnly
|
|
1061
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1062
1062
|
}
|
|
1063
1063
|
);
|
|
1064
1064
|
}
|
|
@@ -1073,7 +1073,7 @@ function BuilderIconButton(props) {
|
|
|
1073
1073
|
icon,
|
|
1074
1074
|
className: primitiveClassName(props.className, "feb-icon-button", context.unstyled),
|
|
1075
1075
|
disabled: context.readOnly || props.disabled === true,
|
|
1076
|
-
readOnly: context.readOnly
|
|
1076
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1077
1077
|
}
|
|
1078
1078
|
);
|
|
1079
1079
|
}
|
|
@@ -1086,7 +1086,7 @@ function BuilderTextInput(props) {
|
|
|
1086
1086
|
...props,
|
|
1087
1087
|
className: primitiveClassName(props.className, "feb-input", context.unstyled),
|
|
1088
1088
|
disabled: context.readOnly || props.disabled === true,
|
|
1089
|
-
readOnly: context.readOnly
|
|
1089
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1090
1090
|
}
|
|
1091
1091
|
);
|
|
1092
1092
|
}
|
|
@@ -1099,7 +1099,7 @@ function BuilderTextArea(props) {
|
|
|
1099
1099
|
...props,
|
|
1100
1100
|
className: primitiveClassName(props.className, "feb-textarea", context.unstyled),
|
|
1101
1101
|
disabled: context.readOnly || props.disabled === true,
|
|
1102
|
-
readOnly: context.readOnly
|
|
1102
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1103
1103
|
}
|
|
1104
1104
|
);
|
|
1105
1105
|
}
|
|
@@ -1112,7 +1112,7 @@ function BuilderSelect(props) {
|
|
|
1112
1112
|
...props,
|
|
1113
1113
|
className: primitiveClassName(props.className, "feb-select", context.unstyled),
|
|
1114
1114
|
disabled: context.readOnly || props.disabled === true,
|
|
1115
|
-
readOnly: context.readOnly
|
|
1115
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1116
1116
|
}
|
|
1117
1117
|
);
|
|
1118
1118
|
}
|
|
@@ -1125,7 +1125,7 @@ function BuilderCheckbox(props) {
|
|
|
1125
1125
|
...props,
|
|
1126
1126
|
className: primitiveClassName(props.className, "feb-checkbox", context.unstyled),
|
|
1127
1127
|
disabled: context.readOnly || props.disabled === true,
|
|
1128
|
-
readOnly: context.readOnly
|
|
1128
|
+
readOnly: context.readOnly || props.readOnly === true
|
|
1129
1129
|
}
|
|
1130
1130
|
);
|
|
1131
1131
|
}
|
|
@@ -1271,6 +1271,59 @@ function fieldTypeKey(type) {
|
|
|
1271
1271
|
function operatorKey(operator) {
|
|
1272
1272
|
return `builder.operator.${operator}`;
|
|
1273
1273
|
}
|
|
1274
|
+
function manualTranslationContext(schema, locale, target, property, translatedText) {
|
|
1275
|
+
if (target.kind === "form") {
|
|
1276
|
+
if (property !== "title" && property !== "description" && property !== "completionMessage") return void 0;
|
|
1277
|
+
return {
|
|
1278
|
+
locale,
|
|
1279
|
+
kind: "form",
|
|
1280
|
+
nodeId: schema.id,
|
|
1281
|
+
property,
|
|
1282
|
+
sourceText: schema[property] ?? "",
|
|
1283
|
+
translatedText,
|
|
1284
|
+
...schema.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[locale]?.[property] }
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
if (target.id === void 0) return void 0;
|
|
1288
|
+
if (target.kind === "field") {
|
|
1289
|
+
const field = schema.fields.find((candidate) => candidate.id === target.id);
|
|
1290
|
+
if (field === void 0 || property !== "title" && property !== "description") return void 0;
|
|
1291
|
+
return {
|
|
1292
|
+
locale,
|
|
1293
|
+
kind: "field",
|
|
1294
|
+
nodeId: field.id,
|
|
1295
|
+
property,
|
|
1296
|
+
sourceText: field[property] ?? "",
|
|
1297
|
+
translatedText,
|
|
1298
|
+
...field.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: field.translationMetadata[locale]?.[property] }
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
if (target.kind === "page") {
|
|
1302
|
+
const page = schema.pages?.find((candidate) => candidate.id === target.id);
|
|
1303
|
+
if (page === void 0 || property !== "title" && property !== "description") return void 0;
|
|
1304
|
+
return {
|
|
1305
|
+
locale,
|
|
1306
|
+
kind: "page",
|
|
1307
|
+
nodeId: page.id,
|
|
1308
|
+
property,
|
|
1309
|
+
sourceText: page[property] ?? "",
|
|
1310
|
+
translatedText,
|
|
1311
|
+
...page.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: page.translationMetadata[locale]?.[property] }
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (property !== "label") return void 0;
|
|
1315
|
+
const option = schema.fields.filter((field) => "options" in field).flatMap((field) => field.options).find((candidate) => candidate.id === target.id);
|
|
1316
|
+
if (option === void 0) return void 0;
|
|
1317
|
+
return {
|
|
1318
|
+
locale,
|
|
1319
|
+
kind: "option",
|
|
1320
|
+
nodeId: option.id,
|
|
1321
|
+
property,
|
|
1322
|
+
sourceText: option.label,
|
|
1323
|
+
translatedText,
|
|
1324
|
+
...option.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: option.translationMetadata[locale]?.[property] }
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1274
1327
|
function defaultConditionValue(field) {
|
|
1275
1328
|
if (field.type === "checkbox") return true;
|
|
1276
1329
|
if (field.type === "number" || field.type === "rating") return field.min ?? 1;
|
|
@@ -1529,25 +1582,30 @@ function FormBuilder({
|
|
|
1529
1582
|
setIsTranslating(false);
|
|
1530
1583
|
}
|
|
1531
1584
|
};
|
|
1532
|
-
const
|
|
1533
|
-
if (readOnly) return;
|
|
1534
|
-
const
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1585
|
+
const setManualTranslation = (targetLocale, target, property, text) => {
|
|
1586
|
+
if (readOnly) return { success: true };
|
|
1587
|
+
const normalizedLocale = targetLocale.trim();
|
|
1588
|
+
const context = manualTranslationContext(schema, normalizedLocale, target, property, text);
|
|
1589
|
+
const metadata = context === void 0 ? void 0 : createManualTranslationMetadata?.(context);
|
|
1590
|
+
return executeAction(
|
|
1537
1591
|
() => headless.setLocaleTranslation(
|
|
1538
|
-
|
|
1592
|
+
normalizedLocale,
|
|
1539
1593
|
target,
|
|
1540
|
-
|
|
1541
|
-
|
|
1594
|
+
property,
|
|
1595
|
+
text,
|
|
1542
1596
|
metadata === void 0 ? void 0 : { metadata }
|
|
1543
1597
|
),
|
|
1544
1598
|
{
|
|
1545
|
-
action: "
|
|
1546
|
-
targetId:
|
|
1547
|
-
params: { locale:
|
|
1599
|
+
action: "setManualTranslation",
|
|
1600
|
+
...target.id === void 0 ? {} : { targetId: target.id },
|
|
1601
|
+
params: { locale: normalizedLocale, kind: target.kind, property }
|
|
1548
1602
|
}
|
|
1549
1603
|
);
|
|
1550
1604
|
};
|
|
1605
|
+
const updateManualTranslation = (context) => {
|
|
1606
|
+
const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
|
|
1607
|
+
setManualTranslation(context.locale, target, context.property, context.translatedText);
|
|
1608
|
+
};
|
|
1551
1609
|
const updateFormTranslation = (property, translatedText) => {
|
|
1552
1610
|
if (editingLocale.length === 0) return;
|
|
1553
1611
|
updateManualTranslation({
|
|
@@ -1656,6 +1714,7 @@ function FormBuilder({
|
|
|
1656
1714
|
...target.id === void 0 ? {} : { targetId: target.id },
|
|
1657
1715
|
params: { locale: targetLocale, property }
|
|
1658
1716
|
}),
|
|
1717
|
+
setManualTranslation,
|
|
1659
1718
|
addLocale: (targetLocale) => executeAction(() => headless.addLocale(targetLocale), {
|
|
1660
1719
|
action: "addLocale",
|
|
1661
1720
|
params: { locale: targetLocale }
|
|
@@ -3051,6 +3110,8 @@ function ContextFormRenderer({
|
|
|
3051
3110
|
autoSaveKey,
|
|
3052
3111
|
beforeSubmit,
|
|
3053
3112
|
onDraftSave,
|
|
3113
|
+
successRenderMode = "append",
|
|
3114
|
+
hideFormOnSuccess = false,
|
|
3054
3115
|
submissionGuards = [],
|
|
3055
3116
|
receiptStore,
|
|
3056
3117
|
attemptStore,
|
|
@@ -3066,9 +3127,11 @@ function ContextFormRenderer({
|
|
|
3066
3127
|
const [focusFieldId, setFocusFieldId] = useState4(null);
|
|
3067
3128
|
const [confirmation, setConfirmation] = useState4(null);
|
|
3068
3129
|
const [guardMessage, setGuardMessage] = useState4(null);
|
|
3130
|
+
const [guardsPending, setGuardsPending] = useState4(false);
|
|
3069
3131
|
const [receipt, setReceipt] = useState4(null);
|
|
3070
3132
|
const [receiptLoaded, setReceiptLoaded] = useState4(receiptStore === void 0);
|
|
3071
3133
|
const rendererSubmissionInFlight = useRef2(false);
|
|
3134
|
+
const completionRef = useRef2(null);
|
|
3072
3135
|
const pages = form.schema.pages;
|
|
3073
3136
|
const visiblePageIndexes = useMemo4(
|
|
3074
3137
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
@@ -3078,8 +3141,9 @@ function ContextFormRenderer({
|
|
|
3078
3141
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
3079
3142
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
3080
3143
|
const visibleValues = useMemo4(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
|
|
3081
|
-
const submitState = confirmation === null ? form.submitStatus : "confirming";
|
|
3144
|
+
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
3082
3145
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
3146
|
+
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
3083
3147
|
useEffect3(() => {
|
|
3084
3148
|
let active = true;
|
|
3085
3149
|
if (receiptStore === void 0) {
|
|
@@ -3119,6 +3183,10 @@ function ContextFormRenderer({
|
|
|
3119
3183
|
setFocusFieldId(null);
|
|
3120
3184
|
}
|
|
3121
3185
|
}, [focusFieldId]);
|
|
3186
|
+
useEffect3(() => {
|
|
3187
|
+
if (!isReplaceMode || form.submitStatus !== "success") return;
|
|
3188
|
+
completionRef.current?.focus();
|
|
3189
|
+
}, [form.submitStatus, isReplaceMode]);
|
|
3122
3190
|
useEffect3(() => {
|
|
3123
3191
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
3124
3192
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
@@ -3184,13 +3252,14 @@ function ContextFormRenderer({
|
|
|
3184
3252
|
};
|
|
3185
3253
|
};
|
|
3186
3254
|
const submitValues = async (guardsConfirmed = false) => {
|
|
3187
|
-
if (rendererSubmissionInFlight.current || confirmation !== null && !guardsConfirmed) {
|
|
3255
|
+
if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
|
|
3188
3256
|
return { status: "cancelled" };
|
|
3189
3257
|
}
|
|
3190
3258
|
const validation = validateAnswers2(form.schema, form.values);
|
|
3191
3259
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
3192
3260
|
if (validation.valid && !guardsConfirmed && submissionGuards.length > 0) {
|
|
3193
3261
|
rendererSubmissionInFlight.current = true;
|
|
3262
|
+
setGuardsPending(true);
|
|
3194
3263
|
try {
|
|
3195
3264
|
const guardResult = await runSubmissionGuards(submissionGuards);
|
|
3196
3265
|
if (guardResult.status === "block") {
|
|
@@ -3207,6 +3276,7 @@ function ContextFormRenderer({
|
|
|
3207
3276
|
}
|
|
3208
3277
|
} finally {
|
|
3209
3278
|
rendererSubmissionInFlight.current = false;
|
|
3279
|
+
setGuardsPending(false);
|
|
3210
3280
|
}
|
|
3211
3281
|
}
|
|
3212
3282
|
setGuardMessage(null);
|
|
@@ -3244,7 +3314,6 @@ function ContextFormRenderer({
|
|
|
3244
3314
|
};
|
|
3245
3315
|
try {
|
|
3246
3316
|
await receiptStore.save(storedReceipt);
|
|
3247
|
-
setReceipt(storedReceipt);
|
|
3248
3317
|
} catch (cause) {
|
|
3249
3318
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
3250
3319
|
try {
|
|
@@ -3271,7 +3340,7 @@ function ContextFormRenderer({
|
|
|
3271
3340
|
};
|
|
3272
3341
|
const handleSubmit = (event) => {
|
|
3273
3342
|
event.preventDefault();
|
|
3274
|
-
if (
|
|
3343
|
+
if (submitState === "submitting" || submitState === "confirming" || submitState === "success") return;
|
|
3275
3344
|
void submitValues();
|
|
3276
3345
|
};
|
|
3277
3346
|
const confirmSubmission = () => {
|
|
@@ -3290,7 +3359,17 @@ function ContextFormRenderer({
|
|
|
3290
3359
|
const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
|
|
3291
3360
|
const canPrev = pages !== void 0 && activeVisibleIndex > 0;
|
|
3292
3361
|
const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
|
|
3293
|
-
const renderSubmitButton = () =>
|
|
3362
|
+
const renderSubmitButton = () => {
|
|
3363
|
+
const submitButtonProps = {
|
|
3364
|
+
isSubmitting: submitState === "submitting",
|
|
3365
|
+
submitStatus: submitState,
|
|
3366
|
+
disabled: interactionLocked || submitState === "success",
|
|
3367
|
+
onSubmit: () => void submitValues()
|
|
3368
|
+
};
|
|
3369
|
+
return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: submitButtonProps.disabled, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
|
|
3370
|
+
};
|
|
3371
|
+
const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
|
|
3372
|
+
const completionRegion = /* @__PURE__ */ jsx3("div", { ref: completionRef, className: "fe-completion", role: "status", "aria-live": "polite", tabIndex: -1, children: slots.renderCompletion?.({ message: completionMessage }) ?? /* @__PURE__ */ jsx3("div", { children: completionMessage }) });
|
|
3294
3373
|
if (!receiptLoaded) return null;
|
|
3295
3374
|
if (receipt !== null) {
|
|
3296
3375
|
return /* @__PURE__ */ jsx3("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
|
|
@@ -3301,6 +3380,9 @@ function ContextFormRenderer({
|
|
|
3301
3380
|
receiptStore === void 0 ? null : /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => void resetReceipt(), children: form.translate("form.submitAnother") })
|
|
3302
3381
|
] }) });
|
|
3303
3382
|
}
|
|
3383
|
+
if (form.submitStatus === "success" && isReplaceMode) {
|
|
3384
|
+
return /* @__PURE__ */ jsx3("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
|
|
3385
|
+
}
|
|
3304
3386
|
return /* @__PURE__ */ jsxs2("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
|
|
3305
3387
|
slots.renderHeader?.({
|
|
3306
3388
|
title: form.schema.title,
|
|
@@ -3419,9 +3501,7 @@ function ContextFormRenderer({
|
|
|
3419
3501
|
canNext ? null : renderSubmitButton()
|
|
3420
3502
|
] }),
|
|
3421
3503
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
3422
|
-
form.submitStatus === "success" ?
|
|
3423
|
-
message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
|
|
3424
|
-
}) ?? /* @__PURE__ */ jsx3("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
|
|
3504
|
+
form.submitStatus === "success" ? completionRegion : null,
|
|
3425
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
|
|
3426
3506
|
] })
|
|
3427
3507
|
] });
|
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.5",
|
|
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.5",
|
|
46
|
+
"@form-engine-ts/privacy": "2.9.5"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|