@form-engine-ts/mui 2.9.6 → 3.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/dist/index.js CHANGED
@@ -9,8 +9,15 @@ var DEFAULT_MUI_SECTION_ORDER = [
9
9
  "basicSettings",
10
10
  "questions",
11
11
  "addQuestion",
12
- "localization"
12
+ "localization",
13
+ "completionMessage"
13
14
  ];
15
+ var MUI_LOCALIZATION_SECTION_ORDERS = {
16
+ top: ["localization", "basicSettings", "completionMessage", "questions", "addQuestion"],
17
+ beforeQuestions: ["basicSettings", "localization", "completionMessage", "questions", "addQuestion"],
18
+ afterQuestions: ["basicSettings", "questions", "addQuestion", "localization", "completionMessage"],
19
+ bottom: ["basicSettings", "completionMessage", "questions", "addQuestion", "localization"]
20
+ };
14
21
  function resolveMuiAdapterOptions(options = {}) {
15
22
  const buttonVariant = options.buttonVariant ?? "contained";
16
23
  return {
@@ -36,8 +43,13 @@ function resolveMuiAdapterOptions(options = {}) {
36
43
  collapsible: options.localizationOptions?.collapsible ?? false,
37
44
  defaultExpanded: options.localizationOptions?.defaultExpanded ?? false,
38
45
  showSummary: options.localizationOptions?.showSummary ?? false,
46
+ ...options.localizationOptions?.availableLocales === void 0 ? {} : { availableLocales: options.localizationOptions.availableLocales },
47
+ ...options.localizationOptions?.placement === void 0 ? {} : { placement: options.localizationOptions.placement },
48
+ ...options.localizationOptions?.renderSummary === void 0 ? {} : { renderSummary: options.localizationOptions.renderSummary },
39
49
  ...options.localizationOptions?.emptyStateMessage === void 0 ? {} : { emptyStateMessage: options.localizationOptions.emptyStateMessage },
40
- defaultLocaleControl: options.localizationOptions?.defaultLocaleControl ?? "editable"
50
+ defaultLocaleControl: options.localizationOptions?.defaultLocaleControl ?? "editable",
51
+ noWrapActions: options.localizationOptions?.noWrapActions ?? true,
52
+ autoFocusNewTab: options.localizationOptions?.autoFocusNewTab ?? true
41
53
  },
42
54
  muiSlotProps: options.muiSlotProps ?? {}
43
55
  };
@@ -75,6 +87,7 @@ function createMuiButtonAdapter(options) {
75
87
  "aria-describedby": ariaDescribedBy,
76
88
  "aria-labelledby": ariaLabelledBy,
77
89
  onClick,
90
+ noWrap,
78
91
  children,
79
92
  title,
80
93
  variant,
@@ -82,9 +95,11 @@ function createMuiButtonAdapter(options) {
82
95
  targetId
83
96
  }) {
84
97
  const resolved = useResolvedMuiAdapterOptions(options);
98
+ const buttonSlotProps = resolved.muiSlotProps?.button;
85
99
  return /* @__PURE__ */ jsx(
86
100
  Button,
87
101
  {
102
+ ...buttonSlotProps,
88
103
  id,
89
104
  className,
90
105
  type: "button",
@@ -98,9 +113,9 @@ function createMuiButtonAdapter(options) {
98
113
  "data-target-id": targetId,
99
114
  onClick,
100
115
  color: variant === "danger" ? "error" : "primary",
101
- variant: resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
116
+ variant: buttonSlotProps?.variant ?? resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
102
117
  fullWidth: resolved.buttonFullWidth ?? false,
103
- sx: { whiteSpace: "nowrap" },
118
+ sx: { whiteSpace: noWrap === false ? "normal" : "nowrap" },
104
119
  children
105
120
  }
106
121
  );
@@ -129,6 +144,7 @@ function createMuiCheckboxAdapter(options) {
129
144
  label
130
145
  }) {
131
146
  const resolved = useResolvedMuiAdapterOptions(options);
147
+ const checkboxSlotProps = resolved.muiSlotProps?.checkbox;
132
148
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
133
149
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
134
150
  return /* @__PURE__ */ jsxs(FormControl, { className, error: Boolean(error), required, disabled: disabled || readOnly, children: [
@@ -139,9 +155,10 @@ function createMuiCheckboxAdapter(options) {
139
155
  control: /* @__PURE__ */ jsx2(
140
156
  Checkbox,
141
157
  {
158
+ ...checkboxSlotProps,
142
159
  id,
143
160
  name,
144
- size: resolved.size,
161
+ size: checkboxSlotProps?.size ?? resolved.size,
145
162
  checked,
146
163
  required,
147
164
  disabled: disabled || readOnly,
@@ -253,16 +270,18 @@ function createMuiIconButtonAdapter(options) {
253
270
  title
254
271
  }) {
255
272
  const resolved = useResolvedMuiAdapterOptions(options);
273
+ const iconButtonSlotProps = resolved.muiSlotProps?.iconButton;
256
274
  const actionLabel = actionType === void 0 ? "Action" : resolved.getActionLabel?.(actionType) ?? FALLBACK_ACTION_LABELS[actionType];
257
275
  const tooltip = title ?? actionLabel;
258
276
  const color = actionType === "delete" ? "error" : actionType === "add" ? "primary" : "default";
259
277
  return /* @__PURE__ */ jsx5(Tooltip, { title: tooltip, children: /* @__PURE__ */ jsx5("span", { children: /* @__PURE__ */ jsx5(
260
278
  IconButton,
261
279
  {
280
+ ...iconButtonSlotProps,
262
281
  id,
263
282
  className,
264
283
  type: "button",
265
- size: resolved.size,
284
+ size: iconButtonSlotProps?.size ?? resolved.size,
266
285
  color,
267
286
  disabled: disabled || readOnly,
268
287
  "aria-label": ariaLabel ?? tooltip,
@@ -326,8 +345,20 @@ function createMuiSectionAdapter(options) {
326
345
  var MuiSectionAdapter = createMuiSectionAdapter();
327
346
 
328
347
  // src/adapters/Select.tsx
329
- import { FormControl as FormControl2, FormHelperText as FormHelperText2, InputLabel, MenuItem, Select } from "@mui/material";
330
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
348
+ import {
349
+ Box as Box2,
350
+ FormControl as FormControl2,
351
+ FormHelperText as FormHelperText2,
352
+ InputLabel,
353
+ ListItemIcon,
354
+ ListItemText,
355
+ MenuItem,
356
+ Select
357
+ } from "@mui/material";
358
+ import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
359
+ function normalizeOptions(options) {
360
+ return options.map((option) => typeof option === "string" ? { value: option, label: option } : option);
361
+ }
331
362
  function createMuiSelectAdapter(options) {
332
363
  return function MuiSelect({
333
364
  id,
@@ -344,13 +375,19 @@ function createMuiSelectAdapter(options) {
344
375
  helperText,
345
376
  value,
346
377
  onChange,
347
- options: selectOptions
378
+ onKeyDown,
379
+ options: selectOptions,
380
+ renderOption,
381
+ renderValue
348
382
  }) {
349
383
  const resolved = useResolvedMuiAdapterOptions(options);
384
+ const normalizedOptions = normalizeOptions(selectOptions);
385
+ const selectSlotProps = resolved.muiSlotProps?.select;
386
+ const menuProps = { ...selectSlotProps?.MenuProps, ...resolved.muiSlotProps?.selectMenu };
350
387
  const labelId = id === void 0 || label === void 0 ? void 0 : `${id}-label`;
351
388
  const helperId = id === void 0 ? void 0 : ariaDescribedBy?.split(/\s+/u)[0] ?? `${id}-helper-text`;
352
389
  const describedBy = ariaDescribedBy ?? (helperText === void 0 || helperText.length === 0 ? void 0 : helperId);
353
- const handleChange = (event) => onChange(event.target.value);
390
+ const handleChange = (event) => onChange(String(event.target.value));
354
391
  return /* @__PURE__ */ jsxs4(
355
392
  FormControl2,
356
393
  {
@@ -366,20 +403,39 @@ function createMuiSelectAdapter(options) {
366
403
  /* @__PURE__ */ jsx7(
367
404
  Select,
368
405
  {
406
+ ...selectSlotProps,
369
407
  id,
370
408
  labelId,
371
409
  name,
372
410
  label,
373
411
  value,
412
+ disabled,
374
413
  onChange: handleChange,
414
+ onKeyDown,
375
415
  required,
376
416
  readOnly,
417
+ variant: selectSlotProps?.variant ?? resolved.variant,
418
+ ...Object.keys(menuProps).length === 0 ? {} : { MenuProps: menuProps },
419
+ renderValue: (selected) => {
420
+ const selectedValue = typeof selected === "string" ? selected : String(selected ?? "");
421
+ const option = normalizedOptions.find((candidate) => candidate.value === selectedValue);
422
+ if (renderValue !== void 0) return renderValue(option);
423
+ if (option === void 0) return selectedValue;
424
+ return /* @__PURE__ */ jsxs4(Box2, { display: "flex", alignItems: "center", gap: 0.75, children: [
425
+ option.icon,
426
+ /* @__PURE__ */ jsx7("span", { children: option.label })
427
+ ] });
428
+ },
377
429
  inputProps: {
430
+ ...selectSlotProps?.inputProps,
378
431
  "aria-label": ariaLabel,
379
432
  "aria-describedby": describedBy,
380
433
  "aria-labelledby": ariaLabelledBy
381
434
  },
382
- children: selectOptions.map((option) => /* @__PURE__ */ jsx7(MenuItem, { value: option.value, disabled: option.disabled, children: option.label }, option.value))
435
+ children: normalizedOptions.map((option) => /* @__PURE__ */ jsx7(MenuItem, { value: option.value, disabled: option.disabled, children: renderOption === void 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
436
+ option.icon === void 0 ? null : /* @__PURE__ */ jsx7(ListItemIcon, { sx: { minWidth: 32 }, children: option.icon }),
437
+ /* @__PURE__ */ jsx7(ListItemText, { primary: option.label, secondary: option.description })
438
+ ] }) : renderOption(option) }, option.value))
383
439
  }
384
440
  ),
385
441
  helperText === void 0 || helperText.length === 0 ? null : /* @__PURE__ */ jsx7(FormHelperText2, { id: helperId, children: helperText })
@@ -414,9 +470,11 @@ function createMuiTextAreaAdapter(options) {
414
470
  rows
415
471
  }) {
416
472
  const resolved = useResolvedMuiAdapterOptions(options);
473
+ const textFieldSlotProps = resolved.muiSlotProps?.textField;
417
474
  return /* @__PURE__ */ jsx8(
418
475
  TextField,
419
476
  {
477
+ ...textFieldSlotProps,
420
478
  id,
421
479
  className,
422
480
  name,
@@ -442,7 +500,7 @@ function createMuiTextAreaAdapter(options) {
442
500
  placeholder,
443
501
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
444
502
  size: resolved.size,
445
- variant: resolved.variant,
503
+ variant: textFieldSlotProps?.variant ?? resolved.variant,
446
504
  onChange: (event) => onChange(event.currentTarget.value)
447
505
  }
448
506
  );
@@ -469,6 +527,7 @@ function createMuiTextInputAdapter(options) {
469
527
  helperText,
470
528
  value,
471
529
  onChange,
530
+ onKeyDown,
472
531
  placeholder,
473
532
  maxLength,
474
533
  inputMode,
@@ -478,9 +537,11 @@ function createMuiTextInputAdapter(options) {
478
537
  step
479
538
  }) {
480
539
  const resolved = useResolvedMuiAdapterOptions(options);
540
+ const textFieldSlotProps = resolved.muiSlotProps?.textField;
481
541
  return /* @__PURE__ */ jsx9(
482
542
  TextField2,
483
543
  {
544
+ ...textFieldSlotProps,
484
545
  id,
485
546
  className,
486
547
  name,
@@ -509,8 +570,9 @@ function createMuiTextInputAdapter(options) {
509
570
  placeholder,
510
571
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
511
572
  size: resolved.size,
512
- variant: resolved.variant,
513
- onChange: (event) => onChange(event.currentTarget.value)
573
+ variant: textFieldSlotProps?.variant ?? resolved.variant,
574
+ onChange: (event) => onChange(event.currentTarget.value),
575
+ onKeyDown
514
576
  }
515
577
  );
516
578
  };
@@ -521,12 +583,19 @@ var MuiTextInputAdapter = createMuiTextInputAdapter();
521
583
  import {
522
584
  Add,
523
585
  ArrowDownward,
586
+ ArrowDropDown,
524
587
  ArrowUpward,
588
+ CheckBox,
525
589
  Close,
526
590
  Delete,
527
591
  DragHandle,
528
592
  Edit,
593
+ Numbers,
594
+ RadioButtonChecked,
529
595
  Settings,
596
+ Star,
597
+ Subject,
598
+ TextFields,
530
599
  Translate
531
600
  } from "@mui/icons-material";
532
601
  import { jsx as jsx10 } from "react/jsx-runtime";
@@ -552,6 +621,26 @@ function muiDefaultIconResolver(actionType) {
552
621
  return /* @__PURE__ */ jsx10(DragHandle, { fontSize: "small" });
553
622
  }
554
623
  }
624
+ function muiDefaultFieldTypeIcon(type) {
625
+ switch (type) {
626
+ case "text":
627
+ return /* @__PURE__ */ jsx10(TextFields, { fontSize: "small" });
628
+ case "textarea":
629
+ return /* @__PURE__ */ jsx10(Subject, { fontSize: "small" });
630
+ case "number":
631
+ return /* @__PURE__ */ jsx10(Numbers, { fontSize: "small" });
632
+ case "rating":
633
+ return /* @__PURE__ */ jsx10(Star, { fontSize: "small" });
634
+ case "select":
635
+ return /* @__PURE__ */ jsx10(ArrowDropDown, { fontSize: "small" });
636
+ case "multi-select":
637
+ return /* @__PURE__ */ jsx10(CheckBox, { fontSize: "small" });
638
+ case "checkbox":
639
+ return /* @__PURE__ */ jsx10(CheckBox, { fontSize: "small" });
640
+ case "radio":
641
+ return /* @__PURE__ */ jsx10(RadioButtonChecked, { fontSize: "small" });
642
+ }
643
+ }
555
644
 
556
645
  // src/components.ts
557
646
  function isAdapterOptions(value) {
@@ -568,7 +657,8 @@ function buildMuiBuilderComponents(options) {
568
657
  Section: createMuiSectionAdapter(options),
569
658
  Fieldset: createMuiFieldsetAdapter(options),
570
659
  ErrorMessage: createMuiErrorMessageAdapter(options),
571
- renderIcon: muiDefaultIconResolver
660
+ renderIcon: muiDefaultIconResolver,
661
+ renderFieldTypeIcon: muiDefaultFieldTypeIcon
572
662
  };
573
663
  }
574
664
  var muiBuilderComponents = {
@@ -581,7 +671,8 @@ var muiBuilderComponents = {
581
671
  Section: MuiSectionAdapter,
582
672
  Fieldset: MuiFieldsetAdapter,
583
673
  ErrorMessage: MuiErrorMessageAdapter,
584
- renderIcon: muiDefaultIconResolver
674
+ renderIcon: muiDefaultIconResolver,
675
+ renderFieldTypeIcon: muiDefaultFieldTypeIcon
585
676
  };
586
677
  function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
587
678
  const options = isAdapterOptions(optionsOrOverrides) ? optionsOrOverrides : void 0;
@@ -590,6 +681,7 @@ function createMuiBuilderComponents(optionsOrOverrides = {}, customOverrides) {
590
681
  }
591
682
 
592
683
  // src/slots/FieldEditor.tsx
684
+ import { DEFAULT_FIELD_TYPE_DEFINITIONS } from "@form-engine-ts/core";
593
685
  import { Card, Stack as Stack3, Typography as Typography3 } from "@mui/material";
594
686
 
595
687
  // src/slots/OptionEditor.tsx
@@ -735,17 +827,8 @@ function createMuiToolbarSlot(options) {
735
827
  var MuiToolbarSlot = createMuiToolbarSlot();
736
828
 
737
829
  // src/slots/FieldEditor.tsx
738
- import { Fragment, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
739
- var FIELD_TYPES = [
740
- "text",
741
- "textarea",
742
- "number",
743
- "rating",
744
- "select",
745
- "multi-select",
746
- "checkbox",
747
- "radio"
748
- ];
830
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
831
+ var FIELD_TYPES = DEFAULT_FIELD_TYPE_DEFINITIONS.map((definition) => definition.type);
749
832
  function isFieldType(value) {
750
833
  return FIELD_TYPES.some((type) => type === value);
751
834
  }
@@ -784,7 +867,8 @@ function createMuiFieldEditorSlot(options) {
784
867
  readOnly,
785
868
  actions,
786
869
  components,
787
- translate
870
+ translate,
871
+ slots
788
872
  }) {
789
873
  const resolved = useResolvedMuiAdapterOptions(options);
790
874
  const { Button: Button2, Checkbox: Checkbox2, Select: Select2, TextArea, TextInput } = components;
@@ -795,6 +879,11 @@ function createMuiFieldEditorSlot(options) {
795
879
  const conditionSource = conditionSources.find((source) => source.id === condition?.questionId);
796
880
  const descriptionMode = resolved.fieldEditorOptions?.description ?? "editable";
797
881
  const titleErrorId = field.title.trim().length === 0 ? `mui-field-${field.id}-title-error` : void 0;
882
+ const FieldTypeSelect = slots?.fieldTypeSelect;
883
+ const FieldEditorHeader = slots?.fieldEditorHeader;
884
+ const changeFieldType = (nextType) => {
885
+ if (allowedTypes.includes(nextType)) actions.changeFieldType(field.id, nextType);
886
+ };
798
887
  return /* @__PURE__ */ jsxs7(
799
888
  Card,
800
889
  {
@@ -803,26 +892,28 @@ function createMuiFieldEditorSlot(options) {
803
892
  variant: "outlined",
804
893
  sx: resolved.muiSlotProps?.card?.sx ?? { mb: resolved.dense ? 1 : 2, p: resolved.dense ? 1.5 : 2 },
805
894
  children: [
806
- /* @__PURE__ */ jsx13(
807
- Toolbar,
808
- {
809
- schema,
810
- translate,
811
- kind: "field",
812
- targetId: field.id,
813
- index,
814
- total: schema.fields.length,
815
- title: field.title,
816
- onMoveUp: () => actions.moveField(field.id, index - 1),
817
- onMoveDown: () => actions.moveField(field.id, index + 1),
818
- onRemove: () => actions.removeField(field.id),
819
- readOnly,
820
- actions,
821
- components
822
- }
823
- ),
895
+ FieldEditorHeader === void 0 ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
896
+ /* @__PURE__ */ jsx13(
897
+ Toolbar,
898
+ {
899
+ schema,
900
+ translate,
901
+ kind: "field",
902
+ targetId: field.id,
903
+ index,
904
+ total: schema.fields.length,
905
+ title: field.title,
906
+ onMoveUp: () => actions.moveField(field.id, index - 1),
907
+ onMoveDown: () => actions.moveField(field.id, index + 1),
908
+ onRemove: () => actions.removeField(field.id),
909
+ readOnly,
910
+ actions,
911
+ components
912
+ }
913
+ ),
914
+ /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle1", fontWeight: "bold", children: field.title })
915
+ ] }) : /* @__PURE__ */ jsx13(FieldEditorHeader, { field, index, totalFields: schema.fields.length, actions }),
824
916
  /* @__PURE__ */ jsxs7(Stack3, { ...resolved.muiSlotProps?.stack, spacing: resolved.dense ? 1 : 2, children: [
825
- /* @__PURE__ */ jsx13(Typography3, { variant: "subtitle1", fontWeight: "bold", children: field.title }),
826
917
  /* @__PURE__ */ jsxs7(Stack3, { direction: { xs: "column", md: "row" }, spacing: resolved.dense ? 1 : 2, children: [
827
918
  /* @__PURE__ */ jsx13(
828
919
  TextInput,
@@ -839,22 +930,37 @@ function createMuiFieldEditorSlot(options) {
839
930
  onChange: (value) => actions.setSourceText({ kind: "field", id: field.id }, "title", value)
840
931
  }
841
932
  ),
842
- /* @__PURE__ */ jsx13(
933
+ FieldTypeSelect === void 0 ? /* @__PURE__ */ jsx13(
843
934
  Select2,
844
935
  {
845
936
  id: `mui-field-${field.id}-type`,
846
937
  name: `fields.${field.id}.type`,
847
938
  label: translate("builder.type"),
848
939
  value: allowedTypes.includes(field.type) ? field.type : "",
849
- options: FIELD_TYPES.filter((type) => allowedTypes.includes(type)).map((type) => ({
850
- value: type,
851
- label: translate(`builder.fieldType.${type}`)
852
- })),
940
+ options: FIELD_TYPES.filter((type) => allowedTypes.includes(type)).map((type) => {
941
+ const definition = DEFAULT_FIELD_TYPE_DEFINITIONS.find((candidate) => candidate.type === type);
942
+ return {
943
+ value: type,
944
+ label: translate(definition?.labelKey ?? `builder.fieldType.${type}`),
945
+ icon: components.renderFieldTypeIcon(type),
946
+ ...definition?.category === void 0 ? {} : { kind: definition.category }
947
+ };
948
+ }),
853
949
  disabled: readOnly,
854
950
  onChange: (value) => {
855
- if (isFieldType(value)) actions.changeFieldType(field.id, value);
951
+ if (isFieldType(value)) changeFieldType(value);
856
952
  }
857
953
  }
954
+ ) : /* @__PURE__ */ jsx13(
955
+ FieldTypeSelect,
956
+ {
957
+ currentType: field.type,
958
+ allowedTypes,
959
+ onChangeType: changeFieldType,
960
+ disabled: readOnly,
961
+ readOnly,
962
+ renderIcon: components.renderFieldTypeIcon
963
+ }
858
964
  )
859
965
  ] }),
860
966
  descriptionMode === "hidden" ? null : /* @__PURE__ */ jsx13(
@@ -937,7 +1043,7 @@ function createMuiFieldEditorSlot(options) {
937
1043
  )
938
1044
  }
939
1045
  ),
940
- condition === void 0 ? null : /* @__PURE__ */ jsxs7(Fragment, { children: [
1046
+ condition === void 0 ? null : /* @__PURE__ */ jsxs7(Fragment2, { children: [
941
1047
  /* @__PURE__ */ jsx13(
942
1048
  Select2,
943
1049
  {
@@ -1033,10 +1139,32 @@ var MuiFieldEditorSlot = createMuiFieldEditorSlot();
1033
1139
 
1034
1140
  // src/slots/Localization.tsx
1035
1141
  import { ExpandMore } from "@mui/icons-material";
1036
- import { Accordion, AccordionDetails, AccordionSummary, Box as Box2, Chip, Stack as Stack4, Tab, Tabs, Typography as Typography4 } from "@mui/material";
1037
- import { useState } from "react";
1142
+ import {
1143
+ Accordion,
1144
+ AccordionDetails,
1145
+ AccordionSummary,
1146
+ Alert as Alert2,
1147
+ Box as Box3,
1148
+ Chip,
1149
+ Stack as Stack4,
1150
+ Tab,
1151
+ Tabs,
1152
+ Typography as Typography4
1153
+ } from "@mui/material";
1154
+ import { useEffect, useState } from "react";
1038
1155
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1039
1156
  var DEFAULT_EMPTY_STATE_MESSAGE = "No translation locales have been added yet. Select a language from the dropdown above to add one.";
1157
+ function normalizeLocaleOptions(options, getLocaleLabel) {
1158
+ const seen = /* @__PURE__ */ new Set();
1159
+ const normalized = [];
1160
+ for (const item of options) {
1161
+ const value = typeof item === "string" ? item : item.value;
1162
+ if (value.length === 0 || seen.has(value)) continue;
1163
+ seen.add(value);
1164
+ normalized.push({ value, label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label });
1165
+ }
1166
+ return normalized;
1167
+ }
1040
1168
  function createMuiLocalizationSlot(options) {
1041
1169
  return function MuiLocalization({
1042
1170
  schema,
@@ -1055,6 +1183,7 @@ function createMuiLocalizationSlot(options) {
1055
1183
  const resolved = useResolvedMuiAdapterOptions(options);
1056
1184
  const { Button: Button2, ErrorMessage, Select: Select2, TextArea, TextInput } = components;
1057
1185
  const [newLocale, setNewLocale] = useState("");
1186
+ const [pendingFocusLocale, setPendingFocusLocale] = useState(null);
1058
1187
  const locales = Array.from(
1059
1188
  new Set((schema.supportedLocales ?? []).filter((locale) => locale !== schema.defaultLocale))
1060
1189
  );
@@ -1063,21 +1192,44 @@ function createMuiLocalizationSlot(options) {
1063
1192
  ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1064
1193
  ...schema.supportedLocales ?? []
1065
1194
  ]);
1066
- const availableAllowedLocales = policy?.allowedLocales?.filter((locale) => !registeredLocales.has(locale));
1195
+ const localizationOptions = resolved.localizationOptions ?? {};
1196
+ const availableLocaleSource = localizationOptions.availableLocales;
1197
+ const availableLocaleOptions = availableLocaleSource === void 0 ? policy?.allowedLocales?.map((value) => ({
1198
+ value,
1199
+ label: resolved.getLocaleLabel?.(value) ?? value
1200
+ })) : normalizeLocaleOptions(availableLocaleSource, resolved.getLocaleLabel);
1201
+ const filteredAvailableLocales = availableLocaleOptions?.filter(
1202
+ (locale) => !registeredLocales.has(locale.value) && (policy?.allowedLocales === void 0 || policy.allowedLocales.includes(locale.value))
1203
+ );
1204
+ const hasLocaleSelector = filteredAvailableLocales !== void 0;
1067
1205
  const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1068
1206
  const normalizedLocale = newLocale.trim();
1207
+ const selectedLocaleAvailable = !hasLocaleSelector || filteredAvailableLocales.some((locale) => locale.value === normalizedLocale);
1069
1208
  const localeAllowed = policy?.allowedLocales === void 0 || policy.allowedLocales.includes(normalizedLocale);
1070
- const canAddLocale = !readOnly && normalizedLocale.length > 0 && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
1209
+ const canAddLocale = !readOnly && normalizedLocale.length > 0 && selectedLocaleAvailable && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
1071
1210
  const handleTabChange = (_event, value) => onCurrentLocaleChange(value);
1072
1211
  const addLocale = () => {
1073
1212
  if (!canAddLocale) return;
1074
1213
  const result = actions.addLocale(normalizedLocale);
1075
1214
  if (!result.success) return;
1076
1215
  onCurrentLocaleChange(normalizedLocale);
1216
+ if (localizationOptions.autoFocusNewTab ?? true) setPendingFocusLocale(normalizedLocale);
1077
1217
  setNewLocale("");
1078
1218
  };
1219
+ const handleAddKeyDown = (event) => {
1220
+ if (event.key !== "Enter" || !canAddLocale) return;
1221
+ event.preventDefault();
1222
+ event.stopPropagation();
1223
+ addLocale();
1224
+ };
1225
+ useEffect(() => {
1226
+ if (pendingFocusLocale === null || currentLocale !== pendingFocusLocale) return;
1227
+ const tab = document.getElementById(`mui-builder-locale-tab-${pendingFocusLocale}`);
1228
+ if (tab === null) return;
1229
+ tab.focus();
1230
+ setPendingFocusLocale(null);
1231
+ }, [currentLocale, pendingFocusLocale]);
1079
1232
  const stackProps = resolved.muiSlotProps?.stack;
1080
- const localizationOptions = resolved.localizationOptions ?? {};
1081
1233
  const collapsible = localizationOptions.collapsible ?? false;
1082
1234
  const defaultLocaleControl = localizationOptions.defaultLocaleControl ?? "editable";
1083
1235
  const configuredTranslationLocales = locales.filter((locale) => locale !== schema.defaultLocale);
@@ -1085,24 +1237,35 @@ function createMuiLocalizationSlot(options) {
1085
1237
  ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1086
1238
  ...configuredTranslationLocales
1087
1239
  ];
1088
- const summaryLabel = configuredTranslationLocales.length === 0 ? "Translations not configured" : `${configuredLocales.length} ${configuredLocales.length === 1 ? "language" : "languages"} configured: ${configuredLocales.map(
1240
+ const legacySummaryLabel = configuredTranslationLocales.length === 0 ? "Translations not configured" : `${configuredLocales.length} ${configuredLocales.length === 1 ? "language" : "languages"} configured: ${configuredLocales.map(
1089
1241
  (locale) => locale === schema.defaultLocale ? `${resolved.getLocaleLabel?.(locale) ?? locale} (default)` : resolved.getLocaleLabel?.(locale) ?? locale
1090
1242
  ).join(", ")}`;
1091
1243
  const emptyStateMessage = localizationOptions.emptyStateMessage === void 0 ? DEFAULT_EMPTY_STATE_MESSAGE : translate(localizationOptions.emptyStateMessage);
1244
+ const summaryContext = {
1245
+ defaultLocale: schema.defaultLocale ?? "",
1246
+ supportedLocales: schema.supportedLocales ?? [],
1247
+ totalLocales: registeredLocales.size
1248
+ };
1249
+ const summary = localizationOptions.renderSummary === void 0 ? localizationOptions.showSummary ? configuredTranslationLocales.length > 0 ? /* @__PURE__ */ jsx14(Alert2, { severity: "success", sx: { mb: 2 }, children: translate("builder.localization.localesConfiguredSummary", {
1250
+ count: schema.supportedLocales?.length ?? 0
1251
+ }) }) : /* @__PURE__ */ jsx14(Alert2, { severity: "info", sx: { mb: 2 }, children: translate("builder.localization.noLocalesConfigured") }) : null : localizationOptions.renderSummary(summaryContext);
1092
1252
  const title = /* @__PURE__ */ jsxs8(Stack4, { alignItems: "center", direction: "row", spacing: 1, children: [
1093
1253
  /* @__PURE__ */ jsx14(Typography4, { variant: "subtitle1", fontWeight: "bold", children: translate("builder.localization") }),
1094
- localizationOptions.showSummary ? /* @__PURE__ */ jsx14(
1254
+ localizationOptions.renderSummary === void 0 && localizationOptions.showSummary && configuredTranslationLocales.length > 0 ? /* @__PURE__ */ jsx14(
1095
1255
  Chip,
1096
1256
  {
1097
- label: summaryLabel,
1257
+ label: legacySummaryLabel,
1098
1258
  size: "small",
1099
1259
  color: configuredTranslationLocales.length > 0 ? "primary" : "default",
1100
1260
  variant: "outlined"
1101
1261
  }
1102
1262
  ) : null
1103
1263
  ] });
1264
+ const noCandidateLocales = hasLocaleSelector && filteredAvailableLocales.length === 0;
1265
+ const candidateHelperText = localeLimitReached ? void 0 : noCandidateLocales ? translate("builder.localization.allLocalesAdded") : void 0;
1104
1266
  const content = /* @__PURE__ */ jsxs8(Stack4, { ...stackProps, spacing: resolved.dense ? 1 : 2, children: [
1105
1267
  !collapsible ? title : null,
1268
+ summary,
1106
1269
  /* @__PURE__ */ jsxs8(
1107
1270
  Stack4,
1108
1271
  {
@@ -1112,7 +1275,7 @@ function createMuiLocalizationSlot(options) {
1112
1275
  spacing: resolved.dense ? 1 : 2,
1113
1276
  sx: { mt: 1 },
1114
1277
  children: [
1115
- 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: [
1278
+ 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: [
1116
1279
  /* @__PURE__ */ jsx14(Typography4, { variant: "caption", color: "text.secondary", children: translate("builder.defaultLocale") }),
1117
1280
  /* @__PURE__ */ jsx14(
1118
1281
  Chip,
@@ -1134,36 +1297,52 @@ function createMuiLocalizationSlot(options) {
1134
1297
  }
1135
1298
  }
1136
1299
  ) }),
1137
- /* @__PURE__ */ jsx14(Box2, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, children: availableAllowedLocales === void 0 ? /* @__PURE__ */ jsx14(
1138
- TextInput,
1300
+ /* @__PURE__ */ jsx14(Box3, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, onKeyDownCapture: handleAddKeyDown, children: hasLocaleSelector ? /* @__PURE__ */ jsx14(
1301
+ Select2,
1139
1302
  {
1140
1303
  id: "mui-builder-new-locale",
1141
- label: translate("builder.addLocale"),
1304
+ label: translate("builder.localization.selectLocaleToAdd"),
1142
1305
  value: newLocale,
1143
- disabled: readOnly || localeLimitReached,
1306
+ options: filteredAvailableLocales.map((locale) => ({ value: locale.value, label: locale.label })),
1307
+ ...candidateHelperText === void 0 ? {} : { helperText: candidateHelperText },
1308
+ disabled: readOnly || localeLimitReached || noCandidateLocales,
1144
1309
  onChange: setNewLocale
1145
1310
  }
1146
1311
  ) : /* @__PURE__ */ jsx14(
1147
- Select2,
1312
+ TextInput,
1148
1313
  {
1149
1314
  id: "mui-builder-new-locale",
1150
1315
  label: translate("builder.addLocale"),
1151
1316
  value: newLocale,
1152
- options: [
1153
- { value: "", label: "\u2014" },
1154
- ...availableAllowedLocales.map((locale) => ({
1155
- value: locale,
1156
- label: resolved.getLocaleLabel?.(locale) ?? locale
1157
- }))
1158
- ],
1159
- disabled: readOnly || localeLimitReached || availableAllowedLocales.length === 0,
1317
+ disabled: readOnly || localeLimitReached,
1160
1318
  onChange: setNewLocale
1161
1319
  }
1162
1320
  ) }),
1163
- /* @__PURE__ */ jsx14(Box2, { sx: { flexShrink: 0, minWidth: "max-content", whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsx14(Button2, { variant: "primary", action: "addLocale", disabled: !canAddLocale, onClick: addLocale, children: translate("builder.addLocale") }) })
1321
+ /* @__PURE__ */ jsx14(
1322
+ Box3,
1323
+ {
1324
+ sx: {
1325
+ flexShrink: 0,
1326
+ minWidth: "max-content",
1327
+ ...localizationOptions.noWrapActions ?? true ? { whiteSpace: "nowrap" } : {}
1328
+ },
1329
+ children: /* @__PURE__ */ jsx14(
1330
+ Button2,
1331
+ {
1332
+ variant: "primary",
1333
+ action: "addLocale",
1334
+ noWrap: localizationOptions.noWrapActions ?? true,
1335
+ disabled: !canAddLocale,
1336
+ onClick: addLocale,
1337
+ children: translate("builder.addLocale")
1338
+ }
1339
+ )
1340
+ }
1341
+ )
1164
1342
  ]
1165
1343
  }
1166
1344
  ),
1345
+ localeLimitReached ? /* @__PURE__ */ jsx14(Typography4, { variant: "body2", color: "text.secondary", children: translate("builder.localization.maxLocalesReached", { max: policy?.maxLocales ?? 0 }) }) : null,
1167
1346
  locales.length === 0 ? null : /* @__PURE__ */ jsx14(
1168
1347
  Tabs,
1169
1348
  {
@@ -1175,6 +1354,7 @@ function createMuiLocalizationSlot(options) {
1175
1354
  children: locales.map((locale) => /* @__PURE__ */ jsx14(
1176
1355
  Tab,
1177
1356
  {
1357
+ id: `mui-builder-locale-tab-${locale}`,
1178
1358
  value: locale,
1179
1359
  label: resolved.getLocaleLabel?.(locale) ?? locale,
1180
1360
  disabled: readOnly && locale !== currentLocale,
@@ -1184,10 +1364,11 @@ function createMuiLocalizationSlot(options) {
1184
1364
  ))
1185
1365
  }
1186
1366
  ),
1187
- !translationAdapterAvailable ? null : /* @__PURE__ */ jsx14(Box2, { sx: { minWidth: "max-content", whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsx14(
1367
+ !translationAdapterAvailable ? null : /* @__PURE__ */ jsx14(Box3, { sx: { minWidth: "max-content", whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsx14(
1188
1368
  Button2,
1189
1369
  {
1190
1370
  variant: "secondary",
1371
+ noWrap: localizationOptions.noWrapActions ?? true,
1191
1372
  disabled: readOnly || !editingLocaleConfigured || isTranslating,
1192
1373
  onClick: onAutoTranslate,
1193
1374
  children: isTranslating ? translate("builder.translating") : translate("builder.autoTranslate")
@@ -1236,7 +1417,7 @@ function createMuiLocalizationSlot(options) {
1236
1417
  ] });
1237
1418
  }
1238
1419
  return /* @__PURE__ */ jsx14(
1239
- Box2,
1420
+ Box3,
1240
1421
  {
1241
1422
  "data-mui-slot": "localization",
1242
1423
  sx: {
@@ -1308,7 +1489,8 @@ function MuiFormBuilder({
1308
1489
  const contextValue = useMemo(() => ({ options: contextOptions }), [contextOptions]);
1309
1490
  const components = useMemo(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1310
1491
  const slots = useMemo(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1311
- const resolvedSectionOrder = sectionOrder ?? contextOptions.layoutOptions?.sectionOrder;
1492
+ const placement = contextOptions.localizationOptions?.placement;
1493
+ const resolvedSectionOrder = sectionOrder ?? (placement === void 0 ? contextOptions.layoutOptions?.sectionOrder : MUI_LOCALIZATION_SECTION_ORDERS[placement]);
1312
1494
  return /* @__PURE__ */ jsx15(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx15(
1313
1495
  FormBuilder,
1314
1496
  {
@@ -1322,6 +1504,7 @@ function MuiFormBuilder({
1322
1504
  }
1323
1505
  export {
1324
1506
  DEFAULT_MUI_SECTION_ORDER,
1507
+ MUI_LOCALIZATION_SECTION_ORDERS,
1325
1508
  MuiButtonAdapter,
1326
1509
  MuiCheckboxAdapter,
1327
1510
  MuiErrorMessageAdapter,
@@ -1356,6 +1539,7 @@ export {
1356
1539
  mergeMuiAdapterOptions,
1357
1540
  muiBuilderComponents,
1358
1541
  muiBuilderSlots,
1542
+ muiDefaultFieldTypeIcon,
1359
1543
  muiDefaultIconResolver,
1360
1544
  resolveMuiAdapterOptions,
1361
1545
  useResolvedMuiAdapterOptions