@form-engine-ts/react 2.9.6 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -13
- package/dist/index.cjs +288 -50
- package/dist/index.d.cts +84 -8
- package/dist/index.d.ts +84 -8
- package/dist/index.js +287 -50
- package/dist/styles.css +34 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -99,6 +99,7 @@ function createLocalStorageSubmissionAttemptStore(options = {}) {
|
|
|
99
99
|
|
|
100
100
|
// src/builder.tsx
|
|
101
101
|
import {
|
|
102
|
+
DEFAULT_FIELD_TYPE_DEFINITIONS,
|
|
102
103
|
populateSchemaTranslations
|
|
103
104
|
} from "@form-engine-ts/core";
|
|
104
105
|
import { Children, createContext, isValidElement, useContext, useState } from "react";
|
|
@@ -743,6 +744,33 @@ function useFormBuilder({
|
|
|
743
744
|
};
|
|
744
745
|
}
|
|
745
746
|
|
|
747
|
+
// src/i18n.ts
|
|
748
|
+
var BUILDER_TRANSLATION_KEYS = {
|
|
749
|
+
ADD_FIELD: "builder.actions.addField",
|
|
750
|
+
SELECT_FIELD_TYPE: "builder.fields.selectType",
|
|
751
|
+
SELECT_LOCALE_TO_ADD: "builder.localization.selectLocaleToAdd",
|
|
752
|
+
FIELD_TYPE_TEXT: "builder.fields.typeText",
|
|
753
|
+
FIELD_TYPE_TEXTAREA: "builder.fields.typeTextarea",
|
|
754
|
+
FIELD_TYPE_NUMBER: "builder.fields.typeNumber",
|
|
755
|
+
FIELD_TYPE_RATING: "builder.fields.typeRating",
|
|
756
|
+
FIELD_TYPE_RADIO: "builder.fields.typeRadio",
|
|
757
|
+
FIELD_TYPE_CHECKBOX: "builder.fields.typeCheckbox",
|
|
758
|
+
FIELD_TYPE_SELECT: "builder.fields.typeSelect",
|
|
759
|
+
FIELD_TYPE_MULTI_SELECT: "builder.fields.typeMultiSelect"
|
|
760
|
+
};
|
|
761
|
+
var BUILDER_TRANSLATION_ALIASES = {
|
|
762
|
+
"builder.actions.addField": "builder.addQuestion",
|
|
763
|
+
"builder.localization.selectLocaleToAdd": "builder.selectLocaleToAdd",
|
|
764
|
+
"builder.fields.typeText": "builder.fieldType.text",
|
|
765
|
+
"builder.fields.typeTextarea": "builder.fieldType.textarea",
|
|
766
|
+
"builder.fields.typeNumber": "builder.fieldType.number",
|
|
767
|
+
"builder.fields.typeRating": "builder.fieldType.rating",
|
|
768
|
+
"builder.fields.typeRadio": "builder.fieldType.radio",
|
|
769
|
+
"builder.fields.typeCheckbox": "builder.fieldType.checkbox",
|
|
770
|
+
"builder.fields.typeSelect": "builder.fieldType.select",
|
|
771
|
+
"builder.fields.typeMultiSelect": "builder.fieldType.multi-select"
|
|
772
|
+
};
|
|
773
|
+
|
|
746
774
|
// src/builder.tsx
|
|
747
775
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
748
776
|
function DefaultButton({
|
|
@@ -813,6 +841,26 @@ function defaultIconFor(actionType) {
|
|
|
813
841
|
return "\u6587";
|
|
814
842
|
}
|
|
815
843
|
}
|
|
844
|
+
function defaultFieldTypeIcon(type) {
|
|
845
|
+
switch (type) {
|
|
846
|
+
case "text":
|
|
847
|
+
return "T";
|
|
848
|
+
case "textarea":
|
|
849
|
+
return "\u2261";
|
|
850
|
+
case "number":
|
|
851
|
+
return "#";
|
|
852
|
+
case "rating":
|
|
853
|
+
return "\u2605";
|
|
854
|
+
case "select":
|
|
855
|
+
return "\u25BE";
|
|
856
|
+
case "multi-select":
|
|
857
|
+
return "\u2611";
|
|
858
|
+
case "checkbox":
|
|
859
|
+
return "\u25A1";
|
|
860
|
+
case "radio":
|
|
861
|
+
return "\u25C9";
|
|
862
|
+
}
|
|
863
|
+
}
|
|
816
864
|
function DefaultTextInput({
|
|
817
865
|
id,
|
|
818
866
|
className,
|
|
@@ -925,8 +973,15 @@ function DefaultSelect({
|
|
|
925
973
|
helperText,
|
|
926
974
|
value,
|
|
927
975
|
onChange,
|
|
928
|
-
|
|
976
|
+
onKeyDown,
|
|
977
|
+
options,
|
|
978
|
+
renderOption,
|
|
979
|
+
renderValue
|
|
929
980
|
}) {
|
|
981
|
+
const normalizedOptions = options.map(
|
|
982
|
+
(option) => typeof option === "string" ? { value: option, label: option } : option
|
|
983
|
+
);
|
|
984
|
+
const selectedOption = normalizedOptions.find((option) => option.value === value);
|
|
930
985
|
const labelElement = label === void 0 ? null : id === void 0 ? /* @__PURE__ */ jsx("span", { children: label }) : /* @__PURE__ */ jsx("label", { htmlFor: id, children: label });
|
|
931
986
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
932
987
|
labelElement,
|
|
@@ -944,9 +999,11 @@ function DefaultSelect({
|
|
|
944
999
|
"aria-invalid": error === true ? true : void 0,
|
|
945
1000
|
value,
|
|
946
1001
|
onChange: (event) => onChange(event.currentTarget.value),
|
|
947
|
-
|
|
1002
|
+
onKeyDown,
|
|
1003
|
+
children: normalizedOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: renderOption === void 0 ? option.label : renderOption(option) }, option.value))
|
|
948
1004
|
}
|
|
949
1005
|
),
|
|
1006
|
+
renderValue === void 0 ? null : /* @__PURE__ */ jsx("output", { children: renderValue(selectedOption) }),
|
|
950
1007
|
helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ jsx("small", { id: ariaDescribedBy, children: helperText })
|
|
951
1008
|
] });
|
|
952
1009
|
}
|
|
@@ -1034,7 +1091,8 @@ var DEFAULT_COMPONENTS = {
|
|
|
1034
1091
|
Section: DefaultSection,
|
|
1035
1092
|
Fieldset: DefaultFieldset,
|
|
1036
1093
|
ErrorMessage: DefaultErrorMessage,
|
|
1037
|
-
renderIcon: defaultIconFor
|
|
1094
|
+
renderIcon: defaultIconFor,
|
|
1095
|
+
renderFieldTypeIcon: defaultFieldTypeIcon
|
|
1038
1096
|
};
|
|
1039
1097
|
var BuilderPrimitiveContext = createContext({
|
|
1040
1098
|
components: DEFAULT_COMPONENTS,
|
|
@@ -1214,6 +1272,11 @@ var BUILDER_DEFAULTS = {
|
|
|
1214
1272
|
"builder.translating": "Translating\u2026",
|
|
1215
1273
|
"builder.translationLocale": "Translation locale",
|
|
1216
1274
|
"builder.selectLocale": "Select a locale to edit translations.",
|
|
1275
|
+
"builder.localization.selectLocaleToAdd": "Select a locale to add",
|
|
1276
|
+
"builder.localization.noLocalesConfigured": "Translations not configured",
|
|
1277
|
+
"builder.localization.localesConfiguredSummary": "{{count}} locales configured",
|
|
1278
|
+
"builder.localization.allLocalesAdded": "\u3059\u3079\u3066\u306E\u5019\u88DC\u8A00\u8A9E\u304C\u8FFD\u52A0\u6E08\u307F\u3067\u3059",
|
|
1279
|
+
"builder.localization.maxLocalesReached": "\u767B\u9332\u53EF\u80FD\u306A\u6700\u5927\u8A00\u8A9E\u6570\uFF08{{max}}\uFF09\u306B\u9054\u3057\u307E\u3057\u305F",
|
|
1217
1280
|
"builder.translation": "{{locale}} translation",
|
|
1218
1281
|
"builder.translatedFormTitle": "Translated form title",
|
|
1219
1282
|
"builder.translatedFormDescription": "Translated form description",
|
|
@@ -1230,6 +1293,16 @@ var BUILDER_DEFAULTS = {
|
|
|
1230
1293
|
"builder.actions.close": "Close",
|
|
1231
1294
|
"builder.actions.dragHandle": "Reorder",
|
|
1232
1295
|
"builder.translationUnavailable": "Provide an async translation adapter to enable automatic translation.",
|
|
1296
|
+
"builder.fields.selectType": "Select field type",
|
|
1297
|
+
"builder.fields.typeText": "Text",
|
|
1298
|
+
"builder.fields.typeTextarea": "Textarea",
|
|
1299
|
+
"builder.fields.typeNumber": "Number",
|
|
1300
|
+
"builder.fields.typeRating": "Rating",
|
|
1301
|
+
"builder.fields.typeSelect": "Select",
|
|
1302
|
+
"builder.fields.typeMultiSelect": "Multi-select",
|
|
1303
|
+
"builder.fields.typeCheckbox": "Checkbox",
|
|
1304
|
+
"builder.fields.typeRadio": "Radio",
|
|
1305
|
+
"builder.actions.addField": "Add question",
|
|
1233
1306
|
"builder.fieldType.text": "Text",
|
|
1234
1307
|
"builder.fieldType.textarea": "Textarea",
|
|
1235
1308
|
"builder.fieldType.number": "Number",
|
|
@@ -1266,7 +1339,7 @@ function OrderedBuilderSections({
|
|
|
1266
1339
|
return groups.map((group, index) => ({ group, index, rank: ranks.get(sectionGroupName(group) ?? "questions") ?? order.length })).sort((left, right) => left.rank - right.rank || left.index - right.index).map(({ group }) => group);
|
|
1267
1340
|
}
|
|
1268
1341
|
function fieldTypeKey(type) {
|
|
1269
|
-
return `builder.fieldType.${type}`;
|
|
1342
|
+
return DEFAULT_FIELD_TYPE_DEFINITIONS.find((definition) => definition.type === type)?.labelKey ?? `builder.fieldType.${type}`;
|
|
1270
1343
|
}
|
|
1271
1344
|
function operatorKey(operator) {
|
|
1272
1345
|
return `builder.operator.${operator}`;
|
|
@@ -1427,7 +1500,8 @@ function FormBuilder({
|
|
|
1427
1500
|
const resolvedComponents = { ...DEFAULT_COMPONENTS, ...componentOverrides };
|
|
1428
1501
|
const components = {
|
|
1429
1502
|
...GUARDED_COMPONENTS,
|
|
1430
|
-
renderIcon: resolvedComponents.renderIcon
|
|
1503
|
+
renderIcon: resolvedComponents.renderIcon,
|
|
1504
|
+
renderFieldTypeIcon: resolvedComponents.renderFieldTypeIcon
|
|
1431
1505
|
};
|
|
1432
1506
|
const defaultStylesDisabled = disableDefaultStyles || unstyled;
|
|
1433
1507
|
const builderClass = (value) => defaultStylesDisabled ? void 0 : value;
|
|
@@ -1458,6 +1532,12 @@ function FormBuilder({
|
|
|
1458
1532
|
if (typeof value === "string" || typeof value === "number") translatorParams[name] = value;
|
|
1459
1533
|
}
|
|
1460
1534
|
const translated = translator?.translate(key, locale, translatorParams);
|
|
1535
|
+
if (translated !== void 0 && translated !== key) return translated;
|
|
1536
|
+
const alias = BUILDER_TRANSLATION_ALIASES[key];
|
|
1537
|
+
if (alias !== void 0) {
|
|
1538
|
+
const aliased = translator?.translate(alias, locale, translatorParams);
|
|
1539
|
+
if (aliased !== void 0 && aliased !== alias) return aliased;
|
|
1540
|
+
}
|
|
1461
1541
|
return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
|
|
1462
1542
|
};
|
|
1463
1543
|
const pagesEnabled = features?.pages ?? true;
|
|
@@ -2193,6 +2273,7 @@ function FormBuilder({
|
|
|
2193
2273
|
index,
|
|
2194
2274
|
currentLocale: editingLocale,
|
|
2195
2275
|
translate,
|
|
2276
|
+
...slots === void 0 ? {} : { slots },
|
|
2196
2277
|
...policy === void 0 ? {} : { policy },
|
|
2197
2278
|
...features === void 0 ? {} : { features },
|
|
2198
2279
|
readOnly,
|
|
@@ -2564,7 +2645,7 @@ function FormBuilder({
|
|
|
2564
2645
|
action: "addField",
|
|
2565
2646
|
disabled: initialFieldType === null || maxFieldsReached,
|
|
2566
2647
|
onClick: addField,
|
|
2567
|
-
children: translate(
|
|
2648
|
+
children: translate(BUILDER_TRANSLATION_KEYS.ADD_FIELD)
|
|
2568
2649
|
}
|
|
2569
2650
|
) })
|
|
2570
2651
|
] }) })
|
|
@@ -2585,6 +2666,18 @@ import {
|
|
|
2585
2666
|
validatePageAnswers
|
|
2586
2667
|
} from "@form-engine-ts/core";
|
|
2587
2668
|
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
2669
|
+
|
|
2670
|
+
// src/types.ts
|
|
2671
|
+
var FormSubmissionError = class extends Error {
|
|
2672
|
+
payload;
|
|
2673
|
+
constructor(message, payload) {
|
|
2674
|
+
super(message);
|
|
2675
|
+
this.name = "FormSubmissionError";
|
|
2676
|
+
this.payload = payload ?? { formError: message };
|
|
2677
|
+
}
|
|
2678
|
+
};
|
|
2679
|
+
|
|
2680
|
+
// src/context.tsx
|
|
2588
2681
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
2589
2682
|
var FormContext = createContext2(null);
|
|
2590
2683
|
function issuesByField(issues) {
|
|
@@ -2592,6 +2685,18 @@ function issuesByField(issues) {
|
|
|
2592
2685
|
for (const issue of issues) result[issue.fieldId] ??= issue;
|
|
2593
2686
|
return result;
|
|
2594
2687
|
}
|
|
2688
|
+
function defaultAttemptId2() {
|
|
2689
|
+
const randomUuid = globalThis.crypto?.randomUUID;
|
|
2690
|
+
if (typeof randomUuid === "function") return randomUuid.call(globalThis.crypto);
|
|
2691
|
+
return `attempt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
2692
|
+
}
|
|
2693
|
+
function isServerErrorPayload(value) {
|
|
2694
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2695
|
+
const record = value;
|
|
2696
|
+
const fieldErrors = record.fieldErrors;
|
|
2697
|
+
const formError = record.formError;
|
|
2698
|
+
return (Object.hasOwn(record, "fieldErrors") || Object.hasOwn(record, "formError")) && (fieldErrors === void 0 || typeof fieldErrors === "object" && fieldErrors !== null && !Array.isArray(fieldErrors) && Object.values(fieldErrors).every((message) => typeof message === "string")) && (formError === void 0 || typeof formError === "string");
|
|
2699
|
+
}
|
|
2595
2700
|
function FormProvider({
|
|
2596
2701
|
schema,
|
|
2597
2702
|
locale,
|
|
@@ -2676,7 +2781,7 @@ function FormProvider({
|
|
|
2676
2781
|
setSubmitError(null);
|
|
2677
2782
|
}, [initialValues]);
|
|
2678
2783
|
const submit = useCallback2(
|
|
2679
|
-
async (beforeSubmit,
|
|
2784
|
+
async (beforeSubmit, submitContext) => {
|
|
2680
2785
|
if (submissionInFlight.current) return { status: "cancelled" };
|
|
2681
2786
|
const validation = validateAnswers(validSchema, values);
|
|
2682
2787
|
if (!validation.valid) {
|
|
@@ -2697,13 +2802,22 @@ function FormProvider({
|
|
|
2697
2802
|
setSubmitStatus("idle");
|
|
2698
2803
|
return { status: "cancelled" };
|
|
2699
2804
|
}
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2805
|
+
const context = submitContext ?? {
|
|
2806
|
+
attemptId: defaultAttemptId2(),
|
|
2807
|
+
formId: validSchema.id,
|
|
2808
|
+
formVersion: validSchema.version,
|
|
2809
|
+
locale,
|
|
2810
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2811
|
+
};
|
|
2812
|
+
const response = await onSubmit({ ...visibleValues }, context);
|
|
2702
2813
|
if (resetOnSuccess) setValues({ ...initialValues });
|
|
2703
2814
|
setSubmitStatus("success");
|
|
2704
2815
|
return response === void 0 ? { status: "success" } : { status: "success", response };
|
|
2705
2816
|
} catch (cause) {
|
|
2706
|
-
const error = cause
|
|
2817
|
+
const error = isServerErrorPayload(cause) ? new FormSubmissionError(
|
|
2818
|
+
cause.formError ?? (cause instanceof Error ? cause.message : "Form submission failed."),
|
|
2819
|
+
cause
|
|
2820
|
+
) : cause instanceof Error ? cause : new Error(String(cause));
|
|
2707
2821
|
setSubmitError(error);
|
|
2708
2822
|
setSubmitStatus("error");
|
|
2709
2823
|
return { status: "error", error };
|
|
@@ -2711,7 +2825,7 @@ function FormProvider({
|
|
|
2711
2825
|
submissionInFlight.current = false;
|
|
2712
2826
|
}
|
|
2713
2827
|
},
|
|
2714
|
-
[initialValues, onSubmit, resetOnSuccess, validSchema, values]
|
|
2828
|
+
[initialValues, locale, onSubmit, resetOnSuccess, validSchema, values]
|
|
2715
2829
|
);
|
|
2716
2830
|
const translate = useCallback2(
|
|
2717
2831
|
(key, params) => translator.translate(key, locale, params),
|
|
@@ -2902,18 +3016,6 @@ import {
|
|
|
2902
3016
|
useRef as useRef2,
|
|
2903
3017
|
useState as useState4
|
|
2904
3018
|
} 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
|
|
2917
3019
|
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2918
3020
|
function describedBy(field, error, helpId, errorId) {
|
|
2919
3021
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
@@ -3100,7 +3202,11 @@ function DefaultField(props) {
|
|
|
3100
3202
|
fieldId: field.id,
|
|
3101
3203
|
current: typeof value === "string" ? value.length : 0,
|
|
3102
3204
|
max: field.maxLength
|
|
3103
|
-
}) :
|
|
3205
|
+
}) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-character-count", "aria-live": "polite", children: [
|
|
3206
|
+
typeof value === "string" ? value.length : 0,
|
|
3207
|
+
" / ",
|
|
3208
|
+
field.maxLength
|
|
3209
|
+
] }) : null,
|
|
3104
3210
|
/* @__PURE__ */ jsx3(FieldMessage, { props })
|
|
3105
3211
|
] });
|
|
3106
3212
|
}
|
|
@@ -3152,11 +3258,58 @@ function parseDraft(serialized) {
|
|
|
3152
3258
|
return null;
|
|
3153
3259
|
}
|
|
3154
3260
|
}
|
|
3261
|
+
var DEFAULT_RENDERER_MESSAGES = {
|
|
3262
|
+
en: {
|
|
3263
|
+
submitButton: "Submit",
|
|
3264
|
+
submittingButton: "Submitting...",
|
|
3265
|
+
retryButton: "Retry",
|
|
3266
|
+
requiredField: "This field is required.",
|
|
3267
|
+
alreadySubmittedTitle: "Already Submitted",
|
|
3268
|
+
alreadySubmittedMessage: "Already submitted.",
|
|
3269
|
+
serverErrorSummary: "Submission failed. Please check your answers and try again.",
|
|
3270
|
+
confirmSensitiveDataTitle: "Sensitive data may be included",
|
|
3271
|
+
confirmSensitiveDataMessage: "The following answers may contain personal information. Continue submitting?",
|
|
3272
|
+
confirmButton: "Proceed",
|
|
3273
|
+
cancelButton: "Cancel"
|
|
3274
|
+
},
|
|
3275
|
+
ja: {
|
|
3276
|
+
submitButton: "\u9001\u4FE1\u3059\u308B",
|
|
3277
|
+
submittingButton: "\u9001\u4FE1\u4E2D...",
|
|
3278
|
+
retryButton: "\u518D\u9001\u4FE1\u3059\u308B",
|
|
3279
|
+
requiredField: "\u3053\u306E\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059",
|
|
3280
|
+
alreadySubmittedTitle: "\u56DE\u7B54\u6E08\u307F\u3067\u3059",
|
|
3281
|
+
alreadySubmittedMessage: "\u3053\u306E\u30A2\u30F3\u30B1\u30FC\u30C8\u306B\u306F\u3059\u3067\u306B\u56DE\u7B54\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
3282
|
+
serverErrorSummary: "\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u5185\u5BB9\u3092\u3054\u78BA\u8A8D\u306E\u4E0A\u3001\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",
|
|
3283
|
+
confirmSensitiveDataTitle: "\u500B\u4EBA\u60C5\u5831\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059",
|
|
3284
|
+
confirmSensitiveDataMessage: "\u4EE5\u4E0B\u306E\u9805\u76EE\u306B\u500B\u4EBA\u60C5\u5831\u3068\u307F\u3089\u308C\u308B\u8A18\u8FF0\u304C\u3042\u308A\u307E\u3059\u3002\u3053\u306E\u307E\u307E\u9001\u4FE1\u3057\u3066\u3082\u3088\u308D\u3057\u3044\u3067\u3059\u304B\uFF1F",
|
|
3285
|
+
confirmButton: "\u3053\u306E\u307E\u307E\u9001\u4FE1",
|
|
3286
|
+
cancelButton: "\u4FEE\u6B63\u3059\u308B"
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
function createRendererAttemptId() {
|
|
3290
|
+
const randomUuid = globalThis.crypto?.randomUUID;
|
|
3291
|
+
if (typeof randomUuid === "function") return randomUuid.call(globalThis.crypto);
|
|
3292
|
+
return `attempt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
3293
|
+
}
|
|
3294
|
+
function maskSensitiveValue(finding) {
|
|
3295
|
+
if (finding.maskedText !== void 0) return finding.maskedText;
|
|
3296
|
+
const value = finding.matchedText;
|
|
3297
|
+
if (value === void 0) return void 0;
|
|
3298
|
+
if (finding.type === "email") {
|
|
3299
|
+
const separator = value.indexOf("@");
|
|
3300
|
+
if (separator > 0) return `${value.slice(0, Math.min(2, separator))}***${value.slice(separator)}`;
|
|
3301
|
+
}
|
|
3302
|
+
if (finding.type === "phone" || finding.type === "postal_code") return "***";
|
|
3303
|
+
return value.length <= 2 ? "***" : `${value.slice(0, 2)}***`;
|
|
3304
|
+
}
|
|
3155
3305
|
function ContextFormRenderer({
|
|
3156
3306
|
components = {},
|
|
3157
3307
|
className = "",
|
|
3158
3308
|
successMessageKey,
|
|
3159
3309
|
errorMessageKey,
|
|
3310
|
+
attemptIdFactory,
|
|
3311
|
+
messages = {},
|
|
3312
|
+
messageResolver,
|
|
3160
3313
|
autoSaveKey,
|
|
3161
3314
|
beforeSubmit,
|
|
3162
3315
|
onDraftSave,
|
|
@@ -3185,6 +3338,7 @@ function ContextFormRenderer({
|
|
|
3185
3338
|
const [completionData, setCompletionData] = useState4(null);
|
|
3186
3339
|
const [receiptLoaded, setReceiptLoaded] = useState4(receiptStore === void 0);
|
|
3187
3340
|
const rendererSubmissionInFlight = useRef2(false);
|
|
3341
|
+
const fallbackAttemptId = useRef2(null);
|
|
3188
3342
|
const completionRef = useRef2(null);
|
|
3189
3343
|
const confirmationRef = useRef2(null);
|
|
3190
3344
|
const pages = form.schema.pages;
|
|
@@ -3199,6 +3353,18 @@ function ContextFormRenderer({
|
|
|
3199
3353
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
3200
3354
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
3201
3355
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
3356
|
+
const resolveMessage = useCallback3(
|
|
3357
|
+
(key, fallback) => {
|
|
3358
|
+
const defaultText = fallback ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
3359
|
+
const configured = messages[key];
|
|
3360
|
+
return messageResolver?.(key, configured ?? defaultText) ?? configured ?? defaultText;
|
|
3361
|
+
},
|
|
3362
|
+
[form.locale, messageResolver, messages]
|
|
3363
|
+
);
|
|
3364
|
+
const fieldTranslate = useCallback3(
|
|
3365
|
+
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
3366
|
+
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
3367
|
+
);
|
|
3202
3368
|
const focusSubmitButton = useCallback3(() => {
|
|
3203
3369
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
3204
3370
|
button?.focus();
|
|
@@ -3238,6 +3404,7 @@ function ContextFormRenderer({
|
|
|
3238
3404
|
);
|
|
3239
3405
|
const control = fieldContainer?.querySelector("input, select, textarea");
|
|
3240
3406
|
if (control !== void 0 && control !== null) {
|
|
3407
|
+
control.scrollIntoView?.({ behavior: "smooth", block: "center" });
|
|
3241
3408
|
control.focus();
|
|
3242
3409
|
setFocusFieldId(null);
|
|
3243
3410
|
}
|
|
@@ -3256,6 +3423,23 @@ function ContextFormRenderer({
|
|
|
3256
3423
|
event.preventDefault();
|
|
3257
3424
|
setConfirmation(null);
|
|
3258
3425
|
globalThis.setTimeout(focusSubmitButton, 0);
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
if (event.key !== "Tab") return;
|
|
3429
|
+
const focusable = [
|
|
3430
|
+
...confirmationRef.current?.querySelectorAll(
|
|
3431
|
+
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
|
|
3432
|
+
) ?? []
|
|
3433
|
+
].filter((element) => !element.hasAttribute("disabled"));
|
|
3434
|
+
if (focusable.length === 0) return;
|
|
3435
|
+
const first = focusable[0];
|
|
3436
|
+
const last = focusable[focusable.length - 1];
|
|
3437
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
3438
|
+
event.preventDefault();
|
|
3439
|
+
last?.focus();
|
|
3440
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
3441
|
+
event.preventDefault();
|
|
3442
|
+
first?.focus();
|
|
3259
3443
|
}
|
|
3260
3444
|
};
|
|
3261
3445
|
globalThis.addEventListener("keydown", onKeyDown);
|
|
@@ -3357,17 +3541,24 @@ function ContextFormRenderer({
|
|
|
3357
3541
|
rendererSubmissionInFlight.current = true;
|
|
3358
3542
|
try {
|
|
3359
3543
|
let submissionAttempt;
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3544
|
+
let attemptId = fallbackAttemptId.current;
|
|
3545
|
+
if (attemptStore !== void 0) {
|
|
3546
|
+
submissionAttempt = await attemptStore.getOrCreate(form.schema.id, form.schema.version, attemptIdFactory);
|
|
3547
|
+
attemptId = submissionAttempt.attemptId;
|
|
3548
|
+
} else if (attemptId === null) {
|
|
3549
|
+
attemptId = attemptIdFactory?.() ?? createRendererAttemptId();
|
|
3550
|
+
fallbackAttemptId.current = attemptId;
|
|
3551
|
+
}
|
|
3552
|
+
if (attemptId === null) throw new Error("Unable to create a submission attempt id.");
|
|
3553
|
+
const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3554
|
+
const submitContext = {
|
|
3555
|
+
attemptId,
|
|
3556
|
+
formId: form.schema.id,
|
|
3557
|
+
formVersion: form.schema.version,
|
|
3558
|
+
locale: form.locale,
|
|
3559
|
+
submittedAt
|
|
3560
|
+
};
|
|
3561
|
+
const result = await form.submit(beforeSubmit, submitContext);
|
|
3371
3562
|
if (result.status === "invalid") {
|
|
3372
3563
|
const invalidPageIndex = pages?.findIndex(
|
|
3373
3564
|
(page) => firstInvalidFieldId === void 0 ? false : page.questionIds.includes(firstInvalidFieldId)
|
|
@@ -3380,7 +3571,7 @@ function ContextFormRenderer({
|
|
|
3380
3571
|
if (result.error instanceof FormSubmissionError) {
|
|
3381
3572
|
const fieldErrors = result.error.payload.fieldErrors ?? {};
|
|
3382
3573
|
form.setServerErrors?.(fieldErrors);
|
|
3383
|
-
const firstServerFieldId = Object.keys(fieldErrors)[0];
|
|
3574
|
+
const firstServerFieldId = form.schema.fields.find((field) => Object.hasOwn(fieldErrors, field.id))?.id ?? Object.keys(fieldErrors)[0];
|
|
3384
3575
|
if (firstServerFieldId !== void 0) {
|
|
3385
3576
|
const invalidPageIndex = pages?.findIndex((page) => page.questionIds.includes(firstServerFieldId));
|
|
3386
3577
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
@@ -3405,11 +3596,11 @@ function ContextFormRenderer({
|
|
|
3405
3596
|
});
|
|
3406
3597
|
if (receiptStore !== void 0) {
|
|
3407
3598
|
const response = result.response;
|
|
3408
|
-
const submissionId = response?.submissionId ?? submissionAttempt?.attemptId;
|
|
3599
|
+
const submissionId = response?.submissionId ?? submissionAttempt?.attemptId ?? attemptId;
|
|
3409
3600
|
const storedReceipt = {
|
|
3410
3601
|
formId: form.schema.id,
|
|
3411
3602
|
formVersion: form.schema.version,
|
|
3412
|
-
submittedAt: response?.submittedAt ??
|
|
3603
|
+
submittedAt: response?.submittedAt ?? submittedAt,
|
|
3413
3604
|
...submissionId === void 0 ? {} : { submissionId }
|
|
3414
3605
|
};
|
|
3415
3606
|
try {
|
|
@@ -3428,6 +3619,7 @@ function ContextFormRenderer({
|
|
|
3428
3619
|
} catch {
|
|
3429
3620
|
}
|
|
3430
3621
|
}
|
|
3622
|
+
if (attemptStore === void 0) fallbackAttemptId.current = null;
|
|
3431
3623
|
if (autoSaveKey !== void 0 && typeof globalThis.localStorage !== "undefined") {
|
|
3432
3624
|
globalThis.localStorage.removeItem(autoSaveKey);
|
|
3433
3625
|
setDraftRestored(false);
|
|
@@ -3467,7 +3659,10 @@ function ContextFormRenderer({
|
|
|
3467
3659
|
disabled: interactionLocked || submitState === "success",
|
|
3468
3660
|
onSubmit: () => void submitValues()
|
|
3469
3661
|
};
|
|
3470
|
-
return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */
|
|
3662
|
+
return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */ jsxs2("button", { className: "fe-submit", type: "submit", disabled: submitButtonProps.disabled, children: [
|
|
3663
|
+
submitState === "submitting" ? /* @__PURE__ */ jsx3("span", { className: "fe-spinner", "aria-hidden": "true" }) : null,
|
|
3664
|
+
submitState === "submitting" ? resolveMessage("submittingButton") : resolveMessage("submitButton", form.translate(form.schema.submitLabelKey ?? "form.submit"))
|
|
3665
|
+
] });
|
|
3471
3666
|
};
|
|
3472
3667
|
const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
|
|
3473
3668
|
const activeCompletionData = completionData ?? {
|
|
@@ -3500,15 +3695,31 @@ function ContextFormRenderer({
|
|
|
3500
3695
|
role: submissionConfirmationRenderMode === "dialog" ? void 0 : "dialog",
|
|
3501
3696
|
children: slots.renderSubmissionConfirmation?.({
|
|
3502
3697
|
findings: confirmation?.findings ?? [],
|
|
3503
|
-
message: confirmation?.message ?? form.translate("form.confirmSensitiveData"),
|
|
3698
|
+
message: confirmation?.message ?? resolveMessage("confirmSensitiveDataMessage", form.translate("form.confirmSensitiveData")),
|
|
3504
3699
|
schema: form.schema,
|
|
3505
3700
|
visibleValues,
|
|
3506
3701
|
onConfirm: confirmSubmission,
|
|
3507
3702
|
onCancel: cancelSubmission
|
|
3508
3703
|
}) ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
3509
|
-
/* @__PURE__ */ jsx3("
|
|
3510
|
-
/* @__PURE__ */ jsx3("
|
|
3511
|
-
/* @__PURE__ */ jsx3("
|
|
3704
|
+
/* @__PURE__ */ jsx3("h2", { children: resolveMessage("confirmSensitiveDataTitle") }),
|
|
3705
|
+
/* @__PURE__ */ jsx3("p", { children: confirmation?.message ?? resolveMessage("confirmSensitiveDataMessage", form.translate("form.confirmSensitiveData")) }),
|
|
3706
|
+
(confirmation?.findings ?? []).length === 0 ? null : /* @__PURE__ */ jsx3("ul", { children: (confirmation?.findings ?? []).map((finding, index) => {
|
|
3707
|
+
const field = form.schema.fields.find((candidate) => candidate.id === finding.fieldId);
|
|
3708
|
+
const typeLabels = form.locale.toLowerCase().startsWith("ja") ? { email: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9", phone: "\u96FB\u8A71\u756A\u53F7", url: "URL", postal_code: "\u90F5\u4FBF\u756A\u53F7" } : { email: "Email address", phone: "Phone number", url: "URL", postal_code: "Postal code" };
|
|
3709
|
+
const typeLabel = finding.typeLabel ?? typeLabels[finding.type] ?? finding.type;
|
|
3710
|
+
const value = maskSensitiveValue(finding);
|
|
3711
|
+
return /* @__PURE__ */ jsxs2("li", { children: [
|
|
3712
|
+
/* @__PURE__ */ jsx3("span", { children: finding.fieldTitle ?? field?.title ?? finding.fieldId }),
|
|
3713
|
+
" ",
|
|
3714
|
+
/* @__PURE__ */ jsx3("span", { className: "fe-sensitive-type", children: typeLabel }),
|
|
3715
|
+
value === void 0 ? null : /* @__PURE__ */ jsxs2("span", { className: "fe-sensitive-value", children: [
|
|
3716
|
+
" ",
|
|
3717
|
+
value
|
|
3718
|
+
] })
|
|
3719
|
+
] }, `${finding.fieldId}-${finding.type}-${finding.start ?? index}`);
|
|
3720
|
+
}) }),
|
|
3721
|
+
/* @__PURE__ */ jsx3("button", { type: "button", "data-fe-confirm": "true", onClick: confirmSubmission, children: resolveMessage("confirmButton", form.translate("form.confirmSubmission")) }),
|
|
3722
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: cancelSubmission, children: resolveMessage("cancelButton", form.translate("form.cancelSubmission")) })
|
|
3512
3723
|
] })
|
|
3513
3724
|
}
|
|
3514
3725
|
);
|
|
@@ -3518,8 +3729,9 @@ function ContextFormRenderer({
|
|
|
3518
3729
|
receipt,
|
|
3519
3730
|
...receiptStore === void 0 ? {} : { onReset: () => void resetReceipt() }
|
|
3520
3731
|
}) ?? /* @__PURE__ */ jsxs2("div", { role: "status", children: [
|
|
3521
|
-
|
|
3522
|
-
|
|
3732
|
+
/* @__PURE__ */ jsx3("h2", { children: resolveMessage("alreadySubmittedTitle") }),
|
|
3733
|
+
/* @__PURE__ */ jsx3("p", { children: resolveMessage("alreadySubmittedMessage", form.translate("form.alreadySubmitted")) }),
|
|
3734
|
+
receiptStore === void 0 ? null : /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => void resetReceipt(), children: resolveMessage("submitButton", form.translate("form.submitAnother")) })
|
|
3523
3735
|
] }) });
|
|
3524
3736
|
}
|
|
3525
3737
|
if (form.submitStatus === "success" && isReplaceMode) {
|
|
@@ -3582,7 +3794,7 @@ function ContextFormRenderer({
|
|
|
3582
3794
|
value: form.values[field.id],
|
|
3583
3795
|
error,
|
|
3584
3796
|
setValue: (value) => form.setValue(field.id, value),
|
|
3585
|
-
translate:
|
|
3797
|
+
translate: fieldTranslate,
|
|
3586
3798
|
inputId: `${prefix}-${field.id}`,
|
|
3587
3799
|
errorId: `${prefix}-${field.id}-error`,
|
|
3588
3800
|
helpId: `${prefix}-${field.id}-help`,
|
|
@@ -3649,7 +3861,13 @@ function ContextFormRenderer({
|
|
|
3649
3861
|
] }),
|
|
3650
3862
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
3651
3863
|
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__ */
|
|
3864
|
+
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__ */ jsxs2("div", { role: "alert", children: [
|
|
3865
|
+
form.submitError.payload.formError,
|
|
3866
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
3867
|
+
] }) : /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
3868
|
+
errorMessageKey === void 0 ? resolveMessage("serverErrorSummary") : form.translate(errorMessageKey),
|
|
3869
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
3870
|
+
] })) : null
|
|
3653
3871
|
] })
|
|
3654
3872
|
]
|
|
3655
3873
|
}
|
|
@@ -3665,7 +3883,7 @@ var RENDERER_MESSAGES = {
|
|
|
3665
3883
|
"form.draftRestored": "Draft restored",
|
|
3666
3884
|
"form.submissionBlocked": "Submission blocked because sensitive data was detected.",
|
|
3667
3885
|
"form.confirmSensitiveData": "Sensitive data may be included. Confirm before submitting.",
|
|
3668
|
-
"form.confirmSubmission": "
|
|
3886
|
+
"form.confirmSubmission": "Proceed",
|
|
3669
3887
|
"form.cancelSubmission": "Cancel",
|
|
3670
3888
|
"form.yes": "Yes",
|
|
3671
3889
|
"form.no": "No",
|
|
@@ -3673,9 +3891,26 @@ var RENDERER_MESSAGES = {
|
|
|
3673
3891
|
"form.submitAnother": "Submit another response",
|
|
3674
3892
|
"validation.required": "This field is required."
|
|
3675
3893
|
};
|
|
3894
|
+
var RENDERER_MESSAGES_JA = {
|
|
3895
|
+
"form.submit": "\u9001\u4FE1\u3059\u308B",
|
|
3896
|
+
"form.back": "\u623B\u308B",
|
|
3897
|
+
"form.next": "\u6B21\u3078",
|
|
3898
|
+
"form.step": "{{current}} / {{total}}",
|
|
3899
|
+
"form.draftRestored": "\u4E0B\u66F8\u304D\u3092\u5FA9\u5143\u3057\u307E\u3057\u305F",
|
|
3900
|
+
"form.submissionBlocked": "\u500B\u4EBA\u60C5\u5831\u304C\u691C\u51FA\u3055\u308C\u305F\u305F\u3081\u9001\u4FE1\u3067\u304D\u307E\u305B\u3093\u3002",
|
|
3901
|
+
"form.confirmSensitiveData": "\u500B\u4EBA\u60C5\u5831\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u9001\u4FE1\u524D\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
3902
|
+
"form.confirmSubmission": "\u3053\u306E\u307E\u307E\u9001\u4FE1",
|
|
3903
|
+
"form.cancelSubmission": "\u4FEE\u6B63\u3059\u308B",
|
|
3904
|
+
"form.yes": "\u306F\u3044",
|
|
3905
|
+
"form.no": "\u3044\u3044\u3048",
|
|
3906
|
+
"form.alreadySubmitted": "\u56DE\u7B54\u6E08\u307F\u3067\u3059",
|
|
3907
|
+
"form.submitAnother": "\u5225\u306E\u56DE\u7B54\u3092\u9001\u4FE1",
|
|
3908
|
+
"validation.required": "\u3053\u306E\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059"
|
|
3909
|
+
};
|
|
3676
3910
|
var defaultRendererTranslator = {
|
|
3677
|
-
translate(key,
|
|
3678
|
-
|
|
3911
|
+
translate(key, locale, params = {}) {
|
|
3912
|
+
const localizedMessages = locale.toLowerCase().startsWith("ja") ? RENDERER_MESSAGES_JA : RENDERER_MESSAGES;
|
|
3913
|
+
return (localizedMessages[key] ?? key).replace(
|
|
3679
3914
|
/\{\{(\w+)\}\}/g,
|
|
3680
3915
|
(token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
|
|
3681
3916
|
);
|
|
@@ -3706,6 +3941,8 @@ function FormRenderer(props) {
|
|
|
3706
3941
|
);
|
|
3707
3942
|
}
|
|
3708
3943
|
export {
|
|
3944
|
+
BUILDER_TRANSLATION_ALIASES,
|
|
3945
|
+
BUILDER_TRANSLATION_KEYS,
|
|
3709
3946
|
FormBuilder,
|
|
3710
3947
|
FormProvider,
|
|
3711
3948
|
FormRenderer,
|
package/dist/styles.css
CHANGED
|
@@ -93,6 +93,27 @@
|
|
|
93
93
|
font-size: 0.875rem;
|
|
94
94
|
font-weight: 600;
|
|
95
95
|
}
|
|
96
|
+
.fe-character-count {
|
|
97
|
+
color: #667085;
|
|
98
|
+
font-size: 0.8rem;
|
|
99
|
+
text-align: right;
|
|
100
|
+
}
|
|
101
|
+
.fe-spinner {
|
|
102
|
+
display: inline-block;
|
|
103
|
+
width: 0.85em;
|
|
104
|
+
height: 0.85em;
|
|
105
|
+
margin-right: 0.4em;
|
|
106
|
+
border: 0.12em solid currentColor;
|
|
107
|
+
border-right-color: transparent;
|
|
108
|
+
border-radius: 50%;
|
|
109
|
+
animation: fe-spin 0.8s linear infinite;
|
|
110
|
+
vertical-align: -0.1em;
|
|
111
|
+
}
|
|
112
|
+
@keyframes fe-spin {
|
|
113
|
+
to {
|
|
114
|
+
transform: rotate(360deg);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
96
117
|
.fe-check-label {
|
|
97
118
|
align-items: flex-start;
|
|
98
119
|
display: flex;
|
|
@@ -298,3 +319,16 @@
|
|
|
298
319
|
border-radius: 0.5rem;
|
|
299
320
|
box-shadow: 0 1rem 3rem rgb(0 0 0 / 25%);
|
|
300
321
|
}
|
|
322
|
+
.fe-sensitive-type {
|
|
323
|
+
border-radius: 999px;
|
|
324
|
+
background: #f2f4f7;
|
|
325
|
+
padding: 0.15rem 0.45rem;
|
|
326
|
+
font-size: 0.8rem;
|
|
327
|
+
}
|
|
328
|
+
.fe-sensitive-value {
|
|
329
|
+
font-family:
|
|
330
|
+
ui-monospace,
|
|
331
|
+
SFMono-Regular,
|
|
332
|
+
Menlo,
|
|
333
|
+
monospace;
|
|
334
|
+
}
|