@form-engine-ts/mui 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,13 @@ import { MuiFormBuilder } from "@form-engine-ts/mui";
36
36
  emptyStateMessage: "No translation locales have been added yet.",
37
37
  defaultLocaleControl: "readOnly"
38
38
  }}
39
- muiSlotProps={{ card: { sx: { p: 2 } }, accordion: { elevation: 0 } }}
39
+ muiSlotProps={{
40
+ card: { sx: { p: 2 } },
41
+ accordion: { elevation: 0 },
42
+ textField: { "data-testid": "builder-input" },
43
+ select: { variant: "filled" },
44
+ selectMenu: { PaperProps: { elevation: 4 } }
45
+ }}
40
46
  />;
41
47
  ```
42
48
 
@@ -70,6 +76,12 @@ default.
70
76
  `layoutOptions`, `localizationOptions`, and `muiSlotProps` are also accepted in `muiOptions` for low-level factories such
71
77
  as `createMuiBuilderSlots`; the dedicated `MuiFormBuilder` props take precedence.
72
78
 
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
81
+ MUI field editor supplies icons for every field type and accepts `slots.fieldTypeSelect` and `slots.fieldEditorHeader`
82
+ for focused customization. `muiSlotProps` also supports `textField`, `select`, `selectMenu`, `checkbox`, `button`, and
83
+ `iconButton` MUI props in addition to the layout props.
84
+
73
85
  `MuiFormBuilder` supplies options through `MuiFormBuilderContext` to module-stable adapter and slot component types.
74
86
  Controlled schema updates therefore preserve input focus and uncontrolled MUI state such as an open localization
75
87
  accordion even when the parent passes inline option objects.
package/dist/index.cjs CHANGED
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  mergeMuiAdapterOptions: () => mergeMuiAdapterOptions,
57
57
  muiBuilderComponents: () => muiBuilderComponents,
58
58
  muiBuilderSlots: () => muiBuilderSlots,
59
+ muiDefaultFieldTypeIcon: () => muiDefaultFieldTypeIcon,
59
60
  muiDefaultIconResolver: () => muiDefaultIconResolver,
60
61
  resolveMuiAdapterOptions: () => resolveMuiAdapterOptions,
61
62
  useResolvedMuiAdapterOptions: () => useResolvedMuiAdapterOptions
@@ -159,9 +160,11 @@ function createMuiButtonAdapter(options) {
159
160
  targetId
160
161
  }) {
161
162
  const resolved = useResolvedMuiAdapterOptions(options);
163
+ const buttonSlotProps = resolved.muiSlotProps?.button;
162
164
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
163
165
  import_material.Button,
164
166
  {
167
+ ...buttonSlotProps,
165
168
  id,
166
169
  className,
167
170
  type: "button",
@@ -175,7 +178,7 @@ function createMuiButtonAdapter(options) {
175
178
  "data-target-id": targetId,
176
179
  onClick,
177
180
  color: variant === "danger" ? "error" : "primary",
178
- variant: resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
181
+ variant: buttonSlotProps?.variant ?? resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
179
182
  fullWidth: resolved.buttonFullWidth ?? false,
180
183
  sx: { whiteSpace: noWrap === false ? "normal" : "nowrap" },
181
184
  children
@@ -206,6 +209,8 @@ function createMuiCheckboxAdapter(options) {
206
209
  label
207
210
  }) {
208
211
  const resolved = useResolvedMuiAdapterOptions(options);
212
+ const checkboxSlotProps = resolved.muiSlotProps?.checkbox;
213
+ const checkboxInputProps = checkboxSlotProps?.inputProps;
209
214
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
210
215
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
211
216
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_material2.FormControl, { className, error: Boolean(error), required, disabled: disabled || readOnly, children: [
@@ -216,16 +221,18 @@ function createMuiCheckboxAdapter(options) {
216
221
  control: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
217
222
  import_material2.Checkbox,
218
223
  {
224
+ ...checkboxSlotProps,
219
225
  id,
220
226
  name,
221
- size: resolved.size,
227
+ size: checkboxSlotProps?.size ?? resolved.size,
222
228
  checked,
223
229
  required,
224
230
  disabled: disabled || readOnly,
225
231
  inputProps: {
226
- "aria-label": ariaLabel,
227
- "aria-describedby": describedBy,
228
- "aria-labelledby": ariaLabelledBy
232
+ ...checkboxInputProps,
233
+ ...ariaLabel === void 0 ? {} : { "aria-label": ariaLabel },
234
+ ...describedBy === void 0 ? {} : { "aria-describedby": describedBy },
235
+ ...ariaLabelledBy === void 0 ? {} : { "aria-labelledby": ariaLabelledBy }
229
236
  },
230
237
  onChange: (event) => onChange(event.target.checked)
231
238
  }
@@ -330,16 +337,18 @@ function createMuiIconButtonAdapter(options) {
330
337
  title
331
338
  }) {
332
339
  const resolved = useResolvedMuiAdapterOptions(options);
340
+ const iconButtonSlotProps = resolved.muiSlotProps?.iconButton;
333
341
  const actionLabel = actionType === void 0 ? "Action" : resolved.getActionLabel?.(actionType) ?? FALLBACK_ACTION_LABELS[actionType];
334
342
  const tooltip = title ?? actionLabel;
335
343
  const color = actionType === "delete" ? "error" : actionType === "add" ? "primary" : "default";
336
344
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_material5.Tooltip, { title: tooltip, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
337
345
  import_material5.IconButton,
338
346
  {
347
+ ...iconButtonSlotProps,
339
348
  id,
340
349
  className,
341
350
  type: "button",
342
- size: resolved.size,
351
+ size: iconButtonSlotProps?.size ?? resolved.size,
343
352
  color,
344
353
  disabled: disabled || readOnly,
345
354
  "aria-label": ariaLabel ?? tooltip,
@@ -405,6 +414,9 @@ var MuiSectionAdapter = createMuiSectionAdapter();
405
414
  // src/adapters/Select.tsx
406
415
  var import_material7 = require("@mui/material");
407
416
  var import_jsx_runtime7 = require("react/jsx-runtime");
417
+ function normalizeOptions(options) {
418
+ return options.map((option) => typeof option === "string" ? { value: option, label: option } : option);
419
+ }
408
420
  function createMuiSelectAdapter(options) {
409
421
  return function MuiSelect({
410
422
  id,
@@ -422,13 +434,19 @@ function createMuiSelectAdapter(options) {
422
434
  value,
423
435
  onChange,
424
436
  onKeyDown,
425
- options: selectOptions
437
+ options: selectOptions,
438
+ renderOption,
439
+ renderValue
426
440
  }) {
427
441
  const resolved = useResolvedMuiAdapterOptions(options);
442
+ const normalizedOptions = normalizeOptions(selectOptions);
443
+ const selectSlotProps = resolved.muiSlotProps?.select;
444
+ const menuProps = { ...selectSlotProps?.MenuProps, ...resolved.muiSlotProps?.selectMenu };
445
+ const slotInputProps = selectSlotProps?.inputProps;
428
446
  const labelId = id === void 0 || label === void 0 ? void 0 : `${id}-label`;
429
447
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
430
448
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
431
- const handleChange = (event) => onChange(event.target.value);
449
+ const handleChange = (event) => onChange(String(event.target.value));
432
450
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
433
451
  import_material7.FormControl,
434
452
  {
@@ -444,6 +462,7 @@ function createMuiSelectAdapter(options) {
444
462
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
445
463
  import_material7.Select,
446
464
  {
465
+ ...selectSlotProps,
447
466
  id,
448
467
  labelId,
449
468
  name,
@@ -454,12 +473,28 @@ function createMuiSelectAdapter(options) {
454
473
  onKeyDown,
455
474
  required,
456
475
  readOnly,
476
+ variant: selectSlotProps?.variant ?? resolved.variant,
477
+ ...Object.keys(menuProps).length === 0 ? {} : { MenuProps: menuProps },
478
+ renderValue: (selected) => {
479
+ const selectedValue = typeof selected === "string" ? selected : String(selected ?? "");
480
+ const option = normalizedOptions.find((candidate) => candidate.value === selectedValue);
481
+ if (renderValue !== void 0) return renderValue(option);
482
+ if (option === void 0) return selectedValue;
483
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_material7.Box, { display: "flex", alignItems: "center", gap: 0.75, children: [
484
+ option.icon,
485
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: option.label })
486
+ ] });
487
+ },
457
488
  inputProps: {
458
- "aria-label": ariaLabel,
459
- "aria-describedby": describedBy,
460
- "aria-labelledby": ariaLabelledBy
489
+ ...slotInputProps,
490
+ ...ariaLabel === void 0 ? {} : { "aria-label": ariaLabel },
491
+ ...describedBy === void 0 ? {} : { "aria-describedby": describedBy },
492
+ ...ariaLabelledBy === void 0 ? {} : { "aria-labelledby": ariaLabelledBy }
461
493
  },
462
- children: selectOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.MenuItem, { value: option.value, disabled: option.disabled, children: option.label }, option.value))
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))
463
498
  }
464
499
  ),
465
500
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_material7.FormHelperText, { id: helperId, children: helperText })
@@ -494,9 +529,11 @@ function createMuiTextAreaAdapter(options) {
494
529
  rows
495
530
  }) {
496
531
  const resolved = useResolvedMuiAdapterOptions(options);
532
+ const textFieldSlotProps = resolved.muiSlotProps?.textField;
497
533
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
498
534
  import_material8.TextField,
499
535
  {
536
+ ...textFieldSlotProps,
500
537
  id,
501
538
  className,
502
539
  name,
@@ -522,7 +559,7 @@ function createMuiTextAreaAdapter(options) {
522
559
  placeholder,
523
560
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
524
561
  size: resolved.size,
525
- variant: resolved.variant,
562
+ variant: textFieldSlotProps?.variant ?? resolved.variant,
526
563
  onChange: (event) => onChange(event.currentTarget.value)
527
564
  }
528
565
  );
@@ -559,9 +596,11 @@ function createMuiTextInputAdapter(options) {
559
596
  step
560
597
  }) {
561
598
  const resolved = useResolvedMuiAdapterOptions(options);
599
+ const textFieldSlotProps = resolved.muiSlotProps?.textField;
562
600
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
563
601
  import_material9.TextField,
564
602
  {
603
+ ...textFieldSlotProps,
565
604
  id,
566
605
  className,
567
606
  name,
@@ -590,7 +629,7 @@ function createMuiTextInputAdapter(options) {
590
629
  placeholder,
591
630
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
592
631
  size: resolved.size,
593
- variant: resolved.variant,
632
+ variant: textFieldSlotProps?.variant ?? resolved.variant,
594
633
  onChange: (event) => onChange(event.currentTarget.value),
595
634
  onKeyDown
596
635
  }
@@ -624,6 +663,26 @@ function muiDefaultIconResolver(actionType) {
624
663
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.DragHandle, { fontSize: "small" });
625
664
  }
626
665
  }
666
+ function muiDefaultFieldTypeIcon(type) {
667
+ switch (type) {
668
+ case "text":
669
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.TextFields, { fontSize: "small" });
670
+ case "textarea":
671
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.Subject, { fontSize: "small" });
672
+ case "number":
673
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.Numbers, { fontSize: "small" });
674
+ case "rating":
675
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.Star, { fontSize: "small" });
676
+ case "select":
677
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.ArrowDropDown, { fontSize: "small" });
678
+ case "multi-select":
679
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.CheckBox, { fontSize: "small" });
680
+ case "checkbox":
681
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.CheckBox, { fontSize: "small" });
682
+ case "radio":
683
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons_material.RadioButtonChecked, { fontSize: "small" });
684
+ }
685
+ }
627
686
 
628
687
  // src/components.ts
629
688
  function isAdapterOptions(value) {
@@ -640,7 +699,8 @@ function buildMuiBuilderComponents(options) {
640
699
  Section: createMuiSectionAdapter(options),
641
700
  Fieldset: createMuiFieldsetAdapter(options),
642
701
  ErrorMessage: createMuiErrorMessageAdapter(options),
643
- renderIcon: muiDefaultIconResolver
702
+ renderIcon: muiDefaultIconResolver,
703
+ renderFieldTypeIcon: muiDefaultFieldTypeIcon
644
704
  };
645
705
  }
646
706
  var muiBuilderComponents = {
@@ -653,7 +713,8 @@ var muiBuilderComponents = {
653
713
  Section: MuiSectionAdapter,
654
714
  Fieldset: MuiFieldsetAdapter,
655
715
  ErrorMessage: MuiErrorMessageAdapter,
656
- renderIcon: muiDefaultIconResolver
716
+ renderIcon: muiDefaultIconResolver,
717
+ renderFieldTypeIcon: muiDefaultFieldTypeIcon
657
718
  };
658
719
  function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
659
720
  const options = isAdapterOptions(optionsOrOverrides) ? optionsOrOverrides : void 0;
@@ -662,6 +723,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
662
723
  }
663
724
 
664
725
  // src/slots/FieldEditor.tsx
726
+ var import_core = require("@form-engine-ts/core");
665
727
  var import_material12 = require("@mui/material");
666
728
 
667
729
  // src/slots/OptionEditor.tsx
@@ -808,16 +870,7 @@ var MuiToolbarSlot = createMuiToolbarSlot();
808
870
 
809
871
  // src/slots/FieldEditor.tsx
810
872
  var import_jsx_runtime13 = require("react/jsx-runtime");
811
- var FIELD_TYPES = [
812
- "text",
813
- "textarea",
814
- "number",
815
- "rating",
816
- "select",
817
- "multi-select",
818
- "checkbox",
819
- "radio"
820
- ];
873
+ var FIELD_TYPES = import_core.DEFAULT_FIELD_TYPE_DEFINITIONS.map((definition) => definition.type);
821
874
  function isFieldType(value) {
822
875
  return FIELD_TYPES.some((type) => type === value);
823
876
  }
@@ -856,7 +909,8 @@ function createMuiFieldEditorSlot(options) {
856
909
  readOnly,
857
910
  actions,
858
911
  components,
859
- translate
912
+ translate,
913
+ slots
860
914
  }) {
861
915
  const resolved = useResolvedMuiAdapterOptions(options);
862
916
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -867,6 +921,11 @@ function createMuiFieldEditorSlot(options) {
867
921
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
868
922
  const descriptionMode = resolved.fieldEditorOptions?.description ?? "editable";
869
923
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
924
+ const FieldTypeSelect = slots?.fieldTypeSelect;
925
+ const FieldEditorHeader = slots?.fieldEditorHeader;
926
+ const changeFieldType = (nextType) => {
927
+ if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
928
+ };
870
929
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
871
930
  import_material12.Card,
872
931
  {
@@ -875,26 +934,28 @@ function createMuiFieldEditorSlot(options) {
875
934
  variant: "outlined",
876
935
  sx: resolved.muiSlotProps?.card?.sx ?? { mb: resolved.dense ? 1 : 2, p: resolved.dense ? 1.5 : 2 },
877
936
  children: [
878
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
879
- Toolbar,
880
- {
881
- schema,
882
- translate,
883
- kind: "field",
884
- targetId: field.id,
885
- index,
886
- total: schema.fields.length,
887
- title: field.title,
888
- onMoveUp: () => actions.moveField(field.id, index - 1),
889
- onMoveDown: () => actions.moveField(field.id, index + 1),
890
- onRemove: () => actions.removeField(field.id),
891
- readOnly,
892
- actions,
893
- components
894
- }
895
- ),
937
+ FieldEditorHeader === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
938
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
939
+ Toolbar,
940
+ {
941
+ schema,
942
+ translate,
943
+ kind: "field",
944
+ targetId: field.id,
945
+ index,
946
+ total: schema.fields.length,
947
+ title: field.title,
948
+ onMoveUp: () => actions.moveField(field.id, index - 1),
949
+ onMoveDown: () => actions.moveField(field.id, index + 1),
950
+ onRemove: () => actions.removeField(field.id),
951
+ readOnly,
952
+ actions,
953
+ components
954
+ }
955
+ ),
956
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material12.Typography, { variant: "subtitle1", fontWeight: "bold", children: field.title })
957
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
896
958
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
897
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_material12.Typography, { variant: "subtitle1", fontWeight: "bold", children: field.title }),
898
959
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_material12.Stack, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
899
960
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
900
961
  TextInput,
@@ -911,22 +972,37 @@ function createMuiFieldEditorSlot(options) {
911
972
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
912
973
  }
913
974
  ),
914
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
975
+ FieldTypeSelect === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
915
976
  Select2,
916
977
  {
917
978
  id: `mui-field-${field.id}-type`,
918
979
  name: `fields.${field.id}.type`,
919
980
  label: translate("builder.type"),
920
981
  value: allowedTypes.includes(field.type) ? field.type : "",
921
- options: FIELD_TYPES.filter((type) => allowedTypes.includes(type)).map((type) => ({
922
- value: type,
923
- label: translate(`builder.fieldType.${type}`)
924
- })),
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
+ }),
925
991
  disabled: readOnly,
926
992
  onChange: (value) => {
927
- if (isFieldType(value)) actions.changeFieldType(field.id, value);
993
+ if (isFieldType(value)) changeFieldType(value);
928
994
  }
929
995
  }
996
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
997
+ FieldTypeSelect,
998
+ {
999
+ currentType: field.type,
1000
+ allowedTypes,
1001
+ onChangeType: changeFieldType,
1002
+ disabled: readOnly,
1003
+ readOnly,
1004
+ renderIcon: components.renderFieldTypeIcon
1005
+ }
930
1006
  )
931
1007
  ] }),
932
1008
  descriptionMode === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
@@ -1493,6 +1569,7 @@ function MuiFormBuilder({
1493
1569
  mergeMuiAdapterOptions,
1494
1570
  muiBuilderComponents,
1495
1571
  muiBuilderSlots,
1572
+ muiDefaultFieldTypeIcon,
1496
1573
  muiDefaultIconResolver,
1497
1574
  resolveMuiAdapterOptions,
1498
1575
  useResolvedMuiAdapterOptions
package/dist/index.d.cts CHANGED
@@ -1,10 +1,13 @@
1
- import { BuilderActionIconType, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
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';
2
2
  import * as react from 'react';
3
3
  import { ReactNode, ComponentType } from 'react';
4
- import { CardProps, PaperProps, StackProps, AccordionProps } from '@mui/material';
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";
7
7
  type MuiButtonVariant = "contained" | "outlined" | "text";
8
+ type MuiComponentSlotProps<T> = Partial<T> & {
9
+ readonly [key: `data-${string}`]: string | number | boolean | undefined;
10
+ };
8
11
  interface LocaleOptionItem {
9
12
  readonly value: string;
10
13
  readonly label: string;
@@ -28,11 +31,29 @@ interface MuiLocalizationOptions {
28
31
  interface MuiFieldEditorOptions {
29
32
  readonly description?: "editable" | "readOnly" | "hidden";
30
33
  }
31
- interface MuiBuilderSlotProps {
34
+ interface MuiSlotProps {
32
35
  readonly card?: Partial<CardProps>;
33
36
  readonly paper?: Partial<PaperProps>;
37
+ readonly accordion?: Partial<AccordionProps>;
34
38
  readonly stack?: Partial<StackProps>;
39
+ readonly textField?: MuiComponentSlotProps<TextFieldProps>;
40
+ readonly select?: MuiComponentSlotProps<SelectProps>;
41
+ readonly selectMenu?: Partial<MenuProps>;
42
+ readonly checkbox?: MuiComponentSlotProps<CheckboxProps>;
43
+ readonly button?: MuiComponentSlotProps<ButtonProps>;
44
+ readonly iconButton?: MuiComponentSlotProps<IconButtonProps>;
45
+ }
46
+ interface MuiBuilderSlotProps {
47
+ readonly card?: Partial<CardProps>;
48
+ readonly paper?: Partial<PaperProps>;
35
49
  readonly accordion?: Partial<AccordionProps>;
50
+ readonly stack?: Partial<StackProps>;
51
+ readonly textField?: MuiComponentSlotProps<TextFieldProps>;
52
+ readonly select?: MuiComponentSlotProps<SelectProps>;
53
+ readonly selectMenu?: Partial<MenuProps>;
54
+ readonly checkbox?: MuiComponentSlotProps<CheckboxProps>;
55
+ readonly button?: MuiComponentSlotProps<ButtonProps>;
56
+ readonly iconButton?: MuiComponentSlotProps<IconButtonProps>;
36
57
  }
37
58
  interface MuiAdapterOptions {
38
59
  readonly size?: "small" | "medium";
@@ -123,6 +144,7 @@ declare function mergeMuiAdapterOptions(base?: MuiAdapterOptions, overrides?: Mu
123
144
  declare function useResolvedMuiAdapterOptions(overrides?: MuiAdapterOptions): ResolvedMuiAdapterOptions;
124
145
 
125
146
  declare function muiDefaultIconResolver(actionType: BuilderActionIconType): ReactNode;
147
+ declare function muiDefaultFieldTypeIcon(type: QuestionType): ReactNode;
126
148
 
127
149
  interface MuiFormBuilderProps extends Omit<FormBuilderProps, "components" | "disableDefaultStyles" | "slots" | "unstyled"> {
128
150
  readonly muiOptions?: MuiAdapterOptions;
@@ -149,4 +171,4 @@ declare const MuiToolbarSlot: NonNullable<FormBuilderSlots["toolbar"]>;
149
171
  declare const muiBuilderSlots: FormBuilderSlots;
150
172
  declare function createMuiBuilderSlots(options?: MuiAdapterOptions, customOverrides?: Partial<FormBuilderSlots>): FormBuilderSlots;
151
173
 
152
- export { type BuilderSectionName, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
174
+ export { type BuilderSectionName, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, type MuiSlotProps, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultFieldTypeIcon, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,13 @@
1
- import { BuilderActionIconType, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
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';
2
2
  import * as react from 'react';
3
3
  import { ReactNode, ComponentType } from 'react';
4
- import { CardProps, PaperProps, StackProps, AccordionProps } from '@mui/material';
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";
7
7
  type MuiButtonVariant = "contained" | "outlined" | "text";
8
+ type MuiComponentSlotProps<T> = Partial<T> & {
9
+ readonly [key: `data-${string}`]: string | number | boolean | undefined;
10
+ };
8
11
  interface LocaleOptionItem {
9
12
  readonly value: string;
10
13
  readonly label: string;
@@ -28,11 +31,29 @@ interface MuiLocalizationOptions {
28
31
  interface MuiFieldEditorOptions {
29
32
  readonly description?: "editable" | "readOnly" | "hidden";
30
33
  }
31
- interface MuiBuilderSlotProps {
34
+ interface MuiSlotProps {
32
35
  readonly card?: Partial<CardProps>;
33
36
  readonly paper?: Partial<PaperProps>;
37
+ readonly accordion?: Partial<AccordionProps>;
34
38
  readonly stack?: Partial<StackProps>;
39
+ readonly textField?: MuiComponentSlotProps<TextFieldProps>;
40
+ readonly select?: MuiComponentSlotProps<SelectProps>;
41
+ readonly selectMenu?: Partial<MenuProps>;
42
+ readonly checkbox?: MuiComponentSlotProps<CheckboxProps>;
43
+ readonly button?: MuiComponentSlotProps<ButtonProps>;
44
+ readonly iconButton?: MuiComponentSlotProps<IconButtonProps>;
45
+ }
46
+ interface MuiBuilderSlotProps {
47
+ readonly card?: Partial<CardProps>;
48
+ readonly paper?: Partial<PaperProps>;
35
49
  readonly accordion?: Partial<AccordionProps>;
50
+ readonly stack?: Partial<StackProps>;
51
+ readonly textField?: MuiComponentSlotProps<TextFieldProps>;
52
+ readonly select?: MuiComponentSlotProps<SelectProps>;
53
+ readonly selectMenu?: Partial<MenuProps>;
54
+ readonly checkbox?: MuiComponentSlotProps<CheckboxProps>;
55
+ readonly button?: MuiComponentSlotProps<ButtonProps>;
56
+ readonly iconButton?: MuiComponentSlotProps<IconButtonProps>;
36
57
  }
37
58
  interface MuiAdapterOptions {
38
59
  readonly size?: "small" | "medium";
@@ -123,6 +144,7 @@ declare function mergeMuiAdapterOptions(base?: MuiAdapterOptions, overrides?: Mu
123
144
  declare function useResolvedMuiAdapterOptions(overrides?: MuiAdapterOptions): ResolvedMuiAdapterOptions;
124
145
 
125
146
  declare function muiDefaultIconResolver(actionType: BuilderActionIconType): ReactNode;
147
+ declare function muiDefaultFieldTypeIcon(type: QuestionType): ReactNode;
126
148
 
127
149
  interface MuiFormBuilderProps extends Omit<FormBuilderProps, "components" | "disableDefaultStyles" | "slots" | "unstyled"> {
128
150
  readonly muiOptions?: MuiAdapterOptions;
@@ -149,4 +171,4 @@ declare const MuiToolbarSlot: NonNullable<FormBuilderSlots["toolbar"]>;
149
171
  declare const muiBuilderSlots: FormBuilderSlots;
150
172
  declare function createMuiBuilderSlots(options?: MuiAdapterOptions, customOverrides?: Partial<FormBuilderSlots>): FormBuilderSlots;
151
173
 
152
- export { type BuilderSectionName, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
174
+ export { type BuilderSectionName, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, type MuiSlotProps, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultFieldTypeIcon, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
package/dist/index.js CHANGED
@@ -95,9 +95,11 @@ function createMuiButtonAdapter(options) {
95
95
  targetId
96
96
  }) {
97
97
  const resolved = useResolvedMuiAdapterOptions(options);
98
+ const buttonSlotProps = resolved.muiSlotProps?.button;
98
99
  return /* @__PURE__ */ jsx(
99
100
  Button,
100
101
  {
102
+ ...buttonSlotProps,
101
103
  id,
102
104
  className,
103
105
  type: "button",
@@ -111,7 +113,7 @@ function createMuiButtonAdapter(options) {
111
113
  "data-target-id": targetId,
112
114
  onClick,
113
115
  color: variant === "danger" ? "error" : "primary",
114
- variant: resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
116
+ variant: buttonSlotProps?.variant ?? resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
115
117
  fullWidth: resolved.buttonFullWidth ?? false,
116
118
  sx: { whiteSpace: noWrap === false ? "normal" : "nowrap" },
117
119
  children
@@ -142,6 +144,8 @@ function createMuiCheckboxAdapter(options) {
142
144
  label
143
145
  }) {
144
146
  const resolved = useResolvedMuiAdapterOptions(options);
147
+ const checkboxSlotProps = resolved.muiSlotProps?.checkbox;
148
+ const checkboxInputProps = checkboxSlotProps?.inputProps;
145
149
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
146
150
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
147
151
  return /* @__PURE__ */ jsxs(FormControl, { className, error: Boolean(error), required, disabled: disabled || readOnly, children: [
@@ -152,16 +156,18 @@ function createMuiCheckboxAdapter(options) {
152
156
  control: /* @__PURE__ */ jsx2(
153
157
  Checkbox,
154
158
  {
159
+ ...checkboxSlotProps,
155
160
  id,
156
161
  name,
157
- size: resolved.size,
162
+ size: checkboxSlotProps?.size ?? resolved.size,
158
163
  checked,
159
164
  required,
160
165
  disabled: disabled || readOnly,
161
166
  inputProps: {
162
- "aria-label": ariaLabel,
163
- "aria-describedby": describedBy,
164
- "aria-labelledby": ariaLabelledBy
167
+ ...checkboxInputProps,
168
+ ...ariaLabel === void 0 ? {} : { "aria-label": ariaLabel },
169
+ ...describedBy === void 0 ? {} : { "aria-describedby": describedBy },
170
+ ...ariaLabelledBy === void 0 ? {} : { "aria-labelledby": ariaLabelledBy }
165
171
  },
166
172
  onChange: (event) => onChange(event.target.checked)
167
173
  }
@@ -266,16 +272,18 @@ function createMuiIconButtonAdapter(options) {
266
272
  title
267
273
  }) {
268
274
  const resolved = useResolvedMuiAdapterOptions(options);
275
+ const iconButtonSlotProps = resolved.muiSlotProps?.iconButton;
269
276
  const actionLabel = actionType === void 0 ? "Action" : resolved.getActionLabel?.(actionType) ?? FALLBACK_ACTION_LABELS[actionType];
270
277
  const tooltip = title ?? actionLabel;
271
278
  const color = actionType === "delete" ? "error" : actionType === "add" ? "primary" : "default";
272
279
  return /* @__PURE__ */ jsx5(Tooltip, { title: tooltip, children: /* @__PURE__ */ jsx5("span", { children: /* @__PURE__ */ jsx5(
273
280
  IconButton,
274
281
  {
282
+ ...iconButtonSlotProps,
275
283
  id,
276
284
  className,
277
285
  type: "button",
278
- size: resolved.size,
286
+ size: iconButtonSlotProps?.size ?? resolved.size,
279
287
  color,
280
288
  disabled: disabled || readOnly,
281
289
  "aria-label": ariaLabel ?? tooltip,
@@ -339,8 +347,20 @@ function createMuiSectionAdapter(options) {
339
347
  var MuiSectionAdapter = createMuiSectionAdapter();
340
348
 
341
349
  // src/adapters/Select.tsx
342
- import { FormControl as FormControl2, FormHelperText as FormHelperText2, InputLabel, MenuItem, Select } from "@mui/material";
343
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
350
+ import {
351
+ Box as Box2,
352
+ FormControl as FormControl2,
353
+ FormHelperText as FormHelperText2,
354
+ InputLabel,
355
+ ListItemIcon,
356
+ ListItemText,
357
+ MenuItem,
358
+ Select
359
+ } from "@mui/material";
360
+ import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
361
+ function normalizeOptions(options) {
362
+ return options.map((option) => typeof option === "string" ? { value: option, label: option } : option);
363
+ }
344
364
  function createMuiSelectAdapter(options) {
345
365
  return function MuiSelect({
346
366
  id,
@@ -358,13 +378,19 @@ function createMuiSelectAdapter(options) {
358
378
  value,
359
379
  onChange,
360
380
  onKeyDown,
361
- options: selectOptions
381
+ options: selectOptions,
382
+ renderOption,
383
+ renderValue
362
384
  }) {
363
385
  const resolved = useResolvedMuiAdapterOptions(options);
386
+ const normalizedOptions = normalizeOptions(selectOptions);
387
+ const selectSlotProps = resolved.muiSlotProps?.select;
388
+ const menuProps = { ...selectSlotProps?.MenuProps, ...resolved.muiSlotProps?.selectMenu };
389
+ const slotInputProps = selectSlotProps?.inputProps;
364
390
  const labelId = id === void 0 || label === void 0 ? void 0 : `${id}-label`;
365
391
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
366
392
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
367
- const handleChange = (event) => onChange(event.target.value);
393
+ const handleChange = (event) => onChange(String(event.target.value));
368
394
  return /* @__PURE__ */ jsxs4(
369
395
  FormControl2,
370
396
  {
@@ -380,6 +406,7 @@ function createMuiSelectAdapter(options) {
380
406
  /* @__PURE__ */ jsx7(
381
407
  Select,
382
408
  {
409
+ ...selectSlotProps,
383
410
  id,
384
411
  labelId,
385
412
  name,
@@ -390,12 +417,28 @@ function createMuiSelectAdapter(options) {
390
417
  onKeyDown,
391
418
  required,
392
419
  readOnly,
420
+ variant: selectSlotProps?.variant ?? resolved.variant,
421
+ ...Object.keys(menuProps).length === 0 ? {} : { MenuProps: menuProps },
422
+ renderValue: (selected) => {
423
+ const selectedValue = typeof selected === "string" ? selected : String(selected ?? "");
424
+ const option = normalizedOptions.find((candidate) => candidate.value === selectedValue);
425
+ if (renderValue !== void 0) return renderValue(option);
426
+ if (option === void 0) return selectedValue;
427
+ return /* @__PURE__ */ jsxs4(Box2, { display: "flex", alignItems: "center", gap: 0.75, children: [
428
+ option.icon,
429
+ /* @__PURE__ */ jsx7("span", { children: option.label })
430
+ ] });
431
+ },
393
432
  inputProps: {
394
- "aria-label": ariaLabel,
395
- "aria-describedby": describedBy,
396
- "aria-labelledby": ariaLabelledBy
433
+ ...slotInputProps,
434
+ ...ariaLabel === void 0 ? {} : { "aria-label": ariaLabel },
435
+ ...describedBy === void 0 ? {} : { "aria-describedby": describedBy },
436
+ ...ariaLabelledBy === void 0 ? {} : { "aria-labelledby": ariaLabelledBy }
397
437
  },
398
- children: selectOptions.map((option) => /* @__PURE__ */ jsx7(MenuItem, { value: option.value, disabled: option.disabled, children: option.label }, option.value))
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))
399
442
  }
400
443
  ),
401
444
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ jsx7(FormHelperText2, { id: helperId, children: helperText })
@@ -430,9 +473,11 @@ function createMuiTextAreaAdapter(options) {
430
473
  rows
431
474
  }) {
432
475
  const resolved = useResolvedMuiAdapterOptions(options);
476
+ const textFieldSlotProps = resolved.muiSlotProps?.textField;
433
477
  return /* @__PURE__ */ jsx8(
434
478
  TextField,
435
479
  {
480
+ ...textFieldSlotProps,
436
481
  id,
437
482
  className,
438
483
  name,
@@ -458,7 +503,7 @@ function createMuiTextAreaAdapter(options) {
458
503
  placeholder,
459
504
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
460
505
  size: resolved.size,
461
- variant: resolved.variant,
506
+ variant: textFieldSlotProps?.variant ?? resolved.variant,
462
507
  onChange: (event) => onChange(event.currentTarget.value)
463
508
  }
464
509
  );
@@ -495,9 +540,11 @@ function createMuiTextInputAdapter(options) {
495
540
  step
496
541
  }) {
497
542
  const resolved = useResolvedMuiAdapterOptions(options);
543
+ const textFieldSlotProps = resolved.muiSlotProps?.textField;
498
544
  return /* @__PURE__ */ jsx9(
499
545
  TextField2,
500
546
  {
547
+ ...textFieldSlotProps,
501
548
  id,
502
549
  className,
503
550
  name,
@@ -526,7 +573,7 @@ function createMuiTextInputAdapter(options) {
526
573
  placeholder,
527
574
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
528
575
  size: resolved.size,
529
- variant: resolved.variant,
576
+ variant: textFieldSlotProps?.variant ?? resolved.variant,
530
577
  onChange: (event) => onChange(event.currentTarget.value),
531
578
  onKeyDown
532
579
  }
@@ -539,12 +586,19 @@ var MuiTextInputAdapter = createMuiTextInputAdapter();
539
586
  import {
540
587
  Add,
541
588
  ArrowDownward,
589
+ ArrowDropDown,
542
590
  ArrowUpward,
591
+ CheckBox,
543
592
  Close,
544
593
  Delete,
545
594
  DragHandle,
546
595
  Edit,
596
+ Numbers,
597
+ RadioButtonChecked,
547
598
  Settings,
599
+ Star,
600
+ Subject,
601
+ TextFields,
548
602
  Translate
549
603
  } from "@mui/icons-material";
550
604
  import { jsx as jsx10 } from "react/jsx-runtime";
@@ -570,6 +624,26 @@ function muiDefaultIconResolver(actionType) {
570
624
  return /* @__PURE__ */ jsx10(DragHandle, { fontSize: "small" });
571
625
  }
572
626
  }
627
+ function muiDefaultFieldTypeIcon(type) {
628
+ switch (type) {
629
+ case "text":
630
+ return /* @__PURE__ */ jsx10(TextFields, { fontSize: "small" });
631
+ case "textarea":
632
+ return /* @__PURE__ */ jsx10(Subject, { fontSize: "small" });
633
+ case "number":
634
+ return /* @__PURE__ */ jsx10(Numbers, { fontSize: "small" });
635
+ case "rating":
636
+ return /* @__PURE__ */ jsx10(Star, { fontSize: "small" });
637
+ case "select":
638
+ return /* @__PURE__ */ jsx10(ArrowDropDown, { fontSize: "small" });
639
+ case "multi-select":
640
+ return /* @__PURE__ */ jsx10(CheckBox, { fontSize: "small" });
641
+ case "checkbox":
642
+ return /* @__PURE__ */ jsx10(CheckBox, { fontSize: "small" });
643
+ case "radio":
644
+ return /* @__PURE__ */ jsx10(RadioButtonChecked, { fontSize: "small" });
645
+ }
646
+ }
573
647
 
574
648
  // src/components.ts
575
649
  function isAdapterOptions(value) {
@@ -586,7 +660,8 @@ function buildMuiBuilderComponents(options) {
586
660
  Section: createMuiSectionAdapter(options),
587
661
  Fieldset: createMuiFieldsetAdapter(options),
588
662
  ErrorMessage: createMuiErrorMessageAdapter(options),
589
- renderIcon: muiDefaultIconResolver
663
+ renderIcon: muiDefaultIconResolver,
664
+ renderFieldTypeIcon: muiDefaultFieldTypeIcon
590
665
  };
591
666
  }
592
667
  var muiBuilderComponents = {
@@ -599,7 +674,8 @@ var muiBuilderComponents = {
599
674
  Section: MuiSectionAdapter,
600
675
  Fieldset: MuiFieldsetAdapter,
601
676
  ErrorMessage: MuiErrorMessageAdapter,
602
- renderIcon: muiDefaultIconResolver
677
+ renderIcon: muiDefaultIconResolver,
678
+ renderFieldTypeIcon: muiDefaultFieldTypeIcon
603
679
  };
604
680
  function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
605
681
  const options = isAdapterOptions(optionsOrOverrides) ? optionsOrOverrides : void 0;
@@ -608,6 +684,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
608
684
  }
609
685
 
610
686
  // src/slots/FieldEditor.tsx
687
+ import { DEFAULT_FIELD_TYPE_DEFINITIONS } from "@form-engine-ts/core";
611
688
  import { Card, Stack as Stack3, Typography as Typography3 } from "@mui/material";
612
689
 
613
690
  // src/slots/OptionEditor.tsx
@@ -753,17 +830,8 @@ function createMuiToolbarSlot(options) {
753
830
  var MuiToolbarSlot = createMuiToolbarSlot();
754
831
 
755
832
  // src/slots/FieldEditor.tsx
756
- import { Fragment, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
757
- var FIELD_TYPES = [
758
- "text",
759
- "textarea",
760
- "number",
761
- "rating",
762
- "select",
763
- "multi-select",
764
- "checkbox",
765
- "radio"
766
- ];
833
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
834
+ var FIELD_TYPES = DEFAULT_FIELD_TYPE_DEFINITIONS.map((definition) => definition.type);
767
835
  function isFieldType(value) {
768
836
  return FIELD_TYPES.some((type) => type === value);
769
837
  }
@@ -802,7 +870,8 @@ function createMuiFieldEditorSlot(options) {
802
870
  readOnly,
803
871
  actions,
804
872
  components,
805
- translate
873
+ translate,
874
+ slots
806
875
  }) {
807
876
  const resolved = useResolvedMuiAdapterOptions(options);
808
877
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -813,6 +882,11 @@ function createMuiFieldEditorSlot(options) {
813
882
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
814
883
  const descriptionMode = resolved.fieldEditorOptions?.description ?? "editable";
815
884
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
885
+ const FieldTypeSelect = slots?.fieldTypeSelect;
886
+ const FieldEditorHeader = slots?.fieldEditorHeader;
887
+ const changeFieldType = (nextType) => {
888
+ if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
889
+ };
816
890
  return /* @__PURE__ */ jsxs7(
817
891
  Card,
818
892
  {
@@ -821,26 +895,28 @@ function createMuiFieldEditorSlot(options) {
821
895
  variant: "outlined",
822
896
  sx: resolved.muiSlotProps?.card?.sx ?? { mb: resolved.dense ? 1 : 2, p: resolved.dense ? 1.5 : 2 },
823
897
  children: [
824
- /* @__PURE__ */ jsx13(
825
- Toolbar,
826
- {
827
- schema,
828
- translate,
829
- kind: "field",
830
- targetId: field.id,
831
- index,
832
- total: schema.fields.length,
833
- title: field.title,
834
- onMoveUp: () => actions.moveField(field.id, index - 1),
835
- onMoveDown: () => actions.moveField(field.id, index + 1),
836
- onRemove: () => actions.removeField(field.id),
837
- readOnly,
838
- actions,
839
- components
840
- }
841
- ),
898
+ FieldEditorHeader === void 0 ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
899
+ /* @__PURE__ */ jsx13(
900
+ Toolbar,
901
+ {
902
+ schema,
903
+ translate,
904
+ kind: "field",
905
+ targetId: field.id,
906
+ index,
907
+ total: schema.fields.length,
908
+ title: field.title,
909
+ onMoveUp: () => actions.moveField(field.id, index - 1),
910
+ onMoveDown: () => actions.moveField(field.id, index + 1),
911
+ onRemove: () => actions.removeField(field.id),
912
+ readOnly,
913
+ actions,
914
+ components
915
+ }
916
+ ),
917
+ /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle1", fontWeight: "bold", children: field.title })
918
+ ] }) : /* @__PURE__ */ jsx13(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
842
919
  /* @__PURE__ */ jsxs7(Stack3, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
843
- /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle1", fontWeight: "bold", children: field.title }),
844
920
  /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
845
921
  /* @__PURE__ */ jsx13(
846
922
  TextInput,
@@ -857,22 +933,37 @@ function createMuiFieldEditorSlot(options) {
857
933
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
858
934
  }
859
935
  ),
860
- /* @__PURE__ */ jsx13(
936
+ FieldTypeSelect === void 0 ? /* @__PURE__ */ jsx13(
861
937
  Select2,
862
938
  {
863
939
  id: `mui-field-${field.id}-type`,
864
940
  name: `fields.${field.id}.type`,
865
941
  label: translate("builder.type"),
866
942
  value: allowedTypes.includes(field.type) ? field.type : "",
867
- options: FIELD_TYPES.filter((type) => allowedTypes.includes(type)).map((type) => ({
868
- value: type,
869
- label: translate(`builder.fieldType.${type}`)
870
- })),
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
+ }),
871
952
  disabled: readOnly,
872
953
  onChange: (value) => {
873
- if (isFieldType(value)) actions.changeFieldType(field.id, value);
954
+ if (isFieldType(value)) changeFieldType(value);
874
955
  }
875
956
  }
957
+ ) : /* @__PURE__ */ jsx13(
958
+ FieldTypeSelect,
959
+ {
960
+ currentType: field.type,
961
+ allowedTypes,
962
+ onChangeType: changeFieldType,
963
+ disabled: readOnly,
964
+ readOnly,
965
+ renderIcon: components.renderFieldTypeIcon
966
+ }
876
967
  )
877
968
  ] }),
878
969
  descriptionMode === "hidden" ? null : /* @__PURE__ */ jsx13(
@@ -955,7 +1046,7 @@ function createMuiFieldEditorSlot(options) {
955
1046
  )
956
1047
  }
957
1048
  ),
958
- condition === void 0 ? null : /* @__PURE__ */ jsxs7(Fragment, { children: [
1049
+ condition === void 0 ? null : /* @__PURE__ */ jsxs7(Fragment2, { children: [
959
1050
  /* @__PURE__ */ jsx13(
960
1051
  Select2,
961
1052
  {
@@ -1056,7 +1147,7 @@ import {
1056
1147
  AccordionDetails,
1057
1148
  AccordionSummary,
1058
1149
  Alert as Alert2,
1059
- Box as Box2,
1150
+ Box as Box3,
1060
1151
  Chip,
1061
1152
  Stack as Stack4,
1062
1153
  Tab,
@@ -1187,7 +1278,7 @@ function createMuiLocalizationSlot(options) {
1187
1278
  spacing: resolved.dense ? 1 : 2,
1188
1279
  sx: { mt: 1 },
1189
1280
  children: [
1190
- defaultLocaleControl === "hidden" ? null : /* @__PURE__ */ jsx14(Box2, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, children: defaultLocaleControl === "readOnly" ? /* @__PURE__ */ jsxs8(Stack4, { spacing: 0.5, children: [
1281
+ defaultLocaleControl === "hidden" ? null : /* @__PURE__ */ jsx14(Box3, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, children: defaultLocaleControl === "readOnly" ? /* @__PURE__ */ jsxs8(Stack4, { spacing: 0.5, children: [
1191
1282
  /* @__PURE__ */ jsx14(Typography4, { variant: "caption", color: "text.secondary", children: translate("builder.defaultLocale") }),
1192
1283
  /* @__PURE__ */ jsx14(
1193
1284
  Chip,
@@ -1209,7 +1300,7 @@ function createMuiLocalizationSlot(options) {
1209
1300
  }
1210
1301
  }
1211
1302
  ) }),
1212
- /* @__PURE__ */ jsx14(Box2, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, onKeyDownCapture: handleAddKeyDown, children: hasLocaleSelector ? /* @__PURE__ */ jsx14(
1303
+ /* @__PURE__ */ jsx14(Box3, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, onKeyDownCapture: handleAddKeyDown, children: hasLocaleSelector ? /* @__PURE__ */ jsx14(
1213
1304
  Select2,
1214
1305
  {
1215
1306
  id: "mui-builder-new-locale",
@@ -1231,7 +1322,7 @@ function createMuiLocalizationSlot(options) {
1231
1322
  }
1232
1323
  ) }),
1233
1324
  /* @__PURE__ */ jsx14(
1234
- Box2,
1325
+ Box3,
1235
1326
  {
1236
1327
  sx: {
1237
1328
  flexShrink: 0,
@@ -1276,7 +1367,7 @@ function createMuiLocalizationSlot(options) {
1276
1367
  ))
1277
1368
  }
1278
1369
  ),
1279
- !translationAdapterAvailable ? null : /* @__PURE__ */ jsx14(Box2, { sx: { minWidth: "max-content", whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsx14(
1370
+ !translationAdapterAvailable ? null : /* @__PURE__ */ jsx14(Box3, { sx: { minWidth: "max-content", whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsx14(
1280
1371
  Button2,
1281
1372
  {
1282
1373
  variant: "secondary",
@@ -1329,7 +1420,7 @@ function createMuiLocalizationSlot(options) {
1329
1420
  ] });
1330
1421
  }
1331
1422
  return /* @__PURE__ */ jsx14(
1332
- Box2,
1423
+ Box3,
1333
1424
  {
1334
1425
  "data-mui-slot": "localization",
1335
1426
  sx: {
@@ -1451,6 +1542,7 @@ export {
1451
1542
  mergeMuiAdapterOptions,
1452
1543
  muiBuilderComponents,
1453
1544
  muiBuilderSlots,
1545
+ muiDefaultFieldTypeIcon,
1454
1546
  muiDefaultIconResolver,
1455
1547
  resolveMuiAdapterOptions,
1456
1548
  useResolvedMuiAdapterOptions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/mui",
3
- "version": "3.0.0",
3
+ "version": "3.2.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.0.0",
50
- "@form-engine-ts/react": "3.0.0"
49
+ "@form-engine-ts/core": "3.2.0",
50
+ "@form-engine-ts/react": "3.2.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.0.0",
62
- "@form-engine-ts/react": "3.0.0"
61
+ "@form-engine-ts/core": "3.2.0",
62
+ "@form-engine-ts/react": "3.2.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",