@form-engine-ts/mui 4.0.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`,
@@ -82,6 +87,8 @@ in the selected value. The standard
82
87
  MUI field editor supplies icons for every field type and accepts `slots.fieldTypeSelect` and `slots.fieldEditorHeader`
83
88
  for focused customization. `muiSlotProps` also supports `textField`, `select`, `selectMenu`, `checkbox`, `button`, and
84
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.
85
92
 
86
93
  `MuiFormBuilder` supplies options through `MuiFormBuilderContext` to module-stable adapter and slot component types.
87
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
  };
@@ -774,6 +790,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
774
790
 
775
791
  // src/slots/FieldEditor.tsx
776
792
  var import_core = require("@form-engine-ts/core");
793
+ var import_react2 = require("@form-engine-ts/react");
777
794
  var import_material12 = require("@mui/material");
778
795
 
779
796
  // src/slots/OptionEditor.tsx
@@ -946,6 +963,13 @@ function updateBound(field, property, value) {
946
963
  const { [property]: _removed, ...remaining } = field;
947
964
  return remaining;
948
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
+ }
949
973
  function createMuiFieldEditorSlot(options) {
950
974
  const Toolbar = createMuiToolbarSlot(options);
951
975
  const OptionEditor = createMuiOptionEditorSlot(options);
@@ -960,7 +984,9 @@ function createMuiFieldEditorSlot(options) {
960
984
  actions,
961
985
  components,
962
986
  translate,
963
- slots
987
+ slots,
988
+ fieldEditorControls,
989
+ fieldTypeOptions: fieldTypeOptionsConfig
964
990
  }) {
965
991
  const resolved = useResolvedMuiAdapterOptions(options);
966
992
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -969,13 +995,18 @@ function createMuiFieldEditorSlot(options) {
969
995
  const condition = field.displayCondition;
970
996
  const conditionSources = schema.fields.slice(0, index);
971
997
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
972
- 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
+ });
973
1004
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
974
1005
  const FieldTypeSelect = slots?.fieldTypeSelect;
975
1006
  const FieldEditorHeader = slots?.fieldEditorHeader;
976
1007
  const fieldTypeSelectId = `mui-field-${field.id}-type`;
977
1008
  const fieldTypeSelectLabel = translate("builder.type");
978
- const fieldTypeOptions = FIELD_TYPES.filter(
1009
+ const generatedFieldTypeOptions = FIELD_TYPES.filter(
979
1010
  (type) => allowedTypes.includes(type)
980
1011
  ).map((type) => {
981
1012
  const definition = import_core.DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
@@ -991,6 +1022,14 @@ function createMuiFieldEditorSlot(options) {
991
1022
  }
992
1023
  };
993
1024
  });
1025
+ const fieldTypeOptions = (0, import_react2.resolveFieldTypeSelectOptions)(
1026
+ generatedFieldTypeOptions,
1027
+ fieldTypeOptionsConfig ?? fieldEditorOptions.fieldTypeOptions,
1028
+ {
1029
+ currentType: field.type,
1030
+ allowedTypes
1031
+ }
1032
+ );
994
1033
  const changeFieldType = (nextType) => {
995
1034
  if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
996
1035
  };
@@ -1025,7 +1064,7 @@ function createMuiFieldEditorSlot(options) {
1025
1064
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
1026
1065
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
1027
1066
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
1028
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1067
+ controls.title === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1029
1068
  TextInput,
1030
1069
  {
1031
1070
  id: `mui-field-${field.id}-title`,
@@ -1036,11 +1075,11 @@ function createMuiFieldEditorSlot(options) {
1036
1075
  error: field.title.trim().length === 0,
1037
1076
  helperText: field.title.trim().length === 0 ? translate("builder.required") : "",
1038
1077
  "aria-describedby": titleErrorId,
1039
- disabled: readOnly,
1078
+ disabled: readOnly || controls.title === "readOnly",
1040
1079
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
1041
1080
  }
1042
1081
  ),
1043
- FieldTypeSelect === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1082
+ controls.typeSelect === "hidden" ? null : FieldTypeSelect === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1044
1083
  Select2,
1045
1084
  {
1046
1085
  id: fieldTypeSelectId,
@@ -1048,7 +1087,7 @@ function createMuiFieldEditorSlot(options) {
1048
1087
  label: fieldTypeSelectLabel,
1049
1088
  value: allowedTypes.includes(field.type) ? field.type : "",
1050
1089
  options: fieldTypeOptions,
1051
- disabled: readOnly,
1090
+ disabled: readOnly || controls.typeSelect === "readOnly",
1052
1091
  onChange: (value) => {
1053
1092
  if (isFieldType(value)) changeFieldType(value);
1054
1093
  }
@@ -1063,14 +1102,14 @@ function createMuiFieldEditorSlot(options) {
1063
1102
  allowedTypes,
1064
1103
  options: fieldTypeOptions,
1065
1104
  onChangeType: changeFieldType,
1066
- disabled: readOnly,
1067
- readOnly,
1105
+ disabled: readOnly || controls.typeSelect === "readOnly",
1106
+ readOnly: readOnly || controls.typeSelect === "readOnly",
1068
1107
  "aria-labelledby": `${fieldTypeSelectId}-label`,
1069
1108
  renderIcon: components.renderFieldTypeIcon
1070
1109
  }
1071
1110
  )
1072
1111
  ] }),
1073
- descriptionMode === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1112
+ controls.description === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1074
1113
  TextArea,
1075
1114
  {
1076
1115
  id: `mui-field-${field.id}-description`,
@@ -1078,19 +1117,19 @@ function createMuiFieldEditorSlot(options) {
1078
1117
  label: translate("builder.description"),
1079
1118
  value: field.description ?? "",
1080
1119
  rows: resolved.dense ? 2 : 3,
1081
- disabled: readOnly,
1082
- readOnly: descriptionMode === "readOnly",
1120
+ disabled: readOnly || controls.description === "readOnly",
1121
+ readOnly: readOnly || controls.description === "readOnly",
1083
1122
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "description", value)
1084
1123
  }
1085
1124
  ),
1086
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1125
+ controls.required === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1087
1126
  Checkbox2,
1088
1127
  {
1089
1128
  id: `mui-field-${field.id}-required`,
1090
1129
  name: `fields.${field.id}.required`,
1091
1130
  label: translate("builder.required"),
1092
1131
  checked: field.required,
1093
- disabled: readOnly,
1132
+ disabled: readOnly || controls.required === "readOnly",
1094
1133
  onChange: (checked) => actions.updateField(field.id, (current) => ({ ...current, required: checked }))
1095
1134
  }
1096
1135
  ),
@@ -1108,7 +1147,7 @@ function createMuiFieldEditorSlot(options) {
1108
1147
  onChange: (value) => actions.assignFieldToPage(field.id, value.length === 0 ? null : value)
1109
1148
  }
1110
1149
  ),
1111
- 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: [
1112
1151
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1113
1152
  TextInput,
1114
1153
  {
@@ -1116,7 +1155,7 @@ function createMuiFieldEditorSlot(options) {
1116
1155
  label: translate("builder.minimum"),
1117
1156
  type: "number",
1118
1157
  value: field.min === void 0 ? "" : String(field.min),
1119
- disabled: readOnly,
1158
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1120
1159
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "min", value))
1121
1160
  }
1122
1161
  ),
@@ -1127,12 +1166,68 @@ function createMuiFieldEditorSlot(options) {
1127
1166
  label: translate("builder.maximum"),
1128
1167
  type: "number",
1129
1168
  value: field.max === void 0 ? "" : String(field.max),
1130
- disabled: readOnly,
1169
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1131
1170
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "max", value))
1132
1171
  }
1133
1172
  )
1134
1173
  ] }) : null,
1135
- 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: [
1136
1231
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1137
1232
  Select2,
1138
1233
  {
@@ -1143,7 +1238,7 @@ function createMuiFieldEditorSlot(options) {
1143
1238
  { value: "", label: translate("builder.alwaysVisible") },
1144
1239
  ...conditionSources.map((source) => ({ value: source.id, label: source.title }))
1145
1240
  ],
1146
- disabled: readOnly,
1241
+ disabled: readOnly || controls.displayConditions === "readOnly",
1147
1242
  onChange: (value) => actions.setDisplayCondition(
1148
1243
  field.id,
1149
1244
  value.length === 0 ? void 0 : { questionId: value, operator: "equals", value: "" }
@@ -1160,7 +1255,7 @@ function createMuiFieldEditorSlot(options) {
1160
1255
  options: (conditionSource === void 0 ? [] : conditionOperators(conditionSource)).map(
1161
1256
  (operator) => ({ value: operator, label: translate(`builder.operator.${operator}`) })
1162
1257
  ),
1163
- disabled: readOnly,
1258
+ disabled: readOnly || controls.displayConditions === "readOnly",
1164
1259
  onChange: (value) => {
1165
1260
  if (!isConditionOperator(value)) return;
1166
1261
  actions.setDisplayCondition(
@@ -1176,7 +1271,7 @@ function createMuiFieldEditorSlot(options) {
1176
1271
  id: `mui-field-${field.id}-condition-value`,
1177
1272
  label: translate("builder.conditionValue"),
1178
1273
  value: condition.value === void 0 ? "" : String(condition.value),
1179
- disabled: readOnly,
1274
+ disabled: readOnly || controls.displayConditions === "readOnly",
1180
1275
  onChange: (value) => actions.setDisplayCondition(field.id, { ...condition, value })
1181
1276
  }
1182
1277
  )
@@ -1186,29 +1281,29 @@ function createMuiFieldEditorSlot(options) {
1186
1281
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material12.Typography, { variant: "subtitle2", children: translate("builder.translation", {
1187
1282
  locale: resolved.getLocaleLabel?.(currentLocale) ?? currentLocale
1188
1283
  }) }),
1189
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1284
+ controls.title === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1190
1285
  TextInput,
1191
1286
  {
1192
1287
  id: `mui-field-${field.id}-${currentLocale}-title`,
1193
1288
  label: translate("builder.translatedQuestionTitle"),
1194
1289
  value: field.translations?.[currentLocale]?.title ?? "",
1195
- disabled: readOnly,
1290
+ disabled: readOnly || controls.title === "readOnly",
1196
1291
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "title", value)
1197
1292
  }
1198
1293
  ),
1199
- descriptionMode === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1294
+ controls.description === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1200
1295
  TextArea,
1201
1296
  {
1202
1297
  id: `mui-field-${field.id}-${currentLocale}-description`,
1203
1298
  label: translate("builder.translatedDescription"),
1204
1299
  value: field.translations?.[currentLocale]?.description ?? "",
1205
- disabled: readOnly,
1206
- readOnly: descriptionMode === "readOnly",
1300
+ disabled: readOnly || controls.description === "readOnly",
1301
+ readOnly: readOnly || controls.description === "readOnly",
1207
1302
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "description", value)
1208
1303
  }
1209
1304
  )
1210
1305
  ] }),
1211
- "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: [
1212
1307
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material12.Typography, { variant: "subtitle2", children: translate("builder.options") }),
1213
1308
  field.options.map((option, optionIndex) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1214
1309
  OptionEditor,
@@ -1219,7 +1314,7 @@ function createMuiFieldEditorSlot(options) {
1219
1314
  index: optionIndex,
1220
1315
  currentLocale,
1221
1316
  translate,
1222
- readOnly,
1317
+ readOnly: readOnly || controls.options === "readOnly",
1223
1318
  actions,
1224
1319
  components
1225
1320
  },
@@ -1230,7 +1325,7 @@ function createMuiFieldEditorSlot(options) {
1230
1325
  {
1231
1326
  action: "addOption",
1232
1327
  targetId: field.id,
1233
- 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,
1234
1329
  onClick: () => actions.addOption(field.id),
1235
1330
  children: translate("builder.addOption")
1236
1331
  }
@@ -1247,7 +1342,7 @@ var MuiFieldEditorSlot = createMuiFieldEditorSlot();
1247
1342
  // src/slots/Localization.tsx
1248
1343
  var import_icons_material2 = require("@mui/icons-material");
1249
1344
  var import_material13 = require("@mui/material");
1250
- var import_react2 = require("react");
1345
+ var import_react3 = require("react");
1251
1346
  var import_jsx_runtime14 = require("react/jsx-runtime");
1252
1347
  var DEFAULT_EMPTY_STATE_MESSAGE = "No translation locales have been added yet. Select a language from the dropdown above to add one.";
1253
1348
  function normalizeLocaleOptions(options, getLocaleLabel) {
@@ -1278,8 +1373,8 @@ function createMuiLocalizationSlot(options) {
1278
1373
  }) {
1279
1374
  const resolved = useResolvedMuiAdapterOptions(options);
1280
1375
  const { Button: Button2, ErrorMessage, Select: Select2, TextArea, TextInput } = components;
1281
- const [newLocale, setNewLocale] = (0, import_react2.useState)("");
1282
- 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);
1283
1378
  const locales = Array.from(
1284
1379
  new Set((schema.supportedLocales ?? []).filter((locale) => locale !== schema.defaultLocale))
1285
1380
  );
@@ -1318,7 +1413,7 @@ function createMuiLocalizationSlot(options) {
1318
1413
  event.stopPropagation();
1319
1414
  addLocale();
1320
1415
  };
1321
- (0, import_react2.useEffect)(() => {
1416
+ (0, import_react3.useEffect)(() => {
1322
1417
  if (pendingFocusLocale === null || currentLocale !== pendingFocusLocale) return;
1323
1418
  const tab = document.getElementById(`mui-builder-locale-tab-${pendingFocusLocale}`);
1324
1419
  if (tab === null) return;
@@ -1559,8 +1654,8 @@ function createMuiBuilderProps(options, overrides = {}) {
1559
1654
  }
1560
1655
 
1561
1656
  // src/MuiFormBuilder.tsx
1562
- var import_react3 = require("@form-engine-ts/react");
1563
- var import_react4 = require("react");
1657
+ var import_react4 = require("@form-engine-ts/react");
1658
+ var import_react5 = require("react");
1564
1659
  var import_jsx_runtime15 = require("react/jsx-runtime");
1565
1660
  function MuiFormBuilder({
1566
1661
  muiOptions,
@@ -1572,7 +1667,7 @@ function MuiFormBuilder({
1572
1667
  sectionOrder,
1573
1668
  ...props
1574
1669
  }) {
1575
- const contextOptions = (0, import_react4.useMemo)(
1670
+ const contextOptions = (0, import_react5.useMemo)(
1576
1671
  () => mergeMuiAdapterOptions(muiOptions, {
1577
1672
  ...layoutOptions === void 0 ? {} : { layoutOptions },
1578
1673
  ...localizationOptions === void 0 ? {} : { localizationOptions },
@@ -1580,13 +1675,13 @@ function MuiFormBuilder({
1580
1675
  }),
1581
1676
  [layoutOptions, localizationOptions, muiOptions, muiSlotProps]
1582
1677
  );
1583
- const contextValue = (0, import_react4.useMemo)(() => ({ options: contextOptions }), [contextOptions]);
1584
- const components = (0, import_react4.useMemo)(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1585
- 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]);
1586
1681
  const placement = contextOptions.localizationOptions?.placement;
1587
1682
  const resolvedSectionOrder = sectionOrder ?? (placement === void 0 ? contextOptions.layoutOptions?.sectionOrder : MUI_LOCALIZATION_SECTION_ORDERS[placement]);
1588
1683
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1589
- import_react3.FormBuilder,
1684
+ import_react4.FormBuilder,
1590
1685
  {
1591
1686
  ...props,
1592
1687
  components,
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
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
3
  import { ReactNode, ComponentType, ReactElement } from 'react';
4
4
  import { CardProps, PaperProps, AccordionProps, StackProps, TextFieldProps, SelectProps, MenuProps, CheckboxProps, ButtonProps, IconButtonProps } from '@mui/material';
@@ -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>;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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
3
  import { ReactNode, ComponentType, ReactElement } from 'react';
4
4
  import { CardProps, PaperProps, AccordionProps, StackProps, TextFieldProps, SelectProps, MenuProps, CheckboxProps, ButtonProps, IconButtonProps } from '@mui/material';
@@ -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>;
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
  };
@@ -736,6 +752,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
736
752
 
737
753
  // src/slots/FieldEditor.tsx
738
754
  import { DEFAULT_FIELD_TYPE_DEFINITIONS } from "@form-engine-ts/core";
755
+ import { resolveFieldEditorControls, resolveFieldTypeSelectOptions } from "@form-engine-ts/react";
739
756
  import { Card, Stack as Stack3, Typography as Typography3 } from "@mui/material";
740
757
 
741
758
  // src/slots/OptionEditor.tsx
@@ -908,6 +925,13 @@ function updateBound(field, property, value) {
908
925
  const { [property]: _removed, ...remaining } = field;
909
926
  return remaining;
910
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
+ }
911
935
  function createMuiFieldEditorSlot(options) {
912
936
  const Toolbar = createMuiToolbarSlot(options);
913
937
  const OptionEditor = createMuiOptionEditorSlot(options);
@@ -922,7 +946,9 @@ function createMuiFieldEditorSlot(options) {
922
946
  actions,
923
947
  components,
924
948
  translate,
925
- slots
949
+ slots,
950
+ fieldEditorControls,
951
+ fieldTypeOptions: fieldTypeOptionsConfig
926
952
  }) {
927
953
  const resolved = useResolvedMuiAdapterOptions(options);
928
954
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -931,13 +957,18 @@ function createMuiFieldEditorSlot(options) {
931
957
  const condition = field.displayCondition;
932
958
  const conditionSources = schema.fields.slice(0, index);
933
959
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
934
- 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
+ });
935
966
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
936
967
  const FieldTypeSelect = slots?.fieldTypeSelect;
937
968
  const FieldEditorHeader = slots?.fieldEditorHeader;
938
969
  const fieldTypeSelectId = `mui-field-${field.id}-type`;
939
970
  const fieldTypeSelectLabel = translate("builder.type");
940
- const fieldTypeOptions = FIELD_TYPES.filter(
971
+ const generatedFieldTypeOptions = FIELD_TYPES.filter(
941
972
  (type) => allowedTypes.includes(type)
942
973
  ).map((type) => {
943
974
  const definition = DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
@@ -953,6 +984,14 @@ function createMuiFieldEditorSlot(options) {
953
984
  }
954
985
  };
955
986
  });
987
+ const fieldTypeOptions = resolveFieldTypeSelectOptions(
988
+ generatedFieldTypeOptions,
989
+ fieldTypeOptionsConfig ?? fieldEditorOptions.fieldTypeOptions,
990
+ {
991
+ currentType: field.type,
992
+ allowedTypes
993
+ }
994
+ );
956
995
  const changeFieldType = (nextType) => {
957
996
  if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
958
997
  };
@@ -987,7 +1026,7 @@ function createMuiFieldEditorSlot(options) {
987
1026
  ] }) : /* @__PURE__ */ jsx13(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
988
1027
  /* @__PURE__ */ jsxs7(Stack3, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
989
1028
  /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
990
- /* @__PURE__ */ jsx13(
1029
+ controls.title === "hidden" ? null : /* @__PURE__ */ jsx13(
991
1030
  TextInput,
992
1031
  {
993
1032
  id: `mui-field-${field.id}-title`,
@@ -998,11 +1037,11 @@ function createMuiFieldEditorSlot(options) {
998
1037
  error: field.title.trim().length === 0,
999
1038
  helperText: field.title.trim().length === 0 ? translate("builder.required") : "",
1000
1039
  "aria-describedby": titleErrorId,
1001
- disabled: readOnly,
1040
+ disabled: readOnly || controls.title === "readOnly",
1002
1041
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
1003
1042
  }
1004
1043
  ),
1005
- FieldTypeSelect === void 0 ? /* @__PURE__ */ jsx13(
1044
+ controls.typeSelect === "hidden" ? null : FieldTypeSelect === void 0 ? /* @__PURE__ */ jsx13(
1006
1045
  Select2,
1007
1046
  {
1008
1047
  id: fieldTypeSelectId,
@@ -1010,7 +1049,7 @@ function createMuiFieldEditorSlot(options) {
1010
1049
  label: fieldTypeSelectLabel,
1011
1050
  value: allowedTypes.includes(field.type) ? field.type : "",
1012
1051
  options: fieldTypeOptions,
1013
- disabled: readOnly,
1052
+ disabled: readOnly || controls.typeSelect === "readOnly",
1014
1053
  onChange: (value) => {
1015
1054
  if (isFieldType(value)) changeFieldType(value);
1016
1055
  }
@@ -1025,14 +1064,14 @@ function createMuiFieldEditorSlot(options) {
1025
1064
  allowedTypes,
1026
1065
  options: fieldTypeOptions,
1027
1066
  onChangeType: changeFieldType,
1028
- disabled: readOnly,
1029
- readOnly,
1067
+ disabled: readOnly || controls.typeSelect === "readOnly",
1068
+ readOnly: readOnly || controls.typeSelect === "readOnly",
1030
1069
  "aria-labelledby": `${fieldTypeSelectId}-label`,
1031
1070
  renderIcon: components.renderFieldTypeIcon
1032
1071
  }
1033
1072
  )
1034
1073
  ] }),
1035
- descriptionMode === "hidden" ? null : /* @__PURE__ */ jsx13(
1074
+ controls.description === "hidden" ? null : /* @__PURE__ */ jsx13(
1036
1075
  TextArea,
1037
1076
  {
1038
1077
  id: `mui-field-${field.id}-description`,
@@ -1040,19 +1079,19 @@ function createMuiFieldEditorSlot(options) {
1040
1079
  label: translate("builder.description"),
1041
1080
  value: field.description ?? "",
1042
1081
  rows: resolved.dense ? 2 : 3,
1043
- disabled: readOnly,
1044
- readOnly: descriptionMode === "readOnly",
1082
+ disabled: readOnly || controls.description === "readOnly",
1083
+ readOnly: readOnly || controls.description === "readOnly",
1045
1084
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "description", value)
1046
1085
  }
1047
1086
  ),
1048
- /* @__PURE__ */ jsx13(
1087
+ controls.required === "hidden" ? null : /* @__PURE__ */ jsx13(
1049
1088
  Checkbox2,
1050
1089
  {
1051
1090
  id: `mui-field-${field.id}-required`,
1052
1091
  name: `fields.${field.id}.required`,
1053
1092
  label: translate("builder.required"),
1054
1093
  checked: field.required,
1055
- disabled: readOnly,
1094
+ disabled: readOnly || controls.required === "readOnly",
1056
1095
  onChange: (checked) => actions.updateField(field.id, (current) => ({ ...current, required: checked }))
1057
1096
  }
1058
1097
  ),
@@ -1070,7 +1109,7 @@ function createMuiFieldEditorSlot(options) {
1070
1109
  onChange: (value) => actions.assignFieldToPage(field.id, value.length === 0 ? null : value)
1071
1110
  }
1072
1111
  ),
1073
- 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: [
1074
1113
  /* @__PURE__ */ jsx13(
1075
1114
  TextInput,
1076
1115
  {
@@ -1078,7 +1117,7 @@ function createMuiFieldEditorSlot(options) {
1078
1117
  label: translate("builder.minimum"),
1079
1118
  type: "number",
1080
1119
  value: field.min === void 0 ? "" : String(field.min),
1081
- disabled: readOnly,
1120
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1082
1121
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "min", value))
1083
1122
  }
1084
1123
  ),
@@ -1089,12 +1128,68 @@ function createMuiFieldEditorSlot(options) {
1089
1128
  label: translate("builder.maximum"),
1090
1129
  type: "number",
1091
1130
  value: field.max === void 0 ? "" : String(field.max),
1092
- disabled: readOnly,
1131
+ disabled: readOnly || (field.type === "number" ? controls.numberLimits : controls.ratingBounds) === "readOnly",
1093
1132
  onChange: (value) => actions.updateField(field.id, (current) => updateBound(current, "max", value))
1094
1133
  }
1095
1134
  )
1096
1135
  ] }) : null,
1097
- 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: [
1098
1193
  /* @__PURE__ */ jsx13(
1099
1194
  Select2,
1100
1195
  {
@@ -1105,7 +1200,7 @@ function createMuiFieldEditorSlot(options) {
1105
1200
  { value: "", label: translate("builder.alwaysVisible") },
1106
1201
  ...conditionSources.map((source) => ({ value: source.id, label: source.title }))
1107
1202
  ],
1108
- disabled: readOnly,
1203
+ disabled: readOnly || controls.displayConditions === "readOnly",
1109
1204
  onChange: (value) => actions.setDisplayCondition(
1110
1205
  field.id,
1111
1206
  value.length === 0 ? void 0 : { questionId: value, operator: "equals", value: "" }
@@ -1122,7 +1217,7 @@ function createMuiFieldEditorSlot(options) {
1122
1217
  options: (conditionSource === void 0 ? [] : conditionOperators(conditionSource)).map(
1123
1218
  (operator) => ({ value: operator, label: translate(`builder.operator.${operator}`) })
1124
1219
  ),
1125
- disabled: readOnly,
1220
+ disabled: readOnly || controls.displayConditions === "readOnly",
1126
1221
  onChange: (value) => {
1127
1222
  if (!isConditionOperator(value)) return;
1128
1223
  actions.setDisplayCondition(
@@ -1138,7 +1233,7 @@ function createMuiFieldEditorSlot(options) {
1138
1233
  id: `mui-field-${field.id}-condition-value`,
1139
1234
  label: translate("builder.conditionValue"),
1140
1235
  value: condition.value === void 0 ? "" : String(condition.value),
1141
- disabled: readOnly,
1236
+ disabled: readOnly || controls.displayConditions === "readOnly",
1142
1237
  onChange: (value) => actions.setDisplayCondition(field.id, { ...condition, value })
1143
1238
  }
1144
1239
  )
@@ -1148,29 +1243,29 @@ function createMuiFieldEditorSlot(options) {
1148
1243
  /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle2", children: translate("builder.translation", {
1149
1244
  locale: resolved.getLocaleLabel?.(currentLocale) ?? currentLocale
1150
1245
  }) }),
1151
- /* @__PURE__ */ jsx13(
1246
+ controls.title === "hidden" ? null : /* @__PURE__ */ jsx13(
1152
1247
  TextInput,
1153
1248
  {
1154
1249
  id: `mui-field-${field.id}-${currentLocale}-title`,
1155
1250
  label: translate("builder.translatedQuestionTitle"),
1156
1251
  value: field.translations?.[currentLocale]?.title ?? "",
1157
- disabled: readOnly,
1252
+ disabled: readOnly || controls.title === "readOnly",
1158
1253
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "title", value)
1159
1254
  }
1160
1255
  ),
1161
- descriptionMode === "hidden" ? null : /* @__PURE__ */ jsx13(
1256
+ controls.description === "hidden" ? null : /* @__PURE__ */ jsx13(
1162
1257
  TextArea,
1163
1258
  {
1164
1259
  id: `mui-field-${field.id}-${currentLocale}-description`,
1165
1260
  label: translate("builder.translatedDescription"),
1166
1261
  value: field.translations?.[currentLocale]?.description ?? "",
1167
- disabled: readOnly,
1168
- readOnly: descriptionMode === "readOnly",
1262
+ disabled: readOnly || controls.description === "readOnly",
1263
+ readOnly: readOnly || controls.description === "readOnly",
1169
1264
  onChange: (value) => actions.setManualTranslation(currentLocale, { kind: "field", id: field.id }, "description", value)
1170
1265
  }
1171
1266
  )
1172
1267
  ] }),
1173
- "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: [
1174
1269
  /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle2", children: translate("builder.options") }),
1175
1270
  field.options.map((option, optionIndex) => /* @__PURE__ */ jsx13(
1176
1271
  OptionEditor,
@@ -1181,7 +1276,7 @@ function createMuiFieldEditorSlot(options) {
1181
1276
  index: optionIndex,
1182
1277
  currentLocale,
1183
1278
  translate,
1184
- readOnly,
1279
+ readOnly: readOnly || controls.options === "readOnly",
1185
1280
  actions,
1186
1281
  components
1187
1282
  },
@@ -1192,7 +1287,7 @@ function createMuiFieldEditorSlot(options) {
1192
1287
  {
1193
1288
  action: "addOption",
1194
1289
  targetId: field.id,
1195
- 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,
1196
1291
  onClick: () => actions.addOption(field.id),
1197
1292
  children: translate("builder.addOption")
1198
1293
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/mui",
3
- "version": "4.0.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": "4.0.0",
50
- "@form-engine-ts/react": "4.0.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/react": "4.0.0",
62
- "@form-engine-ts/core": "4.0.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",