@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.js
CHANGED
|
@@ -922,7 +922,9 @@ function resolveTranslation(key, aliases, adapter, defaultCatalog, params = {},
|
|
|
922
922
|
}
|
|
923
923
|
|
|
924
924
|
// src/i18n/provider.tsx
|
|
925
|
-
import {
|
|
925
|
+
import {
|
|
926
|
+
createFormEngineTranslator
|
|
927
|
+
} from "@form-engine-ts/core";
|
|
926
928
|
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
927
929
|
import { jsx } from "react/jsx-runtime";
|
|
928
930
|
var defaultTranslator = createFormEngineTranslator({ locale: "ja" });
|
|
@@ -936,6 +938,8 @@ function FormEngineI18nProvider({
|
|
|
936
938
|
fallbackLocale = "en",
|
|
937
939
|
messages,
|
|
938
940
|
customCatalogs,
|
|
941
|
+
onMissingKey,
|
|
942
|
+
strict,
|
|
939
943
|
translator: customTranslator,
|
|
940
944
|
children
|
|
941
945
|
}) {
|
|
@@ -945,9 +949,11 @@ function FormEngineI18nProvider({
|
|
|
945
949
|
locale,
|
|
946
950
|
fallbackLocale,
|
|
947
951
|
...messages === void 0 ? {} : { messages },
|
|
948
|
-
...customCatalogs === void 0 ? {} : { customCatalogs }
|
|
952
|
+
...customCatalogs === void 0 ? {} : { customCatalogs },
|
|
953
|
+
...onMissingKey === void 0 ? {} : { onMissingKey },
|
|
954
|
+
...strict === void 0 ? {} : { strict }
|
|
949
955
|
});
|
|
950
|
-
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages]);
|
|
956
|
+
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages, onMissingKey, strict]);
|
|
951
957
|
const value = useMemo2(() => ({ uiLocale: locale, translator }), [locale, translator]);
|
|
952
958
|
return /* @__PURE__ */ jsx(FormEngineI18nProviderScopeContext.Provider, { value: true, children: /* @__PURE__ */ jsx(FormEngineI18nContext.Provider, { value, children }) });
|
|
953
959
|
}
|
|
@@ -2524,6 +2530,7 @@ function FormBuilder({
|
|
|
2524
2530
|
LocalizationSlot,
|
|
2525
2531
|
{
|
|
2526
2532
|
schema,
|
|
2533
|
+
onChange,
|
|
2527
2534
|
translate,
|
|
2528
2535
|
currentLocale: editingLocale,
|
|
2529
2536
|
onCurrentLocaleChange: setEditingLocale,
|
|
@@ -3048,24 +3055,14 @@ import {
|
|
|
3048
3055
|
assertValidFormSchema,
|
|
3049
3056
|
calculateFieldVisibility,
|
|
3050
3057
|
calculatePageVisibility,
|
|
3058
|
+
FormSubmissionError,
|
|
3059
|
+
isFormSubmissionSerializedError,
|
|
3051
3060
|
resolveLocalizedSchema,
|
|
3052
3061
|
selectVisibleAnswers,
|
|
3053
3062
|
validateAnswers,
|
|
3054
3063
|
validatePageAnswers
|
|
3055
3064
|
} from "@form-engine-ts/core";
|
|
3056
3065
|
import { createContext as createContext3, useCallback as useCallback2, useContext as useContext3, useEffect, useMemo as useMemo3, useRef, useState as useState3 } from "react";
|
|
3057
|
-
|
|
3058
|
-
// src/types.ts
|
|
3059
|
-
var FormSubmissionError = class extends Error {
|
|
3060
|
-
payload;
|
|
3061
|
-
constructor(message, payload) {
|
|
3062
|
-
super(message);
|
|
3063
|
-
this.name = "FormSubmissionError";
|
|
3064
|
-
this.payload = payload ?? { formError: message };
|
|
3065
|
-
}
|
|
3066
|
-
};
|
|
3067
|
-
|
|
3068
|
-
// src/context.tsx
|
|
3069
3066
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3070
3067
|
var FormContext = createContext3(null);
|
|
3071
3068
|
function issuesByField(issues) {
|
|
@@ -3202,10 +3199,12 @@ function FormProvider({
|
|
|
3202
3199
|
setSubmitStatus("success");
|
|
3203
3200
|
return response === void 0 ? { status: "success" } : { status: "success", response };
|
|
3204
3201
|
} catch (cause) {
|
|
3205
|
-
const error = isServerErrorPayload(cause) ? new FormSubmissionError(
|
|
3206
|
-
|
|
3207
|
-
cause
|
|
3208
|
-
|
|
3202
|
+
const error = isFormSubmissionSerializedError(cause) ? new FormSubmissionError(cause) : isServerErrorPayload(cause) ? new FormSubmissionError({
|
|
3203
|
+
code: "VALIDATION_FAILED",
|
|
3204
|
+
messageKey: cause.formError ?? "Submission failed",
|
|
3205
|
+
fieldErrors: cause.fieldErrors ?? {},
|
|
3206
|
+
formErrors: cause.formError === void 0 ? [] : [cause.formError]
|
|
3207
|
+
}) : cause instanceof Error ? cause : new Error(String(cause));
|
|
3209
3208
|
setSubmitError(error);
|
|
3210
3209
|
setSubmitStatus("error");
|
|
3211
3210
|
return { status: "error", error };
|
|
@@ -3273,6 +3272,10 @@ function useField(fieldId) {
|
|
|
3273
3272
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
3274
3273
|
}
|
|
3275
3274
|
|
|
3275
|
+
// src/hooks/useTranslationComparison.ts
|
|
3276
|
+
import { computeSourceTextHash as computeSourceTextHash2 } from "@form-engine-ts/core";
|
|
3277
|
+
import { useCallback as useCallback4, useMemo as useMemo5, useState as useState5 } from "react";
|
|
3278
|
+
|
|
3276
3279
|
// src/hooks/useTranslationWorkspace.ts
|
|
3277
3280
|
import {
|
|
3278
3281
|
collectSchemaLocales as collectSchemaLocales2,
|
|
@@ -3462,6 +3465,7 @@ function useTranslationWorkspace({
|
|
|
3462
3465
|
sourceLocale = schema.defaultLocale ?? "en",
|
|
3463
3466
|
targetLocale,
|
|
3464
3467
|
translationAdapter,
|
|
3468
|
+
signal,
|
|
3465
3469
|
readOnly = false,
|
|
3466
3470
|
policy,
|
|
3467
3471
|
availableLocales,
|
|
@@ -3473,6 +3477,7 @@ function useTranslationWorkspace({
|
|
|
3473
3477
|
slots: workspaceSlots,
|
|
3474
3478
|
onTranslationStart,
|
|
3475
3479
|
onTranslationSuccess,
|
|
3480
|
+
onTranslationReport,
|
|
3476
3481
|
onTranslationError,
|
|
3477
3482
|
onTranslationChange,
|
|
3478
3483
|
validateLocale
|
|
@@ -3480,8 +3485,10 @@ function useTranslationWorkspace({
|
|
|
3480
3485
|
const [draftSchema, setDraftSchema] = useState4(schema);
|
|
3481
3486
|
const [selectedLocale, setSelectedLocale] = useState4(normalizeLocale(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3482
3487
|
const [isTranslating, setIsTranslating] = useState4(false);
|
|
3488
|
+
const [progress, setProgress] = useState4();
|
|
3483
3489
|
const [error, setError] = useState4();
|
|
3484
3490
|
const [pendingRemoval, setPendingRemoval] = useState4();
|
|
3491
|
+
const activeAbortController = useRef2(void 0);
|
|
3485
3492
|
const pendingRemovalResolver = useRef2(void 0);
|
|
3486
3493
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3487
3494
|
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
@@ -3721,7 +3728,15 @@ function useTranslationWorkspace({
|
|
|
3721
3728
|
}
|
|
3722
3729
|
setIsTranslating(true);
|
|
3723
3730
|
setError(void 0);
|
|
3731
|
+
setProgress(void 0);
|
|
3724
3732
|
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3733
|
+
const controller = new AbortController();
|
|
3734
|
+
activeAbortController.current = controller;
|
|
3735
|
+
const operationSignal = options.signal ?? signal;
|
|
3736
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3737
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3738
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3739
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3725
3740
|
try {
|
|
3726
3741
|
const populated = await populateSchemaTranslations2(
|
|
3727
3742
|
currentSchema,
|
|
@@ -3730,13 +3745,36 @@ function useTranslationWorkspace({
|
|
|
3730
3745
|
{
|
|
3731
3746
|
overwrite: "stale-and-missing",
|
|
3732
3747
|
preserveManualTranslations: true,
|
|
3733
|
-
...options
|
|
3748
|
+
...options,
|
|
3749
|
+
continueOnError: true,
|
|
3750
|
+
signal: controller.signal,
|
|
3751
|
+
onProgress: (nextProgress) => {
|
|
3752
|
+
setProgress(nextProgress);
|
|
3753
|
+
options.onProgress?.(nextProgress);
|
|
3754
|
+
}
|
|
3734
3755
|
}
|
|
3735
3756
|
);
|
|
3736
3757
|
commit(populated.schema);
|
|
3758
|
+
onTranslationReport?.(populated.report);
|
|
3737
3759
|
const missingSlotsCount = collectTranslationSlots(populated.schema, activeLocale).filter(
|
|
3738
3760
|
(slot) => slot.status === "missing"
|
|
3739
3761
|
).length;
|
|
3762
|
+
if (populated.report.cancelled === true) {
|
|
3763
|
+
const workspaceError = { type: "cancelled" };
|
|
3764
|
+
setError(workspaceError);
|
|
3765
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3766
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3767
|
+
}
|
|
3768
|
+
if ((populated.report.failed ?? 0) > 0) {
|
|
3769
|
+
const workspaceError = {
|
|
3770
|
+
type: "partial_failure",
|
|
3771
|
+
succeeded: populated.report.succeeded ?? populated.report.updatedSlots.length,
|
|
3772
|
+
failed: populated.report.failed ?? 0
|
|
3773
|
+
};
|
|
3774
|
+
setError(workspaceError);
|
|
3775
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3776
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3777
|
+
}
|
|
3740
3778
|
onTranslationSuccess?.({
|
|
3741
3779
|
sourceLocale,
|
|
3742
3780
|
targetLocale: activeLocale,
|
|
@@ -3747,6 +3785,12 @@ function useTranslationWorkspace({
|
|
|
3747
3785
|
});
|
|
3748
3786
|
return { success: true, report: populated.report };
|
|
3749
3787
|
} catch (cause) {
|
|
3788
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3789
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3790
|
+
setError(workspaceError2);
|
|
3791
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError2 });
|
|
3792
|
+
return { success: false, error: workspaceError2 };
|
|
3793
|
+
}
|
|
3750
3794
|
const workspaceError = {
|
|
3751
3795
|
type: "translation_failed",
|
|
3752
3796
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -3756,6 +3800,11 @@ function useTranslationWorkspace({
|
|
|
3756
3800
|
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3757
3801
|
return { success: false, error: workspaceError };
|
|
3758
3802
|
} finally {
|
|
3803
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3804
|
+
const listener = abortListener;
|
|
3805
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3806
|
+
}
|
|
3807
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3759
3808
|
setIsTranslating(false);
|
|
3760
3809
|
}
|
|
3761
3810
|
},
|
|
@@ -3767,14 +3816,19 @@ function useTranslationWorkspace({
|
|
|
3767
3816
|
onTranslationError,
|
|
3768
3817
|
onTranslationStart,
|
|
3769
3818
|
onTranslationSuccess,
|
|
3819
|
+
onTranslationReport,
|
|
3770
3820
|
readOnly,
|
|
3771
3821
|
slots,
|
|
3772
3822
|
sourceLocale,
|
|
3773
|
-
translationAdapter
|
|
3823
|
+
translationAdapter,
|
|
3824
|
+
signal
|
|
3774
3825
|
]
|
|
3775
3826
|
);
|
|
3827
|
+
const cancelTranslation = useCallback3(() => {
|
|
3828
|
+
activeAbortController.current?.abort(new DOMException("The translation was cancelled.", "AbortError"));
|
|
3829
|
+
}, []);
|
|
3776
3830
|
const translateSlot = useCallback3(
|
|
3777
|
-
async (slot) => {
|
|
3831
|
+
async (slot, options = {}) => {
|
|
3778
3832
|
if (translationAdapter === void 0) {
|
|
3779
3833
|
const workspaceError = { type: "adapter_not_configured" };
|
|
3780
3834
|
setError(workspaceError);
|
|
@@ -3787,8 +3841,21 @@ function useTranslationWorkspace({
|
|
|
3787
3841
|
}
|
|
3788
3842
|
setIsTranslating(true);
|
|
3789
3843
|
setError(void 0);
|
|
3844
|
+
setProgress({ total: 1, completed: 0, succeeded: 0, failed: 0, percentage: 0 });
|
|
3845
|
+
const controller = new AbortController();
|
|
3846
|
+
activeAbortController.current = controller;
|
|
3847
|
+
const operationSignal = options.signal ?? signal;
|
|
3848
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3849
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3850
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3851
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3790
3852
|
try {
|
|
3791
|
-
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3853
|
+
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3854
|
+
slot.sourceText,
|
|
3855
|
+
slot.locale,
|
|
3856
|
+
sourceLocale,
|
|
3857
|
+
controller.signal
|
|
3858
|
+
);
|
|
3792
3859
|
const metadata = translationMetadata(slot.sourceText, sourceLocale, "automatic");
|
|
3793
3860
|
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic"));
|
|
3794
3861
|
onTranslationChange?.({
|
|
@@ -3798,8 +3865,15 @@ function useTranslationWorkspace({
|
|
|
3798
3865
|
mode: "automatic",
|
|
3799
3866
|
metadata
|
|
3800
3867
|
});
|
|
3868
|
+
setProgress({ total: 1, completed: 1, succeeded: 1, failed: 0, percentage: 100 });
|
|
3801
3869
|
return { success: true };
|
|
3802
3870
|
} catch (cause) {
|
|
3871
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3872
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3873
|
+
setError(workspaceError2);
|
|
3874
|
+
onTranslationError?.({ targetLocale: slot.locale, error: workspaceError2 });
|
|
3875
|
+
return { success: false, error: workspaceError2 };
|
|
3876
|
+
}
|
|
3803
3877
|
const workspaceError = {
|
|
3804
3878
|
type: "translation_failed",
|
|
3805
3879
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -3808,10 +3882,15 @@ function useTranslationWorkspace({
|
|
|
3808
3882
|
setError(workspaceError);
|
|
3809
3883
|
return { success: false, error: workspaceError };
|
|
3810
3884
|
} finally {
|
|
3885
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3886
|
+
const listener = abortListener;
|
|
3887
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3888
|
+
}
|
|
3889
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3811
3890
|
setIsTranslating(false);
|
|
3812
3891
|
}
|
|
3813
3892
|
},
|
|
3814
|
-
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale, translationAdapter]
|
|
3893
|
+
[commit, currentSchema, onTranslationChange, onTranslationError, readOnly, signal, sourceLocale, translationAdapter]
|
|
3815
3894
|
);
|
|
3816
3895
|
return {
|
|
3817
3896
|
sourceLocale,
|
|
@@ -3832,13 +3911,159 @@ function useTranslationWorkspace({
|
|
|
3832
3911
|
setTranslation,
|
|
3833
3912
|
translateAll,
|
|
3834
3913
|
translateSlot,
|
|
3914
|
+
cancelTranslation,
|
|
3835
3915
|
isTranslating,
|
|
3916
|
+
...progress === void 0 ? {} : { progress },
|
|
3836
3917
|
...error === void 0 ? {} : { error }
|
|
3837
3918
|
};
|
|
3838
3919
|
}
|
|
3839
3920
|
|
|
3921
|
+
// src/hooks/useTranslationComparison.ts
|
|
3922
|
+
function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
3923
|
+
if (translatedText === void 0 || translatedText.trim().length === 0) return void 0;
|
|
3924
|
+
if (metadata !== void 0 && typeof metadata.sourceLocale === "string" && typeof metadata.sourceTextHash === "string" && (metadata.translationSource === "automatic" || metadata.translationSource === "manual")) {
|
|
3925
|
+
return {
|
|
3926
|
+
...metadata,
|
|
3927
|
+
sourceLocale: metadata.sourceLocale,
|
|
3928
|
+
sourceTextHash: metadata.sourceTextHash,
|
|
3929
|
+
translationSource: metadata.translationSource,
|
|
3930
|
+
...typeof metadata.translatedAt === "string" ? { translatedAt: metadata.translatedAt } : {},
|
|
3931
|
+
...typeof metadata.editedAt === "string" ? { editedAt: metadata.editedAt } : {}
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3934
|
+
const canonicalKeys = /* @__PURE__ */ new Set(["sourceLocale", "sourceTextHash", "translationSource", "translatedAt", "editedAt"]);
|
|
3935
|
+
const extensions = Object.fromEntries(Object.entries(metadata ?? {}).filter(([key]) => !canonicalKeys.has(key)));
|
|
3936
|
+
return {
|
|
3937
|
+
...extensions,
|
|
3938
|
+
sourceLocale: typeof metadata?.sourceLocale === "string" ? metadata.sourceLocale : sourceLocale,
|
|
3939
|
+
sourceTextHash: computeSourceTextHash2(sourceText),
|
|
3940
|
+
translationSource: metadata?.isManual === true || metadata?.isManuallyEdited === true || metadata?.translationSource === "manual" ? "manual" : "automatic"
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
function optionParentId(slot) {
|
|
3944
|
+
if (slot.kind !== "option" || slot.path === void 0) return void 0;
|
|
3945
|
+
return /^fields\.([^.]+)\.options\./u.exec(slot.path)?.[1];
|
|
3946
|
+
}
|
|
3947
|
+
function getNodeTitle(schema, slot) {
|
|
3948
|
+
if (slot.kind === "field") return schema.fields.find((field) => field.id === slot.nodeId)?.title;
|
|
3949
|
+
if (slot.kind === "page") return schema.pages?.find((page) => page.id === slot.nodeId)?.title;
|
|
3950
|
+
if (slot.kind === "option") return schema.fields.find((field) => field.id === optionParentId(slot))?.title;
|
|
3951
|
+
return void 0;
|
|
3952
|
+
}
|
|
3953
|
+
function comparisonItem(schema, slot, sourceLocale, hasAdapter) {
|
|
3954
|
+
const path = slot.path ?? `${slot.kind}.${slot.nodeId}.${slot.property}`;
|
|
3955
|
+
const nodeTitle = getNodeTitle(schema, slot);
|
|
3956
|
+
const metadata = canonicalMetadata(
|
|
3957
|
+
slot.existingTranslationMetadata,
|
|
3958
|
+
slot.sourceText,
|
|
3959
|
+
sourceLocale,
|
|
3960
|
+
slot.existingText
|
|
3961
|
+
);
|
|
3962
|
+
return {
|
|
3963
|
+
id: path,
|
|
3964
|
+
path,
|
|
3965
|
+
targetKind: slot.kind,
|
|
3966
|
+
targetProperty: slot.property,
|
|
3967
|
+
...nodeTitle === void 0 ? {} : { nodeTitle },
|
|
3968
|
+
sourceText: slot.sourceText,
|
|
3969
|
+
translatedText: slot.existingText ?? "",
|
|
3970
|
+
status: slot.status ?? "missing",
|
|
3971
|
+
...metadata === void 0 ? {} : { metadata },
|
|
3972
|
+
translatable: hasAdapter && slot.sourceText.trim().length > 0
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
function summaryFor(items) {
|
|
3976
|
+
const counts = {
|
|
3977
|
+
missing: 0,
|
|
3978
|
+
translated: 0,
|
|
3979
|
+
stale: 0,
|
|
3980
|
+
manual: 0,
|
|
3981
|
+
"manual-stale": 0
|
|
3982
|
+
};
|
|
3983
|
+
for (const item of items) counts[item.status] += 1;
|
|
3984
|
+
return {
|
|
3985
|
+
total: items.length,
|
|
3986
|
+
translated: counts.translated + counts.manual,
|
|
3987
|
+
missing: counts.missing,
|
|
3988
|
+
stale: counts.stale + counts["manual-stale"],
|
|
3989
|
+
manual: counts.manual + counts["manual-stale"]
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
function useTranslationComparison({
|
|
3993
|
+
schema,
|
|
3994
|
+
sourceLocale = schema.defaultLocale ?? "en",
|
|
3995
|
+
targetLocale,
|
|
3996
|
+
translationAdapter,
|
|
3997
|
+
readOnly = false,
|
|
3998
|
+
onChange,
|
|
3999
|
+
onTranslationChange,
|
|
4000
|
+
onTranslationReport,
|
|
4001
|
+
onTranslationError,
|
|
4002
|
+
signal
|
|
4003
|
+
}) {
|
|
4004
|
+
const workspace = useTranslationWorkspace({
|
|
4005
|
+
schema,
|
|
4006
|
+
sourceLocale,
|
|
4007
|
+
targetLocale,
|
|
4008
|
+
...translationAdapter === void 0 ? {} : { translationAdapter },
|
|
4009
|
+
readOnly,
|
|
4010
|
+
...onChange === void 0 ? {} : { onChange },
|
|
4011
|
+
...onTranslationChange === void 0 ? {} : { onTranslationChange },
|
|
4012
|
+
...onTranslationReport === void 0 ? {} : { onTranslationReport },
|
|
4013
|
+
...onTranslationError === void 0 ? {} : { onTranslationError },
|
|
4014
|
+
...signal === void 0 ? {} : { signal }
|
|
4015
|
+
});
|
|
4016
|
+
const [report, setReport] = useState5();
|
|
4017
|
+
const items = useMemo5(
|
|
4018
|
+
() => workspace.slots.map((slot) => comparisonItem(schema, slot, sourceLocale, translationAdapter !== void 0)),
|
|
4019
|
+
[schema, sourceLocale, translationAdapter, workspace.slots]
|
|
4020
|
+
);
|
|
4021
|
+
const itemByPath = useMemo5(() => new Map(workspace.slots.map((slot) => [slot.path, slot])), [workspace.slots]);
|
|
4022
|
+
const updateTranslation = useCallback4(
|
|
4023
|
+
(path, text) => {
|
|
4024
|
+
const slot = itemByPath.get(path);
|
|
4025
|
+
if (slot !== void 0) workspace.setTranslation(slot, text);
|
|
4026
|
+
},
|
|
4027
|
+
[itemByPath, workspace]
|
|
4028
|
+
);
|
|
4029
|
+
const translateSingle = useCallback4(
|
|
4030
|
+
async (path) => {
|
|
4031
|
+
const slot = itemByPath.get(path);
|
|
4032
|
+
if (slot === void 0) return;
|
|
4033
|
+
const result = await workspace.translateSlot(slot);
|
|
4034
|
+
if (!result.success) throw new Error(result.error?.type ?? "Translation failed.");
|
|
4035
|
+
},
|
|
4036
|
+
[itemByPath, workspace]
|
|
4037
|
+
);
|
|
4038
|
+
const translateAll = useCallback4(
|
|
4039
|
+
async (options) => {
|
|
4040
|
+
const result = await workspace.translateAll(options);
|
|
4041
|
+
if (result.report !== void 0) {
|
|
4042
|
+
setReport(result.report);
|
|
4043
|
+
return result.report;
|
|
4044
|
+
}
|
|
4045
|
+
throw new Error(result.error?.type ?? "Translation failed.");
|
|
4046
|
+
},
|
|
4047
|
+
[workspace]
|
|
4048
|
+
);
|
|
4049
|
+
return {
|
|
4050
|
+
sourceLocale: workspace.sourceLocale,
|
|
4051
|
+
targetLocale: workspace.targetLocale,
|
|
4052
|
+
items,
|
|
4053
|
+
summary: summaryFor(items),
|
|
4054
|
+
isTranslating: workspace.isTranslating,
|
|
4055
|
+
updateTranslation,
|
|
4056
|
+
translateSingle,
|
|
4057
|
+
translateAll,
|
|
4058
|
+
cancelTranslation: workspace.cancelTranslation,
|
|
4059
|
+
...workspace.progress === void 0 ? {} : { progress: workspace.progress },
|
|
4060
|
+
...workspace.error === void 0 ? {} : { error: workspace.error },
|
|
4061
|
+
...report === void 0 ? {} : { report }
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
|
|
3840
4065
|
// src/receipt.ts
|
|
3841
|
-
import { useEffect as useEffect2, useMemo as
|
|
4066
|
+
import { useEffect as useEffect2, useMemo as useMemo6, useState as useState6 } from "react";
|
|
3842
4067
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3843
4068
|
return `${formId}:v${formVersion}`;
|
|
3844
4069
|
}
|
|
@@ -3909,14 +4134,14 @@ function createLocalStorageSubmissionReceiptStore(options = {}) {
|
|
|
3909
4134
|
}
|
|
3910
4135
|
function useSubmissionReceipts(store, queries) {
|
|
3911
4136
|
const querySignature = JSON.stringify(queries.map(({ formId, formVersion }) => [formId, formVersion]));
|
|
3912
|
-
const stableQueries =
|
|
4137
|
+
const stableQueries = useMemo6(() => {
|
|
3913
4138
|
const parsed = JSON.parse(querySignature);
|
|
3914
4139
|
if (!Array.isArray(parsed)) return [];
|
|
3915
4140
|
return parsed.flatMap(
|
|
3916
4141
|
(entry) => Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number" && Number.isSafeInteger(entry[1]) ? [{ formId: entry[0], formVersion: entry[1] }] : []
|
|
3917
4142
|
);
|
|
3918
4143
|
}, [querySignature]);
|
|
3919
|
-
const [state, setState] =
|
|
4144
|
+
const [state, setState] = useState6({
|
|
3920
4145
|
receipts: /* @__PURE__ */ new Map(),
|
|
3921
4146
|
isLoading: stableQueries.length > 0,
|
|
3922
4147
|
error: null
|
|
@@ -3956,18 +4181,20 @@ function useSubmissionReceipts(store, queries) {
|
|
|
3956
4181
|
|
|
3957
4182
|
// src/renderer.tsx
|
|
3958
4183
|
import {
|
|
4184
|
+
FormSubmissionError as FormSubmissionError2,
|
|
4185
|
+
isFormSubmissionSerializedError as isFormSubmissionSerializedError2,
|
|
3959
4186
|
selectVisibleAnswers as selectVisibleAnswers2,
|
|
3960
4187
|
validateAnswers as validateAnswers2
|
|
3961
4188
|
} from "@form-engine-ts/core";
|
|
3962
4189
|
import {
|
|
3963
4190
|
Fragment as Fragment2,
|
|
3964
|
-
useCallback as
|
|
4191
|
+
useCallback as useCallback5,
|
|
3965
4192
|
useContext as useContext4,
|
|
3966
4193
|
useEffect as useEffect3,
|
|
3967
4194
|
useId,
|
|
3968
|
-
useMemo as
|
|
4195
|
+
useMemo as useMemo7,
|
|
3969
4196
|
useRef as useRef3,
|
|
3970
|
-
useState as
|
|
4197
|
+
useState as useState7
|
|
3971
4198
|
} from "react";
|
|
3972
4199
|
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
3973
4200
|
var isChoiceFieldType = (type) => type === "radio" || type === "checkbox" || type === "multi-select" || type === "select";
|
|
@@ -4484,29 +4711,29 @@ function ContextFormRenderer({
|
|
|
4484
4711
|
const prefix = useId().replace(/:/g, "");
|
|
4485
4712
|
const formRef = useRef3(null);
|
|
4486
4713
|
const loadedDraftKey = useRef3(null);
|
|
4487
|
-
const [draftRestored, setDraftRestored] =
|
|
4488
|
-
const [currentPageIndex, setCurrentPageIndex] =
|
|
4489
|
-
const [focusFieldId, setFocusFieldId] =
|
|
4490
|
-
const [confirmation, setConfirmation] =
|
|
4491
|
-
const [guardMessage, setGuardMessage] =
|
|
4492
|
-
const [guardsPending, setGuardsPending] =
|
|
4493
|
-
const [receipt, setReceipt] =
|
|
4494
|
-
const [completionData, setCompletionData] =
|
|
4495
|
-
const [receiptLoaded, setReceiptLoaded] =
|
|
4714
|
+
const [draftRestored, setDraftRestored] = useState7(false);
|
|
4715
|
+
const [currentPageIndex, setCurrentPageIndex] = useState7(0);
|
|
4716
|
+
const [focusFieldId, setFocusFieldId] = useState7(null);
|
|
4717
|
+
const [confirmation, setConfirmation] = useState7(null);
|
|
4718
|
+
const [guardMessage, setGuardMessage] = useState7(null);
|
|
4719
|
+
const [guardsPending, setGuardsPending] = useState7(false);
|
|
4720
|
+
const [receipt, setReceipt] = useState7(null);
|
|
4721
|
+
const [completionData, setCompletionData] = useState7(null);
|
|
4722
|
+
const [receiptLoaded, setReceiptLoaded] = useState7(receiptStore === void 0);
|
|
4496
4723
|
const rendererSubmissionInFlight = useRef3(false);
|
|
4497
4724
|
const fallbackAttemptId = useRef3(null);
|
|
4498
4725
|
const completionRef = useRef3(null);
|
|
4499
4726
|
const confirmationRef = useRef3(null);
|
|
4500
4727
|
const pages = form.schema.pages;
|
|
4501
|
-
const visiblePageIndexes =
|
|
4728
|
+
const visiblePageIndexes = useMemo7(
|
|
4502
4729
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
4503
4730
|
[form.pageVisibility, pages]
|
|
4504
4731
|
);
|
|
4505
4732
|
const activePage = pages?.[currentPageIndex];
|
|
4506
4733
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
4507
4734
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
4508
|
-
const visibleValues =
|
|
4509
|
-
const visibleItems =
|
|
4735
|
+
const visibleValues = useMemo7(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
|
|
4736
|
+
const visibleItems = useMemo7(
|
|
4510
4737
|
() => buildSubmittedItems(form.schema, form.values, form.visibility, (key) => form.translate(key), false),
|
|
4511
4738
|
[form.schema, form.translate, form.values, form.visibility]
|
|
4512
4739
|
);
|
|
@@ -4515,7 +4742,7 @@ function ContextFormRenderer({
|
|
|
4515
4742
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
4516
4743
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
4517
4744
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
4518
|
-
const resolveMessage =
|
|
4745
|
+
const resolveMessage = useCallback5(
|
|
4519
4746
|
(key, fallback) => {
|
|
4520
4747
|
const providerDefault = isProviderValue ? i18n.translator(`renderer.${key}`) : void 0;
|
|
4521
4748
|
const defaultText = fallback ?? (providerDefault === "" ? void 0 : providerDefault) ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
@@ -4524,11 +4751,11 @@ function ContextFormRenderer({
|
|
|
4524
4751
|
},
|
|
4525
4752
|
[form.locale, i18n, isProviderValue, messageResolver, messages]
|
|
4526
4753
|
);
|
|
4527
|
-
const fieldTranslate =
|
|
4754
|
+
const fieldTranslate = useCallback5(
|
|
4528
4755
|
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
4529
4756
|
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
4530
4757
|
);
|
|
4531
|
-
const focusSubmitButton =
|
|
4758
|
+
const focusSubmitButton = useCallback5(() => {
|
|
4532
4759
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
4533
4760
|
button?.focus();
|
|
4534
4761
|
}, []);
|
|
@@ -4739,8 +4966,9 @@ function ContextFormRenderer({
|
|
|
4739
4966
|
return result;
|
|
4740
4967
|
}
|
|
4741
4968
|
if (result.status === "error") {
|
|
4742
|
-
|
|
4743
|
-
|
|
4969
|
+
const payload = result.error instanceof FormSubmissionError2 ? result.error.payload : isFormSubmissionSerializedError2(result.error) ? result.error : void 0;
|
|
4970
|
+
if (payload !== void 0) {
|
|
4971
|
+
const fieldErrors = payload.fieldErrors ?? {};
|
|
4744
4972
|
form.setServerErrors?.(fieldErrors);
|
|
4745
4973
|
const firstServerFieldId = form.schema.fields.find((field) => Object.hasOwn(fieldErrors, field.id))?.id ?? Object.keys(fieldErrors)[0];
|
|
4746
4974
|
if (firstServerFieldId !== void 0) {
|
|
@@ -4748,6 +4976,9 @@ function ContextFormRenderer({
|
|
|
4748
4976
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
4749
4977
|
focusFirstIssue(firstServerFieldId);
|
|
4750
4978
|
}
|
|
4979
|
+
if (payload.piiFindings !== void 0 && payload.piiFindings.length > 0) {
|
|
4980
|
+
setConfirmation({ findings: payload.piiFindings, generic: false });
|
|
4981
|
+
}
|
|
4751
4982
|
}
|
|
4752
4983
|
return result;
|
|
4753
4984
|
}
|
|
@@ -5049,8 +5280,8 @@ function ContextFormRenderer({
|
|
|
5049
5280
|
] }),
|
|
5050
5281
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
5051
5282
|
form.submitStatus === "success" ? completionRegion : null,
|
|
5052
|
-
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof
|
|
5053
|
-
form.submitError.payload.
|
|
5283
|
+
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof FormSubmissionError2 && (form.submitError.payload.formErrors?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
5284
|
+
form.submitError.payload.formErrors?.map((message) => /* @__PURE__ */ jsx4("div", { children: message }, message)),
|
|
5054
5285
|
/* @__PURE__ */ jsx4("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
5055
5286
|
] }) : /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
5056
5287
|
errorMessageKey === void 0 ? resolveMessage("serverErrorSummary") : form.translate(errorMessageKey),
|
|
@@ -5133,6 +5364,9 @@ function FormRenderer(props) {
|
|
|
5133
5364
|
}
|
|
5134
5365
|
);
|
|
5135
5366
|
}
|
|
5367
|
+
|
|
5368
|
+
// src/types.ts
|
|
5369
|
+
import { FormSubmissionError as FormSubmissionError3 } from "@form-engine-ts/core";
|
|
5136
5370
|
export {
|
|
5137
5371
|
BUILDER_TRANSLATION_ALIASES,
|
|
5138
5372
|
BUILDER_TRANSLATION_KEYS,
|
|
@@ -5141,7 +5375,7 @@ export {
|
|
|
5141
5375
|
FormEngineI18nProvider,
|
|
5142
5376
|
FormProvider,
|
|
5143
5377
|
FormRenderer,
|
|
5144
|
-
FormSubmissionError,
|
|
5378
|
+
FormSubmissionError3 as FormSubmissionError,
|
|
5145
5379
|
createLocalStorageSubmissionAttemptStore,
|
|
5146
5380
|
createLocalStorageSubmissionReceiptStore,
|
|
5147
5381
|
isTranslationUnresolved,
|
|
@@ -5156,6 +5390,7 @@ export {
|
|
|
5156
5390
|
useFormBuilder,
|
|
5157
5391
|
useFormEngineI18n,
|
|
5158
5392
|
useSubmissionReceipts,
|
|
5393
|
+
useTranslationComparison,
|
|
5159
5394
|
useTranslationWorkspace,
|
|
5160
5395
|
validateLocalePipeline
|
|
5161
5396
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@form-engine-ts/core": "6.
|
|
46
|
-
"@form-engine-ts/privacy": "6.
|
|
45
|
+
"@form-engine-ts/core": "6.2.0",
|
|
46
|
+
"@form-engine-ts/privacy": "6.2.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|