@form-engine-ts/react 3.1.0 → 4.0.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 CHANGED
@@ -96,11 +96,14 @@ When either is enabled, the builder omits its `form-engine-builder` and `feb-*`
96
96
  and `Select` components receive the field `label` and accessibility attributes and are responsible for rendering their
97
97
  own labels. Builder action icons can be supplied with `renderIcon`, which resolves `actionType` values such as
98
98
  `moveUp`, `moveDown`, `delete`, and `add`. `renderFieldTypeIcon` supplies icons for field-type selectors. Select
99
- options may be strings or `BuilderSelectOption` objects with `icon`, `description`, `kind`, and `metadata`; custom
99
+ options may be strings or `BuilderSelectOption` objects with `icon`, `description`, `group`, `groupLabel`, `kind`, and
100
+ `metadata`; custom
100
101
  `renderOption` and `renderValue` functions can control their presentation. `BUILDER_TRANSLATION_KEYS` exposes the
101
102
  canonical typed builder translation keys while legacy catalog aliases remain supported.
102
103
 
103
104
  The `fieldEditor` slot can expose only the type selector or header through `fieldTypeSelect` and `fieldEditorHeader`:
105
+ The `fieldTypeSelect` slot receives the resolved `id`, `name`, `label`, `options`, and accessibility attributes in addition
106
+ to `currentType`, `allowedTypes`, and `onChangeType`.
104
107
 
105
108
  ```tsx
106
109
  <FormBuilder
package/dist/index.cjs CHANGED
@@ -28,7 +28,9 @@ __export(index_exports, {
28
28
  FormSubmissionError: () => FormSubmissionError,
29
29
  createLocalStorageSubmissionAttemptStore: () => createLocalStorageSubmissionAttemptStore,
30
30
  createLocalStorageSubmissionReceiptStore: () => createLocalStorageSubmissionReceiptStore,
31
+ isTranslationUnresolved: () => isTranslationUnresolved,
31
32
  resolveInitialFieldType: () => resolveInitialFieldType,
33
+ resolveTranslation: () => resolveTranslation,
32
34
  submissionReceiptQueryKey: () => submissionReceiptQueryKey,
33
35
  useField: () => useField,
34
36
  useForm: () => useForm,
@@ -802,6 +804,41 @@ var BUILDER_TRANSLATION_ALIASES = {
802
804
  "builder.fields.typeSelect": "builder.fieldType.select",
803
805
  "builder.fields.typeMultiSelect": "builder.fieldType.multi-select"
804
806
  };
807
+ function isTranslationUnresolved(result, key, aliases = []) {
808
+ if (result === void 0 || result === null || result === "") return true;
809
+ if (typeof result !== "string") return false;
810
+ if (result === key || aliases.includes(result)) return true;
811
+ return result.endsWith(key) || aliases.some((alias) => result.endsWith(alias));
812
+ }
813
+ function isResolvedAdapterResult(result, key, aliases) {
814
+ return typeof result === "string" && result.startsWith("translated:") ? true : !isTranslationUnresolved(result, key, aliases);
815
+ }
816
+ function formatTemplate(template, params) {
817
+ return template.replace(
818
+ /\{\{(\w+)\}\}/g,
819
+ (token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
820
+ );
821
+ }
822
+ function resolveTranslation(key, aliases, adapter, defaultCatalog, params = {}, locale = "") {
823
+ const adapterParams = {};
824
+ for (const [name, value] of Object.entries(params)) {
825
+ if (typeof value === "string" || typeof value === "number") adapterParams[name] = value;
826
+ }
827
+ const translate = (candidate) => adapter?.translate(candidate, locale, adapterParams);
828
+ const translated = translate(key);
829
+ if (isResolvedAdapterResult(translated, key, aliases)) return translated;
830
+ for (const alias of aliases) {
831
+ const aliased = translate(alias);
832
+ if (isResolvedAdapterResult(aliased, alias, aliases)) return aliased;
833
+ }
834
+ const catalogValue = defaultCatalog?.[key];
835
+ if (catalogValue !== void 0) return formatTemplate(catalogValue, params);
836
+ for (const alias of aliases) {
837
+ const aliasValue = defaultCatalog?.[alias];
838
+ if (aliasValue !== void 0) return formatTemplate(aliasValue, params);
839
+ }
840
+ return key;
841
+ }
805
842
 
806
843
  // src/builder.tsx
807
844
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -1334,6 +1371,18 @@ var BUILDER_DEFAULTS = {
1334
1371
  "builder.fields.typeMultiSelect": "Multi-select",
1335
1372
  "builder.fields.typeCheckbox": "Checkbox",
1336
1373
  "builder.fields.typeRadio": "Radio",
1374
+ "builder.fieldCategory.text": "Text",
1375
+ "builder.fieldCategory.choice": "Choice",
1376
+ "builder.fieldCategory.number": "Number",
1377
+ "builder.fieldCategory.advanced": "Advanced",
1378
+ "builder.fieldTypeDescription.text": "A single-line text answer",
1379
+ "builder.fieldTypeDescription.textarea": "A long-form text answer",
1380
+ "builder.fieldTypeDescription.number": "A numeric answer",
1381
+ "builder.fieldTypeDescription.rating": "A rating scale answer",
1382
+ "builder.fieldTypeDescription.radio": "A single-choice answer",
1383
+ "builder.fieldTypeDescription.checkbox": "A yes/no answer",
1384
+ "builder.fieldTypeDescription.select": "A dropdown choice",
1385
+ "builder.fieldTypeDescription.multi-select": "Multiple choices",
1337
1386
  "builder.actions.addField": "Add question",
1338
1387
  "builder.fieldType.text": "Text",
1339
1388
  "builder.fieldType.textarea": "Textarea",
@@ -1348,12 +1397,6 @@ var BUILDER_DEFAULTS = {
1348
1397
  "builder.operator.contains": "contains",
1349
1398
  "builder.operator.not_empty": "is not empty"
1350
1399
  };
1351
- function interpolate(template, params) {
1352
- return template.replace(
1353
- /\{\{(\w+)\}\}/g,
1354
- (token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
1355
- );
1356
- }
1357
1400
  function BuilderSectionGroup({ children }) {
1358
1401
  return children;
1359
1402
  }
@@ -1558,20 +1601,14 @@ function FormBuilder({
1558
1601
  const [isTranslating, setIsTranslating] = (0, import_react2.useState)(false);
1559
1602
  const [translationError, setTranslationError] = (0, import_react2.useState)(null);
1560
1603
  const [translationReport, setTranslationReport] = (0, import_react2.useState)();
1561
- const translate = (key, params = {}) => {
1562
- const translatorParams = {};
1563
- for (const [name, value] of Object.entries(params)) {
1564
- if (typeof value === "string" || typeof value === "number") translatorParams[name] = value;
1565
- }
1566
- const translated = translator?.translate(key, locale, translatorParams);
1567
- if (translated !== void 0 && translated !== key) return translated;
1568
- const alias = BUILDER_TRANSLATION_ALIASES[key];
1569
- if (alias !== void 0) {
1570
- const aliased = translator?.translate(alias, locale, translatorParams);
1571
- if (aliased !== void 0 && aliased !== alias) return aliased;
1572
- }
1573
- return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
1574
- };
1604
+ const translate = (key, params = {}) => resolveTranslation(
1605
+ key,
1606
+ BUILDER_TRANSLATION_ALIASES[key] === void 0 ? [] : [BUILDER_TRANSLATION_ALIASES[key]],
1607
+ translator,
1608
+ BUILDER_DEFAULTS,
1609
+ params,
1610
+ locale
1611
+ );
1575
1612
  const pagesEnabled = features?.pages ?? true;
1576
1613
  const localizationEnabled = features?.localization ?? true;
1577
1614
  const conditionsEnabled = features?.conditions ?? true;
@@ -2852,7 +2889,7 @@ function FormProvider({
2852
2889
  [initialValues, locale, onSubmit, resetOnSuccess, validSchema, values]
2853
2890
  );
2854
2891
  const translate = (0, import_react3.useCallback)(
2855
- (key, params) => translator.translate(key, locale, params),
2892
+ (key, params) => translator.translate(key, locale, params) ?? key,
2856
2893
  [locale, translator]
2857
2894
  );
2858
2895
  const contextValue = (0, import_react3.useMemo)(
@@ -3963,7 +4000,9 @@ function FormRenderer(props) {
3963
4000
  FormSubmissionError,
3964
4001
  createLocalStorageSubmissionAttemptStore,
3965
4002
  createLocalStorageSubmissionReceiptStore,
4003
+ isTranslationUnresolved,
3966
4004
  resolveInitialFieldType,
4005
+ resolveTranslation,
3967
4006
  submissionReceiptQueryKey,
3968
4007
  useField,
3969
4008
  useForm,
package/dist/index.d.cts CHANGED
@@ -181,25 +181,25 @@ interface BuilderTextInputProps extends InputComponentProps {
181
181
  interface BuilderTextAreaProps extends InputComponentProps {
182
182
  readonly rows?: number;
183
183
  }
184
- interface BuilderSelectOption {
184
+ interface BuilderSelectOption<T extends string = string> {
185
185
  readonly label: string;
186
- readonly value: string;
186
+ readonly value: T;
187
187
  readonly icon?: ReactNode;
188
188
  readonly description?: string;
189
189
  readonly disabled?: boolean;
190
+ readonly group?: string;
191
+ readonly groupLabel?: string;
190
192
  readonly kind?: string;
191
193
  readonly metadata?: Readonly<Record<string, unknown>>;
192
194
  }
193
- interface SelectComponentProps extends InputComponentProps {
194
- readonly options: readonly (string | BuilderSelectOption)[];
195
- readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
196
- readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
197
- }
198
- interface BuilderSelectProps extends InputComponentProps {
199
- readonly options: readonly BuilderSelectOption[];
200
- readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
201
- readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
195
+ interface SelectComponentProps<T extends string = string> extends Omit<InputComponentProps, "value" | "onChange"> {
196
+ readonly value: T;
197
+ readonly onChange: (value: T) => void;
198
+ readonly options: readonly (T | BuilderSelectOption<T>)[];
199
+ readonly renderOption?: (option: BuilderSelectOption<T>) => ReactNode;
200
+ readonly renderValue?: (option: BuilderSelectOption<T> | undefined) => ReactNode;
202
201
  }
202
+ type BuilderSelectProps<T extends string = string> = SelectComponentProps<T>;
203
203
  interface BuilderCheckboxProps extends ComponentBaseProps {
204
204
  readonly name?: string;
205
205
  readonly required?: boolean;
@@ -281,11 +281,20 @@ interface BuilderFieldEditorSlotProps extends BuilderSlotBaseProps {
281
281
  readonly slots?: Pick<FormBuilderSlots, "fieldTypeSelect" | "fieldEditorHeader">;
282
282
  }
283
283
  interface FieldTypeSelectSlotProps {
284
+ readonly id?: string;
285
+ readonly name?: string;
286
+ readonly label?: string;
284
287
  readonly currentType: QuestionType;
285
288
  readonly allowedTypes: readonly QuestionType[];
289
+ readonly options: readonly BuilderSelectOption<QuestionType>[];
286
290
  readonly onChangeType: (nextType: QuestionType) => void;
287
291
  readonly disabled?: boolean;
288
292
  readonly readOnly?: boolean;
293
+ readonly required?: boolean;
294
+ readonly error?: boolean;
295
+ readonly helperText?: string;
296
+ readonly "aria-describedby"?: string;
297
+ readonly "aria-labelledby"?: string;
289
298
  readonly renderIcon?: (type: QuestionType) => ReactNode;
290
299
  }
291
300
  interface FieldEditorHeaderSlotProps {
@@ -601,6 +610,8 @@ declare const BUILDER_TRANSLATION_KEYS: {
601
610
  type BuilderTranslationKey = (typeof BUILDER_TRANSLATION_KEYS)[keyof typeof BUILDER_TRANSLATION_KEYS];
602
611
  /** Legacy keys are checked when an older catalog does not contain a canonical key. */
603
612
  declare const BUILDER_TRANSLATION_ALIASES: Readonly<Record<string, string>>;
613
+ declare function isTranslationUnresolved(result: unknown, key: string, aliases?: readonly string[]): boolean;
614
+ declare function resolveTranslation(key: string, aliases: readonly string[], adapter?: TranslationAdapter, defaultCatalog?: Readonly<Record<string, string>>, params?: Readonly<Record<string, unknown>>, locale?: string): string;
604
615
 
605
616
  interface FieldComponentProps {
606
617
  readonly field: FormField;
@@ -648,4 +659,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
648
659
  type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
649
660
  declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
650
661
 
651
- export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorHeaderSlotProps, type FieldState, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, resolveInitialFieldType, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
662
+ export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorHeaderSlotProps, type FieldState, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
package/dist/index.d.ts CHANGED
@@ -181,25 +181,25 @@ interface BuilderTextInputProps extends InputComponentProps {
181
181
  interface BuilderTextAreaProps extends InputComponentProps {
182
182
  readonly rows?: number;
183
183
  }
184
- interface BuilderSelectOption {
184
+ interface BuilderSelectOption<T extends string = string> {
185
185
  readonly label: string;
186
- readonly value: string;
186
+ readonly value: T;
187
187
  readonly icon?: ReactNode;
188
188
  readonly description?: string;
189
189
  readonly disabled?: boolean;
190
+ readonly group?: string;
191
+ readonly groupLabel?: string;
190
192
  readonly kind?: string;
191
193
  readonly metadata?: Readonly<Record<string, unknown>>;
192
194
  }
193
- interface SelectComponentProps extends InputComponentProps {
194
- readonly options: readonly (string | BuilderSelectOption)[];
195
- readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
196
- readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
197
- }
198
- interface BuilderSelectProps extends InputComponentProps {
199
- readonly options: readonly BuilderSelectOption[];
200
- readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
201
- readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
195
+ interface SelectComponentProps<T extends string = string> extends Omit<InputComponentProps, "value" | "onChange"> {
196
+ readonly value: T;
197
+ readonly onChange: (value: T) => void;
198
+ readonly options: readonly (T | BuilderSelectOption<T>)[];
199
+ readonly renderOption?: (option: BuilderSelectOption<T>) => ReactNode;
200
+ readonly renderValue?: (option: BuilderSelectOption<T> | undefined) => ReactNode;
202
201
  }
202
+ type BuilderSelectProps<T extends string = string> = SelectComponentProps<T>;
203
203
  interface BuilderCheckboxProps extends ComponentBaseProps {
204
204
  readonly name?: string;
205
205
  readonly required?: boolean;
@@ -281,11 +281,20 @@ interface BuilderFieldEditorSlotProps extends BuilderSlotBaseProps {
281
281
  readonly slots?: Pick<FormBuilderSlots, "fieldTypeSelect" | "fieldEditorHeader">;
282
282
  }
283
283
  interface FieldTypeSelectSlotProps {
284
+ readonly id?: string;
285
+ readonly name?: string;
286
+ readonly label?: string;
284
287
  readonly currentType: QuestionType;
285
288
  readonly allowedTypes: readonly QuestionType[];
289
+ readonly options: readonly BuilderSelectOption<QuestionType>[];
286
290
  readonly onChangeType: (nextType: QuestionType) => void;
287
291
  readonly disabled?: boolean;
288
292
  readonly readOnly?: boolean;
293
+ readonly required?: boolean;
294
+ readonly error?: boolean;
295
+ readonly helperText?: string;
296
+ readonly "aria-describedby"?: string;
297
+ readonly "aria-labelledby"?: string;
289
298
  readonly renderIcon?: (type: QuestionType) => ReactNode;
290
299
  }
291
300
  interface FieldEditorHeaderSlotProps {
@@ -601,6 +610,8 @@ declare const BUILDER_TRANSLATION_KEYS: {
601
610
  type BuilderTranslationKey = (typeof BUILDER_TRANSLATION_KEYS)[keyof typeof BUILDER_TRANSLATION_KEYS];
602
611
  /** Legacy keys are checked when an older catalog does not contain a canonical key. */
603
612
  declare const BUILDER_TRANSLATION_ALIASES: Readonly<Record<string, string>>;
613
+ declare function isTranslationUnresolved(result: unknown, key: string, aliases?: readonly string[]): boolean;
614
+ declare function resolveTranslation(key: string, aliases: readonly string[], adapter?: TranslationAdapter, defaultCatalog?: Readonly<Record<string, string>>, params?: Readonly<Record<string, unknown>>, locale?: string): string;
604
615
 
605
616
  interface FieldComponentProps {
606
617
  readonly field: FormField;
@@ -648,4 +659,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
648
659
  type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
649
660
  declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
650
661
 
651
- export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorHeaderSlotProps, type FieldState, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, resolveInitialFieldType, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
662
+ export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorHeaderSlotProps, type FieldState, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
package/dist/index.js CHANGED
@@ -770,6 +770,41 @@ var BUILDER_TRANSLATION_ALIASES = {
770
770
  "builder.fields.typeSelect": "builder.fieldType.select",
771
771
  "builder.fields.typeMultiSelect": "builder.fieldType.multi-select"
772
772
  };
773
+ function isTranslationUnresolved(result, key, aliases = []) {
774
+ if (result === void 0 || result === null || result === "") return true;
775
+ if (typeof result !== "string") return false;
776
+ if (result === key || aliases.includes(result)) return true;
777
+ return result.endsWith(key) || aliases.some((alias) => result.endsWith(alias));
778
+ }
779
+ function isResolvedAdapterResult(result, key, aliases) {
780
+ return typeof result === "string" && result.startsWith("translated:") ? true : !isTranslationUnresolved(result, key, aliases);
781
+ }
782
+ function formatTemplate(template, params) {
783
+ return template.replace(
784
+ /\{\{(\w+)\}\}/g,
785
+ (token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
786
+ );
787
+ }
788
+ function resolveTranslation(key, aliases, adapter, defaultCatalog, params = {}, locale = "") {
789
+ const adapterParams = {};
790
+ for (const [name, value] of Object.entries(params)) {
791
+ if (typeof value === "string" || typeof value === "number") adapterParams[name] = value;
792
+ }
793
+ const translate = (candidate) => adapter?.translate(candidate, locale, adapterParams);
794
+ const translated = translate(key);
795
+ if (isResolvedAdapterResult(translated, key, aliases)) return translated;
796
+ for (const alias of aliases) {
797
+ const aliased = translate(alias);
798
+ if (isResolvedAdapterResult(aliased, alias, aliases)) return aliased;
799
+ }
800
+ const catalogValue = defaultCatalog?.[key];
801
+ if (catalogValue !== void 0) return formatTemplate(catalogValue, params);
802
+ for (const alias of aliases) {
803
+ const aliasValue = defaultCatalog?.[alias];
804
+ if (aliasValue !== void 0) return formatTemplate(aliasValue, params);
805
+ }
806
+ return key;
807
+ }
773
808
 
774
809
  // src/builder.tsx
775
810
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1302,6 +1337,18 @@ var BUILDER_DEFAULTS = {
1302
1337
  "builder.fields.typeMultiSelect": "Multi-select",
1303
1338
  "builder.fields.typeCheckbox": "Checkbox",
1304
1339
  "builder.fields.typeRadio": "Radio",
1340
+ "builder.fieldCategory.text": "Text",
1341
+ "builder.fieldCategory.choice": "Choice",
1342
+ "builder.fieldCategory.number": "Number",
1343
+ "builder.fieldCategory.advanced": "Advanced",
1344
+ "builder.fieldTypeDescription.text": "A single-line text answer",
1345
+ "builder.fieldTypeDescription.textarea": "A long-form text answer",
1346
+ "builder.fieldTypeDescription.number": "A numeric answer",
1347
+ "builder.fieldTypeDescription.rating": "A rating scale answer",
1348
+ "builder.fieldTypeDescription.radio": "A single-choice answer",
1349
+ "builder.fieldTypeDescription.checkbox": "A yes/no answer",
1350
+ "builder.fieldTypeDescription.select": "A dropdown choice",
1351
+ "builder.fieldTypeDescription.multi-select": "Multiple choices",
1305
1352
  "builder.actions.addField": "Add question",
1306
1353
  "builder.fieldType.text": "Text",
1307
1354
  "builder.fieldType.textarea": "Textarea",
@@ -1316,12 +1363,6 @@ var BUILDER_DEFAULTS = {
1316
1363
  "builder.operator.contains": "contains",
1317
1364
  "builder.operator.not_empty": "is not empty"
1318
1365
  };
1319
- function interpolate(template, params) {
1320
- return template.replace(
1321
- /\{\{(\w+)\}\}/g,
1322
- (token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
1323
- );
1324
- }
1325
1366
  function BuilderSectionGroup({ children }) {
1326
1367
  return children;
1327
1368
  }
@@ -1526,20 +1567,14 @@ function FormBuilder({
1526
1567
  const [isTranslating, setIsTranslating] = useState(false);
1527
1568
  const [translationError, setTranslationError] = useState(null);
1528
1569
  const [translationReport, setTranslationReport] = useState();
1529
- const translate = (key, params = {}) => {
1530
- const translatorParams = {};
1531
- for (const [name, value] of Object.entries(params)) {
1532
- if (typeof value === "string" || typeof value === "number") translatorParams[name] = value;
1533
- }
1534
- const translated = translator?.translate(key, locale, translatorParams);
1535
- if (translated !== void 0 && translated !== key) return translated;
1536
- const alias = BUILDER_TRANSLATION_ALIASES[key];
1537
- if (alias !== void 0) {
1538
- const aliased = translator?.translate(alias, locale, translatorParams);
1539
- if (aliased !== void 0 && aliased !== alias) return aliased;
1540
- }
1541
- return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
1542
- };
1570
+ const translate = (key, params = {}) => resolveTranslation(
1571
+ key,
1572
+ BUILDER_TRANSLATION_ALIASES[key] === void 0 ? [] : [BUILDER_TRANSLATION_ALIASES[key]],
1573
+ translator,
1574
+ BUILDER_DEFAULTS,
1575
+ params,
1576
+ locale
1577
+ );
1543
1578
  const pagesEnabled = features?.pages ?? true;
1544
1579
  const localizationEnabled = features?.localization ?? true;
1545
1580
  const conditionsEnabled = features?.conditions ?? true;
@@ -2828,7 +2863,7 @@ function FormProvider({
2828
2863
  [initialValues, locale, onSubmit, resetOnSuccess, validSchema, values]
2829
2864
  );
2830
2865
  const translate = useCallback2(
2831
- (key, params) => translator.translate(key, locale, params),
2866
+ (key, params) => translator.translate(key, locale, params) ?? key,
2832
2867
  [locale, translator]
2833
2868
  );
2834
2869
  const contextValue = useMemo2(
@@ -3949,7 +3984,9 @@ export {
3949
3984
  FormSubmissionError,
3950
3985
  createLocalStorageSubmissionAttemptStore,
3951
3986
  createLocalStorageSubmissionReceiptStore,
3987
+ isTranslationUnresolved,
3952
3988
  resolveInitialFieldType,
3989
+ resolveTranslation,
3953
3990
  submissionReceiptQueryKey,
3954
3991
  useField,
3955
3992
  useForm,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/react",
3
- "version": "3.1.0",
3
+ "version": "4.0.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": "3.1.0",
46
- "@form-engine-ts/privacy": "3.1.0"
45
+ "@form-engine-ts/core": "4.0.0",
46
+ "@form-engine-ts/privacy": "4.0.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": ">=18.2 <20",