@form-engine-ts/mui 3.2.0 → 4.1.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
@@ -17,7 +17,10 @@ import { MuiFormBuilder } from "@form-engine-ts/mui";
17
17
  dense: true,
18
18
  inputFullWidth: true,
19
19
  buttonFullWidth: false,
20
- fieldEditorOptions: { description: "hidden" },
20
+ fieldEditorOptions: {
21
+ description: "hidden",
22
+ byType: { rating: { ratingBounds: "readOnly" } }
23
+ },
21
24
  getLocaleLabel: (locale) => localeNames[locale] ?? locale,
22
25
  buttonVariants: { primary: "contained", secondary: "outlined", danger: "outlined" }
23
26
  }}
@@ -64,8 +67,10 @@ slots as it does for the standard builder.
64
67
 
65
68
  `MuiAdapterOptions` defaults to `size: "medium"`, `variant: "outlined"`, `buttonVariant: "contained"`,
66
69
  `inputFullWidth: true`, and `buttonFullWidth: false`. The legacy `fullWidth` option remains the fallback for both new
67
- width options. `fieldEditorOptions.description` and `localizationOptions.defaultLocaleControl` independently make those
68
- controls editable, read-only, or hidden. Set `dense` to reduce section, editor, option, and toolbar spacing.
70
+ width options. `fieldEditorOptions` controls title, description, required, type selection, options, display conditions,
71
+ text limits, rating bounds, and number limits. Each control can be `editable`, `readOnly`, or `hidden`, and `byType`
72
+ can override controls for individual question types. `localizationOptions.defaultLocaleControl` independently controls
73
+ the default locale input. Set `dense` to reduce section, editor, option, and toolbar spacing.
69
74
  `buttonVariants` can override the MUI variant for `primary`, `secondary`, and `danger` actions independently.
70
75
  `localizationOptions.availableLocales` supplies display-ready locale candidates independently from the policy; when
71
76
  both are present, only candidates allowed by `allowedLocales` are shown. `placement` supports `top`,
@@ -76,11 +81,14 @@ default.
76
81
  `layoutOptions`, `localizationOptions`, and `muiSlotProps` are also accepted in `muiOptions` for low-level factories such
77
82
  as `createMuiBuilderSlots`; the dedicated `MuiFormBuilder` props take precedence.
78
83
 
79
- Select options in the MUI adapter support icons, descriptions, custom metadata, and custom `renderOption`/`renderValue`
80
- callbacks. Without a custom renderer, icons appear beside labels in the menu and in the selected value. The standard
84
+ Select options in the MUI adapter support icons, descriptions, groups (`group`/`groupLabel` or `kind`), custom metadata,
85
+ and custom `renderOption`/`renderValue` callbacks. Without a custom renderer, icons appear beside labels in the menu and
86
+ in the selected value. The standard
81
87
  MUI field editor supplies icons for every field type and accepts `slots.fieldTypeSelect` and `slots.fieldEditorHeader`
82
88
  for focused customization. `muiSlotProps` also supports `textField`, `select`, `selectMenu`, `checkbox`, `button`, and
83
89
  `iconButton` MUI props in addition to the layout props.
90
+ `fieldEditorOptions.fieldTypeOptions` can explicitly order, sort, or transform the generated type choices without
91
+ mutating the defaults.
84
92
 
85
93
  `MuiFormBuilder` supplies options through `MuiFormBuilderContext` to module-stable adapter and slot component types.
86
94
  Controlled schema updates therefore preserve input focus and uncontrolled MUI state such as an open localization
package/dist/index.cjs CHANGED
@@ -102,7 +102,17 @@ function resolveMuiAdapterOptions(options = {}) {
102
102
  ...options.getActionLabel === void 0 ? {} : { getActionLabel: options.getActionLabel },
103
103
  layoutOptions: options.layoutOptions ?? {},
104
104
  fieldEditorOptions: {
105
- description: options.fieldEditorOptions?.description ?? "editable"
105
+ title: options.fieldEditorOptions?.title ?? "editable",
106
+ description: options.fieldEditorOptions?.description ?? "editable",
107
+ required: options.fieldEditorOptions?.required ?? "editable",
108
+ typeSelect: options.fieldEditorOptions?.typeSelect ?? "editable",
109
+ options: options.fieldEditorOptions?.options ?? "editable",
110
+ displayConditions: options.fieldEditorOptions?.displayConditions ?? "editable",
111
+ textLimits: options.fieldEditorOptions?.textLimits ?? "editable",
112
+ ratingBounds: options.fieldEditorOptions?.ratingBounds ?? "editable",
113
+ numberLimits: options.fieldEditorOptions?.numberLimits ?? "editable",
114
+ ...options.fieldEditorOptions?.byType === void 0 ? {} : { byType: options.fieldEditorOptions.byType },
115
+ ...options.fieldEditorOptions?.fieldTypeOptions === void 0 ? {} : { fieldTypeOptions: options.fieldEditorOptions.fieldTypeOptions }
106
116
  },
107
117
  localizationOptions: {
108
118
  collapsible: options.localizationOptions?.collapsible ?? false,
@@ -128,7 +138,13 @@ function mergeMuiAdapterOptions(base = {}, overrides = {}) {
128
138
  ...overrides,
129
139
  ...base.buttonVariants === void 0 && overrides.buttonVariants === void 0 ? {} : { buttonVariants: { ...base.buttonVariants, ...overrides.buttonVariants } },
130
140
  ...base.layoutOptions === void 0 && overrides.layoutOptions === void 0 ? {} : { layoutOptions: { ...base.layoutOptions, ...overrides.layoutOptions } },
131
- ...base.fieldEditorOptions === void 0 && overrides.fieldEditorOptions === void 0 ? {} : { fieldEditorOptions: { ...base.fieldEditorOptions, ...overrides.fieldEditorOptions } },
141
+ ...base.fieldEditorOptions === void 0 && overrides.fieldEditorOptions === void 0 ? {} : {
142
+ fieldEditorOptions: {
143
+ ...base.fieldEditorOptions,
144
+ ...overrides.fieldEditorOptions,
145
+ ...base.fieldEditorOptions?.byType === void 0 && overrides.fieldEditorOptions?.byType === void 0 ? {} : { byType: { ...base.fieldEditorOptions?.byType, ...overrides.fieldEditorOptions?.byType } }
146
+ }
147
+ },
132
148
  ...base.localizationOptions === void 0 && overrides.localizationOptions === void 0 ? {} : { localizationOptions: { ...base.localizationOptions, ...overrides.localizationOptions } },
133
149
  ...base.muiSlotProps === void 0 && overrides.muiSlotProps === void 0 ? {} : { muiSlotProps: { ...base.muiSlotProps, ...overrides.muiSlotProps } }
134
150
  };
@@ -417,6 +433,40 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
417
433
  function normalizeOptions(options) {
418
434
  return options.map((option) => typeof option === "string" ? { value: option, label: option } : option);
419
435
  }
436
+ function SelectGroupHeader({ children }) {
437
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.ListSubheader, { role: "presentation", children });
438
+ }
439
+ function groupSelectOptions(options) {
440
+ const groups = [];
441
+ const groupIndexes = /* @__PURE__ */ new Map();
442
+ for (const option of options) {
443
+ const groupKey = option.group ?? option.kind;
444
+ const groupLabel = option.groupLabel ?? groupKey;
445
+ if (groupKey === void 0 && groupLabel === void 0) {
446
+ groups.push({ items: [option] });
447
+ continue;
448
+ }
449
+ const key = groupKey ?? groupLabel;
450
+ if (key === void 0) {
451
+ groups.push({ items: [option] });
452
+ continue;
453
+ }
454
+ const existingIndex = groupIndexes.get(key);
455
+ if (existingIndex === void 0) {
456
+ groupIndexes.set(key, groups.length);
457
+ groups.push({
458
+ groupKey: key,
459
+ ...groupLabel === void 0 ? {} : { groupLabel },
460
+ items: [option]
461
+ });
462
+ continue;
463
+ }
464
+ const existing = groups[existingIndex];
465
+ if (existing === void 0) continue;
466
+ groups[existingIndex] = { ...existing, items: [...existing.items, option] };
467
+ }
468
+ return groups;
469
+ }
420
470
  function createMuiSelectAdapter(options) {
421
471
  return function MuiSelect({
422
472
  id,
@@ -440,13 +490,14 @@ function createMuiSelectAdapter(options) {
440
490
  }) {
441
491
  const resolved = useResolvedMuiAdapterOptions(options);
442
492
  const normalizedOptions = normalizeOptions(selectOptions);
493
+ const groupedOptions = groupSelectOptions(normalizedOptions);
443
494
  const selectSlotProps = resolved.muiSlotProps?.select;
444
495
  const menuProps = { ...selectSlotProps?.MenuProps, ...resolved.muiSlotProps?.selectMenu };
445
496
  const slotInputProps = selectSlotProps?.inputProps;
446
497
  const labelId = id === void 0 || label === void 0 ? void 0 : `${id}-label`;
447
498
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
448
499
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
449
- const handleChange = (event) => onChange(String(event.target.value));
500
+ const handleChange = (event) => onChange(event.target.value);
450
501
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
451
502
  import_material7.FormControl,
452
503
  {
@@ -491,10 +542,22 @@ function createMuiSelectAdapter(options) {
491
542
  ...describedBy === void 0 ? {} : { "aria-describedby": describedBy },
492
543
  ...ariaLabelledBy === void 0 ? {} : { "aria-labelledby": ariaLabelledBy }
493
544
  },
494
- children: normalizedOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.MenuItem, { value: option.value, disabled: option.disabled, children: renderOption === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
495
- option.icon === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.ListItemIcon, { sx: { minWidth: 32 }, children: option.icon }),
496
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.ListItemText, { primary: option.label, secondary: option.description })
497
- ] }) : renderOption(option) }, option.value))
545
+ children: groupedOptions.flatMap((group) => [
546
+ group.groupLabel === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectGroupHeader, { children: group.groupLabel }, `header-${group.groupKey ?? group.groupLabel}`),
547
+ ...group.items.map((option) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
548
+ import_material7.MenuItem,
549
+ {
550
+ value: option.value,
551
+ disabled: option.disabled,
552
+ ...option.group === void 0 && option.groupLabel === void 0 && option.kind === void 0 ? {} : { "aria-label": option.label },
553
+ children: renderOption === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
554
+ option.icon === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.ListItemIcon, { sx: { minWidth: 32 }, children: option.icon }),
555
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.ListItemText, { primary: option.label, secondary: option.description })
556
+ ] }) : renderOption(option)
557
+ },
558
+ option.value
559
+ ))
560
+ ])
498
561
  }
499
562
  ),
500
563
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.FormHelperText, { id: helperId, children: helperText })
@@ -503,7 +566,10 @@ function createMuiSelectAdapter(options) {
503
566
  );
504
567
  };
505
568
  }
506
- var MuiSelectAdapter = createMuiSelectAdapter();
569
+ function MuiSelectAdapter(props) {
570
+ const Adapter = createMuiSelectAdapter();
571
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Adapter, { ...props });
572
+ }
507
573
 
508
574
  // src/adapters/TextArea.tsx
509
575
  var import_material8 = require("@mui/material");
@@ -724,6 +790,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
724
790
 
725
791
  // src/slots/FieldEditor.tsx
726
792
  var import_core = require("@form-engine-ts/core");
793
+ var import_react2 = require("@form-engine-ts/react");
727
794
  var import_material12 = require("@mui/material");
728
795
 
729
796
  // src/slots/OptionEditor.tsx
@@ -896,6 +963,13 @@ function updateBound(field, property, value) {
896
963
  const { [property]: _removed, ...remaining } = field;
897
964
  return remaining;
898
965
  }
966
+ function updateNumberProperty(field, property, value) {
967
+ if (field.type !== "number") return field;
968
+ const nextValue = numericValue(value);
969
+ if (nextValue !== void 0) return { ...field, [property]: nextValue };
970
+ const { [property]: _removed, ...remaining } = field;
971
+ return remaining;
972
+ }
899
973
  function createMuiFieldEditorSlot(options) {
900
974
  const Toolbar = createMuiToolbarSlot(options);
901
975
  const OptionEditor = createMuiOptionEditorSlot(options);
@@ -910,7 +984,9 @@ function createMuiFieldEditorSlot(options) {
910
984
  actions,
911
985
  components,
912
986
  translate,
913
- slots
987
+ slots,
988
+ fieldEditorControls,
989
+ fieldTypeOptions: fieldTypeOptionsConfig
914
990
  }) {
915
991
  const resolved = useResolvedMuiAdapterOptions(options);
916
992
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -919,10 +995,41 @@ function createMuiFieldEditorSlot(options) {
919
995
  const condition = field.displayCondition;
920
996
  const conditionSources = schema.fields.slice(0, index);
921
997
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
922
- const descriptionMode = resolved.fieldEditorOptions?.description ?? "editable";
998
+ const fieldEditorOptions = resolved.fieldEditorOptions ?? {};
999
+ const controls = (0, import_react2.resolveFieldEditorControls)({
1000
+ ...fieldEditorControls ?? {},
1001
+ ...fieldEditorOptions,
1002
+ ...fieldEditorOptions.byType?.[field.type] ?? {}
1003
+ });
923
1004
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
924
1005
  const FieldTypeSelect = slots?.fieldTypeSelect;
925
1006
  const FieldEditorHeader = slots?.fieldEditorHeader;
1007
+ const fieldTypeSelectId = `mui-field-${field.id}-type`;
1008
+ const fieldTypeSelectLabel = translate("builder.type");
1009
+ const generatedFieldTypeOptions = FIELD_TYPES.filter(
1010
+ (type) => allowedTypes.includes(type)
1011
+ ).map((type) => {
1012
+ const definition = import_core.DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
1013
+ const group = definition?.category;
1014
+ return {
1015
+ value: type,
1016
+ label: translate(definition?.labelKey ?? `builder.fieldType.${type}`),
1017
+ description: translate(`builder.fieldTypeDescription.${type}`),
1018
+ icon: components.renderFieldTypeIcon(type),
1019
+ ...group === void 0 ? {} : {
1020
+ group,
1021
+ groupLabel: translate(`builder.fieldCategory.${group}`)
1022
+ }
1023
+ };
1024
+ });
1025
+ const fieldTypeOptions = (0, import_react2.resolveFieldTypeSelectOptions)(
1026
+ generatedFieldTypeOptions,
1027
+ fieldTypeOptionsConfig ?? fieldEditorOptions.fieldTypeOptions,
1028
+ {
1029
+ currentType: field.type,
1030
+ allowedTypes
1031
+ }
1032
+ );
926
1033
  const changeFieldType = (nextType) => {
927
1034
  if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
928
1035
  };
@@ -957,7 +1064,7 @@ function createMuiFieldEditorSlot(options) {
957
1064
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
958
1065
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
959
1066
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
960
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1067
+ controls.title === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
961
1068
  TextInput,
962
1069
  {
963
1070
  id: `mui-field-${field.id}-title`,
@@ -968,27 +1075,19 @@ function createMuiFieldEditorSlot(options) {
968
1075
  error: field.title.trim().length === 0,
969
1076
  helperText: field.title.trim().length === 0 ? translate("builder.required") : "",
970
1077
  "aria-describedby": titleErrorId,
971
- disabled: readOnly,
1078
+ disabled: readOnly || controls.title === "readOnly",
972
1079
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
973
1080
  }
974
1081
  ),
975
- FieldTypeSelect === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1082
+ controls.typeSelect === "hidden" ? null : FieldTypeSelect === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
976
1083
  Select2,
977
1084
  {
978
- id: `mui-field-${field.id}-type`,
1085
+ id: fieldTypeSelectId,
979
1086
  name: `fields.${field.id}.type`,
980
- label: translate("builder.type"),
1087
+ label: fieldTypeSelectLabel,
981
1088
  value: allowedTypes.includes(field.type) ? field.type : "",
982
- options: FIELD_TYPES.filter((type) => allowedTypes.includes(type)).map((type) => {
983
- const definition = import_core.DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
984
- return {
985
- value: type,
986
- label: translate(definition?.labelKey ?? `builder.fieldType.${type}`),
987
- icon: components.renderFieldTypeIcon(type),
988
- ...definition?.category === void 0 ? {} : { kind: definition.category }
989
- };
990
- }),
991
- disabled: readOnly,
1089
+ options: fieldTypeOptions,
1090
+ disabled: readOnly || controls.typeSelect === "readOnly",
992
1091
  onChange: (value) => {
993
1092
  if (isFieldType(value)) changeFieldType(value);
994
1093
  }
@@ -996,16 +1095,21 @@ function createMuiFieldEditorSlot(options) {
996
1095
  ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
997
1096
  FieldTypeSelect,
998
1097
  {
1098
+ id: fieldTypeSelectId,
1099
+ name: `fields.${field.id}.type`,
1100
+ label: fieldTypeSelectLabel,
999
1101
  currentType: field.type,
1000
1102
  allowedTypes,
1103
+ options: fieldTypeOptions,
1001
1104
  onChangeType: changeFieldType,
1002
- disabled: readOnly,
1003
- readOnly,
1105
+ disabled: readOnly || controls.typeSelect === "readOnly",
1106
+ readOnly: readOnly || controls.typeSelect === "readOnly",
1107
+ "aria-labelledby": `${fieldTypeSelectId}-label`,
1004
1108
  renderIcon: components.renderFieldTypeIcon
1005
1109
  }
1006
1110
  )
1007
1111
  ] }),
1008
- descriptionMode === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1112
+ controls.description === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1009
1113
  TextArea,
1010
1114
  {
1011
1115
  id: `mui-field-${field.id}-description`,
@@ -1013,19 +1117,19 @@ function createMuiFieldEditorSlot(options) {
1013
1117
  label: translate("builder.description"),
1014
1118
  value: field.description ?? "",
1015
1119
  rows: resolved.dense ? 2 : 3,
1016
- disabled: readOnly,
1017
- readOnly: descriptionMode === "readOnly",
1120
+ disabled: readOnly || controls.description === "readOnly",
1121
+ readOnly: readOnly || controls.description === "readOnly",
1018
1122
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "description", value)
1019
1123
  }
1020
1124
  ),
1021
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1125
+ controls.required === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1022
1126
  Checkbox2,
1023
1127
  {
1024
1128
  id: `mui-field-${field.id}-required`,
1025
1129
  name: `fields.${field.id}.required`,
1026
1130
  label: translate("builder.required"),
1027
1131
  checked: field.required,
1028
- disabled: readOnly,
1132
+ disabled: readOnly || controls.required === "readOnly",
1029
1133
  onChange: (checked) => actions.updateField(field.id, (current) => ({ ...current, required: checked }))
1030
1134
  }
1031
1135
  ),
@@ -1043,7 +1147,7 @@ function createMuiFieldEditorSlot(options) {
1043
1147
  onChange: (value) => actions.assignFieldToPage(field.id, value.length === 0 ? null : value)
1044
1148
  }
1045
1149
  ),
1046
- field.type === "number" || field.type === "rating" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", sm: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1150
+ field.type === "number" || field.type === "rating" ? (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", sm: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1047
1151
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1048
1152
  TextInput,
1049
1153
  {
@@ -1051,7 +1155,7 @@ function createMuiFieldEditorSlot(options) {
1051
1155
  label: translate("builder.minimum"),
1052
1156
  type: "number",
1053
1157
  value: field.min === void 0 ? "" : String(field.min),
1054
- disabled: readOnly,
1158
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1055
1159
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "min", value))
1056
1160
  }
1057
1161
  ),
@@ -1062,12 +1166,68 @@ function createMuiFieldEditorSlot(options) {
1062
1166
  label: translate("builder.maximum"),
1063
1167
  type: "number",
1064
1168
  value: field.max === void 0 ? "" : String(field.max),
1065
- disabled: readOnly,
1169
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1066
1170
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "max", value))
1067
1171
  }
1068
1172
  )
1069
1173
  ] }) : null,
1070
- features?.conditions === false || conditionSources.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1174
+ field.type === "number" && controls.numberLimits !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1175
+ TextInput,
1176
+ {
1177
+ id: `mui-field-${field.id}-step`,
1178
+ label: translate("builder.step"),
1179
+ type: "number",
1180
+ value: field.step === void 0 ? "" : String(field.step),
1181
+ disabled: readOnly || controls.numberLimits === "readOnly",
1182
+ onChange: (value) => actions.updateField(field.id, (current) => updateNumberProperty(current, "step", value))
1183
+ }
1184
+ ) : null,
1185
+ (field.type === "text" || field.type === "textarea") && controls.textLimits !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", sm: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1187
+ TextInput,
1188
+ {
1189
+ id: `mui-field-${field.id}-min-length`,
1190
+ label: translate("builder.minimumLength"),
1191
+ type: "number",
1192
+ value: field.minLength === void 0 ? "" : String(field.minLength),
1193
+ disabled: readOnly || controls.textLimits === "readOnly",
1194
+ onChange: (value) => actions.updateField(field.id, (current) => {
1195
+ if (current.type !== "text" && current.type !== "textarea") return current;
1196
+ const parsed = numericValue(value);
1197
+ return parsed === void 0 ? current : { ...current, minLength: Math.max(0, Math.floor(parsed)) };
1198
+ })
1199
+ }
1200
+ ),
1201
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1202
+ TextInput,
1203
+ {
1204
+ id: `mui-field-${field.id}-max-length`,
1205
+ label: translate("builder.maximumLength"),
1206
+ type: "number",
1207
+ value: field.maxLength === void 0 ? "" : String(field.maxLength),
1208
+ disabled: readOnly || controls.textLimits === "readOnly",
1209
+ onChange: (value) => actions.updateField(field.id, (current) => {
1210
+ if (current.type !== "text" && current.type !== "textarea") return current;
1211
+ const parsed = numericValue(value);
1212
+ return parsed === void 0 ? current : { ...current, maxLength: Math.max(0, Math.floor(parsed)) };
1213
+ })
1214
+ }
1215
+ ),
1216
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1217
+ TextInput,
1218
+ {
1219
+ id: `mui-field-${field.id}-pattern`,
1220
+ label: translate("builder.pattern"),
1221
+ value: field.pattern ?? "",
1222
+ disabled: readOnly || controls.textLimits === "readOnly",
1223
+ onChange: (value) => actions.updateField(
1224
+ field.id,
1225
+ (current) => current.type === "text" || current.type === "textarea" ? value.length === 0 ? current : { ...current, pattern: value } : current
1226
+ )
1227
+ }
1228
+ )
1229
+ ] }) : null,
1230
+ features?.conditions === false || conditionSources.length === 0 || controls.displayConditions === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1071
1231
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1072
1232
  Select2,
1073
1233
  {
@@ -1078,7 +1238,7 @@ function createMuiFieldEditorSlot(options) {
1078
1238
  { value: "", label: translate("builder.alwaysVisible") },
1079
1239
  ...conditionSources.map((source) => ({ value: source.id, label: source.title }))
1080
1240
  ],
1081
- disabled: readOnly,
1241
+ disabled: readOnly || controls.displayConditions === "readOnly",
1082
1242
  onChange: (value) => actions.setDisplayCondition(
1083
1243
  field.id,
1084
1244
  value.length === 0 ? void 0 : { questionId: value, operator: "equals", value: "" }
@@ -1095,7 +1255,7 @@ function createMuiFieldEditorSlot(options) {
1095
1255
  options: (conditionSource === void 0 ? [] : conditionOperators(conditionSource)).map(
1096
1256
  (operator) => ({ value: operator, label: translate(`builder.operator.${operator}`) })
1097
1257
  ),
1098
- disabled: readOnly,
1258
+ disabled: readOnly || controls.displayConditions === "readOnly",
1099
1259
  onChange: (value) => {
1100
1260
  if (!isConditionOperator(value)) return;
1101
1261
  actions.setDisplayCondition(
@@ -1111,7 +1271,7 @@ function createMuiFieldEditorSlot(options) {
1111
1271
  id: `mui-field-${field.id}-condition-value`,
1112
1272
  label: translate("builder.conditionValue"),
1113
1273
  value: condition.value === void 0 ? "" : String(condition.value),
1114
- disabled: readOnly,
1274
+ disabled: readOnly || controls.displayConditions === "readOnly",
1115
1275
  onChange: (value) => actions.setDisplayCondition(field.id, { ...condition, value })
1116
1276
  }
1117
1277
  )
@@ -1121,29 +1281,29 @@ function createMuiFieldEditorSlot(options) {
1121
1281
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material12.Typography, { variant: "subtitle2", children: translate("builder.translation", {
1122
1282
  locale: resolved.getLocaleLabel?.(currentLocale) ?? currentLocale
1123
1283
  }) }),
1124
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1284
+ controls.title === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1125
1285
  TextInput,
1126
1286
  {
1127
1287
  id: `mui-field-${field.id}-${currentLocale}-title`,
1128
1288
  label: translate("builder.translatedQuestionTitle"),
1129
1289
  value: field.translations?.[currentLocale]?.title ?? "",
1130
- disabled: readOnly,
1290
+ disabled: readOnly || controls.title === "readOnly",
1131
1291
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "title", value)
1132
1292
  }
1133
1293
  ),
1134
- descriptionMode === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1294
+ controls.description === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1135
1295
  TextArea,
1136
1296
  {
1137
1297
  id: `mui-field-${field.id}-${currentLocale}-description`,
1138
1298
  label: translate("builder.translatedDescription"),
1139
1299
  value: field.translations?.[currentLocale]?.description ?? "",
1140
- disabled: readOnly,
1141
- readOnly: descriptionMode === "readOnly",
1300
+ disabled: readOnly || controls.description === "readOnly",
1301
+ readOnly: readOnly || controls.description === "readOnly",
1142
1302
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "description", value)
1143
1303
  }
1144
1304
  )
1145
1305
  ] }),
1146
- "options" in field ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { "data-mui-slot": "options", spacing: resolved.dense ? 1 : 2, children: [
1306
+ "options" in field && controls.options !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { "data-mui-slot": "options", spacing: resolved.dense ? 1 : 2, children: [
1147
1307
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material12.Typography, { variant: "subtitle2", children: translate("builder.options") }),
1148
1308
  field.options.map((option, optionIndex) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1149
1309
  OptionEditor,
@@ -1154,7 +1314,7 @@ function createMuiFieldEditorSlot(options) {
1154
1314
  index: optionIndex,
1155
1315
  currentLocale,
1156
1316
  translate,
1157
- readOnly,
1317
+ readOnly: readOnly || controls.options === "readOnly",
1158
1318
  actions,
1159
1319
  components
1160
1320
  },
@@ -1165,7 +1325,7 @@ function createMuiFieldEditorSlot(options) {
1165
1325
  {
1166
1326
  action: "addOption",
1167
1327
  targetId: field.id,
1168
- disabled: readOnly || policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1328
+ disabled: readOnly || controls.options === "readOnly" || policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1169
1329
  onClick: () => actions.addOption(field.id),
1170
1330
  children: translate("builder.addOption")
1171
1331
  }
@@ -1182,7 +1342,7 @@ var MuiFieldEditorSlot = createMuiFieldEditorSlot();
1182
1342
  // src/slots/Localization.tsx
1183
1343
  var import_icons_material2 = require("@mui/icons-material");
1184
1344
  var import_material13 = require("@mui/material");
1185
- var import_react2 = require("react");
1345
+ var import_react3 = require("react");
1186
1346
  var import_jsx_runtime14 = require("react/jsx-runtime");
1187
1347
  var DEFAULT_EMPTY_STATE_MESSAGE = "No translation locales have been added yet. Select a language from the dropdown above to add one.";
1188
1348
  function normalizeLocaleOptions(options, getLocaleLabel) {
@@ -1213,8 +1373,8 @@ function createMuiLocalizationSlot(options) {
1213
1373
  }) {
1214
1374
  const resolved = useResolvedMuiAdapterOptions(options);
1215
1375
  const { Button: Button2, ErrorMessage, Select: Select2, TextArea, TextInput } = components;
1216
- const [newLocale, setNewLocale] = (0, import_react2.useState)("");
1217
- const [pendingFocusLocale, setPendingFocusLocale] = (0, import_react2.useState)(null);
1376
+ const [newLocale, setNewLocale] = (0, import_react3.useState)("");
1377
+ const [pendingFocusLocale, setPendingFocusLocale] = (0, import_react3.useState)(null);
1218
1378
  const locales = Array.from(
1219
1379
  new Set((schema.supportedLocales ?? []).filter((locale) => locale !== schema.defaultLocale))
1220
1380
  );
@@ -1253,7 +1413,7 @@ function createMuiLocalizationSlot(options) {
1253
1413
  event.stopPropagation();
1254
1414
  addLocale();
1255
1415
  };
1256
- (0, import_react2.useEffect)(() => {
1416
+ (0, import_react3.useEffect)(() => {
1257
1417
  if (pendingFocusLocale === null || currentLocale !== pendingFocusLocale) return;
1258
1418
  const tab = document.getElementById(`mui-builder-locale-tab-${pendingFocusLocale}`);
1259
1419
  if (tab === null) return;
@@ -1494,8 +1654,8 @@ function createMuiBuilderProps(options, overrides = {}) {
1494
1654
  }
1495
1655
 
1496
1656
  // src/MuiFormBuilder.tsx
1497
- var import_react3 = require("@form-engine-ts/react");
1498
- var import_react4 = require("react");
1657
+ var import_react4 = require("@form-engine-ts/react");
1658
+ var import_react5 = require("react");
1499
1659
  var import_jsx_runtime15 = require("react/jsx-runtime");
1500
1660
  function MuiFormBuilder({
1501
1661
  muiOptions,
@@ -1507,7 +1667,7 @@ function MuiFormBuilder({
1507
1667
  sectionOrder,
1508
1668
  ...props
1509
1669
  }) {
1510
- const contextOptions = (0, import_react4.useMemo)(
1670
+ const contextOptions = (0, import_react5.useMemo)(
1511
1671
  () => mergeMuiAdapterOptions(muiOptions, {
1512
1672
  ...layoutOptions === void 0 ? {} : { layoutOptions },
1513
1673
  ...localizationOptions === void 0 ? {} : { localizationOptions },
@@ -1515,13 +1675,13 @@ function MuiFormBuilder({
1515
1675
  }),
1516
1676
  [layoutOptions, localizationOptions, muiOptions, muiSlotProps]
1517
1677
  );
1518
- const contextValue = (0, import_react4.useMemo)(() => ({ options: contextOptions }), [contextOptions]);
1519
- const components = (0, import_react4.useMemo)(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1520
- const slots = (0, import_react4.useMemo)(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1678
+ const contextValue = (0, import_react5.useMemo)(() => ({ options: contextOptions }), [contextOptions]);
1679
+ const components = (0, import_react5.useMemo)(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1680
+ const slots = (0, import_react5.useMemo)(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1521
1681
  const placement = contextOptions.localizationOptions?.placement;
1522
1682
  const resolvedSectionOrder = sectionOrder ?? (placement === void 0 ? contextOptions.layoutOptions?.sectionOrder : MUI_LOCALIZATION_SECTION_ORDERS[placement]);
1523
1683
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1524
- import_react3.FormBuilder,
1684
+ import_react4.FormBuilder,
1525
1685
  {
1526
1686
  ...props,
1527
1687
  components,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { BuilderActionIconType, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, QuestionType, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
1
+ import { BuilderActionIconType, FieldPropertyControlMode, QuestionType, FieldEditorControlsConfig, FieldTypeSelectOptionsConfig, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
2
2
  import * as react from 'react';
3
- import { ReactNode, ComponentType } from 'react';
3
+ import { ReactNode, ComponentType, ReactElement } from 'react';
4
4
  import { CardProps, PaperProps, AccordionProps, StackProps, TextFieldProps, SelectProps, MenuProps, CheckboxProps, ButtonProps, IconButtonProps } from '@mui/material';
5
5
 
6
6
  type BuilderSectionName = "basicSettings" | "completionMessage" | "questions" | "addQuestion" | "localization";
@@ -28,8 +28,23 @@ interface MuiLocalizationOptions {
28
28
  readonly noWrapActions?: boolean;
29
29
  readonly autoFocusNewTab?: boolean;
30
30
  }
31
+ /**
32
+ * MUI's field editor controls mirror `FieldEditorControlsConfig` while keeping
33
+ * the original interface members directly declared for semver/reporting compatibility.
34
+ */
31
35
  interface MuiFieldEditorOptions {
36
+ readonly title?: FieldPropertyControlMode;
32
37
  readonly description?: "editable" | "readOnly" | "hidden";
38
+ readonly required?: FieldPropertyControlMode;
39
+ readonly typeSelect?: FieldPropertyControlMode;
40
+ readonly options?: FieldPropertyControlMode;
41
+ readonly displayConditions?: FieldPropertyControlMode;
42
+ readonly textLimits?: FieldPropertyControlMode;
43
+ readonly ratingBounds?: FieldPropertyControlMode;
44
+ readonly numberLimits?: FieldPropertyControlMode;
45
+ /** Per-question-type overrides take precedence over the base controls. */
46
+ readonly byType?: Partial<Record<QuestionType, Partial<FieldEditorControlsConfig>>>;
47
+ readonly fieldTypeOptions?: FieldTypeSelectOptionsConfig;
33
48
  }
34
49
  interface MuiSlotProps {
35
50
  readonly card?: Partial<CardProps>;
@@ -117,8 +132,8 @@ declare const MuiIconButtonAdapter: ComponentType<BuilderIconButtonProps>;
117
132
  declare function createMuiSectionAdapter(options?: MuiAdapterOptions): ComponentType<BuilderSectionProps>;
118
133
  declare const MuiSectionAdapter: ComponentType<BuilderSectionProps>;
119
134
 
120
- declare function createMuiSelectAdapter(options?: MuiAdapterOptions): ComponentType<BuilderSelectProps>;
121
- declare const MuiSelectAdapter: ComponentType<BuilderSelectProps>;
135
+ declare function createMuiSelectAdapter<T extends string = string>(options?: MuiAdapterOptions): ComponentType<BuilderSelectProps<T>>;
136
+ declare function MuiSelectAdapter<T extends string = string>(props: BuilderSelectProps<T>): ReactElement;
122
137
 
123
138
  declare function createMuiTextAreaAdapter(options?: MuiAdapterOptions): ComponentType<BuilderTextAreaProps>;
124
139
  declare const MuiTextAreaAdapter: ComponentType<BuilderTextAreaProps>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { BuilderActionIconType, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, QuestionType, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
1
+ import { BuilderActionIconType, FieldPropertyControlMode, QuestionType, FieldEditorControlsConfig, FieldTypeSelectOptionsConfig, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
2
2
  import * as react from 'react';
3
- import { ReactNode, ComponentType } from 'react';
3
+ import { ReactNode, ComponentType, ReactElement } from 'react';
4
4
  import { CardProps, PaperProps, AccordionProps, StackProps, TextFieldProps, SelectProps, MenuProps, CheckboxProps, ButtonProps, IconButtonProps } from '@mui/material';
5
5
 
6
6
  type BuilderSectionName = "basicSettings" | "completionMessage" | "questions" | "addQuestion" | "localization";
@@ -28,8 +28,23 @@ interface MuiLocalizationOptions {
28
28
  readonly noWrapActions?: boolean;
29
29
  readonly autoFocusNewTab?: boolean;
30
30
  }
31
+ /**
32
+ * MUI's field editor controls mirror `FieldEditorControlsConfig` while keeping
33
+ * the original interface members directly declared for semver/reporting compatibility.
34
+ */
31
35
  interface MuiFieldEditorOptions {
36
+ readonly title?: FieldPropertyControlMode;
32
37
  readonly description?: "editable" | "readOnly" | "hidden";
38
+ readonly required?: FieldPropertyControlMode;
39
+ readonly typeSelect?: FieldPropertyControlMode;
40
+ readonly options?: FieldPropertyControlMode;
41
+ readonly displayConditions?: FieldPropertyControlMode;
42
+ readonly textLimits?: FieldPropertyControlMode;
43
+ readonly ratingBounds?: FieldPropertyControlMode;
44
+ readonly numberLimits?: FieldPropertyControlMode;
45
+ /** Per-question-type overrides take precedence over the base controls. */
46
+ readonly byType?: Partial<Record<QuestionType, Partial<FieldEditorControlsConfig>>>;
47
+ readonly fieldTypeOptions?: FieldTypeSelectOptionsConfig;
33
48
  }
34
49
  interface MuiSlotProps {
35
50
  readonly card?: Partial<CardProps>;
@@ -117,8 +132,8 @@ declare const MuiIconButtonAdapter: ComponentType<BuilderIconButtonProps>;
117
132
  declare function createMuiSectionAdapter(options?: MuiAdapterOptions): ComponentType<BuilderSectionProps>;
118
133
  declare const MuiSectionAdapter: ComponentType<BuilderSectionProps>;
119
134
 
120
- declare function createMuiSelectAdapter(options?: MuiAdapterOptions): ComponentType<BuilderSelectProps>;
121
- declare const MuiSelectAdapter: ComponentType<BuilderSelectProps>;
135
+ declare function createMuiSelectAdapter<T extends string = string>(options?: MuiAdapterOptions): ComponentType<BuilderSelectProps<T>>;
136
+ declare function MuiSelectAdapter<T extends string = string>(props: BuilderSelectProps<T>): ReactElement;
122
137
 
123
138
  declare function createMuiTextAreaAdapter(options?: MuiAdapterOptions): ComponentType<BuilderTextAreaProps>;
124
139
  declare const MuiTextAreaAdapter: ComponentType<BuilderTextAreaProps>;
package/dist/index.js CHANGED
@@ -37,7 +37,17 @@ function resolveMuiAdapterOptions(options = {}) {
37
37
  ...options.getActionLabel === void 0 ? {} : { getActionLabel: options.getActionLabel },
38
38
  layoutOptions: options.layoutOptions ?? {},
39
39
  fieldEditorOptions: {
40
- description: options.fieldEditorOptions?.description ?? "editable"
40
+ title: options.fieldEditorOptions?.title ?? "editable",
41
+ description: options.fieldEditorOptions?.description ?? "editable",
42
+ required: options.fieldEditorOptions?.required ?? "editable",
43
+ typeSelect: options.fieldEditorOptions?.typeSelect ?? "editable",
44
+ options: options.fieldEditorOptions?.options ?? "editable",
45
+ displayConditions: options.fieldEditorOptions?.displayConditions ?? "editable",
46
+ textLimits: options.fieldEditorOptions?.textLimits ?? "editable",
47
+ ratingBounds: options.fieldEditorOptions?.ratingBounds ?? "editable",
48
+ numberLimits: options.fieldEditorOptions?.numberLimits ?? "editable",
49
+ ...options.fieldEditorOptions?.byType === void 0 ? {} : { byType: options.fieldEditorOptions.byType },
50
+ ...options.fieldEditorOptions?.fieldTypeOptions === void 0 ? {} : { fieldTypeOptions: options.fieldEditorOptions.fieldTypeOptions }
41
51
  },
42
52
  localizationOptions: {
43
53
  collapsible: options.localizationOptions?.collapsible ?? false,
@@ -63,7 +73,13 @@ function mergeMuiAdapterOptions(base = {}, overrides = {}) {
63
73
  ...overrides,
64
74
  ...base.buttonVariants === void 0 && overrides.buttonVariants === void 0 ? {} : { buttonVariants: { ...base.buttonVariants, ...overrides.buttonVariants } },
65
75
  ...base.layoutOptions === void 0 && overrides.layoutOptions === void 0 ? {} : { layoutOptions: { ...base.layoutOptions, ...overrides.layoutOptions } },
66
- ...base.fieldEditorOptions === void 0 && overrides.fieldEditorOptions === void 0 ? {} : { fieldEditorOptions: { ...base.fieldEditorOptions, ...overrides.fieldEditorOptions } },
76
+ ...base.fieldEditorOptions === void 0 && overrides.fieldEditorOptions === void 0 ? {} : {
77
+ fieldEditorOptions: {
78
+ ...base.fieldEditorOptions,
79
+ ...overrides.fieldEditorOptions,
80
+ ...base.fieldEditorOptions?.byType === void 0 && overrides.fieldEditorOptions?.byType === void 0 ? {} : { byType: { ...base.fieldEditorOptions?.byType, ...overrides.fieldEditorOptions?.byType } }
81
+ }
82
+ },
67
83
  ...base.localizationOptions === void 0 && overrides.localizationOptions === void 0 ? {} : { localizationOptions: { ...base.localizationOptions, ...overrides.localizationOptions } },
68
84
  ...base.muiSlotProps === void 0 && overrides.muiSlotProps === void 0 ? {} : { muiSlotProps: { ...base.muiSlotProps, ...overrides.muiSlotProps } }
69
85
  };
@@ -354,6 +370,7 @@ import {
354
370
  InputLabel,
355
371
  ListItemIcon,
356
372
  ListItemText,
373
+ ListSubheader,
357
374
  MenuItem,
358
375
  Select
359
376
  } from "@mui/material";
@@ -361,6 +378,40 @@ import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
361
378
  function normalizeOptions(options) {
362
379
  return options.map((option) => typeof option === "string" ? { value: option, label: option } : option);
363
380
  }
381
+ function SelectGroupHeader({ children }) {
382
+ return /* @__PURE__ */ jsx7(ListSubheader, { role: "presentation", children });
383
+ }
384
+ function groupSelectOptions(options) {
385
+ const groups = [];
386
+ const groupIndexes = /* @__PURE__ */ new Map();
387
+ for (const option of options) {
388
+ const groupKey = option.group ?? option.kind;
389
+ const groupLabel = option.groupLabel ?? groupKey;
390
+ if (groupKey === void 0 && groupLabel === void 0) {
391
+ groups.push({ items: [option] });
392
+ continue;
393
+ }
394
+ const key = groupKey ?? groupLabel;
395
+ if (key === void 0) {
396
+ groups.push({ items: [option] });
397
+ continue;
398
+ }
399
+ const existingIndex = groupIndexes.get(key);
400
+ if (existingIndex === void 0) {
401
+ groupIndexes.set(key, groups.length);
402
+ groups.push({
403
+ groupKey: key,
404
+ ...groupLabel === void 0 ? {} : { groupLabel },
405
+ items: [option]
406
+ });
407
+ continue;
408
+ }
409
+ const existing = groups[existingIndex];
410
+ if (existing === void 0) continue;
411
+ groups[existingIndex] = { ...existing, items: [...existing.items, option] };
412
+ }
413
+ return groups;
414
+ }
364
415
  function createMuiSelectAdapter(options) {
365
416
  return function MuiSelect({
366
417
  id,
@@ -384,13 +435,14 @@ function createMuiSelectAdapter(options) {
384
435
  }) {
385
436
  const resolved = useResolvedMuiAdapterOptions(options);
386
437
  const normalizedOptions = normalizeOptions(selectOptions);
438
+ const groupedOptions = groupSelectOptions(normalizedOptions);
387
439
  const selectSlotProps = resolved.muiSlotProps?.select;
388
440
  const menuProps = { ...selectSlotProps?.MenuProps, ...resolved.muiSlotProps?.selectMenu };
389
441
  const slotInputProps = selectSlotProps?.inputProps;
390
442
  const labelId = id === void 0 || label === void 0 ? void 0 : `${id}-label`;
391
443
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
392
444
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
393
- const handleChange = (event) => onChange(String(event.target.value));
445
+ const handleChange = (event) => onChange(event.target.value);
394
446
  return /* @__PURE__ */ jsxs4(
395
447
  FormControl2,
396
448
  {
@@ -435,10 +487,22 @@ function createMuiSelectAdapter(options) {
435
487
  ...describedBy === void 0 ? {} : { "aria-describedby": describedBy },
436
488
  ...ariaLabelledBy === void 0 ? {} : { "aria-labelledby": ariaLabelledBy }
437
489
  },
438
- children: normalizedOptions.map((option) => /* @__PURE__ */ jsx7(MenuItem, { value: option.value, disabled: option.disabled, children: renderOption === void 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
439
- option.icon === void 0 ? null : /* @__PURE__ */ jsx7(ListItemIcon, { sx: { minWidth: 32 }, children: option.icon }),
440
- /* @__PURE__ */ jsx7(ListItemText, { primary: option.label, secondary: option.description })
441
- ] }) : renderOption(option) }, option.value))
490
+ children: groupedOptions.flatMap((group) => [
491
+ group.groupLabel === void 0 ? null : /* @__PURE__ */ jsx7(SelectGroupHeader, { children: group.groupLabel }, `header-${group.groupKey ?? group.groupLabel}`),
492
+ ...group.items.map((option) => /* @__PURE__ */ jsx7(
493
+ MenuItem,
494
+ {
495
+ value: option.value,
496
+ disabled: option.disabled,
497
+ ...option.group === void 0 && option.groupLabel === void 0 && option.kind === void 0 ? {} : { "aria-label": option.label },
498
+ children: renderOption === void 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
499
+ option.icon === void 0 ? null : /* @__PURE__ */ jsx7(ListItemIcon, { sx: { minWidth: 32 }, children: option.icon }),
500
+ /* @__PURE__ */ jsx7(ListItemText, { primary: option.label, secondary: option.description })
501
+ ] }) : renderOption(option)
502
+ },
503
+ option.value
504
+ ))
505
+ ])
442
506
  }
443
507
  ),
444
508
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ jsx7(FormHelperText2, { id: helperId, children: helperText })
@@ -447,7 +511,10 @@ function createMuiSelectAdapter(options) {
447
511
  );
448
512
  };
449
513
  }
450
- var MuiSelectAdapter = createMuiSelectAdapter();
514
+ function MuiSelectAdapter(props) {
515
+ const Adapter = createMuiSelectAdapter();
516
+ return /* @__PURE__ */ jsx7(Adapter, { ...props });
517
+ }
451
518
 
452
519
  // src/adapters/TextArea.tsx
453
520
  import { TextField } from "@mui/material";
@@ -685,6 +752,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
685
752
 
686
753
  // src/slots/FieldEditor.tsx
687
754
  import { DEFAULT_FIELD_TYPE_DEFINITIONS } from "@form-engine-ts/core";
755
+ import { resolveFieldEditorControls, resolveFieldTypeSelectOptions } from "@form-engine-ts/react";
688
756
  import { Card, Stack as Stack3, Typography as Typography3 } from "@mui/material";
689
757
 
690
758
  // src/slots/OptionEditor.tsx
@@ -857,6 +925,13 @@ function updateBound(field, property, value) {
857
925
  const { [property]: _removed, ...remaining } = field;
858
926
  return remaining;
859
927
  }
928
+ function updateNumberProperty(field, property, value) {
929
+ if (field.type !== "number") return field;
930
+ const nextValue = numericValue(value);
931
+ if (nextValue !== void 0) return { ...field, [property]: nextValue };
932
+ const { [property]: _removed, ...remaining } = field;
933
+ return remaining;
934
+ }
860
935
  function createMuiFieldEditorSlot(options) {
861
936
  const Toolbar = createMuiToolbarSlot(options);
862
937
  const OptionEditor = createMuiOptionEditorSlot(options);
@@ -871,7 +946,9 @@ function createMuiFieldEditorSlot(options) {
871
946
  actions,
872
947
  components,
873
948
  translate,
874
- slots
949
+ slots,
950
+ fieldEditorControls,
951
+ fieldTypeOptions: fieldTypeOptionsConfig
875
952
  }) {
876
953
  const resolved = useResolvedMuiAdapterOptions(options);
877
954
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -880,10 +957,41 @@ function createMuiFieldEditorSlot(options) {
880
957
  const condition = field.displayCondition;
881
958
  const conditionSources = schema.fields.slice(0, index);
882
959
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
883
- const descriptionMode = resolved.fieldEditorOptions?.description ?? "editable";
960
+ const fieldEditorOptions = resolved.fieldEditorOptions ?? {};
961
+ const controls = resolveFieldEditorControls({
962
+ ...fieldEditorControls ?? {},
963
+ ...fieldEditorOptions,
964
+ ...fieldEditorOptions.byType?.[field.type] ?? {}
965
+ });
884
966
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
885
967
  const FieldTypeSelect = slots?.fieldTypeSelect;
886
968
  const FieldEditorHeader = slots?.fieldEditorHeader;
969
+ const fieldTypeSelectId = `mui-field-${field.id}-type`;
970
+ const fieldTypeSelectLabel = translate("builder.type");
971
+ const generatedFieldTypeOptions = FIELD_TYPES.filter(
972
+ (type) => allowedTypes.includes(type)
973
+ ).map((type) => {
974
+ const definition = DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
975
+ const group = definition?.category;
976
+ return {
977
+ value: type,
978
+ label: translate(definition?.labelKey ?? `builder.fieldType.${type}`),
979
+ description: translate(`builder.fieldTypeDescription.${type}`),
980
+ icon: components.renderFieldTypeIcon(type),
981
+ ...group === void 0 ? {} : {
982
+ group,
983
+ groupLabel: translate(`builder.fieldCategory.${group}`)
984
+ }
985
+ };
986
+ });
987
+ const fieldTypeOptions = resolveFieldTypeSelectOptions(
988
+ generatedFieldTypeOptions,
989
+ fieldTypeOptionsConfig ?? fieldEditorOptions.fieldTypeOptions,
990
+ {
991
+ currentType: field.type,
992
+ allowedTypes
993
+ }
994
+ );
887
995
  const changeFieldType = (nextType) => {
888
996
  if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
889
997
  };
@@ -918,7 +1026,7 @@ function createMuiFieldEditorSlot(options) {
918
1026
  ] }) : /* @__PURE__ */ jsx13(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
919
1027
  /* @__PURE__ */ jsxs7(Stack3, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
920
1028
  /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
921
- /* @__PURE__ */ jsx13(
1029
+ controls.title === "hidden" ? null : /* @__PURE__ */ jsx13(
922
1030
  TextInput,
923
1031
  {
924
1032
  id: `mui-field-${field.id}-title`,
@@ -929,27 +1037,19 @@ function createMuiFieldEditorSlot(options) {
929
1037
  error: field.title.trim().length === 0,
930
1038
  helperText: field.title.trim().length === 0 ? translate("builder.required") : "",
931
1039
  "aria-describedby": titleErrorId,
932
- disabled: readOnly,
1040
+ disabled: readOnly || controls.title === "readOnly",
933
1041
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
934
1042
  }
935
1043
  ),
936
- FieldTypeSelect === void 0 ? /* @__PURE__ */ jsx13(
1044
+ controls.typeSelect === "hidden" ? null : FieldTypeSelect === void 0 ? /* @__PURE__ */ jsx13(
937
1045
  Select2,
938
1046
  {
939
- id: `mui-field-${field.id}-type`,
1047
+ id: fieldTypeSelectId,
940
1048
  name: `fields.${field.id}.type`,
941
- label: translate("builder.type"),
1049
+ label: fieldTypeSelectLabel,
942
1050
  value: allowedTypes.includes(field.type) ? field.type : "",
943
- options: FIELD_TYPES.filter((type) => allowedTypes.includes(type)).map((type) => {
944
- const definition = DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
945
- return {
946
- value: type,
947
- label: translate(definition?.labelKey ?? `builder.fieldType.${type}`),
948
- icon: components.renderFieldTypeIcon(type),
949
- ...definition?.category === void 0 ? {} : { kind: definition.category }
950
- };
951
- }),
952
- disabled: readOnly,
1051
+ options: fieldTypeOptions,
1052
+ disabled: readOnly || controls.typeSelect === "readOnly",
953
1053
  onChange: (value) => {
954
1054
  if (isFieldType(value)) changeFieldType(value);
955
1055
  }
@@ -957,16 +1057,21 @@ function createMuiFieldEditorSlot(options) {
957
1057
  ) : /* @__PURE__ */ jsx13(
958
1058
  FieldTypeSelect,
959
1059
  {
1060
+ id: fieldTypeSelectId,
1061
+ name: `fields.${field.id}.type`,
1062
+ label: fieldTypeSelectLabel,
960
1063
  currentType: field.type,
961
1064
  allowedTypes,
1065
+ options: fieldTypeOptions,
962
1066
  onChangeType: changeFieldType,
963
- disabled: readOnly,
964
- readOnly,
1067
+ disabled: readOnly || controls.typeSelect === "readOnly",
1068
+ readOnly: readOnly || controls.typeSelect === "readOnly",
1069
+ "aria-labelledby": `${fieldTypeSelectId}-label`,
965
1070
  renderIcon: components.renderFieldTypeIcon
966
1071
  }
967
1072
  )
968
1073
  ] }),
969
- descriptionMode === "hidden" ? null : /* @__PURE__ */ jsx13(
1074
+ controls.description === "hidden" ? null : /* @__PURE__ */ jsx13(
970
1075
  TextArea,
971
1076
  {
972
1077
  id: `mui-field-${field.id}-description`,
@@ -974,19 +1079,19 @@ function createMuiFieldEditorSlot(options) {
974
1079
  label: translate("builder.description"),
975
1080
  value: field.description ?? "",
976
1081
  rows: resolved.dense ? 2 : 3,
977
- disabled: readOnly,
978
- readOnly: descriptionMode === "readOnly",
1082
+ disabled: readOnly || controls.description === "readOnly",
1083
+ readOnly: readOnly || controls.description === "readOnly",
979
1084
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "description", value)
980
1085
  }
981
1086
  ),
982
- /* @__PURE__ */ jsx13(
1087
+ controls.required === "hidden" ? null : /* @__PURE__ */ jsx13(
983
1088
  Checkbox2,
984
1089
  {
985
1090
  id: `mui-field-${field.id}-required`,
986
1091
  name: `fields.${field.id}.required`,
987
1092
  label: translate("builder.required"),
988
1093
  checked: field.required,
989
- disabled: readOnly,
1094
+ disabled: readOnly || controls.required === "readOnly",
990
1095
  onChange: (checked) => actions.updateField(field.id, (current) => ({ ...current, required: checked }))
991
1096
  }
992
1097
  ),
@@ -1004,7 +1109,7 @@ function createMuiFieldEditorSlot(options) {
1004
1109
  onChange: (value) => actions.assignFieldToPage(field.id, value.length === 0 ? null : value)
1005
1110
  }
1006
1111
  ),
1007
- field.type === "number" || field.type === "rating" ? /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", sm: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1112
+ field.type === "number" || field.type === "rating" ? (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "hidden" ? null : /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", sm: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1008
1113
  /* @__PURE__ */ jsx13(
1009
1114
  TextInput,
1010
1115
  {
@@ -1012,7 +1117,7 @@ function createMuiFieldEditorSlot(options) {
1012
1117
  label: translate("builder.minimum"),
1013
1118
  type: "number",
1014
1119
  value: field.min === void 0 ? "" : String(field.min),
1015
- disabled: readOnly,
1120
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1016
1121
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "min", value))
1017
1122
  }
1018
1123
  ),
@@ -1023,12 +1128,68 @@ function createMuiFieldEditorSlot(options) {
1023
1128
  label: translate("builder.maximum"),
1024
1129
  type: "number",
1025
1130
  value: field.max === void 0 ? "" : String(field.max),
1026
- disabled: readOnly,
1131
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1027
1132
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "max", value))
1028
1133
  }
1029
1134
  )
1030
1135
  ] }) : null,
1031
- features?.conditions === false || conditionSources.length === 0 ? null : /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1136
+ field.type === "number" && controls.numberLimits !== "hidden" ? /* @__PURE__ */ jsx13(
1137
+ TextInput,
1138
+ {
1139
+ id: `mui-field-${field.id}-step`,
1140
+ label: translate("builder.step"),
1141
+ type: "number",
1142
+ value: field.step === void 0 ? "" : String(field.step),
1143
+ disabled: readOnly || controls.numberLimits === "readOnly",
1144
+ onChange: (value) => actions.updateField(field.id, (current) => updateNumberProperty(current, "step", value))
1145
+ }
1146
+ ) : null,
1147
+ (field.type === "text" || field.type === "textarea") && controls.textLimits !== "hidden" ? /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", sm: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1148
+ /* @__PURE__ */ jsx13(
1149
+ TextInput,
1150
+ {
1151
+ id: `mui-field-${field.id}-min-length`,
1152
+ label: translate("builder.minimumLength"),
1153
+ type: "number",
1154
+ value: field.minLength === void 0 ? "" : String(field.minLength),
1155
+ disabled: readOnly || controls.textLimits === "readOnly",
1156
+ onChange: (value) => actions.updateField(field.id, (current) => {
1157
+ if (current.type !== "text" && current.type !== "textarea") return current;
1158
+ const parsed = numericValue(value);
1159
+ return parsed === void 0 ? current : { ...current, minLength: Math.max(0, Math.floor(parsed)) };
1160
+ })
1161
+ }
1162
+ ),
1163
+ /* @__PURE__ */ jsx13(
1164
+ TextInput,
1165
+ {
1166
+ id: `mui-field-${field.id}-max-length`,
1167
+ label: translate("builder.maximumLength"),
1168
+ type: "number",
1169
+ value: field.maxLength === void 0 ? "" : String(field.maxLength),
1170
+ disabled: readOnly || controls.textLimits === "readOnly",
1171
+ onChange: (value) => actions.updateField(field.id, (current) => {
1172
+ if (current.type !== "text" && current.type !== "textarea") return current;
1173
+ const parsed = numericValue(value);
1174
+ return parsed === void 0 ? current : { ...current, maxLength: Math.max(0, Math.floor(parsed)) };
1175
+ })
1176
+ }
1177
+ ),
1178
+ /* @__PURE__ */ jsx13(
1179
+ TextInput,
1180
+ {
1181
+ id: `mui-field-${field.id}-pattern`,
1182
+ label: translate("builder.pattern"),
1183
+ value: field.pattern ?? "",
1184
+ disabled: readOnly || controls.textLimits === "readOnly",
1185
+ onChange: (value) => actions.updateField(
1186
+ field.id,
1187
+ (current) => current.type === "text" || current.type === "textarea" ? value.length === 0 ? current : { ...current, pattern: value } : current
1188
+ )
1189
+ }
1190
+ )
1191
+ ] }) : null,
1192
+ features?.conditions === false || conditionSources.length === 0 || controls.displayConditions === "hidden" ? null : /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1032
1193
  /* @__PURE__ */ jsx13(
1033
1194
  Select2,
1034
1195
  {
@@ -1039,7 +1200,7 @@ function createMuiFieldEditorSlot(options) {
1039
1200
  { value: "", label: translate("builder.alwaysVisible") },
1040
1201
  ...conditionSources.map((source) => ({ value: source.id, label: source.title }))
1041
1202
  ],
1042
- disabled: readOnly,
1203
+ disabled: readOnly || controls.displayConditions === "readOnly",
1043
1204
  onChange: (value) => actions.setDisplayCondition(
1044
1205
  field.id,
1045
1206
  value.length === 0 ? void 0 : { questionId: value, operator: "equals", value: "" }
@@ -1056,7 +1217,7 @@ function createMuiFieldEditorSlot(options) {
1056
1217
  options: (conditionSource === void 0 ? [] : conditionOperators(conditionSource)).map(
1057
1218
  (operator) => ({ value: operator, label: translate(`builder.operator.${operator}`) })
1058
1219
  ),
1059
- disabled: readOnly,
1220
+ disabled: readOnly || controls.displayConditions === "readOnly",
1060
1221
  onChange: (value) => {
1061
1222
  if (!isConditionOperator(value)) return;
1062
1223
  actions.setDisplayCondition(
@@ -1072,7 +1233,7 @@ function createMuiFieldEditorSlot(options) {
1072
1233
  id: `mui-field-${field.id}-condition-value`,
1073
1234
  label: translate("builder.conditionValue"),
1074
1235
  value: condition.value === void 0 ? "" : String(condition.value),
1075
- disabled: readOnly,
1236
+ disabled: readOnly || controls.displayConditions === "readOnly",
1076
1237
  onChange: (value) => actions.setDisplayCondition(field.id, { ...condition, value })
1077
1238
  }
1078
1239
  )
@@ -1082,29 +1243,29 @@ function createMuiFieldEditorSlot(options) {
1082
1243
  /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle2", children: translate("builder.translation", {
1083
1244
  locale: resolved.getLocaleLabel?.(currentLocale) ?? currentLocale
1084
1245
  }) }),
1085
- /* @__PURE__ */ jsx13(
1246
+ controls.title === "hidden" ? null : /* @__PURE__ */ jsx13(
1086
1247
  TextInput,
1087
1248
  {
1088
1249
  id: `mui-field-${field.id}-${currentLocale}-title`,
1089
1250
  label: translate("builder.translatedQuestionTitle"),
1090
1251
  value: field.translations?.[currentLocale]?.title ?? "",
1091
- disabled: readOnly,
1252
+ disabled: readOnly || controls.title === "readOnly",
1092
1253
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "title", value)
1093
1254
  }
1094
1255
  ),
1095
- descriptionMode === "hidden" ? null : /* @__PURE__ */ jsx13(
1256
+ controls.description === "hidden" ? null : /* @__PURE__ */ jsx13(
1096
1257
  TextArea,
1097
1258
  {
1098
1259
  id: `mui-field-${field.id}-${currentLocale}-description`,
1099
1260
  label: translate("builder.translatedDescription"),
1100
1261
  value: field.translations?.[currentLocale]?.description ?? "",
1101
- disabled: readOnly,
1102
- readOnly: descriptionMode === "readOnly",
1262
+ disabled: readOnly || controls.description === "readOnly",
1263
+ readOnly: readOnly || controls.description === "readOnly",
1103
1264
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "description", value)
1104
1265
  }
1105
1266
  )
1106
1267
  ] }),
1107
- "options" in field ? /* @__PURE__ */ jsxs7(Stack3, { "data-mui-slot": "options", spacing: resolved.dense ? 1 : 2, children: [
1268
+ "options" in field && controls.options !== "hidden" ? /* @__PURE__ */ jsxs7(Stack3, { "data-mui-slot": "options", spacing: resolved.dense ? 1 : 2, children: [
1108
1269
  /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle2", children: translate("builder.options") }),
1109
1270
  field.options.map((option, optionIndex) => /* @__PURE__ */ jsx13(
1110
1271
  OptionEditor,
@@ -1115,7 +1276,7 @@ function createMuiFieldEditorSlot(options) {
1115
1276
  index: optionIndex,
1116
1277
  currentLocale,
1117
1278
  translate,
1118
- readOnly,
1279
+ readOnly: readOnly || controls.options === "readOnly",
1119
1280
  actions,
1120
1281
  components
1121
1282
  },
@@ -1126,7 +1287,7 @@ function createMuiFieldEditorSlot(options) {
1126
1287
  {
1127
1288
  action: "addOption",
1128
1289
  targetId: field.id,
1129
- disabled: readOnly || policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1290
+ disabled: readOnly || controls.options === "readOnly" || policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1130
1291
  onClick: () => actions.addOption(field.id),
1131
1292
  children: translate("builder.addOption")
1132
1293
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/mui",
3
- "version": "3.2.0",
3
+ "version": "4.1.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -46,8 +46,8 @@
46
46
  "@mui/material": "^6.0.0 || ^7.0.0",
47
47
  "react": "^18.0.0 || ^19.0.0",
48
48
  "react-dom": "^18.0.0 || ^19.0.0",
49
- "@form-engine-ts/core": "3.2.0",
50
- "@form-engine-ts/react": "3.2.0"
49
+ "@form-engine-ts/core": "4.1.0",
50
+ "@form-engine-ts/react": "4.1.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@emotion/react": "^11.14.0",
@@ -58,8 +58,8 @@
58
58
  "@types/react-dom": "^19.2.4",
59
59
  "react": "^19.2.8",
60
60
  "react-dom": "^19.2.8",
61
- "@form-engine-ts/core": "3.2.0",
62
- "@form-engine-ts/react": "3.2.0"
61
+ "@form-engine-ts/core": "4.1.0",
62
+ "@form-engine-ts/react": "4.1.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @emotion/react --external @emotion/styled --external @form-engine-ts/core --external @form-engine-ts/react --external @mui/icons-material --external @mui/material --external react --external react-dom",