@form-engine-ts/react 6.0.0 → 6.2.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 +10 -3
- package/dist/index.cjs +297 -67
- package/dist/index.d.cts +202 -116
- package/dist/index.d.ts +202 -116
- package/dist/index.js +285 -50
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
FormEngineI18nProvider: () => FormEngineI18nProvider,
|
|
28
28
|
FormProvider: () => FormProvider,
|
|
29
29
|
FormRenderer: () => FormRenderer,
|
|
30
|
-
FormSubmissionError: () => FormSubmissionError,
|
|
30
|
+
FormSubmissionError: () => import_core8.FormSubmissionError,
|
|
31
31
|
createLocalStorageSubmissionAttemptStore: () => createLocalStorageSubmissionAttemptStore,
|
|
32
32
|
createLocalStorageSubmissionReceiptStore: () => createLocalStorageSubmissionReceiptStore,
|
|
33
33
|
isTranslationUnresolved: () => isTranslationUnresolved,
|
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
useFormBuilder: () => useFormBuilder,
|
|
43
43
|
useFormEngineI18n: () => useFormEngineI18n,
|
|
44
44
|
useSubmissionReceipts: () => useSubmissionReceipts,
|
|
45
|
+
useTranslationComparison: () => useTranslationComparison,
|
|
45
46
|
useTranslationWorkspace: () => useTranslationWorkspace,
|
|
46
47
|
validateLocalePipeline: () => validateLocalePipeline
|
|
47
48
|
});
|
|
@@ -978,6 +979,8 @@ function FormEngineI18nProvider({
|
|
|
978
979
|
fallbackLocale = "en",
|
|
979
980
|
messages,
|
|
980
981
|
customCatalogs,
|
|
982
|
+
onMissingKey,
|
|
983
|
+
strict,
|
|
981
984
|
translator: customTranslator,
|
|
982
985
|
children
|
|
983
986
|
}) {
|
|
@@ -987,9 +990,11 @@ function FormEngineI18nProvider({
|
|
|
987
990
|
locale,
|
|
988
991
|
fallbackLocale,
|
|
989
992
|
...messages === void 0 ? {} : { messages },
|
|
990
|
-
...customCatalogs === void 0 ? {} : { customCatalogs }
|
|
993
|
+
...customCatalogs === void 0 ? {} : { customCatalogs },
|
|
994
|
+
...onMissingKey === void 0 ? {} : { onMissingKey },
|
|
995
|
+
...strict === void 0 ? {} : { strict }
|
|
991
996
|
});
|
|
992
|
-
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages]);
|
|
997
|
+
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages, onMissingKey, strict]);
|
|
993
998
|
const value = (0, import_react2.useMemo)(() => ({ uiLocale: locale, translator }), [locale, translator]);
|
|
994
999
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormEngineI18nProviderScopeContext.Provider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormEngineI18nContext.Provider, { value, children }) });
|
|
995
1000
|
}
|
|
@@ -2566,6 +2571,7 @@ function FormBuilder({
|
|
|
2566
2571
|
LocalizationSlot,
|
|
2567
2572
|
{
|
|
2568
2573
|
schema,
|
|
2574
|
+
onChange,
|
|
2569
2575
|
translate,
|
|
2570
2576
|
currentLocale: editingLocale,
|
|
2571
2577
|
onCurrentLocaleChange: setEditingLocale,
|
|
@@ -3088,18 +3094,6 @@ function FormBuilder({
|
|
|
3088
3094
|
// src/context.tsx
|
|
3089
3095
|
var import_core4 = require("@form-engine-ts/core");
|
|
3090
3096
|
var import_react4 = require("react");
|
|
3091
|
-
|
|
3092
|
-
// src/types.ts
|
|
3093
|
-
var FormSubmissionError = class extends Error {
|
|
3094
|
-
payload;
|
|
3095
|
-
constructor(message, payload) {
|
|
3096
|
-
super(message);
|
|
3097
|
-
this.name = "FormSubmissionError";
|
|
3098
|
-
this.payload = payload ?? { formError: message };
|
|
3099
|
-
}
|
|
3100
|
-
};
|
|
3101
|
-
|
|
3102
|
-
// src/context.tsx
|
|
3103
3097
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3104
3098
|
var FormContext = (0, import_react4.createContext)(null);
|
|
3105
3099
|
function issuesByField(issues) {
|
|
@@ -3236,10 +3230,12 @@ function FormProvider({
|
|
|
3236
3230
|
setSubmitStatus("success");
|
|
3237
3231
|
return response === void 0 ? { status: "success" } : { status: "success", response };
|
|
3238
3232
|
} catch (cause) {
|
|
3239
|
-
const error = isServerErrorPayload(cause) ? new FormSubmissionError(
|
|
3240
|
-
|
|
3241
|
-
cause
|
|
3242
|
-
|
|
3233
|
+
const error = (0, import_core4.isFormSubmissionSerializedError)(cause) ? new import_core4.FormSubmissionError(cause) : isServerErrorPayload(cause) ? new import_core4.FormSubmissionError({
|
|
3234
|
+
code: "VALIDATION_FAILED",
|
|
3235
|
+
messageKey: cause.formError ?? "Submission failed",
|
|
3236
|
+
fieldErrors: cause.fieldErrors ?? {},
|
|
3237
|
+
formErrors: cause.formError === void 0 ? [] : [cause.formError]
|
|
3238
|
+
}) : cause instanceof Error ? cause : new Error(String(cause));
|
|
3243
3239
|
setSubmitError(error);
|
|
3244
3240
|
setSubmitStatus("error");
|
|
3245
3241
|
return { status: "error", error };
|
|
@@ -3307,6 +3303,10 @@ function useField(fieldId) {
|
|
|
3307
3303
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
3308
3304
|
}
|
|
3309
3305
|
|
|
3306
|
+
// src/hooks/useTranslationComparison.ts
|
|
3307
|
+
var import_core6 = require("@form-engine-ts/core");
|
|
3308
|
+
var import_react6 = require("react");
|
|
3309
|
+
|
|
3310
3310
|
// src/hooks/useTranslationWorkspace.ts
|
|
3311
3311
|
var import_core5 = require("@form-engine-ts/core");
|
|
3312
3312
|
var import_react5 = require("react");
|
|
@@ -3489,6 +3489,7 @@ function useTranslationWorkspace({
|
|
|
3489
3489
|
sourceLocale = schema.defaultLocale ?? "en",
|
|
3490
3490
|
targetLocale,
|
|
3491
3491
|
translationAdapter,
|
|
3492
|
+
signal,
|
|
3492
3493
|
readOnly = false,
|
|
3493
3494
|
policy,
|
|
3494
3495
|
availableLocales,
|
|
@@ -3500,6 +3501,7 @@ function useTranslationWorkspace({
|
|
|
3500
3501
|
slots: workspaceSlots,
|
|
3501
3502
|
onTranslationStart,
|
|
3502
3503
|
onTranslationSuccess,
|
|
3504
|
+
onTranslationReport,
|
|
3503
3505
|
onTranslationError,
|
|
3504
3506
|
onTranslationChange,
|
|
3505
3507
|
validateLocale
|
|
@@ -3507,8 +3509,10 @@ function useTranslationWorkspace({
|
|
|
3507
3509
|
const [draftSchema, setDraftSchema] = (0, import_react5.useState)(schema);
|
|
3508
3510
|
const [selectedLocale, setSelectedLocale] = (0, import_react5.useState)((0, import_core5.normalizeLocale)(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3509
3511
|
const [isTranslating, setIsTranslating] = (0, import_react5.useState)(false);
|
|
3512
|
+
const [progress, setProgress] = (0, import_react5.useState)();
|
|
3510
3513
|
const [error, setError] = (0, import_react5.useState)();
|
|
3511
3514
|
const [pendingRemoval, setPendingRemoval] = (0, import_react5.useState)();
|
|
3515
|
+
const activeAbortController = (0, import_react5.useRef)(void 0);
|
|
3512
3516
|
const pendingRemovalResolver = (0, import_react5.useRef)(void 0);
|
|
3513
3517
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3514
3518
|
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
@@ -3748,7 +3752,15 @@ function useTranslationWorkspace({
|
|
|
3748
3752
|
}
|
|
3749
3753
|
setIsTranslating(true);
|
|
3750
3754
|
setError(void 0);
|
|
3755
|
+
setProgress(void 0);
|
|
3751
3756
|
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3757
|
+
const controller = new AbortController();
|
|
3758
|
+
activeAbortController.current = controller;
|
|
3759
|
+
const operationSignal = options.signal ?? signal;
|
|
3760
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3761
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3762
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3763
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3752
3764
|
try {
|
|
3753
3765
|
const populated = await (0, import_core5.populateSchemaTranslations)(
|
|
3754
3766
|
currentSchema,
|
|
@@ -3757,13 +3769,36 @@ function useTranslationWorkspace({
|
|
|
3757
3769
|
{
|
|
3758
3770
|
overwrite: "stale-and-missing",
|
|
3759
3771
|
preserveManualTranslations: true,
|
|
3760
|
-
...options
|
|
3772
|
+
...options,
|
|
3773
|
+
continueOnError: true,
|
|
3774
|
+
signal: controller.signal,
|
|
3775
|
+
onProgress: (nextProgress) => {
|
|
3776
|
+
setProgress(nextProgress);
|
|
3777
|
+
options.onProgress?.(nextProgress);
|
|
3778
|
+
}
|
|
3761
3779
|
}
|
|
3762
3780
|
);
|
|
3763
3781
|
commit(populated.schema);
|
|
3782
|
+
onTranslationReport?.(populated.report);
|
|
3764
3783
|
const missingSlotsCount = (0, import_core5.collectTranslationSlots)(populated.schema, activeLocale).filter(
|
|
3765
3784
|
(slot) => slot.status === "missing"
|
|
3766
3785
|
).length;
|
|
3786
|
+
if (populated.report.cancelled === true) {
|
|
3787
|
+
const workspaceError = { type: "cancelled" };
|
|
3788
|
+
setError(workspaceError);
|
|
3789
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3790
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3791
|
+
}
|
|
3792
|
+
if ((populated.report.failed ?? 0) > 0) {
|
|
3793
|
+
const workspaceError = {
|
|
3794
|
+
type: "partial_failure",
|
|
3795
|
+
succeeded: populated.report.succeeded ?? populated.report.updatedSlots.length,
|
|
3796
|
+
failed: populated.report.failed ?? 0
|
|
3797
|
+
};
|
|
3798
|
+
setError(workspaceError);
|
|
3799
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3800
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3801
|
+
}
|
|
3767
3802
|
onTranslationSuccess?.({
|
|
3768
3803
|
sourceLocale,
|
|
3769
3804
|
targetLocale: activeLocale,
|
|
@@ -3774,6 +3809,12 @@ function useTranslationWorkspace({
|
|
|
3774
3809
|
});
|
|
3775
3810
|
return { success: true, report: populated.report };
|
|
3776
3811
|
} catch (cause) {
|
|
3812
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3813
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3814
|
+
setError(workspaceError2);
|
|
3815
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError2 });
|
|
3816
|
+
return { success: false, error: workspaceError2 };
|
|
3817
|
+
}
|
|
3777
3818
|
const workspaceError = {
|
|
3778
3819
|
type: "translation_failed",
|
|
3779
3820
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -3783,6 +3824,11 @@ function useTranslationWorkspace({
|
|
|
3783
3824
|
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3784
3825
|
return { success: false, error: workspaceError };
|
|
3785
3826
|
} finally {
|
|
3827
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3828
|
+
const listener = abortListener;
|
|
3829
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3830
|
+
}
|
|
3831
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3786
3832
|
setIsTranslating(false);
|
|
3787
3833
|
}
|
|
3788
3834
|
},
|
|
@@ -3794,14 +3840,19 @@ function useTranslationWorkspace({
|
|
|
3794
3840
|
onTranslationError,
|
|
3795
3841
|
onTranslationStart,
|
|
3796
3842
|
onTranslationSuccess,
|
|
3843
|
+
onTranslationReport,
|
|
3797
3844
|
readOnly,
|
|
3798
3845
|
slots,
|
|
3799
3846
|
sourceLocale,
|
|
3800
|
-
translationAdapter
|
|
3847
|
+
translationAdapter,
|
|
3848
|
+
signal
|
|
3801
3849
|
]
|
|
3802
3850
|
);
|
|
3851
|
+
const cancelTranslation = (0, import_react5.useCallback)(() => {
|
|
3852
|
+
activeAbortController.current?.abort(new DOMException("The translation was cancelled.", "AbortError"));
|
|
3853
|
+
}, []);
|
|
3803
3854
|
const translateSlot = (0, import_react5.useCallback)(
|
|
3804
|
-
async (slot) => {
|
|
3855
|
+
async (slot, options = {}) => {
|
|
3805
3856
|
if (translationAdapter === void 0) {
|
|
3806
3857
|
const workspaceError = { type: "adapter_not_configured" };
|
|
3807
3858
|
setError(workspaceError);
|
|
@@ -3814,8 +3865,21 @@ function useTranslationWorkspace({
|
|
|
3814
3865
|
}
|
|
3815
3866
|
setIsTranslating(true);
|
|
3816
3867
|
setError(void 0);
|
|
3868
|
+
setProgress({ total: 1, completed: 0, succeeded: 0, failed: 0, percentage: 0 });
|
|
3869
|
+
const controller = new AbortController();
|
|
3870
|
+
activeAbortController.current = controller;
|
|
3871
|
+
const operationSignal = options.signal ?? signal;
|
|
3872
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3873
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3874
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3875
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3817
3876
|
try {
|
|
3818
|
-
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3877
|
+
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3878
|
+
slot.sourceText,
|
|
3879
|
+
slot.locale,
|
|
3880
|
+
sourceLocale,
|
|
3881
|
+
controller.signal
|
|
3882
|
+
);
|
|
3819
3883
|
const metadata = translationMetadata(slot.sourceText, sourceLocale, "automatic");
|
|
3820
3884
|
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic"));
|
|
3821
3885
|
onTranslationChange?.({
|
|
@@ -3825,8 +3889,15 @@ function useTranslationWorkspace({
|
|
|
3825
3889
|
mode: "automatic",
|
|
3826
3890
|
metadata
|
|
3827
3891
|
});
|
|
3892
|
+
setProgress({ total: 1, completed: 1, succeeded: 1, failed: 0, percentage: 100 });
|
|
3828
3893
|
return { success: true };
|
|
3829
3894
|
} catch (cause) {
|
|
3895
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3896
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3897
|
+
setError(workspaceError2);
|
|
3898
|
+
onTranslationError?.({ targetLocale: slot.locale, error: workspaceError2 });
|
|
3899
|
+
return { success: false, error: workspaceError2 };
|
|
3900
|
+
}
|
|
3830
3901
|
const workspaceError = {
|
|
3831
3902
|
type: "translation_failed",
|
|
3832
3903
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -3835,10 +3906,15 @@ function useTranslationWorkspace({
|
|
|
3835
3906
|
setError(workspaceError);
|
|
3836
3907
|
return { success: false, error: workspaceError };
|
|
3837
3908
|
} finally {
|
|
3909
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3910
|
+
const listener = abortListener;
|
|
3911
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3912
|
+
}
|
|
3913
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3838
3914
|
setIsTranslating(false);
|
|
3839
3915
|
}
|
|
3840
3916
|
},
|
|
3841
|
-
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale, translationAdapter]
|
|
3917
|
+
[commit, currentSchema, onTranslationChange, onTranslationError, readOnly, signal, sourceLocale, translationAdapter]
|
|
3842
3918
|
);
|
|
3843
3919
|
return {
|
|
3844
3920
|
sourceLocale,
|
|
@@ -3859,13 +3935,159 @@ function useTranslationWorkspace({
|
|
|
3859
3935
|
setTranslation,
|
|
3860
3936
|
translateAll,
|
|
3861
3937
|
translateSlot,
|
|
3938
|
+
cancelTranslation,
|
|
3862
3939
|
isTranslating,
|
|
3940
|
+
...progress === void 0 ? {} : { progress },
|
|
3863
3941
|
...error === void 0 ? {} : { error }
|
|
3864
3942
|
};
|
|
3865
3943
|
}
|
|
3866
3944
|
|
|
3945
|
+
// src/hooks/useTranslationComparison.ts
|
|
3946
|
+
function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
3947
|
+
if (translatedText === void 0 || translatedText.trim().length === 0) return void 0;
|
|
3948
|
+
if (metadata !== void 0 && typeof metadata.sourceLocale === "string" && typeof metadata.sourceTextHash === "string" && (metadata.translationSource === "automatic" || metadata.translationSource === "manual")) {
|
|
3949
|
+
return {
|
|
3950
|
+
...metadata,
|
|
3951
|
+
sourceLocale: metadata.sourceLocale,
|
|
3952
|
+
sourceTextHash: metadata.sourceTextHash,
|
|
3953
|
+
translationSource: metadata.translationSource,
|
|
3954
|
+
...typeof metadata.translatedAt === "string" ? { translatedAt: metadata.translatedAt } : {},
|
|
3955
|
+
...typeof metadata.editedAt === "string" ? { editedAt: metadata.editedAt } : {}
|
|
3956
|
+
};
|
|
3957
|
+
}
|
|
3958
|
+
const canonicalKeys = /* @__PURE__ */ new Set(["sourceLocale", "sourceTextHash", "translationSource", "translatedAt", "editedAt"]);
|
|
3959
|
+
const extensions = Object.fromEntries(Object.entries(metadata ?? {}).filter(([key]) => !canonicalKeys.has(key)));
|
|
3960
|
+
return {
|
|
3961
|
+
...extensions,
|
|
3962
|
+
sourceLocale: typeof metadata?.sourceLocale === "string" ? metadata.sourceLocale : sourceLocale,
|
|
3963
|
+
sourceTextHash: (0, import_core6.computeSourceTextHash)(sourceText),
|
|
3964
|
+
translationSource: metadata?.isManual === true || metadata?.isManuallyEdited === true || metadata?.translationSource === "manual" ? "manual" : "automatic"
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
function optionParentId(slot) {
|
|
3968
|
+
if (slot.kind !== "option" || slot.path === void 0) return void 0;
|
|
3969
|
+
return /^fields\.([^.]+)\.options\./u.exec(slot.path)?.[1];
|
|
3970
|
+
}
|
|
3971
|
+
function getNodeTitle(schema, slot) {
|
|
3972
|
+
if (slot.kind === "field") return schema.fields.find((field) => field.id === slot.nodeId)?.title;
|
|
3973
|
+
if (slot.kind === "page") return schema.pages?.find((page) => page.id === slot.nodeId)?.title;
|
|
3974
|
+
if (slot.kind === "option") return schema.fields.find((field) => field.id === optionParentId(slot))?.title;
|
|
3975
|
+
return void 0;
|
|
3976
|
+
}
|
|
3977
|
+
function comparisonItem(schema, slot, sourceLocale, hasAdapter) {
|
|
3978
|
+
const path = slot.path ?? `${slot.kind}.${slot.nodeId}.${slot.property}`;
|
|
3979
|
+
const nodeTitle = getNodeTitle(schema, slot);
|
|
3980
|
+
const metadata = canonicalMetadata(
|
|
3981
|
+
slot.existingTranslationMetadata,
|
|
3982
|
+
slot.sourceText,
|
|
3983
|
+
sourceLocale,
|
|
3984
|
+
slot.existingText
|
|
3985
|
+
);
|
|
3986
|
+
return {
|
|
3987
|
+
id: path,
|
|
3988
|
+
path,
|
|
3989
|
+
targetKind: slot.kind,
|
|
3990
|
+
targetProperty: slot.property,
|
|
3991
|
+
...nodeTitle === void 0 ? {} : { nodeTitle },
|
|
3992
|
+
sourceText: slot.sourceText,
|
|
3993
|
+
translatedText: slot.existingText ?? "",
|
|
3994
|
+
status: slot.status ?? "missing",
|
|
3995
|
+
...metadata === void 0 ? {} : { metadata },
|
|
3996
|
+
translatable: hasAdapter && slot.sourceText.trim().length > 0
|
|
3997
|
+
};
|
|
3998
|
+
}
|
|
3999
|
+
function summaryFor(items) {
|
|
4000
|
+
const counts = {
|
|
4001
|
+
missing: 0,
|
|
4002
|
+
translated: 0,
|
|
4003
|
+
stale: 0,
|
|
4004
|
+
manual: 0,
|
|
4005
|
+
"manual-stale": 0
|
|
4006
|
+
};
|
|
4007
|
+
for (const item of items) counts[item.status] += 1;
|
|
4008
|
+
return {
|
|
4009
|
+
total: items.length,
|
|
4010
|
+
translated: counts.translated + counts.manual,
|
|
4011
|
+
missing: counts.missing,
|
|
4012
|
+
stale: counts.stale + counts["manual-stale"],
|
|
4013
|
+
manual: counts.manual + counts["manual-stale"]
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
function useTranslationComparison({
|
|
4017
|
+
schema,
|
|
4018
|
+
sourceLocale = schema.defaultLocale ?? "en",
|
|
4019
|
+
targetLocale,
|
|
4020
|
+
translationAdapter,
|
|
4021
|
+
readOnly = false,
|
|
4022
|
+
onChange,
|
|
4023
|
+
onTranslationChange,
|
|
4024
|
+
onTranslationReport,
|
|
4025
|
+
onTranslationError,
|
|
4026
|
+
signal
|
|
4027
|
+
}) {
|
|
4028
|
+
const workspace = useTranslationWorkspace({
|
|
4029
|
+
schema,
|
|
4030
|
+
sourceLocale,
|
|
4031
|
+
targetLocale,
|
|
4032
|
+
...translationAdapter === void 0 ? {} : { translationAdapter },
|
|
4033
|
+
readOnly,
|
|
4034
|
+
...onChange === void 0 ? {} : { onChange },
|
|
4035
|
+
...onTranslationChange === void 0 ? {} : { onTranslationChange },
|
|
4036
|
+
...onTranslationReport === void 0 ? {} : { onTranslationReport },
|
|
4037
|
+
...onTranslationError === void 0 ? {} : { onTranslationError },
|
|
4038
|
+
...signal === void 0 ? {} : { signal }
|
|
4039
|
+
});
|
|
4040
|
+
const [report, setReport] = (0, import_react6.useState)();
|
|
4041
|
+
const items = (0, import_react6.useMemo)(
|
|
4042
|
+
() => workspace.slots.map((slot) => comparisonItem(schema, slot, sourceLocale, translationAdapter !== void 0)),
|
|
4043
|
+
[schema, sourceLocale, translationAdapter, workspace.slots]
|
|
4044
|
+
);
|
|
4045
|
+
const itemByPath = (0, import_react6.useMemo)(() => new Map(workspace.slots.map((slot) => [slot.path, slot])), [workspace.slots]);
|
|
4046
|
+
const updateTranslation = (0, import_react6.useCallback)(
|
|
4047
|
+
(path, text) => {
|
|
4048
|
+
const slot = itemByPath.get(path);
|
|
4049
|
+
if (slot !== void 0) workspace.setTranslation(slot, text);
|
|
4050
|
+
},
|
|
4051
|
+
[itemByPath, workspace]
|
|
4052
|
+
);
|
|
4053
|
+
const translateSingle = (0, import_react6.useCallback)(
|
|
4054
|
+
async (path) => {
|
|
4055
|
+
const slot = itemByPath.get(path);
|
|
4056
|
+
if (slot === void 0) return;
|
|
4057
|
+
const result = await workspace.translateSlot(slot);
|
|
4058
|
+
if (!result.success) throw new Error(result.error?.type ?? "Translation failed.");
|
|
4059
|
+
},
|
|
4060
|
+
[itemByPath, workspace]
|
|
4061
|
+
);
|
|
4062
|
+
const translateAll = (0, import_react6.useCallback)(
|
|
4063
|
+
async (options) => {
|
|
4064
|
+
const result = await workspace.translateAll(options);
|
|
4065
|
+
if (result.report !== void 0) {
|
|
4066
|
+
setReport(result.report);
|
|
4067
|
+
return result.report;
|
|
4068
|
+
}
|
|
4069
|
+
throw new Error(result.error?.type ?? "Translation failed.");
|
|
4070
|
+
},
|
|
4071
|
+
[workspace]
|
|
4072
|
+
);
|
|
4073
|
+
return {
|
|
4074
|
+
sourceLocale: workspace.sourceLocale,
|
|
4075
|
+
targetLocale: workspace.targetLocale,
|
|
4076
|
+
items,
|
|
4077
|
+
summary: summaryFor(items),
|
|
4078
|
+
isTranslating: workspace.isTranslating,
|
|
4079
|
+
updateTranslation,
|
|
4080
|
+
translateSingle,
|
|
4081
|
+
translateAll,
|
|
4082
|
+
cancelTranslation: workspace.cancelTranslation,
|
|
4083
|
+
...workspace.progress === void 0 ? {} : { progress: workspace.progress },
|
|
4084
|
+
...workspace.error === void 0 ? {} : { error: workspace.error },
|
|
4085
|
+
...report === void 0 ? {} : { report }
|
|
4086
|
+
};
|
|
4087
|
+
}
|
|
4088
|
+
|
|
3867
4089
|
// src/receipt.ts
|
|
3868
|
-
var
|
|
4090
|
+
var import_react7 = require("react");
|
|
3869
4091
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3870
4092
|
return `${formId}:v${formVersion}`;
|
|
3871
4093
|
}
|
|
@@ -3936,19 +4158,19 @@ function createLocalStorageSubmissionReceiptStore(options = {}) {
|
|
|
3936
4158
|
}
|
|
3937
4159
|
function useSubmissionReceipts(store, queries) {
|
|
3938
4160
|
const querySignature = JSON.stringify(queries.map(({ formId, formVersion }) => [formId, formVersion]));
|
|
3939
|
-
const stableQueries = (0,
|
|
4161
|
+
const stableQueries = (0, import_react7.useMemo)(() => {
|
|
3940
4162
|
const parsed = JSON.parse(querySignature);
|
|
3941
4163
|
if (!Array.isArray(parsed)) return [];
|
|
3942
4164
|
return parsed.flatMap(
|
|
3943
4165
|
(entry) => Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number" && Number.isSafeInteger(entry[1]) ? [{ formId: entry[0], formVersion: entry[1] }] : []
|
|
3944
4166
|
);
|
|
3945
4167
|
}, [querySignature]);
|
|
3946
|
-
const [state, setState] = (0,
|
|
4168
|
+
const [state, setState] = (0, import_react7.useState)({
|
|
3947
4169
|
receipts: /* @__PURE__ */ new Map(),
|
|
3948
4170
|
isLoading: stableQueries.length > 0,
|
|
3949
4171
|
error: null
|
|
3950
4172
|
});
|
|
3951
|
-
(0,
|
|
4173
|
+
(0, import_react7.useEffect)(() => {
|
|
3952
4174
|
let active = true;
|
|
3953
4175
|
if (stableQueries.length === 0) {
|
|
3954
4176
|
setState({ receipts: /* @__PURE__ */ new Map(), isLoading: false, error: null });
|
|
@@ -3982,8 +4204,8 @@ function useSubmissionReceipts(store, queries) {
|
|
|
3982
4204
|
}
|
|
3983
4205
|
|
|
3984
4206
|
// src/renderer.tsx
|
|
3985
|
-
var
|
|
3986
|
-
var
|
|
4207
|
+
var import_core7 = require("@form-engine-ts/core");
|
|
4208
|
+
var import_react8 = require("react");
|
|
3987
4209
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
3988
4210
|
var isChoiceFieldType = (type) => type === "radio" || type === "checkbox" || type === "multi-select" || type === "select";
|
|
3989
4211
|
function resolveChoiceFieldLayout(type, appearance, groupedChoiceFieldsLegacy = false) {
|
|
@@ -4495,33 +4717,33 @@ function ContextFormRenderer({
|
|
|
4495
4717
|
}) {
|
|
4496
4718
|
const form = useForm();
|
|
4497
4719
|
const i18n = useFormEngineI18n();
|
|
4498
|
-
const isProviderValue = (0,
|
|
4499
|
-
const prefix = (0,
|
|
4500
|
-
const formRef = (0,
|
|
4501
|
-
const loadedDraftKey = (0,
|
|
4502
|
-
const [draftRestored, setDraftRestored] = (0,
|
|
4503
|
-
const [currentPageIndex, setCurrentPageIndex] = (0,
|
|
4504
|
-
const [focusFieldId, setFocusFieldId] = (0,
|
|
4505
|
-
const [confirmation, setConfirmation] = (0,
|
|
4506
|
-
const [guardMessage, setGuardMessage] = (0,
|
|
4507
|
-
const [guardsPending, setGuardsPending] = (0,
|
|
4508
|
-
const [receipt, setReceipt] = (0,
|
|
4509
|
-
const [completionData, setCompletionData] = (0,
|
|
4510
|
-
const [receiptLoaded, setReceiptLoaded] = (0,
|
|
4511
|
-
const rendererSubmissionInFlight = (0,
|
|
4512
|
-
const fallbackAttemptId = (0,
|
|
4513
|
-
const completionRef = (0,
|
|
4514
|
-
const confirmationRef = (0,
|
|
4720
|
+
const isProviderValue = (0, import_react8.useContext)(FormEngineI18nProviderScopeContext);
|
|
4721
|
+
const prefix = (0, import_react8.useId)().replace(/:/g, "");
|
|
4722
|
+
const formRef = (0, import_react8.useRef)(null);
|
|
4723
|
+
const loadedDraftKey = (0, import_react8.useRef)(null);
|
|
4724
|
+
const [draftRestored, setDraftRestored] = (0, import_react8.useState)(false);
|
|
4725
|
+
const [currentPageIndex, setCurrentPageIndex] = (0, import_react8.useState)(0);
|
|
4726
|
+
const [focusFieldId, setFocusFieldId] = (0, import_react8.useState)(null);
|
|
4727
|
+
const [confirmation, setConfirmation] = (0, import_react8.useState)(null);
|
|
4728
|
+
const [guardMessage, setGuardMessage] = (0, import_react8.useState)(null);
|
|
4729
|
+
const [guardsPending, setGuardsPending] = (0, import_react8.useState)(false);
|
|
4730
|
+
const [receipt, setReceipt] = (0, import_react8.useState)(null);
|
|
4731
|
+
const [completionData, setCompletionData] = (0, import_react8.useState)(null);
|
|
4732
|
+
const [receiptLoaded, setReceiptLoaded] = (0, import_react8.useState)(receiptStore === void 0);
|
|
4733
|
+
const rendererSubmissionInFlight = (0, import_react8.useRef)(false);
|
|
4734
|
+
const fallbackAttemptId = (0, import_react8.useRef)(null);
|
|
4735
|
+
const completionRef = (0, import_react8.useRef)(null);
|
|
4736
|
+
const confirmationRef = (0, import_react8.useRef)(null);
|
|
4515
4737
|
const pages = form.schema.pages;
|
|
4516
|
-
const visiblePageIndexes = (0,
|
|
4738
|
+
const visiblePageIndexes = (0, import_react8.useMemo)(
|
|
4517
4739
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
4518
4740
|
[form.pageVisibility, pages]
|
|
4519
4741
|
);
|
|
4520
4742
|
const activePage = pages?.[currentPageIndex];
|
|
4521
4743
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
4522
4744
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
4523
|
-
const visibleValues = (0,
|
|
4524
|
-
const visibleItems = (0,
|
|
4745
|
+
const visibleValues = (0, import_react8.useMemo)(() => (0, import_core7.selectVisibleAnswers)(form.schema, form.values), [form.schema, form.values]);
|
|
4746
|
+
const visibleItems = (0, import_react8.useMemo)(
|
|
4525
4747
|
() => buildSubmittedItems(form.schema, form.values, form.visibility, (key) => form.translate(key), false),
|
|
4526
4748
|
[form.schema, form.translate, form.values, form.visibility]
|
|
4527
4749
|
);
|
|
@@ -4530,7 +4752,7 @@ function ContextFormRenderer({
|
|
|
4530
4752
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
4531
4753
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
4532
4754
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
4533
|
-
const resolveMessage = (0,
|
|
4755
|
+
const resolveMessage = (0, import_react8.useCallback)(
|
|
4534
4756
|
(key, fallback) => {
|
|
4535
4757
|
const providerDefault = isProviderValue ? i18n.translator(`renderer.${key}`) : void 0;
|
|
4536
4758
|
const defaultText = fallback ?? (providerDefault === "" ? void 0 : providerDefault) ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
@@ -4539,15 +4761,15 @@ function ContextFormRenderer({
|
|
|
4539
4761
|
},
|
|
4540
4762
|
[form.locale, i18n, isProviderValue, messageResolver, messages]
|
|
4541
4763
|
);
|
|
4542
|
-
const fieldTranslate = (0,
|
|
4764
|
+
const fieldTranslate = (0, import_react8.useCallback)(
|
|
4543
4765
|
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
4544
4766
|
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
4545
4767
|
);
|
|
4546
|
-
const focusSubmitButton = (0,
|
|
4768
|
+
const focusSubmitButton = (0, import_react8.useCallback)(() => {
|
|
4547
4769
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
4548
4770
|
button?.focus();
|
|
4549
4771
|
}, []);
|
|
4550
|
-
(0,
|
|
4772
|
+
(0, import_react8.useEffect)(() => {
|
|
4551
4773
|
let active = true;
|
|
4552
4774
|
if (receiptStore === void 0) {
|
|
4553
4775
|
setReceipt(null);
|
|
@@ -4568,14 +4790,14 @@ function ContextFormRenderer({
|
|
|
4568
4790
|
active = false;
|
|
4569
4791
|
};
|
|
4570
4792
|
}, [form.schema.id, form.schema.version, receiptStore]);
|
|
4571
|
-
(0,
|
|
4793
|
+
(0, import_react8.useEffect)(() => {
|
|
4572
4794
|
if (pages === void 0 || visiblePageIndexes.length === 0) {
|
|
4573
4795
|
setCurrentPageIndex(0);
|
|
4574
4796
|
return;
|
|
4575
4797
|
}
|
|
4576
4798
|
if (!visiblePageIndexes.includes(currentPageIndex)) setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
4577
4799
|
}, [currentPageIndex, pages, visiblePageIndexes]);
|
|
4578
|
-
(0,
|
|
4800
|
+
(0, import_react8.useEffect)(() => {
|
|
4579
4801
|
if (focusFieldId === null) return;
|
|
4580
4802
|
const fieldContainer = [...formRef.current?.querySelectorAll("[data-field-id]") ?? []].find(
|
|
4581
4803
|
(element) => element.dataset.fieldId === focusFieldId
|
|
@@ -4587,11 +4809,11 @@ function ContextFormRenderer({
|
|
|
4587
4809
|
setFocusFieldId(null);
|
|
4588
4810
|
}
|
|
4589
4811
|
}, [focusFieldId]);
|
|
4590
|
-
(0,
|
|
4812
|
+
(0, import_react8.useEffect)(() => {
|
|
4591
4813
|
if (!isReplaceMode || form.submitStatus !== "success") return;
|
|
4592
4814
|
completionRef.current?.focus();
|
|
4593
4815
|
}, [form.submitStatus, isReplaceMode]);
|
|
4594
|
-
(0,
|
|
4816
|
+
(0, import_react8.useEffect)(() => {
|
|
4595
4817
|
if (confirmation === null) return;
|
|
4596
4818
|
const confirmButton = confirmationRef.current?.querySelector("[data-fe-confirm], button");
|
|
4597
4819
|
confirmButton?.focus();
|
|
@@ -4623,7 +4845,7 @@ function ContextFormRenderer({
|
|
|
4623
4845
|
globalThis.addEventListener("keydown", onKeyDown);
|
|
4624
4846
|
return () => globalThis.removeEventListener("keydown", onKeyDown);
|
|
4625
4847
|
}, [confirmation, confirmationRenderMode, focusSubmitButton]);
|
|
4626
|
-
(0,
|
|
4848
|
+
(0, import_react8.useEffect)(() => {
|
|
4627
4849
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
4628
4850
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
4629
4851
|
if (loadedDraftKey.current === loadIdentity) return;
|
|
@@ -4635,7 +4857,7 @@ function ContextFormRenderer({
|
|
|
4635
4857
|
form.restoreValues(draft.values);
|
|
4636
4858
|
setDraftRestored(true);
|
|
4637
4859
|
}, [autoSaveKey, form.restoreValues, form.schema.id, form.schema.version]);
|
|
4638
|
-
(0,
|
|
4860
|
+
(0, import_react8.useEffect)(() => {
|
|
4639
4861
|
if (form.submitStatus === "success") return;
|
|
4640
4862
|
const timeout = globalThis.setTimeout(() => {
|
|
4641
4863
|
onDraftSave?.(form.values);
|
|
@@ -4691,7 +4913,7 @@ function ContextFormRenderer({
|
|
|
4691
4913
|
if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
|
|
4692
4914
|
return { status: "cancelled" };
|
|
4693
4915
|
}
|
|
4694
|
-
const validation = (0,
|
|
4916
|
+
const validation = (0, import_core7.validateAnswers)(form.schema, form.values);
|
|
4695
4917
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
4696
4918
|
if (validation.valid && !guardsConfirmed) {
|
|
4697
4919
|
rendererSubmissionInFlight.current = true;
|
|
@@ -4754,8 +4976,9 @@ function ContextFormRenderer({
|
|
|
4754
4976
|
return result;
|
|
4755
4977
|
}
|
|
4756
4978
|
if (result.status === "error") {
|
|
4757
|
-
|
|
4758
|
-
|
|
4979
|
+
const payload = result.error instanceof import_core7.FormSubmissionError ? result.error.payload : (0, import_core7.isFormSubmissionSerializedError)(result.error) ? result.error : void 0;
|
|
4980
|
+
if (payload !== void 0) {
|
|
4981
|
+
const fieldErrors = payload.fieldErrors ?? {};
|
|
4759
4982
|
form.setServerErrors?.(fieldErrors);
|
|
4760
4983
|
const firstServerFieldId = form.schema.fields.find((field) => Object.hasOwn(fieldErrors, field.id))?.id ?? Object.keys(fieldErrors)[0];
|
|
4761
4984
|
if (firstServerFieldId !== void 0) {
|
|
@@ -4763,6 +4986,9 @@ function ContextFormRenderer({
|
|
|
4763
4986
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
4764
4987
|
focusFirstIssue(firstServerFieldId);
|
|
4765
4988
|
}
|
|
4989
|
+
if (payload.piiFindings !== void 0 && payload.piiFindings.length > 0) {
|
|
4990
|
+
setConfirmation({ findings: payload.piiFindings, generic: false });
|
|
4991
|
+
}
|
|
4766
4992
|
}
|
|
4767
4993
|
return result;
|
|
4768
4994
|
}
|
|
@@ -4993,7 +5219,7 @@ function ContextFormRenderer({
|
|
|
4993
5219
|
...slots.renderCharacterCount === void 0 ? {} : { renderCharacterCount: slots.renderCharacterCount }
|
|
4994
5220
|
};
|
|
4995
5221
|
if (slots.renderField !== void 0) {
|
|
4996
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
5222
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Fragment, { children: slots.renderField({
|
|
4997
5223
|
question: field,
|
|
4998
5224
|
value: form.values[field.id],
|
|
4999
5225
|
onChange: (value) => {
|
|
@@ -5064,8 +5290,8 @@ function ContextFormRenderer({
|
|
|
5064
5290
|
] }),
|
|
5065
5291
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
5066
5292
|
form.submitStatus === "success" ? completionRegion : null,
|
|
5067
|
-
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof FormSubmissionError && form.submitError.payload.
|
|
5068
|
-
form.submitError.payload.
|
|
5293
|
+
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof import_core7.FormSubmissionError && (form.submitError.payload.formErrors?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { role: "alert", children: [
|
|
5294
|
+
form.submitError.payload.formErrors?.map((message) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: message }, message)),
|
|
5069
5295
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
5070
5296
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { role: "alert", children: [
|
|
5071
5297
|
errorMessageKey === void 0 ? resolveMessage("serverErrorSummary") : form.translate(errorMessageKey),
|
|
@@ -5121,7 +5347,7 @@ var defaultRendererTranslator = {
|
|
|
5121
5347
|
};
|
|
5122
5348
|
function FormRenderer(props) {
|
|
5123
5349
|
const i18n = useFormEngineI18n();
|
|
5124
|
-
const isProviderValue = (0,
|
|
5350
|
+
const isProviderValue = (0, import_react8.useContext)(FormEngineI18nProviderScopeContext);
|
|
5125
5351
|
if (!("schema" in props)) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContextFormRenderer, { ...props });
|
|
5126
5352
|
const {
|
|
5127
5353
|
schema,
|
|
@@ -5148,6 +5374,9 @@ function FormRenderer(props) {
|
|
|
5148
5374
|
}
|
|
5149
5375
|
);
|
|
5150
5376
|
}
|
|
5377
|
+
|
|
5378
|
+
// src/types.ts
|
|
5379
|
+
var import_core8 = require("@form-engine-ts/core");
|
|
5151
5380
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5152
5381
|
0 && (module.exports = {
|
|
5153
5382
|
BUILDER_TRANSLATION_ALIASES,
|
|
@@ -5172,6 +5401,7 @@ function FormRenderer(props) {
|
|
|
5172
5401
|
useFormBuilder,
|
|
5173
5402
|
useFormEngineI18n,
|
|
5174
5403
|
useSubmissionReceipts,
|
|
5404
|
+
useTranslationComparison,
|
|
5175
5405
|
useTranslationWorkspace,
|
|
5176
5406
|
validateLocalePipeline
|
|
5177
5407
|
});
|