@form-engine-ts/react 4.3.2 → 4.5.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 +19 -2
- package/dist/index.cjs +559 -139
- package/dist/index.d.cts +97 -8
- package/dist/index.d.ts +97 -8
- package/dist/index.js +559 -136
- package/dist/styles.css +20 -9
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -102,7 +102,7 @@ import {
|
|
|
102
102
|
DEFAULT_FIELD_TYPE_DEFINITIONS,
|
|
103
103
|
populateSchemaTranslations
|
|
104
104
|
} from "@form-engine-ts/core";
|
|
105
|
-
import { Children, createContext, isValidElement, useContext, useState } from "react";
|
|
105
|
+
import { Children, createContext, isValidElement, useContext, useState as useState2 } from "react";
|
|
106
106
|
|
|
107
107
|
// src/hooks/useFormBuilder.ts
|
|
108
108
|
import {
|
|
@@ -110,7 +110,7 @@ import {
|
|
|
110
110
|
transformFieldType,
|
|
111
111
|
validateFormSchema
|
|
112
112
|
} from "@form-engine-ts/core";
|
|
113
|
-
import { useCallback, useMemo } from "react";
|
|
113
|
+
import { useCallback, useMemo, useState } from "react";
|
|
114
114
|
var DEFAULT_PREFIXES = { field: "q", option: "opt", page: "page" };
|
|
115
115
|
var CHOICE_TYPES = ["select", "radio", "multi-select"];
|
|
116
116
|
function defaultIdFactory(kind, existingIds) {
|
|
@@ -197,8 +197,20 @@ function move(items, sourceIndex, targetIndex) {
|
|
|
197
197
|
result.splice(targetIndex, 0, item);
|
|
198
198
|
return result;
|
|
199
199
|
}
|
|
200
|
+
function displayRuleSourceIds(field) {
|
|
201
|
+
if (field.displayRule === void 0) return [];
|
|
202
|
+
const ids = [];
|
|
203
|
+
const visit = (group) => {
|
|
204
|
+
for (const condition of group.conditions) {
|
|
205
|
+
if ("logic" in condition) visit(condition);
|
|
206
|
+
else ids.push(condition.fieldId);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
visit(field.displayRule.condition);
|
|
210
|
+
return ids;
|
|
211
|
+
}
|
|
200
212
|
function withoutDisplayCondition(field) {
|
|
201
|
-
const { displayCondition: _displayCondition, ...rest } = field;
|
|
213
|
+
const { displayCondition: _displayCondition, displayRule: _displayRule, ...rest } = field;
|
|
202
214
|
return rest;
|
|
203
215
|
}
|
|
204
216
|
function removeLocalizedProperty(translations, locale, property) {
|
|
@@ -221,8 +233,23 @@ function useFormBuilder({
|
|
|
221
233
|
onChange,
|
|
222
234
|
policy,
|
|
223
235
|
idFactory = defaultIdFactory,
|
|
224
|
-
factories = {}
|
|
236
|
+
factories = {},
|
|
237
|
+
fieldEditorMode = "all",
|
|
238
|
+
activeFieldId: controlledActiveFieldId,
|
|
239
|
+
defaultActiveFieldId,
|
|
240
|
+
onActiveFieldChange
|
|
225
241
|
}) {
|
|
242
|
+
const [internalActiveFieldId, setInternalActiveFieldId] = useState(
|
|
243
|
+
defaultActiveFieldId ?? (fieldEditorMode === "single" ? schema.fields[0]?.id : void 0)
|
|
244
|
+
);
|
|
245
|
+
const activeFieldId = controlledActiveFieldId ?? internalActiveFieldId;
|
|
246
|
+
const setActiveFieldId = useCallback(
|
|
247
|
+
(fieldId) => {
|
|
248
|
+
if (controlledActiveFieldId === void 0) setInternalActiveFieldId(fieldId);
|
|
249
|
+
onActiveFieldChange?.(fieldId);
|
|
250
|
+
},
|
|
251
|
+
[controlledActiveFieldId, onActiveFieldChange]
|
|
252
|
+
);
|
|
226
253
|
const createId = useCallback(
|
|
227
254
|
(kind, existingIds) => {
|
|
228
255
|
const rawId = idFactory(kind, existingIds);
|
|
@@ -335,9 +362,10 @@ function useFormBuilder({
|
|
|
335
362
|
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds, field.id] : page.questionIds
|
|
336
363
|
}));
|
|
337
364
|
onChange({ ...schema, fields: [...schema.fields, field], ...pages === void 0 ? {} : { pages } });
|
|
365
|
+
setActiveFieldId(field.id);
|
|
338
366
|
return { success: true };
|
|
339
367
|
},
|
|
340
|
-
[createId, factories, onChange, policy, schema]
|
|
368
|
+
[createId, factories, onChange, policy, schema, setActiveFieldId]
|
|
341
369
|
);
|
|
342
370
|
const removeField = useCallback(
|
|
343
371
|
(fieldId) => {
|
|
@@ -345,15 +373,19 @@ function useFormBuilder({
|
|
|
345
373
|
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
346
374
|
if (schema.fields.length <= 1)
|
|
347
375
|
return { success: false, error: { type: "invalid_operation", message: "A form must contain one field." } };
|
|
348
|
-
const
|
|
376
|
+
const removedIndex = schema.fields.findIndex((field) => field.id === fieldId);
|
|
377
|
+
const fields = schema.fields.filter((field) => field.id !== fieldId).map(
|
|
378
|
+
(field) => field.displayCondition?.questionId === fieldId || displayRuleSourceIds(field).includes(fieldId) ? withoutDisplayCondition(field) : field
|
|
379
|
+
);
|
|
349
380
|
const pages = schema.pages?.map((page) => ({ ...page, questionIds: page.questionIds.filter((id) => id !== fieldId) })).filter((page) => page.questionIds.length > 0);
|
|
350
381
|
if (schema.pages !== void 0 && pages?.length === 0) {
|
|
351
382
|
const { pages: _pages, ...single } = schema;
|
|
352
383
|
onChange({ ...single, fields });
|
|
353
384
|
} else onChange({ ...schema, fields, ...pages === void 0 ? {} : { pages } });
|
|
385
|
+
if (activeFieldId === fieldId) setActiveFieldId(fields[removedIndex]?.id ?? fields.at(-1)?.id);
|
|
354
386
|
return { success: true };
|
|
355
387
|
},
|
|
356
|
-
[onChange, schema]
|
|
388
|
+
[activeFieldId, onChange, schema, setActiveFieldId]
|
|
357
389
|
);
|
|
358
390
|
const moveField = useCallback(
|
|
359
391
|
(fieldId, targetIndex) => {
|
|
@@ -366,8 +398,8 @@ function useFormBuilder({
|
|
|
366
398
|
onChange({
|
|
367
399
|
...schema,
|
|
368
400
|
fields: fields.map((field, index) => {
|
|
369
|
-
const
|
|
370
|
-
return source
|
|
401
|
+
const sources = field.displayCondition?.questionId === void 0 ? displayRuleSourceIds(field) : [field.displayCondition.questionId];
|
|
402
|
+
return sources.every((source) => (indexById.get(source) ?? index) < index) ? field : withoutDisplayCondition(field);
|
|
371
403
|
})
|
|
372
404
|
});
|
|
373
405
|
return { success: true };
|
|
@@ -791,6 +823,14 @@ function useFormBuilder({
|
|
|
791
823
|
const result = validateFormSchema(schema, policy === void 0 ? {} : { policy });
|
|
792
824
|
return result.valid ? [] : result.issues;
|
|
793
825
|
}, [policy, schema]);
|
|
826
|
+
const getFieldEditorProps = useCallback(
|
|
827
|
+
(fieldId) => ({
|
|
828
|
+
isActive: activeFieldId === fieldId,
|
|
829
|
+
isVisible: fieldEditorMode === "all" || activeFieldId === fieldId,
|
|
830
|
+
onSelect: () => setActiveFieldId(fieldId)
|
|
831
|
+
}),
|
|
832
|
+
[activeFieldId, fieldEditorMode, setActiveFieldId]
|
|
833
|
+
);
|
|
794
834
|
return {
|
|
795
835
|
schema,
|
|
796
836
|
addField,
|
|
@@ -812,7 +852,10 @@ function useFormBuilder({
|
|
|
812
852
|
setLocaleTranslation,
|
|
813
853
|
addLocale,
|
|
814
854
|
setDefaultLocale,
|
|
815
|
-
validationIssues
|
|
855
|
+
validationIssues,
|
|
856
|
+
...activeFieldId === void 0 ? {} : { activeFieldId },
|
|
857
|
+
setActiveFieldId,
|
|
858
|
+
getFieldEditorProps
|
|
816
859
|
};
|
|
817
860
|
}
|
|
818
861
|
|
|
@@ -1465,7 +1508,10 @@ var BUILDER_DEFAULTS = {
|
|
|
1465
1508
|
"builder.operator.equals": "equals",
|
|
1466
1509
|
"builder.operator.not_equals": "does not equal",
|
|
1467
1510
|
"builder.operator.contains": "contains",
|
|
1468
|
-
"builder.operator.not_empty": "is not empty"
|
|
1511
|
+
"builder.operator.not_empty": "is not empty",
|
|
1512
|
+
"builder.submissionSettings": "Submission settings",
|
|
1513
|
+
"builder.showConfirmationBeforeSubmit": "Show confirmation before submit",
|
|
1514
|
+
"builder.confirmationRenderMode": "Confirmation display mode"
|
|
1469
1515
|
};
|
|
1470
1516
|
function BuilderSectionGroup({ children }) {
|
|
1471
1517
|
return children;
|
|
@@ -1642,7 +1688,12 @@ function FormBuilder({
|
|
|
1642
1688
|
slots,
|
|
1643
1689
|
sectionOrder,
|
|
1644
1690
|
disableDefaultStyles = false,
|
|
1645
|
-
unstyled = false
|
|
1691
|
+
unstyled = false,
|
|
1692
|
+
fieldEditorMode = "all",
|
|
1693
|
+
activeFieldId,
|
|
1694
|
+
defaultActiveFieldId,
|
|
1695
|
+
onActiveFieldChange,
|
|
1696
|
+
submissionSettingsOptions
|
|
1646
1697
|
}) {
|
|
1647
1698
|
const resolvedComponents = { ...DEFAULT_COMPONENTS, ...componentOverrides };
|
|
1648
1699
|
const components = {
|
|
@@ -1665,14 +1716,18 @@ function FormBuilder({
|
|
|
1665
1716
|
onChange,
|
|
1666
1717
|
...policy === void 0 ? {} : { policy },
|
|
1667
1718
|
...idFactory === void 0 ? {} : { idFactory },
|
|
1668
|
-
...factories === void 0 ? {} : { factories }
|
|
1719
|
+
...factories === void 0 ? {} : { factories },
|
|
1720
|
+
fieldEditorMode,
|
|
1721
|
+
...activeFieldId === void 0 ? {} : { activeFieldId },
|
|
1722
|
+
...defaultActiveFieldId === void 0 ? {} : { defaultActiveFieldId },
|
|
1723
|
+
...onActiveFieldChange === void 0 ? {} : { onActiveFieldChange }
|
|
1669
1724
|
});
|
|
1670
|
-
const [newPageQuestionId, setNewPageQuestionId] =
|
|
1671
|
-
const [newLocale, setNewLocale] =
|
|
1672
|
-
const [editingLocale, setEditingLocale] =
|
|
1673
|
-
const [isTranslating, setIsTranslating] =
|
|
1674
|
-
const [translationError, setTranslationError] =
|
|
1675
|
-
const [translationReport, setTranslationReport] =
|
|
1725
|
+
const [newPageQuestionId, setNewPageQuestionId] = useState2("");
|
|
1726
|
+
const [newLocale, setNewLocale] = useState2("");
|
|
1727
|
+
const [editingLocale, setEditingLocale] = useState2("");
|
|
1728
|
+
const [isTranslating, setIsTranslating] = useState2(false);
|
|
1729
|
+
const [translationError, setTranslationError] = useState2(null);
|
|
1730
|
+
const [translationReport, setTranslationReport] = useState2();
|
|
1676
1731
|
const translate = (key, params = {}) => resolveTranslation(
|
|
1677
1732
|
key,
|
|
1678
1733
|
BUILDER_TRANSLATION_ALIASES[key] === void 0 ? [] : [BUILDER_TRANSLATION_ALIASES[key]],
|
|
@@ -2005,6 +2060,47 @@ function FormBuilder({
|
|
|
2005
2060
|
] })
|
|
2006
2061
|
}
|
|
2007
2062
|
) }),
|
|
2063
|
+
/* @__PURE__ */ jsx(BuilderSectionGroup, { name: "submissionSettings", children: submissionSettingsOptions?.enabled ? /* @__PURE__ */ jsxs(
|
|
2064
|
+
Section,
|
|
2065
|
+
{
|
|
2066
|
+
className: builderClass("form-engine-builder__submission-settings"),
|
|
2067
|
+
title: translate("builder.submissionSettings"),
|
|
2068
|
+
children: [
|
|
2069
|
+
/* @__PURE__ */ jsx(
|
|
2070
|
+
Checkbox,
|
|
2071
|
+
{
|
|
2072
|
+
id: "builder-show-confirmation",
|
|
2073
|
+
label: translate("builder.showConfirmationBeforeSubmit"),
|
|
2074
|
+
checked: schema.submissionSettings?.showConfirmationBeforeSubmit === true,
|
|
2075
|
+
onChange: (checked) => onChange({
|
|
2076
|
+
...schema,
|
|
2077
|
+
submissionSettings: { ...schema.submissionSettings, showConfirmationBeforeSubmit: checked }
|
|
2078
|
+
})
|
|
2079
|
+
}
|
|
2080
|
+
),
|
|
2081
|
+
/* @__PURE__ */ jsx(
|
|
2082
|
+
Select,
|
|
2083
|
+
{
|
|
2084
|
+
id: "builder-confirmation-render-mode",
|
|
2085
|
+
label: translate("builder.confirmationRenderMode"),
|
|
2086
|
+
value: schema.submissionSettings?.confirmationRenderMode ?? "inline",
|
|
2087
|
+
options: [
|
|
2088
|
+
{ value: "dialog", label: "Dialog" },
|
|
2089
|
+
{ value: "inline", label: "Inline" },
|
|
2090
|
+
{ value: "replace", label: "Replace" }
|
|
2091
|
+
],
|
|
2092
|
+
onChange: (value) => {
|
|
2093
|
+
if (value !== "dialog" && value !== "inline" && value !== "replace") return;
|
|
2094
|
+
onChange({
|
|
2095
|
+
...schema,
|
|
2096
|
+
submissionSettings: { ...schema.submissionSettings, confirmationRenderMode: value }
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
)
|
|
2101
|
+
]
|
|
2102
|
+
}
|
|
2103
|
+
) : null }),
|
|
2008
2104
|
resolvedSectionOrder === void 0 ? null : /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "completionMessage", children: /* @__PURE__ */ jsx(Section, { headingId: "builder-completion-message-heading", title: translate("builder.completionMessage"), children: /* @__PURE__ */ jsx(
|
|
2009
2105
|
TextInput,
|
|
2010
2106
|
{
|
|
@@ -2403,9 +2499,25 @@ function FormBuilder({
|
|
|
2403
2499
|
) : null }),
|
|
2404
2500
|
/* @__PURE__ */ jsx(BuilderSectionGroup, { name: "questions", children: /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__list"), children: schema.fields.map((field, index) => {
|
|
2405
2501
|
const controls = resolveFieldEditorControls(fieldEditorControls);
|
|
2502
|
+
const editorState = headless.getFieldEditorProps?.(field.id) ?? {
|
|
2503
|
+
isActive: true,
|
|
2504
|
+
isVisible: true,
|
|
2505
|
+
onSelect: () => void 0
|
|
2506
|
+
};
|
|
2406
2507
|
const condition = field.displayCondition;
|
|
2407
2508
|
const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
|
|
2408
2509
|
const availableSources = schema.fields.slice(0, index);
|
|
2510
|
+
if (!editorState.isVisible) {
|
|
2511
|
+
return /* @__PURE__ */ jsx(
|
|
2512
|
+
"div",
|
|
2513
|
+
{
|
|
2514
|
+
className: builderClass("form-engine-builder__question-preview"),
|
|
2515
|
+
"data-field-id": field.id,
|
|
2516
|
+
children: /* @__PURE__ */ jsx("button", { type: "button", onClick: editorState.onSelect, children: field.title })
|
|
2517
|
+
},
|
|
2518
|
+
field.id
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2409
2521
|
if (FieldEditorSlot !== void 0) {
|
|
2410
2522
|
return /* @__PURE__ */ jsx(
|
|
2411
2523
|
FieldEditorSlot,
|
|
@@ -2901,7 +3013,7 @@ import {
|
|
|
2901
3013
|
validateAnswers,
|
|
2902
3014
|
validatePageAnswers
|
|
2903
3015
|
} from "@form-engine-ts/core";
|
|
2904
|
-
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect, useMemo as useMemo2, useRef, useState as
|
|
3016
|
+
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect, useMemo as useMemo2, useRef, useState as useState3 } from "react";
|
|
2905
3017
|
|
|
2906
3018
|
// src/types.ts
|
|
2907
3019
|
var FormSubmissionError = class extends Error {
|
|
@@ -2948,11 +3060,11 @@ function FormProvider({
|
|
|
2948
3060
|
assertValidFormSchema(localized);
|
|
2949
3061
|
return localized;
|
|
2950
3062
|
}, [locale, schema]);
|
|
2951
|
-
const [values, setValues] =
|
|
2952
|
-
const [errors, setErrors] =
|
|
2953
|
-
const [submitStatus, setSubmitStatus] =
|
|
2954
|
-
const [submitError, setSubmitError] =
|
|
2955
|
-
const [validationPageIndex, setValidationPageIndex] =
|
|
3063
|
+
const [values, setValues] = useState3(() => ({ ...initialValues }));
|
|
3064
|
+
const [errors, setErrors] = useState3({});
|
|
3065
|
+
const [submitStatus, setSubmitStatus] = useState3("idle");
|
|
3066
|
+
const [submitError, setSubmitError] = useState3(null);
|
|
3067
|
+
const [validationPageIndex, setValidationPageIndex] = useState3(null);
|
|
2956
3068
|
const submissionInFlight = useRef(false);
|
|
2957
3069
|
const visibility = useMemo2(() => calculateFieldVisibility(validSchema, values), [validSchema, values]);
|
|
2958
3070
|
const pageVisibility = useMemo2(() => calculatePageVisibility(validSchema, values), [validSchema, values]);
|
|
@@ -3121,8 +3233,237 @@ function useField(fieldId) {
|
|
|
3121
3233
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
3122
3234
|
}
|
|
3123
3235
|
|
|
3236
|
+
// src/hooks/useTranslationWorkspace.ts
|
|
3237
|
+
import {
|
|
3238
|
+
collectSchemaLocales as collectSchemaLocales2,
|
|
3239
|
+
collectTranslationSlots,
|
|
3240
|
+
computeSourceTextHash,
|
|
3241
|
+
populateSchemaTranslations as populateSchemaTranslations2
|
|
3242
|
+
} from "@form-engine-ts/core";
|
|
3243
|
+
import { useCallback as useCallback3, useMemo as useMemo3, useState as useState4 } from "react";
|
|
3244
|
+
function updateTranslationMap(translations, locale, property, text) {
|
|
3245
|
+
if (property === "label") return translations ?? {};
|
|
3246
|
+
const current = translations?.[locale];
|
|
3247
|
+
const next = { ...current, [property]: text };
|
|
3248
|
+
return { ...translations, [locale]: next };
|
|
3249
|
+
}
|
|
3250
|
+
function asAsyncAdapter(adapter) {
|
|
3251
|
+
if ("translateBatch" in adapter) return adapter;
|
|
3252
|
+
return {
|
|
3253
|
+
translateText: async (text, locale, sourceLocale) => adapter.translate(text, locale, sourceLocale === void 0 ? void 0 : { sourceLocale }) ?? text,
|
|
3254
|
+
translateBatch: async (texts, locale, sourceLocale) => texts.map(
|
|
3255
|
+
(text) => adapter.translate(text, locale, sourceLocale === void 0 ? void 0 : { sourceLocale }) ?? text
|
|
3256
|
+
)
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
function manualMetadata(sourceText, sourceLocale) {
|
|
3260
|
+
return {
|
|
3261
|
+
sourceLocale,
|
|
3262
|
+
sourceTextHash: computeSourceTextHash(sourceText),
|
|
3263
|
+
translationSource: "manual",
|
|
3264
|
+
editedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
function setNodeMetadata(node, slot, text, sourceLocale) {
|
|
3268
|
+
const localeMetadata = node.translationMetadata?.[slot.locale];
|
|
3269
|
+
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]: manualMetadata(slot.sourceText, sourceLocale) };
|
|
3270
|
+
return { ...node, translationMetadata: { ...node.translationMetadata, [slot.locale]: nextMetadata } };
|
|
3271
|
+
}
|
|
3272
|
+
function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
3273
|
+
if (slot.kind === "form") {
|
|
3274
|
+
return setNodeMetadata(
|
|
3275
|
+
{ ...schema, translations: updateTranslationMap(schema.translations, slot.locale, slot.property, text) },
|
|
3276
|
+
slot,
|
|
3277
|
+
text,
|
|
3278
|
+
sourceLocale
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
if (slot.kind === "field") {
|
|
3282
|
+
return {
|
|
3283
|
+
...schema,
|
|
3284
|
+
fields: schema.fields.map((field) => {
|
|
3285
|
+
if (field.id !== slot.nodeId) return field;
|
|
3286
|
+
const translations = updateTranslationMap(field.translations, slot.locale, slot.property, text);
|
|
3287
|
+
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale);
|
|
3288
|
+
})
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
if (slot.kind === "option") {
|
|
3292
|
+
return {
|
|
3293
|
+
...schema,
|
|
3294
|
+
fields: schema.fields.map((field) => {
|
|
3295
|
+
if (!("options" in field)) return field;
|
|
3296
|
+
return {
|
|
3297
|
+
...field,
|
|
3298
|
+
options: field.options.map((option) => {
|
|
3299
|
+
if (option.id !== slot.nodeId) return option;
|
|
3300
|
+
const translations = text.trim().length === 0 ? Object.fromEntries(
|
|
3301
|
+
Object.entries(option.translations ?? {}).filter(([locale]) => locale !== slot.locale)
|
|
3302
|
+
) : { ...option.translations, [slot.locale]: text };
|
|
3303
|
+
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale);
|
|
3304
|
+
})
|
|
3305
|
+
};
|
|
3306
|
+
})
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
return {
|
|
3310
|
+
...schema,
|
|
3311
|
+
...schema.pages === void 0 ? {} : {
|
|
3312
|
+
pages: schema.pages.map((page) => {
|
|
3313
|
+
if (page.id !== slot.nodeId) return page;
|
|
3314
|
+
const translations = updateTranslationMap(page.translations, slot.locale, slot.property, text);
|
|
3315
|
+
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale);
|
|
3316
|
+
})
|
|
3317
|
+
}
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
function useTranslationWorkspace({
|
|
3321
|
+
schema,
|
|
3322
|
+
onChange,
|
|
3323
|
+
sourceLocale = schema.defaultLocale ?? "en",
|
|
3324
|
+
targetLocale,
|
|
3325
|
+
translationAdapter,
|
|
3326
|
+
readOnly = false
|
|
3327
|
+
}) {
|
|
3328
|
+
const [draftSchema, setDraftSchema] = useState4(schema);
|
|
3329
|
+
const [selectedLocale, setSelectedLocale] = useState4(targetLocale ?? "");
|
|
3330
|
+
const [isTranslating, setIsTranslating] = useState4(false);
|
|
3331
|
+
const [error, setError] = useState4();
|
|
3332
|
+
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3333
|
+
const targetLocales = useMemo3(() => {
|
|
3334
|
+
const locales = collectSchemaLocales2(currentSchema).allUniqueLocales;
|
|
3335
|
+
return [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], ...locales])].filter(
|
|
3336
|
+
(locale) => locale !== sourceLocale
|
|
3337
|
+
);
|
|
3338
|
+
}, [currentSchema, sourceLocale]);
|
|
3339
|
+
const activeLocale = selectedLocale.length > 0 ? selectedLocale : targetLocales[0] ?? "";
|
|
3340
|
+
const slots = useMemo3(
|
|
3341
|
+
() => activeLocale.length === 0 ? [] : collectTranslationSlots(currentSchema, activeLocale),
|
|
3342
|
+
[activeLocale, currentSchema]
|
|
3343
|
+
);
|
|
3344
|
+
const summary = useMemo3(() => {
|
|
3345
|
+
const counts = {
|
|
3346
|
+
missing: 0,
|
|
3347
|
+
translated: 0,
|
|
3348
|
+
stale: 0,
|
|
3349
|
+
manual: 0,
|
|
3350
|
+
"manual-stale": 0
|
|
3351
|
+
};
|
|
3352
|
+
for (const slot of slots) counts[slot.status ?? "missing"] += 1;
|
|
3353
|
+
const complete = counts.translated + counts.manual;
|
|
3354
|
+
return {
|
|
3355
|
+
totalSlots: slots.length,
|
|
3356
|
+
translatedCount: complete,
|
|
3357
|
+
missingCount: counts.missing,
|
|
3358
|
+
staleCount: counts.stale + counts["manual-stale"],
|
|
3359
|
+
manualCount: counts.manual + counts["manual-stale"],
|
|
3360
|
+
completionPercentage: slots.length === 0 ? 100 : Math.round(complete / slots.length * 100)
|
|
3361
|
+
};
|
|
3362
|
+
}, [slots]);
|
|
3363
|
+
const commit = useCallback3(
|
|
3364
|
+
(next) => {
|
|
3365
|
+
setDraftSchema(next);
|
|
3366
|
+
onChange?.(next);
|
|
3367
|
+
},
|
|
3368
|
+
[onChange]
|
|
3369
|
+
);
|
|
3370
|
+
const setTranslation = useCallback3(
|
|
3371
|
+
(slot, text) => {
|
|
3372
|
+
if (readOnly) return;
|
|
3373
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale));
|
|
3374
|
+
},
|
|
3375
|
+
[commit, currentSchema, readOnly, sourceLocale]
|
|
3376
|
+
);
|
|
3377
|
+
const addLocale = useCallback3(
|
|
3378
|
+
(locale) => {
|
|
3379
|
+
if (readOnly || locale.trim().length === 0) return;
|
|
3380
|
+
const normalized = locale.trim();
|
|
3381
|
+
commit({
|
|
3382
|
+
...currentSchema,
|
|
3383
|
+
supportedLocales: [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], normalized])]
|
|
3384
|
+
});
|
|
3385
|
+
setSelectedLocale(normalized);
|
|
3386
|
+
},
|
|
3387
|
+
[commit, currentSchema, readOnly]
|
|
3388
|
+
);
|
|
3389
|
+
const removeLocale = useCallback3(
|
|
3390
|
+
(locale) => {
|
|
3391
|
+
if (readOnly || locale === sourceLocale) return;
|
|
3392
|
+
commit({
|
|
3393
|
+
...currentSchema,
|
|
3394
|
+
supportedLocales: (currentSchema.supportedLocales ?? []).filter((candidate) => candidate !== locale)
|
|
3395
|
+
});
|
|
3396
|
+
if (activeLocale === locale) setSelectedLocale("");
|
|
3397
|
+
},
|
|
3398
|
+
[activeLocale, commit, currentSchema, readOnly, sourceLocale]
|
|
3399
|
+
);
|
|
3400
|
+
const translateAll = useCallback3(
|
|
3401
|
+
async (options = {}) => {
|
|
3402
|
+
if (translationAdapter === void 0) throw new Error("A translation adapter is required.");
|
|
3403
|
+
if (activeLocale.length === 0) throw new Error("A target locale is required.");
|
|
3404
|
+
setIsTranslating(true);
|
|
3405
|
+
setError(void 0);
|
|
3406
|
+
try {
|
|
3407
|
+
const populated = await populateSchemaTranslations2(
|
|
3408
|
+
currentSchema,
|
|
3409
|
+
[activeLocale],
|
|
3410
|
+
asAsyncAdapter(translationAdapter),
|
|
3411
|
+
{
|
|
3412
|
+
overwrite: "stale-and-missing",
|
|
3413
|
+
preserveManualTranslations: true,
|
|
3414
|
+
...options
|
|
3415
|
+
}
|
|
3416
|
+
);
|
|
3417
|
+
commit(populated.schema);
|
|
3418
|
+
return populated.report;
|
|
3419
|
+
} catch (cause) {
|
|
3420
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
3421
|
+
setError(message);
|
|
3422
|
+
throw cause;
|
|
3423
|
+
} finally {
|
|
3424
|
+
setIsTranslating(false);
|
|
3425
|
+
}
|
|
3426
|
+
},
|
|
3427
|
+
[activeLocale, commit, currentSchema, translationAdapter]
|
|
3428
|
+
);
|
|
3429
|
+
const translateSlot = useCallback3(
|
|
3430
|
+
async (slot) => {
|
|
3431
|
+
if (translationAdapter === void 0) throw new Error("A translation adapter is required.");
|
|
3432
|
+
if (readOnly) return;
|
|
3433
|
+
setIsTranslating(true);
|
|
3434
|
+
setError(void 0);
|
|
3435
|
+
try {
|
|
3436
|
+
const text = await asAsyncAdapter(translationAdapter).translateText(slot.sourceText, slot.locale, sourceLocale);
|
|
3437
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale));
|
|
3438
|
+
} catch (cause) {
|
|
3439
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
3440
|
+
setError(message);
|
|
3441
|
+
throw cause;
|
|
3442
|
+
} finally {
|
|
3443
|
+
setIsTranslating(false);
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
[commit, currentSchema, readOnly, sourceLocale, translationAdapter]
|
|
3447
|
+
);
|
|
3448
|
+
return {
|
|
3449
|
+
sourceLocale,
|
|
3450
|
+
targetLocale: activeLocale,
|
|
3451
|
+
targetLocales,
|
|
3452
|
+
setTargetLocale: setSelectedLocale,
|
|
3453
|
+
slots,
|
|
3454
|
+
summary,
|
|
3455
|
+
addLocale,
|
|
3456
|
+
removeLocale,
|
|
3457
|
+
setTranslation,
|
|
3458
|
+
translateAll,
|
|
3459
|
+
translateSlot,
|
|
3460
|
+
isTranslating,
|
|
3461
|
+
...error === void 0 ? {} : { error }
|
|
3462
|
+
};
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3124
3465
|
// src/receipt.ts
|
|
3125
|
-
import { useEffect as useEffect2, useMemo as
|
|
3466
|
+
import { useEffect as useEffect2, useMemo as useMemo4, useState as useState5 } from "react";
|
|
3126
3467
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3127
3468
|
return `${formId}:v${formVersion}`;
|
|
3128
3469
|
}
|
|
@@ -3193,14 +3534,14 @@ function createLocalStorageSubmissionReceiptStore(options = {}) {
|
|
|
3193
3534
|
}
|
|
3194
3535
|
function useSubmissionReceipts(store, queries) {
|
|
3195
3536
|
const querySignature = JSON.stringify(queries.map(({ formId, formVersion }) => [formId, formVersion]));
|
|
3196
|
-
const stableQueries =
|
|
3537
|
+
const stableQueries = useMemo4(() => {
|
|
3197
3538
|
const parsed = JSON.parse(querySignature);
|
|
3198
3539
|
if (!Array.isArray(parsed)) return [];
|
|
3199
3540
|
return parsed.flatMap(
|
|
3200
3541
|
(entry) => Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number" && Number.isSafeInteger(entry[1]) ? [{ formId: entry[0], formVersion: entry[1] }] : []
|
|
3201
3542
|
);
|
|
3202
3543
|
}, [querySignature]);
|
|
3203
|
-
const [state, setState] =
|
|
3544
|
+
const [state, setState] = useState5({
|
|
3204
3545
|
receipts: /* @__PURE__ */ new Map(),
|
|
3205
3546
|
isLoading: stableQueries.length > 0,
|
|
3206
3547
|
error: null
|
|
@@ -3245,14 +3586,22 @@ import {
|
|
|
3245
3586
|
} from "@form-engine-ts/core";
|
|
3246
3587
|
import {
|
|
3247
3588
|
Fragment as Fragment2,
|
|
3248
|
-
useCallback as
|
|
3589
|
+
useCallback as useCallback4,
|
|
3249
3590
|
useEffect as useEffect3,
|
|
3250
3591
|
useId,
|
|
3251
|
-
useMemo as
|
|
3592
|
+
useMemo as useMemo5,
|
|
3252
3593
|
useRef as useRef2,
|
|
3253
|
-
useState as
|
|
3594
|
+
useState as useState6
|
|
3254
3595
|
} from "react";
|
|
3255
3596
|
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
3597
|
+
var isChoiceFieldType = (type) => type === "radio" || type === "checkbox" || type === "multi-select" || type === "select";
|
|
3598
|
+
function resolveChoiceFieldLayout(type, appearance, groupedChoiceFieldsLegacy = false) {
|
|
3599
|
+
if (groupedChoiceFieldsLegacy) return "grouped";
|
|
3600
|
+
const config = appearance?.choiceField;
|
|
3601
|
+
if (config === void 0) return "default";
|
|
3602
|
+
if (typeof config === "string") return config;
|
|
3603
|
+
return config[type] ?? "default";
|
|
3604
|
+
}
|
|
3256
3605
|
function describedBy(field, error, helpId, errorId) {
|
|
3257
3606
|
const ids = [field.description === void 0 ? void 0 : helpId, error === void 0 ? void 0 : errorId].filter(
|
|
3258
3607
|
Boolean
|
|
@@ -3280,51 +3629,98 @@ function GroupedChoiceDescription({ props }) {
|
|
|
3280
3629
|
function GroupedChoiceError({ props }) {
|
|
3281
3630
|
return props.error === void 0 ? null : /* @__PURE__ */ jsx3("div", { id: props.errorId, className: "fe-field-error", role: "alert", children: props.translate(props.error.messageKey, props.error.params) });
|
|
3282
3631
|
}
|
|
3632
|
+
function ChoiceGroupFrame({
|
|
3633
|
+
props,
|
|
3634
|
+
children,
|
|
3635
|
+
className,
|
|
3636
|
+
slotProps,
|
|
3637
|
+
renderChoiceGroup,
|
|
3638
|
+
disabled,
|
|
3639
|
+
readOnly
|
|
3640
|
+
}) {
|
|
3641
|
+
const { field, error, translate } = props;
|
|
3642
|
+
const groupError = error === void 0 ? void 0 : { ...error, message: translate(error.messageKey, error.params) };
|
|
3643
|
+
const groupProps = {
|
|
3644
|
+
field,
|
|
3645
|
+
title: field.title,
|
|
3646
|
+
...field.description === void 0 ? {} : { description: field.description },
|
|
3647
|
+
...field.required === void 0 ? {} : { required: field.required },
|
|
3648
|
+
...groupError === void 0 ? {} : { error: groupError },
|
|
3649
|
+
...disabled === void 0 ? {} : { disabled },
|
|
3650
|
+
...readOnly === void 0 ? {} : { readOnly },
|
|
3651
|
+
children,
|
|
3652
|
+
className
|
|
3653
|
+
};
|
|
3654
|
+
if (renderChoiceGroup !== void 0) return /* @__PURE__ */ jsx3(Fragment3, { children: renderChoiceGroup(groupProps) });
|
|
3655
|
+
return (
|
|
3656
|
+
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: The grouped fieldset exposes the required state for the complete choice question.
|
|
3657
|
+
/* @__PURE__ */ jsxs2(
|
|
3658
|
+
"fieldset",
|
|
3659
|
+
{
|
|
3660
|
+
className,
|
|
3661
|
+
"data-field-id": field.id,
|
|
3662
|
+
disabled,
|
|
3663
|
+
"aria-describedby": describedBy(field, error, props.helpId, props.errorId),
|
|
3664
|
+
"aria-invalid": Boolean(error),
|
|
3665
|
+
"aria-required": field.required,
|
|
3666
|
+
style: slotProps?.style,
|
|
3667
|
+
children: [
|
|
3668
|
+
/* @__PURE__ */ jsxs2("legend", { className: "fe-choice-legend", children: [
|
|
3669
|
+
field.title,
|
|
3670
|
+
/* @__PURE__ */ jsx3(RequiredMark, { required: field.required, className: "fe-required-badge" })
|
|
3671
|
+
] }),
|
|
3672
|
+
/* @__PURE__ */ jsx3(GroupedChoiceDescription, { props }),
|
|
3673
|
+
children,
|
|
3674
|
+
/* @__PURE__ */ jsx3(GroupedChoiceError, { props })
|
|
3675
|
+
]
|
|
3676
|
+
}
|
|
3677
|
+
)
|
|
3678
|
+
);
|
|
3679
|
+
}
|
|
3283
3680
|
function DefaultField({
|
|
3284
3681
|
groupedChoiceFields,
|
|
3682
|
+
appearance,
|
|
3683
|
+
choiceGroupSlotProps,
|
|
3684
|
+
renderChoiceGroup,
|
|
3685
|
+
disabled,
|
|
3686
|
+
readOnly,
|
|
3285
3687
|
...props
|
|
3286
3688
|
}) {
|
|
3287
3689
|
const { field, value, setValue, inputId, error, translate } = props;
|
|
3690
|
+
const isGroupedChoiceField = isChoiceFieldType(field.type) && resolveChoiceFieldLayout(field.type, appearance, groupedChoiceFields) === "grouped";
|
|
3691
|
+
const choiceGroupClassName = ["fe-choice-group", `fe-field--${field.type}`, choiceGroupSlotProps?.className].filter((item) => item !== void 0 && item.length > 0).join(" ");
|
|
3288
3692
|
const ariaProps = {
|
|
3289
3693
|
"aria-describedby": describedBy(field, error, props.helpId, props.errorId),
|
|
3290
3694
|
"aria-invalid": error === void 0 ? void 0 : true
|
|
3291
3695
|
};
|
|
3292
3696
|
if (field.type === "checkbox") {
|
|
3293
|
-
if (
|
|
3294
|
-
return (
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
}
|
|
3321
|
-
),
|
|
3322
|
-
/* @__PURE__ */ jsx3("span", { children: field.title })
|
|
3323
|
-
] }) }),
|
|
3324
|
-
/* @__PURE__ */ jsx3(GroupedChoiceError, { props })
|
|
3325
|
-
]
|
|
3326
|
-
}
|
|
3327
|
-
)
|
|
3697
|
+
if (isGroupedChoiceField) {
|
|
3698
|
+
return /* @__PURE__ */ jsx3(
|
|
3699
|
+
ChoiceGroupFrame,
|
|
3700
|
+
{
|
|
3701
|
+
props,
|
|
3702
|
+
className: choiceGroupClassName,
|
|
3703
|
+
slotProps: choiceGroupSlotProps,
|
|
3704
|
+
renderChoiceGroup,
|
|
3705
|
+
disabled,
|
|
3706
|
+
readOnly,
|
|
3707
|
+
children: /* @__PURE__ */ jsx3("div", { className: "fe-choice-options", children: /* @__PURE__ */ jsxs2("label", { className: "fe-choice-option", htmlFor: inputId, children: [
|
|
3708
|
+
/* @__PURE__ */ jsx3(
|
|
3709
|
+
"input",
|
|
3710
|
+
{
|
|
3711
|
+
id: inputId,
|
|
3712
|
+
name: field.id,
|
|
3713
|
+
type: "checkbox",
|
|
3714
|
+
checked: value === true,
|
|
3715
|
+
"aria-label": field.title,
|
|
3716
|
+
disabled,
|
|
3717
|
+
readOnly,
|
|
3718
|
+
onChange: (event) => setValue(event.currentTarget.checked)
|
|
3719
|
+
}
|
|
3720
|
+
),
|
|
3721
|
+
/* @__PURE__ */ jsx3("span", { children: field.title })
|
|
3722
|
+
] }) })
|
|
3723
|
+
}
|
|
3328
3724
|
);
|
|
3329
3725
|
}
|
|
3330
3726
|
return /* @__PURE__ */ jsxs2("div", { className: "fe-field fe-field--checkbox", "data-field-id": field.id, children: [
|
|
@@ -3350,56 +3746,55 @@ function DefaultField({
|
|
|
3350
3746
|
}
|
|
3351
3747
|
if (field.type === "radio" || field.type === "multi-select") {
|
|
3352
3748
|
const selected = Array.isArray(value) ? value : [];
|
|
3353
|
-
if (
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
)
|
|
3749
|
+
if (isGroupedChoiceField) {
|
|
3750
|
+
const isRadio = field.type === "radio";
|
|
3751
|
+
return /* @__PURE__ */ jsx3(
|
|
3752
|
+
ChoiceGroupFrame,
|
|
3753
|
+
{
|
|
3754
|
+
props,
|
|
3755
|
+
className: choiceGroupClassName,
|
|
3756
|
+
slotProps: choiceGroupSlotProps,
|
|
3757
|
+
renderChoiceGroup,
|
|
3758
|
+
disabled,
|
|
3759
|
+
readOnly,
|
|
3760
|
+
children: /* @__PURE__ */ jsx3("div", { className: "fe-choice-options", children: field.options.map((option, index) => {
|
|
3761
|
+
const optionId = `${inputId}-${index}`;
|
|
3762
|
+
const checked = isRadio ? value === option.id : selected.includes(option.id);
|
|
3763
|
+
return /* @__PURE__ */ jsxs2("label", { className: "fe-choice-option", htmlFor: optionId, children: [
|
|
3764
|
+
/* @__PURE__ */ jsx3(
|
|
3765
|
+
"input",
|
|
3766
|
+
{
|
|
3767
|
+
id: optionId,
|
|
3768
|
+
name: field.id,
|
|
3769
|
+
type: isRadio ? "radio" : "checkbox",
|
|
3770
|
+
value: option.id,
|
|
3771
|
+
checked,
|
|
3772
|
+
disabled,
|
|
3773
|
+
readOnly,
|
|
3774
|
+
onKeyDown: isRadio ? (event) => {
|
|
3775
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowRight" && event.key !== "ArrowUp" && event.key !== "ArrowLeft")
|
|
3776
|
+
return;
|
|
3777
|
+
event.preventDefault();
|
|
3778
|
+
const offset = event.key === "ArrowDown" || event.key === "ArrowRight" ? 1 : -1;
|
|
3779
|
+
const nextIndex = (index + offset + field.options.length) % field.options.length;
|
|
3780
|
+
const nextOption = field.options[nextIndex];
|
|
3781
|
+
if (nextOption === void 0) return;
|
|
3782
|
+
setValue(nextOption.id);
|
|
3783
|
+
document.getElementById(`${inputId}-${nextIndex}`)?.focus();
|
|
3784
|
+
} : void 0,
|
|
3785
|
+
onChange: (event) => {
|
|
3786
|
+
if (isRadio) setValue(option.id);
|
|
3787
|
+
else
|
|
3788
|
+
setValue(
|
|
3789
|
+
event.currentTarget.checked ? [...selected, option.id] : selected.filter((item) => item !== option.id)
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
),
|
|
3794
|
+
/* @__PURE__ */ jsx3("span", { children: option.label })
|
|
3795
|
+
] }, option.id);
|
|
3796
|
+
}) })
|
|
3797
|
+
}
|
|
3403
3798
|
);
|
|
3404
3799
|
}
|
|
3405
3800
|
if (field.type === "radio") {
|
|
@@ -3536,6 +3931,7 @@ function DefaultField({
|
|
|
3536
3931
|
...ariaProps,
|
|
3537
3932
|
id: inputId,
|
|
3538
3933
|
name: field.id,
|
|
3934
|
+
disabled,
|
|
3539
3935
|
value: typeof value === "string" ? value : "",
|
|
3540
3936
|
onChange: (event) => setValue(event.currentTarget.value || void 0),
|
|
3541
3937
|
children: [
|
|
@@ -3559,6 +3955,20 @@ function DefaultField({
|
|
|
3559
3955
|
}
|
|
3560
3956
|
);
|
|
3561
3957
|
}
|
|
3958
|
+
if (isGroupedChoiceField) {
|
|
3959
|
+
return /* @__PURE__ */ jsx3(
|
|
3960
|
+
ChoiceGroupFrame,
|
|
3961
|
+
{
|
|
3962
|
+
props,
|
|
3963
|
+
className: choiceGroupClassName,
|
|
3964
|
+
slotProps: choiceGroupSlotProps,
|
|
3965
|
+
renderChoiceGroup,
|
|
3966
|
+
disabled,
|
|
3967
|
+
readOnly,
|
|
3968
|
+
children: /* @__PURE__ */ jsx3("div", { className: "fe-choice-options", children: control })
|
|
3969
|
+
}
|
|
3970
|
+
);
|
|
3971
|
+
}
|
|
3562
3972
|
return /* @__PURE__ */ jsxs2("div", { className: `fe-field fe-field--${field.type}`, "data-field-id": field.id, children: [
|
|
3563
3973
|
label,
|
|
3564
3974
|
control,
|
|
@@ -3679,6 +4089,7 @@ function ContextFormRenderer({
|
|
|
3679
4089
|
onDraftSave,
|
|
3680
4090
|
successRenderMode = "append",
|
|
3681
4091
|
appearance,
|
|
4092
|
+
slotProps,
|
|
3682
4093
|
groupedChoiceFields = false,
|
|
3683
4094
|
submissionConfirmation,
|
|
3684
4095
|
submissionConfirmationRenderMode,
|
|
@@ -3692,42 +4103,41 @@ function ContextFormRenderer({
|
|
|
3692
4103
|
slots = {}
|
|
3693
4104
|
}) {
|
|
3694
4105
|
const form = useForm();
|
|
3695
|
-
const isGroupedMode = appearance?.choiceField === "grouped" || groupedChoiceFields;
|
|
3696
4106
|
const prefix = useId().replace(/:/g, "");
|
|
3697
4107
|
const formRef = useRef2(null);
|
|
3698
4108
|
const loadedDraftKey = useRef2(null);
|
|
3699
|
-
const [draftRestored, setDraftRestored] =
|
|
3700
|
-
const [currentPageIndex, setCurrentPageIndex] =
|
|
3701
|
-
const [focusFieldId, setFocusFieldId] =
|
|
3702
|
-
const [confirmation, setConfirmation] =
|
|
3703
|
-
const [guardMessage, setGuardMessage] =
|
|
3704
|
-
const [guardsPending, setGuardsPending] =
|
|
3705
|
-
const [receipt, setReceipt] =
|
|
3706
|
-
const [completionData, setCompletionData] =
|
|
3707
|
-
const [receiptLoaded, setReceiptLoaded] =
|
|
4109
|
+
const [draftRestored, setDraftRestored] = useState6(false);
|
|
4110
|
+
const [currentPageIndex, setCurrentPageIndex] = useState6(0);
|
|
4111
|
+
const [focusFieldId, setFocusFieldId] = useState6(null);
|
|
4112
|
+
const [confirmation, setConfirmation] = useState6(null);
|
|
4113
|
+
const [guardMessage, setGuardMessage] = useState6(null);
|
|
4114
|
+
const [guardsPending, setGuardsPending] = useState6(false);
|
|
4115
|
+
const [receipt, setReceipt] = useState6(null);
|
|
4116
|
+
const [completionData, setCompletionData] = useState6(null);
|
|
4117
|
+
const [receiptLoaded, setReceiptLoaded] = useState6(receiptStore === void 0);
|
|
3708
4118
|
const rendererSubmissionInFlight = useRef2(false);
|
|
3709
4119
|
const fallbackAttemptId = useRef2(null);
|
|
3710
4120
|
const completionRef = useRef2(null);
|
|
3711
4121
|
const confirmationRef = useRef2(null);
|
|
3712
4122
|
const pages = form.schema.pages;
|
|
3713
|
-
const visiblePageIndexes =
|
|
4123
|
+
const visiblePageIndexes = useMemo5(
|
|
3714
4124
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
3715
4125
|
[form.pageVisibility, pages]
|
|
3716
4126
|
);
|
|
3717
4127
|
const activePage = pages?.[currentPageIndex];
|
|
3718
4128
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
3719
4129
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
3720
|
-
const visibleValues =
|
|
3721
|
-
const visibleItems =
|
|
4130
|
+
const visibleValues = useMemo5(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
|
|
4131
|
+
const visibleItems = useMemo5(
|
|
3722
4132
|
() => buildSubmittedItems(form.schema, form.values, form.visibility, (key) => form.translate(key), false),
|
|
3723
4133
|
[form.schema, form.translate, form.values, form.visibility]
|
|
3724
4134
|
);
|
|
3725
|
-
const confirmationRenderMode = submissionConfirmation?.renderMode ?? submissionConfirmationRenderMode ?? "inline";
|
|
3726
|
-
const confirmationEnabled = submissionConfirmation?.enabled
|
|
4135
|
+
const confirmationRenderMode = submissionConfirmation?.renderMode ?? submissionConfirmationRenderMode ?? form.schema.submissionSettings?.confirmationRenderMode ?? "inline";
|
|
4136
|
+
const confirmationEnabled = submissionConfirmation?.enabled ?? form.schema.submissionSettings?.showConfirmationBeforeSubmit ?? false;
|
|
3727
4137
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
3728
4138
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
3729
4139
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
3730
|
-
const resolveMessage =
|
|
4140
|
+
const resolveMessage = useCallback4(
|
|
3731
4141
|
(key, fallback) => {
|
|
3732
4142
|
const defaultText = fallback ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
3733
4143
|
const configured = messages[key];
|
|
@@ -3735,11 +4145,11 @@ function ContextFormRenderer({
|
|
|
3735
4145
|
},
|
|
3736
4146
|
[form.locale, messageResolver, messages]
|
|
3737
4147
|
);
|
|
3738
|
-
const fieldTranslate =
|
|
4148
|
+
const fieldTranslate = useCallback4(
|
|
3739
4149
|
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
3740
4150
|
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
3741
4151
|
);
|
|
3742
|
-
const focusSubmitButton =
|
|
4152
|
+
const focusSubmitButton = useCallback4(() => {
|
|
3743
4153
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
3744
4154
|
button?.focus();
|
|
3745
4155
|
}, []);
|
|
@@ -4199,7 +4609,18 @@ function ContextFormRenderer({
|
|
|
4199
4609
|
}) }, field.id);
|
|
4200
4610
|
}
|
|
4201
4611
|
const Component = components[field.type];
|
|
4202
|
-
return Component === void 0 ? /* @__PURE__ */ jsx3(
|
|
4612
|
+
return Component === void 0 ? /* @__PURE__ */ jsx3(
|
|
4613
|
+
DefaultField,
|
|
4614
|
+
{
|
|
4615
|
+
...props,
|
|
4616
|
+
groupedChoiceFields,
|
|
4617
|
+
appearance,
|
|
4618
|
+
choiceGroupSlotProps: slotProps?.choiceGroup,
|
|
4619
|
+
renderChoiceGroup: slots.renderChoiceGroup,
|
|
4620
|
+
disabled: interactionLocked
|
|
4621
|
+
},
|
|
4622
|
+
field.id
|
|
4623
|
+
) : /* @__PURE__ */ jsx3(Component, { ...props }, field.id);
|
|
4203
4624
|
});
|
|
4204
4625
|
const fieldClassName = `fe-fields${fieldsClassName === void 0 ? "" : ` ${fieldsClassName}`}`;
|
|
4205
4626
|
return slots.renderFields?.({ children: fieldChildren, className: fieldClassName }) ?? /* @__PURE__ */ jsx3("div", { className: fieldClassName, children: fieldChildren });
|
|
@@ -4338,6 +4759,7 @@ export {
|
|
|
4338
4759
|
createLocalStorageSubmissionAttemptStore,
|
|
4339
4760
|
createLocalStorageSubmissionReceiptStore,
|
|
4340
4761
|
isTranslationUnresolved,
|
|
4762
|
+
resolveChoiceFieldLayout,
|
|
4341
4763
|
resolveFieldEditorControls,
|
|
4342
4764
|
resolveFieldTypeSelectOptions,
|
|
4343
4765
|
resolveInitialFieldType,
|
|
@@ -4346,5 +4768,6 @@ export {
|
|
|
4346
4768
|
useField,
|
|
4347
4769
|
useForm,
|
|
4348
4770
|
useFormBuilder,
|
|
4349
|
-
useSubmissionReceipts
|
|
4771
|
+
useSubmissionReceipts,
|
|
4772
|
+
useTranslationWorkspace
|
|
4350
4773
|
};
|