@form-engine-ts/react 3.0.0 → 3.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 CHANGED
@@ -95,7 +95,25 @@ translation lifecycle.
95
95
  When either is enabled, the builder omits its `form-engine-builder` and `feb-*` classes. Injected `TextInput`, `TextArea`,
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
- `moveUp`, `moveDown`, `delete`, and `add`.
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
100
+ `renderOption` and `renderValue` functions can control their presentation. `BUILDER_TRANSLATION_KEYS` exposes the
101
+ canonical typed builder translation keys while legacy catalog aliases remain supported.
102
+
103
+ The `fieldEditor` slot can expose only the type selector or header through `fieldTypeSelect` and `fieldEditorHeader`:
104
+
105
+ ```tsx
106
+ <FormBuilder
107
+ schema={schema}
108
+ onChange={setSchema}
109
+ slots={{
110
+ fieldTypeSelect: ({ currentType, onChangeType }) => (
111
+ <button type="button" onClick={() => onChangeType("textarea")}>{currentType}</button>
112
+ ),
113
+ fieldEditorHeader: ({ field }) => <h3>{field.title}</h3>
114
+ }}
115
+ />
116
+ ```
99
117
 
100
118
  ```tsx
101
119
  <FormBuilder
package/dist/index.cjs CHANGED
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ BUILDER_TRANSLATION_ALIASES: () => BUILDER_TRANSLATION_ALIASES,
24
+ BUILDER_TRANSLATION_KEYS: () => BUILDER_TRANSLATION_KEYS,
23
25
  FormBuilder: () => FormBuilder,
24
26
  FormProvider: () => FormProvider,
25
27
  FormRenderer: () => FormRenderer,
@@ -774,6 +776,33 @@ function useFormBuilder({
774
776
  };
775
777
  }
776
778
 
779
+ // src/i18n.ts
780
+ var BUILDER_TRANSLATION_KEYS = {
781
+ ADD_FIELD: "builder.actions.addField",
782
+ SELECT_FIELD_TYPE: "builder.fields.selectType",
783
+ SELECT_LOCALE_TO_ADD: "builder.localization.selectLocaleToAdd",
784
+ FIELD_TYPE_TEXT: "builder.fields.typeText",
785
+ FIELD_TYPE_TEXTAREA: "builder.fields.typeTextarea",
786
+ FIELD_TYPE_NUMBER: "builder.fields.typeNumber",
787
+ FIELD_TYPE_RATING: "builder.fields.typeRating",
788
+ FIELD_TYPE_RADIO: "builder.fields.typeRadio",
789
+ FIELD_TYPE_CHECKBOX: "builder.fields.typeCheckbox",
790
+ FIELD_TYPE_SELECT: "builder.fields.typeSelect",
791
+ FIELD_TYPE_MULTI_SELECT: "builder.fields.typeMultiSelect"
792
+ };
793
+ var BUILDER_TRANSLATION_ALIASES = {
794
+ "builder.actions.addField": "builder.addQuestion",
795
+ "builder.localization.selectLocaleToAdd": "builder.selectLocaleToAdd",
796
+ "builder.fields.typeText": "builder.fieldType.text",
797
+ "builder.fields.typeTextarea": "builder.fieldType.textarea",
798
+ "builder.fields.typeNumber": "builder.fieldType.number",
799
+ "builder.fields.typeRating": "builder.fieldType.rating",
800
+ "builder.fields.typeRadio": "builder.fieldType.radio",
801
+ "builder.fields.typeCheckbox": "builder.fieldType.checkbox",
802
+ "builder.fields.typeSelect": "builder.fieldType.select",
803
+ "builder.fields.typeMultiSelect": "builder.fieldType.multi-select"
804
+ };
805
+
777
806
  // src/builder.tsx
778
807
  var import_jsx_runtime = require("react/jsx-runtime");
779
808
  function DefaultButton({
@@ -844,6 +873,26 @@ function defaultIconFor(actionType) {
844
873
  return "\u6587";
845
874
  }
846
875
  }
876
+ function defaultFieldTypeIcon(type) {
877
+ switch (type) {
878
+ case "text":
879
+ return "T";
880
+ case "textarea":
881
+ return "\u2261";
882
+ case "number":
883
+ return "#";
884
+ case "rating":
885
+ return "\u2605";
886
+ case "select":
887
+ return "\u25BE";
888
+ case "multi-select":
889
+ return "\u2611";
890
+ case "checkbox":
891
+ return "\u25A1";
892
+ case "radio":
893
+ return "\u25C9";
894
+ }
895
+ }
847
896
  function DefaultTextInput({
848
897
  id,
849
898
  className,
@@ -956,8 +1005,15 @@ function DefaultSelect({
956
1005
  helperText,
957
1006
  value,
958
1007
  onChange,
959
- options
1008
+ onKeyDown,
1009
+ options,
1010
+ renderOption,
1011
+ renderValue
960
1012
  }) {
1013
+ const normalizedOptions = options.map(
1014
+ (option) => typeof option === "string" ? { value: option, label: option } : option
1015
+ );
1016
+ const selectedOption = normalizedOptions.find((option) => option.value === value);
961
1017
  const labelElement = label === void 0 ? null : id === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: id, children: label });
962
1018
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
963
1019
  labelElement,
@@ -975,9 +1031,11 @@ function DefaultSelect({
975
1031
  "aria-invalid": error === true ? true : void 0,
976
1032
  value,
977
1033
  onChange: (event) => onChange(event.currentTarget.value),
978
- children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
1034
+ onKeyDown,
1035
+ children: normalizedOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option.value, disabled: option.disabled, children: renderOption === void 0 ? option.label : renderOption(option) }, option.value))
979
1036
  }
980
1037
  ),
1038
+ renderValue === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("output", { children: renderValue(selectedOption) }),
981
1039
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { id: ariaDescribedBy, children: helperText })
982
1040
  ] });
983
1041
  }
@@ -1065,7 +1123,8 @@ var DEFAULT_COMPONENTS = {
1065
1123
  Section: DefaultSection,
1066
1124
  Fieldset: DefaultFieldset,
1067
1125
  ErrorMessage: DefaultErrorMessage,
1068
- renderIcon: defaultIconFor
1126
+ renderIcon: defaultIconFor,
1127
+ renderFieldTypeIcon: defaultFieldTypeIcon
1069
1128
  };
1070
1129
  var BuilderPrimitiveContext = (0, import_react2.createContext)({
1071
1130
  components: DEFAULT_COMPONENTS,
@@ -1266,6 +1325,16 @@ var BUILDER_DEFAULTS = {
1266
1325
  "builder.actions.close": "Close",
1267
1326
  "builder.actions.dragHandle": "Reorder",
1268
1327
  "builder.translationUnavailable": "Provide an async translation adapter to enable automatic translation.",
1328
+ "builder.fields.selectType": "Select field type",
1329
+ "builder.fields.typeText": "Text",
1330
+ "builder.fields.typeTextarea": "Textarea",
1331
+ "builder.fields.typeNumber": "Number",
1332
+ "builder.fields.typeRating": "Rating",
1333
+ "builder.fields.typeSelect": "Select",
1334
+ "builder.fields.typeMultiSelect": "Multi-select",
1335
+ "builder.fields.typeCheckbox": "Checkbox",
1336
+ "builder.fields.typeRadio": "Radio",
1337
+ "builder.actions.addField": "Add question",
1269
1338
  "builder.fieldType.text": "Text",
1270
1339
  "builder.fieldType.textarea": "Textarea",
1271
1340
  "builder.fieldType.number": "Number",
@@ -1302,7 +1371,7 @@ function OrderedBuilderSections({
1302
1371
  return groups.map((group, index) => ({ group, index, rank: ranks.get(sectionGroupName(group) ?? "questions") ?? order.length })).sort((left, right) => left.rank - right.rank || left.index - right.index).map(({ group }) => group);
1303
1372
  }
1304
1373
  function fieldTypeKey(type) {
1305
- return `builder.fieldType.${type}`;
1374
+ return import_core2.DEFAULT_FIELD_TYPE_DEFINITIONS.find((definition) => definition.type === type)?.labelKey ?? `builder.fieldType.${type}`;
1306
1375
  }
1307
1376
  function operatorKey(operator) {
1308
1377
  return `builder.operator.${operator}`;
@@ -1463,7 +1532,8 @@ function FormBuilder({
1463
1532
  const resolvedComponents = { ...DEFAULT_COMPONENTS, ...componentOverrides };
1464
1533
  const components = {
1465
1534
  ...GUARDED_COMPONENTS,
1466
- renderIcon: resolvedComponents.renderIcon
1535
+ renderIcon: resolvedComponents.renderIcon,
1536
+ renderFieldTypeIcon: resolvedComponents.renderFieldTypeIcon
1467
1537
  };
1468
1538
  const defaultStylesDisabled = disableDefaultStyles || unstyled;
1469
1539
  const builderClass = (value) => defaultStylesDisabled ? void 0 : value;
@@ -1494,6 +1564,12 @@ function FormBuilder({
1494
1564
  if (typeof value === "string" || typeof value === "number") translatorParams[name] = value;
1495
1565
  }
1496
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
+ }
1497
1573
  return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
1498
1574
  };
1499
1575
  const pagesEnabled = features?.pages ?? true;
@@ -2229,6 +2305,7 @@ function FormBuilder({
2229
2305
  index,
2230
2306
  currentLocale: editingLocale,
2231
2307
  translate,
2308
+ ...slots === void 0 ? {} : { slots },
2232
2309
  ...policy === void 0 ? {} : { policy },
2233
2310
  ...features === void 0 ? {} : { features },
2234
2311
  readOnly,
@@ -2600,7 +2677,7 @@ function FormBuilder({
2600
2677
  action: "addField",
2601
2678
  disabled: initialFieldType === null || maxFieldsReached,
2602
2679
  onClick: addField,
2603
- children: translate("builder.addQuestion")
2680
+ children: translate(BUILDER_TRANSLATION_KEYS.ADD_FIELD)
2604
2681
  }
2605
2682
  ) })
2606
2683
  ] }) })
@@ -3878,6 +3955,8 @@ function FormRenderer(props) {
3878
3955
  }
3879
3956
  // Annotate the CommonJS export names for ESM import in node:
3880
3957
  0 && (module.exports = {
3958
+ BUILDER_TRANSLATION_ALIASES,
3959
+ BUILDER_TRANSLATION_KEYS,
3881
3960
  FormBuilder,
3882
3961
  FormProvider,
3883
3962
  FormRenderer,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
+ import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, Question, FieldOption, TranslationReport, ValidationError, FormValues, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, ValidationIssue, AnswerValidationResult, FieldType } from '@form-engine-ts/core';
2
+ export { QuestionType } from '@form-engine-ts/core';
1
3
  import * as react from 'react';
2
4
  import { ReactNode, ComponentType, KeyboardEvent, MouseEvent } from 'react';
3
- import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, FieldOption, TranslationReport, ValidationError, FormValues, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, ValidationIssue, AnswerValidationResult, FieldType } from '@form-engine-ts/core';
4
5
  import { SensitiveDataFinding } from '@form-engine-ts/privacy';
5
6
 
6
7
  interface SubmissionAttempt {
@@ -180,13 +181,24 @@ interface BuilderTextInputProps extends InputComponentProps {
180
181
  interface BuilderTextAreaProps extends InputComponentProps {
181
182
  readonly rows?: number;
182
183
  }
183
- interface BuilderSelectOption {
184
+ interface BuilderSelectOption<T extends string = string> {
184
185
  readonly label: string;
185
- readonly value: string;
186
+ readonly value: T;
187
+ readonly icon?: ReactNode;
188
+ readonly description?: string;
186
189
  readonly disabled?: boolean;
190
+ readonly kind?: string;
191
+ readonly metadata?: Readonly<Record<string, unknown>>;
192
+ }
193
+ interface SelectComponentProps extends InputComponentProps {
194
+ readonly options: readonly (string | BuilderSelectOption)[];
195
+ readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
196
+ readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
187
197
  }
188
198
  interface BuilderSelectProps extends InputComponentProps {
189
199
  readonly options: readonly BuilderSelectOption[];
200
+ readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
201
+ readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
190
202
  }
191
203
  interface BuilderCheckboxProps extends ComponentBaseProps {
192
204
  readonly name?: string;
@@ -229,6 +241,7 @@ interface FormBuilderComponents {
229
241
  readonly message: string;
230
242
  }>;
231
243
  readonly renderIcon?: (actionType: BuilderActionIconType) => ReactNode;
244
+ readonly renderFieldTypeIcon?: (type: QuestionType) => ReactNode;
232
245
  }
233
246
  type FormBuilderActions = Omit<FormBuilderResult, "schema" | "validationIssues">;
234
247
  interface ManualTranslationTarget {
@@ -265,6 +278,21 @@ interface BuilderFieldEditorSlotProps extends BuilderSlotBaseProps {
265
278
  readonly localization?: boolean;
266
279
  readonly conditions?: boolean;
267
280
  };
281
+ readonly slots?: Pick<FormBuilderSlots, "fieldTypeSelect" | "fieldEditorHeader">;
282
+ }
283
+ interface FieldTypeSelectSlotProps {
284
+ readonly currentType: QuestionType;
285
+ readonly allowedTypes: readonly QuestionType[];
286
+ readonly onChangeType: (nextType: QuestionType) => void;
287
+ readonly disabled?: boolean;
288
+ readonly readOnly?: boolean;
289
+ readonly renderIcon?: (type: QuestionType) => ReactNode;
290
+ }
291
+ interface FieldEditorHeaderSlotProps {
292
+ readonly field: Question;
293
+ readonly index: number;
294
+ readonly totalFields: number;
295
+ readonly actions: FormBuilderActions;
268
296
  }
269
297
  interface BuilderOptionEditorSlotProps extends BuilderSlotBaseProps {
270
298
  readonly field: FormField & {
@@ -309,6 +337,8 @@ type FormBuilderSectionName = "basicSettings" | "completionMessage" | "questions
309
337
  interface FormBuilderSlots {
310
338
  readonly toolbar?: ComponentType<BuilderToolbarSlotProps>;
311
339
  readonly fieldEditor?: ComponentType<BuilderFieldEditorSlotProps>;
340
+ readonly fieldTypeSelect?: ComponentType<FieldTypeSelectSlotProps>;
341
+ readonly fieldEditorHeader?: ComponentType<FieldEditorHeaderSlotProps>;
312
342
  readonly optionEditor?: ComponentType<BuilderOptionEditorSlotProps>;
313
343
  readonly pages?: ComponentType<BuilderPagesSlotProps>;
314
344
  readonly localization?: ComponentType<BuilderLocalizationSlotProps>;
@@ -555,6 +585,23 @@ interface FieldState {
555
585
  }
556
586
  declare function useField(fieldId: string): FieldState;
557
587
 
588
+ declare const BUILDER_TRANSLATION_KEYS: {
589
+ readonly ADD_FIELD: "builder.actions.addField";
590
+ readonly SELECT_FIELD_TYPE: "builder.fields.selectType";
591
+ readonly SELECT_LOCALE_TO_ADD: "builder.localization.selectLocaleToAdd";
592
+ readonly FIELD_TYPE_TEXT: "builder.fields.typeText";
593
+ readonly FIELD_TYPE_TEXTAREA: "builder.fields.typeTextarea";
594
+ readonly FIELD_TYPE_NUMBER: "builder.fields.typeNumber";
595
+ readonly FIELD_TYPE_RATING: "builder.fields.typeRating";
596
+ readonly FIELD_TYPE_RADIO: "builder.fields.typeRadio";
597
+ readonly FIELD_TYPE_CHECKBOX: "builder.fields.typeCheckbox";
598
+ readonly FIELD_TYPE_SELECT: "builder.fields.typeSelect";
599
+ readonly FIELD_TYPE_MULTI_SELECT: "builder.fields.typeMultiSelect";
600
+ };
601
+ type BuilderTranslationKey = (typeof BUILDER_TRANSLATION_KEYS)[keyof typeof BUILDER_TRANSLATION_KEYS];
602
+ /** Legacy keys are checked when an older catalog does not contain a canonical key. */
603
+ declare const BUILDER_TRANSLATION_ALIASES: Readonly<Record<string, string>>;
604
+
558
605
  interface FieldComponentProps {
559
606
  readonly field: FormField;
560
607
  readonly value: FormValue;
@@ -601,4 +648,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
601
648
  type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
602
649
  declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
603
650
 
604
- export { 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 ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldState, 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 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
+ import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, Question, FieldOption, TranslationReport, ValidationError, FormValues, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, ValidationIssue, AnswerValidationResult, FieldType } from '@form-engine-ts/core';
2
+ export { QuestionType } from '@form-engine-ts/core';
1
3
  import * as react from 'react';
2
4
  import { ReactNode, ComponentType, KeyboardEvent, MouseEvent } from 'react';
3
- import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, FieldOption, TranslationReport, ValidationError, FormValues, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, ValidationIssue, AnswerValidationResult, FieldType } from '@form-engine-ts/core';
4
5
  import { SensitiveDataFinding } from '@form-engine-ts/privacy';
5
6
 
6
7
  interface SubmissionAttempt {
@@ -180,13 +181,24 @@ interface BuilderTextInputProps extends InputComponentProps {
180
181
  interface BuilderTextAreaProps extends InputComponentProps {
181
182
  readonly rows?: number;
182
183
  }
183
- interface BuilderSelectOption {
184
+ interface BuilderSelectOption<T extends string = string> {
184
185
  readonly label: string;
185
- readonly value: string;
186
+ readonly value: T;
187
+ readonly icon?: ReactNode;
188
+ readonly description?: string;
186
189
  readonly disabled?: boolean;
190
+ readonly kind?: string;
191
+ readonly metadata?: Readonly<Record<string, unknown>>;
192
+ }
193
+ interface SelectComponentProps extends InputComponentProps {
194
+ readonly options: readonly (string | BuilderSelectOption)[];
195
+ readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
196
+ readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
187
197
  }
188
198
  interface BuilderSelectProps extends InputComponentProps {
189
199
  readonly options: readonly BuilderSelectOption[];
200
+ readonly renderOption?: (option: BuilderSelectOption) => ReactNode;
201
+ readonly renderValue?: (option: BuilderSelectOption | undefined) => ReactNode;
190
202
  }
191
203
  interface BuilderCheckboxProps extends ComponentBaseProps {
192
204
  readonly name?: string;
@@ -229,6 +241,7 @@ interface FormBuilderComponents {
229
241
  readonly message: string;
230
242
  }>;
231
243
  readonly renderIcon?: (actionType: BuilderActionIconType) => ReactNode;
244
+ readonly renderFieldTypeIcon?: (type: QuestionType) => ReactNode;
232
245
  }
233
246
  type FormBuilderActions = Omit<FormBuilderResult, "schema" | "validationIssues">;
234
247
  interface ManualTranslationTarget {
@@ -265,6 +278,21 @@ interface BuilderFieldEditorSlotProps extends BuilderSlotBaseProps {
265
278
  readonly localization?: boolean;
266
279
  readonly conditions?: boolean;
267
280
  };
281
+ readonly slots?: Pick<FormBuilderSlots, "fieldTypeSelect" | "fieldEditorHeader">;
282
+ }
283
+ interface FieldTypeSelectSlotProps {
284
+ readonly currentType: QuestionType;
285
+ readonly allowedTypes: readonly QuestionType[];
286
+ readonly onChangeType: (nextType: QuestionType) => void;
287
+ readonly disabled?: boolean;
288
+ readonly readOnly?: boolean;
289
+ readonly renderIcon?: (type: QuestionType) => ReactNode;
290
+ }
291
+ interface FieldEditorHeaderSlotProps {
292
+ readonly field: Question;
293
+ readonly index: number;
294
+ readonly totalFields: number;
295
+ readonly actions: FormBuilderActions;
268
296
  }
269
297
  interface BuilderOptionEditorSlotProps extends BuilderSlotBaseProps {
270
298
  readonly field: FormField & {
@@ -309,6 +337,8 @@ type FormBuilderSectionName = "basicSettings" | "completionMessage" | "questions
309
337
  interface FormBuilderSlots {
310
338
  readonly toolbar?: ComponentType<BuilderToolbarSlotProps>;
311
339
  readonly fieldEditor?: ComponentType<BuilderFieldEditorSlotProps>;
340
+ readonly fieldTypeSelect?: ComponentType<FieldTypeSelectSlotProps>;
341
+ readonly fieldEditorHeader?: ComponentType<FieldEditorHeaderSlotProps>;
312
342
  readonly optionEditor?: ComponentType<BuilderOptionEditorSlotProps>;
313
343
  readonly pages?: ComponentType<BuilderPagesSlotProps>;
314
344
  readonly localization?: ComponentType<BuilderLocalizationSlotProps>;
@@ -555,6 +585,23 @@ interface FieldState {
555
585
  }
556
586
  declare function useField(fieldId: string): FieldState;
557
587
 
588
+ declare const BUILDER_TRANSLATION_KEYS: {
589
+ readonly ADD_FIELD: "builder.actions.addField";
590
+ readonly SELECT_FIELD_TYPE: "builder.fields.selectType";
591
+ readonly SELECT_LOCALE_TO_ADD: "builder.localization.selectLocaleToAdd";
592
+ readonly FIELD_TYPE_TEXT: "builder.fields.typeText";
593
+ readonly FIELD_TYPE_TEXTAREA: "builder.fields.typeTextarea";
594
+ readonly FIELD_TYPE_NUMBER: "builder.fields.typeNumber";
595
+ readonly FIELD_TYPE_RATING: "builder.fields.typeRating";
596
+ readonly FIELD_TYPE_RADIO: "builder.fields.typeRadio";
597
+ readonly FIELD_TYPE_CHECKBOX: "builder.fields.typeCheckbox";
598
+ readonly FIELD_TYPE_SELECT: "builder.fields.typeSelect";
599
+ readonly FIELD_TYPE_MULTI_SELECT: "builder.fields.typeMultiSelect";
600
+ };
601
+ type BuilderTranslationKey = (typeof BUILDER_TRANSLATION_KEYS)[keyof typeof BUILDER_TRANSLATION_KEYS];
602
+ /** Legacy keys are checked when an older catalog does not contain a canonical key. */
603
+ declare const BUILDER_TRANSLATION_ALIASES: Readonly<Record<string, string>>;
604
+
558
605
  interface FieldComponentProps {
559
606
  readonly field: FormField;
560
607
  readonly value: FormValue;
@@ -601,4 +648,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
601
648
  type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
602
649
  declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
603
650
 
604
- export { 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 ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldState, 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 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 };
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 };
package/dist/index.js CHANGED
@@ -99,6 +99,7 @@ function createLocalStorageSubmissionAttemptStore(options = {}) {
99
99
 
100
100
  // src/builder.tsx
101
101
  import {
102
+ DEFAULT_FIELD_TYPE_DEFINITIONS,
102
103
  populateSchemaTranslations
103
104
  } from "@form-engine-ts/core";
104
105
  import { Children, createContext, isValidElement, useContext, useState } from "react";
@@ -743,6 +744,33 @@ function useFormBuilder({
743
744
  };
744
745
  }
745
746
 
747
+ // src/i18n.ts
748
+ var BUILDER_TRANSLATION_KEYS = {
749
+ ADD_FIELD: "builder.actions.addField",
750
+ SELECT_FIELD_TYPE: "builder.fields.selectType",
751
+ SELECT_LOCALE_TO_ADD: "builder.localization.selectLocaleToAdd",
752
+ FIELD_TYPE_TEXT: "builder.fields.typeText",
753
+ FIELD_TYPE_TEXTAREA: "builder.fields.typeTextarea",
754
+ FIELD_TYPE_NUMBER: "builder.fields.typeNumber",
755
+ FIELD_TYPE_RATING: "builder.fields.typeRating",
756
+ FIELD_TYPE_RADIO: "builder.fields.typeRadio",
757
+ FIELD_TYPE_CHECKBOX: "builder.fields.typeCheckbox",
758
+ FIELD_TYPE_SELECT: "builder.fields.typeSelect",
759
+ FIELD_TYPE_MULTI_SELECT: "builder.fields.typeMultiSelect"
760
+ };
761
+ var BUILDER_TRANSLATION_ALIASES = {
762
+ "builder.actions.addField": "builder.addQuestion",
763
+ "builder.localization.selectLocaleToAdd": "builder.selectLocaleToAdd",
764
+ "builder.fields.typeText": "builder.fieldType.text",
765
+ "builder.fields.typeTextarea": "builder.fieldType.textarea",
766
+ "builder.fields.typeNumber": "builder.fieldType.number",
767
+ "builder.fields.typeRating": "builder.fieldType.rating",
768
+ "builder.fields.typeRadio": "builder.fieldType.radio",
769
+ "builder.fields.typeCheckbox": "builder.fieldType.checkbox",
770
+ "builder.fields.typeSelect": "builder.fieldType.select",
771
+ "builder.fields.typeMultiSelect": "builder.fieldType.multi-select"
772
+ };
773
+
746
774
  // src/builder.tsx
747
775
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
748
776
  function DefaultButton({
@@ -813,6 +841,26 @@ function defaultIconFor(actionType) {
813
841
  return "\u6587";
814
842
  }
815
843
  }
844
+ function defaultFieldTypeIcon(type) {
845
+ switch (type) {
846
+ case "text":
847
+ return "T";
848
+ case "textarea":
849
+ return "\u2261";
850
+ case "number":
851
+ return "#";
852
+ case "rating":
853
+ return "\u2605";
854
+ case "select":
855
+ return "\u25BE";
856
+ case "multi-select":
857
+ return "\u2611";
858
+ case "checkbox":
859
+ return "\u25A1";
860
+ case "radio":
861
+ return "\u25C9";
862
+ }
863
+ }
816
864
  function DefaultTextInput({
817
865
  id,
818
866
  className,
@@ -925,8 +973,15 @@ function DefaultSelect({
925
973
  helperText,
926
974
  value,
927
975
  onChange,
928
- options
976
+ onKeyDown,
977
+ options,
978
+ renderOption,
979
+ renderValue
929
980
  }) {
981
+ const normalizedOptions = options.map(
982
+ (option) => typeof option === "string" ? { value: option, label: option } : option
983
+ );
984
+ const selectedOption = normalizedOptions.find((option) => option.value === value);
930
985
  const labelElement = label === void 0 ? null : id === void 0 ? /* @__PURE__ */ jsx("span", { children: label }) : /* @__PURE__ */ jsx("label", { htmlFor: id, children: label });
931
986
  return /* @__PURE__ */ jsxs(Fragment, { children: [
932
987
  labelElement,
@@ -944,9 +999,11 @@ function DefaultSelect({
944
999
  "aria-invalid": error === true ? true : void 0,
945
1000
  value,
946
1001
  onChange: (event) => onChange(event.currentTarget.value),
947
- children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
1002
+ onKeyDown,
1003
+ children: normalizedOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: renderOption === void 0 ? option.label : renderOption(option) }, option.value))
948
1004
  }
949
1005
  ),
1006
+ renderValue === void 0 ? null : /* @__PURE__ */ jsx("output", { children: renderValue(selectedOption) }),
950
1007
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ jsx("small", { id: ariaDescribedBy, children: helperText })
951
1008
  ] });
952
1009
  }
@@ -1034,7 +1091,8 @@ var DEFAULT_COMPONENTS = {
1034
1091
  Section: DefaultSection,
1035
1092
  Fieldset: DefaultFieldset,
1036
1093
  ErrorMessage: DefaultErrorMessage,
1037
- renderIcon: defaultIconFor
1094
+ renderIcon: defaultIconFor,
1095
+ renderFieldTypeIcon: defaultFieldTypeIcon
1038
1096
  };
1039
1097
  var BuilderPrimitiveContext = createContext({
1040
1098
  components: DEFAULT_COMPONENTS,
@@ -1235,6 +1293,16 @@ var BUILDER_DEFAULTS = {
1235
1293
  "builder.actions.close": "Close",
1236
1294
  "builder.actions.dragHandle": "Reorder",
1237
1295
  "builder.translationUnavailable": "Provide an async translation adapter to enable automatic translation.",
1296
+ "builder.fields.selectType": "Select field type",
1297
+ "builder.fields.typeText": "Text",
1298
+ "builder.fields.typeTextarea": "Textarea",
1299
+ "builder.fields.typeNumber": "Number",
1300
+ "builder.fields.typeRating": "Rating",
1301
+ "builder.fields.typeSelect": "Select",
1302
+ "builder.fields.typeMultiSelect": "Multi-select",
1303
+ "builder.fields.typeCheckbox": "Checkbox",
1304
+ "builder.fields.typeRadio": "Radio",
1305
+ "builder.actions.addField": "Add question",
1238
1306
  "builder.fieldType.text": "Text",
1239
1307
  "builder.fieldType.textarea": "Textarea",
1240
1308
  "builder.fieldType.number": "Number",
@@ -1271,7 +1339,7 @@ function OrderedBuilderSections({
1271
1339
  return groups.map((group, index) => ({ group, index, rank: ranks.get(sectionGroupName(group) ?? "questions") ?? order.length })).sort((left, right) => left.rank - right.rank || left.index - right.index).map(({ group }) => group);
1272
1340
  }
1273
1341
  function fieldTypeKey(type) {
1274
- return `builder.fieldType.${type}`;
1342
+ return DEFAULT_FIELD_TYPE_DEFINITIONS.find((definition) => definition.type === type)?.labelKey ?? `builder.fieldType.${type}`;
1275
1343
  }
1276
1344
  function operatorKey(operator) {
1277
1345
  return `builder.operator.${operator}`;
@@ -1432,7 +1500,8 @@ function FormBuilder({
1432
1500
  const resolvedComponents = { ...DEFAULT_COMPONENTS, ...componentOverrides };
1433
1501
  const components = {
1434
1502
  ...GUARDED_COMPONENTS,
1435
- renderIcon: resolvedComponents.renderIcon
1503
+ renderIcon: resolvedComponents.renderIcon,
1504
+ renderFieldTypeIcon: resolvedComponents.renderFieldTypeIcon
1436
1505
  };
1437
1506
  const defaultStylesDisabled = disableDefaultStyles || unstyled;
1438
1507
  const builderClass = (value) => defaultStylesDisabled ? void 0 : value;
@@ -1463,6 +1532,12 @@ function FormBuilder({
1463
1532
  if (typeof value === "string" || typeof value === "number") translatorParams[name] = value;
1464
1533
  }
1465
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
+ }
1466
1541
  return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
1467
1542
  };
1468
1543
  const pagesEnabled = features?.pages ?? true;
@@ -2198,6 +2273,7 @@ function FormBuilder({
2198
2273
  index,
2199
2274
  currentLocale: editingLocale,
2200
2275
  translate,
2276
+ ...slots === void 0 ? {} : { slots },
2201
2277
  ...policy === void 0 ? {} : { policy },
2202
2278
  ...features === void 0 ? {} : { features },
2203
2279
  readOnly,
@@ -2569,7 +2645,7 @@ function FormBuilder({
2569
2645
  action: "addField",
2570
2646
  disabled: initialFieldType === null || maxFieldsReached,
2571
2647
  onClick: addField,
2572
- children: translate("builder.addQuestion")
2648
+ children: translate(BUILDER_TRANSLATION_KEYS.ADD_FIELD)
2573
2649
  }
2574
2650
  ) })
2575
2651
  ] }) })
@@ -3865,6 +3941,8 @@ function FormRenderer(props) {
3865
3941
  );
3866
3942
  }
3867
3943
  export {
3944
+ BUILDER_TRANSLATION_ALIASES,
3945
+ BUILDER_TRANSLATION_KEYS,
3868
3946
  FormBuilder,
3869
3947
  FormProvider,
3870
3948
  FormRenderer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/react",
3
- "version": "3.0.0",
3
+ "version": "3.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": "3.0.0",
46
- "@form-engine-ts/privacy": "3.0.0"
45
+ "@form-engine-ts/core": "3.2.0",
46
+ "@form-engine-ts/privacy": "3.2.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": ">=18.2 <20",