@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/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 { createFormEngineTranslator } from "@form-engine-ts/core";
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
- cause.formError ?? (cause instanceof Error ? cause.message : "Form submission failed."),
3207
- cause
3208
- ) : cause instanceof Error ? cause : new Error(String(cause));
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(slot.sourceText, slot.locale, sourceLocale);
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 useMemo5, useState as useState5 } from "react";
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 = useMemo5(() => {
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] = useState5({
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 useCallback4,
4191
+ useCallback as useCallback5,
3965
4192
  useContext as useContext4,
3966
4193
  useEffect as useEffect3,
3967
4194
  useId,
3968
- useMemo as useMemo6,
4195
+ useMemo as useMemo7,
3969
4196
  useRef as useRef3,
3970
- useState as useState6
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] = useState6(false);
4488
- const [currentPageIndex, setCurrentPageIndex] = useState6(0);
4489
- const [focusFieldId, setFocusFieldId] = useState6(null);
4490
- const [confirmation, setConfirmation] = useState6(null);
4491
- const [guardMessage, setGuardMessage] = useState6(null);
4492
- const [guardsPending, setGuardsPending] = useState6(false);
4493
- const [receipt, setReceipt] = useState6(null);
4494
- const [completionData, setCompletionData] = useState6(null);
4495
- const [receiptLoaded, setReceiptLoaded] = useState6(receiptStore === void 0);
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 = useMemo6(
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 = useMemo6(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
4509
- const visibleItems = useMemo6(
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 = useCallback4(
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 = useCallback4(
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 = useCallback4(() => {
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
- if (result.error instanceof FormSubmissionError) {
4743
- const fieldErrors = result.error.payload.fieldErrors ?? {};
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 FormSubmissionError && form.submitError.payload.formError !== void 0 ? /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
5053
- form.submitError.payload.formError,
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.0.0",
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.0.0",
46
- "@form-engine-ts/privacy": "6.0.0"
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",