@form-engine-ts/mui 2.9.6 → 3.0.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
@@ -25,6 +25,11 @@ import { MuiFormBuilder } from "@form-engine-ts/mui";
25
25
  sectionOrder: ["basicSettings", "completionMessage", "questions", "addQuestion", "localization"]
26
26
  }}
27
27
  localizationOptions={{
28
+ availableLocales: [
29
+ { value: "ja", label: "日本語" },
30
+ { value: "en", label: "English" }
31
+ ],
32
+ placement: "afterQuestions",
28
33
  collapsible: true,
29
34
  defaultExpanded: "when-configured",
30
35
  showSummary: true,
@@ -56,9 +61,12 @@ slots as it does for the standard builder.
56
61
  width options. `fieldEditorOptions.description` and `localizationOptions.defaultLocaleControl` independently make those
57
62
  controls editable, read-only, or hidden. Set `dense` to reduce section, editor, option, and toolbar spacing.
58
63
  `buttonVariants` can override the MUI variant for `primary`, `secondary`, and `danger` actions independently.
59
- `localizationOptions.showSummary` adds a locale summary badge to the localization heading, and
60
- `localizationOptions.emptyStateMessage` customizes the guidance shown before a translation locale is added. The
61
- `defaultExpanded` option also accepts `"always"`.
64
+ `localizationOptions.availableLocales` supplies display-ready locale candidates independently from the policy; when
65
+ both are present, only candidates allowed by `allowedLocales` are shown. `placement` supports `top`,
66
+ `beforeQuestions`, `afterQuestions`, and `bottom` section presets. `showSummary` renders a status alert, while
67
+ `renderSummary` can provide a custom summary. `emptyStateMessage` customizes the guidance shown before a translation
68
+ locale is added. `defaultExpanded` also accepts `"always"`; `autoFocusNewTab` focuses a newly added locale tab by
69
+ default.
62
70
  `layoutOptions`, `localizationOptions`, and `muiSlotProps` are also accepted in `muiOptions` for low-level factories such
63
71
  as `createMuiBuilderSlots`; the dedicated `MuiFormBuilder` props take precedence.
64
72
 
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_MUI_SECTION_ORDER: () => DEFAULT_MUI_SECTION_ORDER,
24
+ MUI_LOCALIZATION_SECTION_ORDERS: () => MUI_LOCALIZATION_SECTION_ORDERS,
24
25
  MuiButtonAdapter: () => MuiButtonAdapter,
25
26
  MuiCheckboxAdapter: () => MuiCheckboxAdapter,
26
27
  MuiErrorMessageAdapter: () => MuiErrorMessageAdapter,
@@ -72,8 +73,15 @@ var DEFAULT_MUI_SECTION_ORDER = [
72
73
  "basicSettings",
73
74
  "questions",
74
75
  "addQuestion",
75
- "localization"
76
+ "localization",
77
+ "completionMessage"
76
78
  ];
79
+ var MUI_LOCALIZATION_SECTION_ORDERS = {
80
+ top: ["localization", "basicSettings", "completionMessage", "questions", "addQuestion"],
81
+ beforeQuestions: ["basicSettings", "localization", "completionMessage", "questions", "addQuestion"],
82
+ afterQuestions: ["basicSettings", "questions", "addQuestion", "localization", "completionMessage"],
83
+ bottom: ["basicSettings", "completionMessage", "questions", "addQuestion", "localization"]
84
+ };
77
85
  function resolveMuiAdapterOptions(options = {}) {
78
86
  const buttonVariant = options.buttonVariant ?? "contained";
79
87
  return {
@@ -99,8 +107,13 @@ function resolveMuiAdapterOptions(options = {}) {
99
107
  collapsible: options.localizationOptions?.collapsible ?? false,
100
108
  defaultExpanded: options.localizationOptions?.defaultExpanded ?? false,
101
109
  showSummary: options.localizationOptions?.showSummary ?? false,
110
+ ...options.localizationOptions?.availableLocales === void 0 ? {} : { availableLocales: options.localizationOptions.availableLocales },
111
+ ...options.localizationOptions?.placement === void 0 ? {} : { placement: options.localizationOptions.placement },
112
+ ...options.localizationOptions?.renderSummary === void 0 ? {} : { renderSummary: options.localizationOptions.renderSummary },
102
113
  ...options.localizationOptions?.emptyStateMessage === void 0 ? {} : { emptyStateMessage: options.localizationOptions.emptyStateMessage },
103
- defaultLocaleControl: options.localizationOptions?.defaultLocaleControl ?? "editable"
114
+ defaultLocaleControl: options.localizationOptions?.defaultLocaleControl ?? "editable",
115
+ noWrapActions: options.localizationOptions?.noWrapActions ?? true,
116
+ autoFocusNewTab: options.localizationOptions?.autoFocusNewTab ?? true
104
117
  },
105
118
  muiSlotProps: options.muiSlotProps ?? {}
106
119
  };
@@ -138,6 +151,7 @@ function createMuiButtonAdapter(options) {
138
151
  "aria-describedby": ariaDescribedBy,
139
152
  "aria-labelledby": ariaLabelledBy,
140
153
  onClick,
154
+ noWrap,
141
155
  children,
142
156
  title,
143
157
  variant,
@@ -163,7 +177,7 @@ function createMuiButtonAdapter(options) {
163
177
  color: variant === "danger" ? "error" : "primary",
164
178
  variant: resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
165
179
  fullWidth: resolved.buttonFullWidth ?? false,
166
- sx: { whiteSpace: "nowrap" },
180
+ sx: { whiteSpace: noWrap === false ? "normal" : "nowrap" },
167
181
  children
168
182
  }
169
183
  );
@@ -407,6 +421,7 @@ function createMuiSelectAdapter(options) {
407
421
  helperText,
408
422
  value,
409
423
  onChange,
424
+ onKeyDown,
410
425
  options: selectOptions
411
426
  }) {
412
427
  const resolved = useResolvedMuiAdapterOptions(options);
@@ -434,7 +449,9 @@ function createMuiSelectAdapter(options) {
434
449
  name,
435
450
  label,
436
451
  value,
452
+ disabled,
437
453
  onChange: handleChange,
454
+ onKeyDown,
438
455
  required,
439
456
  readOnly,
440
457
  inputProps: {
@@ -532,6 +549,7 @@ function createMuiTextInputAdapter(options) {
532
549
  helperText,
533
550
  value,
534
551
  onChange,
552
+ onKeyDown,
535
553
  placeholder,
536
554
  maxLength,
537
555
  inputMode,
@@ -573,7 +591,8 @@ function createMuiTextInputAdapter(options) {
573
591
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
574
592
  size: resolved.size,
575
593
  variant: resolved.variant,
576
- onChange: (event) => onChange(event.currentTarget.value)
594
+ onChange: (event) => onChange(event.currentTarget.value),
595
+ onKeyDown
577
596
  }
578
597
  );
579
598
  };
@@ -1090,6 +1109,17 @@ var import_material13 = require("@mui/material");
1090
1109
  var import_react2 = require("react");
1091
1110
  var import_jsx_runtime14 = require("react/jsx-runtime");
1092
1111
  var DEFAULT_EMPTY_STATE_MESSAGE = "No translation locales have been added yet. Select a language from the dropdown above to add one.";
1112
+ function normalizeLocaleOptions(options, getLocaleLabel) {
1113
+ const seen = /* @__PURE__ */ new Set();
1114
+ const normalized = [];
1115
+ for (const item of options) {
1116
+ const value = typeof item === "string" ? item : item.value;
1117
+ if (value.length === 0 || seen.has(value)) continue;
1118
+ seen.add(value);
1119
+ normalized.push({ value, label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label });
1120
+ }
1121
+ return normalized;
1122
+ }
1093
1123
  function createMuiLocalizationSlot(options) {
1094
1124
  return function MuiLocalization({
1095
1125
  schema,
@@ -1108,6 +1138,7 @@ function createMuiLocalizationSlot(options) {
1108
1138
  const resolved = useResolvedMuiAdapterOptions(options);
1109
1139
  const { Button: Button2, ErrorMessage, Select: Select2, TextArea, TextInput } = components;
1110
1140
  const [newLocale, setNewLocale] = (0, import_react2.useState)("");
1141
+ const [pendingFocusLocale, setPendingFocusLocale] = (0, import_react2.useState)(null);
1111
1142
  const locales = Array.from(
1112
1143
  new Set((schema.supportedLocales ?? []).filter((locale) => locale !== schema.defaultLocale))
1113
1144
  );
@@ -1116,21 +1147,44 @@ function createMuiLocalizationSlot(options) {
1116
1147
  ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1117
1148
  ...schema.supportedLocales ?? []
1118
1149
  ]);
1119
- const availableAllowedLocales = policy?.allowedLocales?.filter((locale) => !registeredLocales.has(locale));
1150
+ const localizationOptions = resolved.localizationOptions ?? {};
1151
+ const availableLocaleSource = localizationOptions.availableLocales;
1152
+ const availableLocaleOptions = availableLocaleSource === void 0 ? policy?.allowedLocales?.map((value) => ({
1153
+ value,
1154
+ label: resolved.getLocaleLabel?.(value) ?? value
1155
+ })) : normalizeLocaleOptions(availableLocaleSource, resolved.getLocaleLabel);
1156
+ const filteredAvailableLocales = availableLocaleOptions?.filter(
1157
+ (locale) => !registeredLocales.has(locale.value) && (policy?.allowedLocales === void 0 || policy.allowedLocales.includes(locale.value))
1158
+ );
1159
+ const hasLocaleSelector = filteredAvailableLocales !== void 0;
1120
1160
  const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1121
1161
  const normalizedLocale = newLocale.trim();
1162
+ const selectedLocaleAvailable = !hasLocaleSelector || filteredAvailableLocales.some((locale) => locale.value === normalizedLocale);
1122
1163
  const localeAllowed = policy?.allowedLocales === void 0 || policy.allowedLocales.includes(normalizedLocale);
1123
- const canAddLocale = !readOnly && normalizedLocale.length > 0 && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
1164
+ const canAddLocale = !readOnly && normalizedLocale.length > 0 && selectedLocaleAvailable && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
1124
1165
  const handleTabChange = (_event, value) => onCurrentLocaleChange(value);
1125
1166
  const addLocale = () => {
1126
1167
  if (!canAddLocale) return;
1127
1168
  const result = actions.addLocale(normalizedLocale);
1128
1169
  if (!result.success) return;
1129
1170
  onCurrentLocaleChange(normalizedLocale);
1171
+ if (localizationOptions.autoFocusNewTab ?? true) setPendingFocusLocale(normalizedLocale);
1130
1172
  setNewLocale("");
1131
1173
  };
1174
+ const handleAddKeyDown = (event) => {
1175
+ if (event.key !== "Enter" || !canAddLocale) return;
1176
+ event.preventDefault();
1177
+ event.stopPropagation();
1178
+ addLocale();
1179
+ };
1180
+ (0, import_react2.useEffect)(() => {
1181
+ if (pendingFocusLocale === null || currentLocale !== pendingFocusLocale) return;
1182
+ const tab = document.getElementById(`mui-builder-locale-tab-${pendingFocusLocale}`);
1183
+ if (tab === null) return;
1184
+ tab.focus();
1185
+ setPendingFocusLocale(null);
1186
+ }, [currentLocale, pendingFocusLocale]);
1132
1187
  const stackProps = resolved.muiSlotProps?.stack;
1133
- const localizationOptions = resolved.localizationOptions ?? {};
1134
1188
  const collapsible = localizationOptions.collapsible ?? false;
1135
1189
  const defaultLocaleControl = localizationOptions.defaultLocaleControl ?? "editable";
1136
1190
  const configuredTranslationLocales = locales.filter((locale) => locale !== schema.defaultLocale);
@@ -1138,24 +1192,35 @@ function createMuiLocalizationSlot(options) {
1138
1192
  ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1139
1193
  ...configuredTranslationLocales
1140
1194
  ];
1141
- const summaryLabel = configuredTranslationLocales.length === 0 ? "Translations not configured" : `${configuredLocales.length} ${configuredLocales.length === 1 ? "language" : "languages"} configured: ${configuredLocales.map(
1195
+ const legacySummaryLabel = configuredTranslationLocales.length === 0 ? "Translations not configured" : `${configuredLocales.length} ${configuredLocales.length === 1 ? "language" : "languages"} configured: ${configuredLocales.map(
1142
1196
  (locale) => locale === schema.defaultLocale ? `${resolved.getLocaleLabel?.(locale) ?? locale} (default)` : resolved.getLocaleLabel?.(locale) ?? locale
1143
1197
  ).join(", ")}`;
1144
1198
  const emptyStateMessage = localizationOptions.emptyStateMessage === void 0 ? DEFAULT_EMPTY_STATE_MESSAGE : translate(localizationOptions.emptyStateMessage);
1199
+ const summaryContext = {
1200
+ defaultLocale: schema.defaultLocale ?? "",
1201
+ supportedLocales: schema.supportedLocales ?? [],
1202
+ totalLocales: registeredLocales.size
1203
+ };
1204
+ const summary = localizationOptions.renderSummary === void 0 ? localizationOptions.showSummary ? configuredTranslationLocales.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Alert, { severity: "success", sx: { mb: 2 }, children: translate("builder.localization.localesConfiguredSummary", {
1205
+ count: schema.supportedLocales?.length ?? 0
1206
+ }) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Alert, { severity: "info", sx: { mb: 2 }, children: translate("builder.localization.noLocalesConfigured") }) : null : localizationOptions.renderSummary(summaryContext);
1145
1207
  const title = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_material13.Stack, { alignItems: "center", direction: "row", spacing: 1, children: [
1146
1208
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Typography, { variant: "subtitle1", fontWeight: "bold", children: translate("builder.localization") }),
1147
- localizationOptions.showSummary ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1209
+ localizationOptions.renderSummary === void 0 && localizationOptions.showSummary && configuredTranslationLocales.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1148
1210
  import_material13.Chip,
1149
1211
  {
1150
- label: summaryLabel,
1212
+ label: legacySummaryLabel,
1151
1213
  size: "small",
1152
1214
  color: configuredTranslationLocales.length > 0 ? "primary" : "default",
1153
1215
  variant: "outlined"
1154
1216
  }
1155
1217
  ) : null
1156
1218
  ] });
1219
+ const noCandidateLocales = hasLocaleSelector && filteredAvailableLocales.length === 0;
1220
+ const candidateHelperText = localeLimitReached ? void 0 : noCandidateLocales ? translate("builder.localization.allLocalesAdded") : void 0;
1157
1221
  const content = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_material13.Stack, { ...stackProps, spacing: resolved.dense ? 1 : 2, children: [
1158
1222
  !collapsible ? title : null,
1223
+ summary,
1159
1224
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1160
1225
  import_material13.Stack,
1161
1226
  {
@@ -1187,36 +1252,52 @@ function createMuiLocalizationSlot(options) {
1187
1252
  }
1188
1253
  }
1189
1254
  ) }),
1190
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Box, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, children: availableAllowedLocales === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1191
- TextInput,
1255
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Box, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, onKeyDownCapture: handleAddKeyDown, children: hasLocaleSelector ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1256
+ Select2,
1192
1257
  {
1193
1258
  id: "mui-builder-new-locale",
1194
- label: translate("builder.addLocale"),
1259
+ label: translate("builder.localization.selectLocaleToAdd"),
1195
1260
  value: newLocale,
1196
- disabled: readOnly || localeLimitReached,
1261
+ options: filteredAvailableLocales.map((locale) => ({ value: locale.value, label: locale.label })),
1262
+ ...candidateHelperText === void 0 ? {} : { helperText: candidateHelperText },
1263
+ disabled: readOnly || localeLimitReached || noCandidateLocales,
1197
1264
  onChange: setNewLocale
1198
1265
  }
1199
1266
  ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1200
- Select2,
1267
+ TextInput,
1201
1268
  {
1202
1269
  id: "mui-builder-new-locale",
1203
1270
  label: translate("builder.addLocale"),
1204
1271
  value: newLocale,
1205
- options: [
1206
- { value: "", label: "\u2014" },
1207
- ...availableAllowedLocales.map((locale) => ({
1208
- value: locale,
1209
- label: resolved.getLocaleLabel?.(locale) ?? locale
1210
- }))
1211
- ],
1212
- disabled: readOnly || localeLimitReached || availableAllowedLocales.length === 0,
1272
+ disabled: readOnly || localeLimitReached,
1213
1273
  onChange: setNewLocale
1214
1274
  }
1215
1275
  ) }),
1216
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Box, { sx: { flexShrink: 0, minWidth: "max-content", whiteSpace: "nowrap" }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Button2, { variant: "primary", action: "addLocale", disabled: !canAddLocale, onClick: addLocale, children: translate("builder.addLocale") }) })
1276
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1277
+ import_material13.Box,
1278
+ {
1279
+ sx: {
1280
+ flexShrink: 0,
1281
+ minWidth: "max-content",
1282
+ ...localizationOptions.noWrapActions ?? true ? { whiteSpace: "nowrap" } : {}
1283
+ },
1284
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1285
+ Button2,
1286
+ {
1287
+ variant: "primary",
1288
+ action: "addLocale",
1289
+ noWrap: localizationOptions.noWrapActions ?? true,
1290
+ disabled: !canAddLocale,
1291
+ onClick: addLocale,
1292
+ children: translate("builder.addLocale")
1293
+ }
1294
+ )
1295
+ }
1296
+ )
1217
1297
  ]
1218
1298
  }
1219
1299
  ),
1300
+ localeLimitReached ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_material13.Typography, { variant: "body2", color: "text.secondary", children: translate("builder.localization.maxLocalesReached", { max: policy?.maxLocales ?? 0 }) }) : null,
1220
1301
  locales.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1221
1302
  import_material13.Tabs,
1222
1303
  {
@@ -1228,6 +1309,7 @@ function createMuiLocalizationSlot(options) {
1228
1309
  children: locales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1229
1310
  import_material13.Tab,
1230
1311
  {
1312
+ id: `mui-builder-locale-tab-${locale}`,
1231
1313
  value: locale,
1232
1314
  label: resolved.getLocaleLabel?.(locale) ?? locale,
1233
1315
  disabled: readOnly && locale !== currentLocale,
@@ -1241,6 +1323,7 @@ function createMuiLocalizationSlot(options) {
1241
1323
  Button2,
1242
1324
  {
1243
1325
  variant: "secondary",
1326
+ noWrap: localizationOptions.noWrapActions ?? true,
1244
1327
  disabled: readOnly || !editingLocaleConfigured || isTranslating,
1245
1328
  onClick: onAutoTranslate,
1246
1329
  children: isTranslating ? translate("builder.translating") : translate("builder.autoTranslate")
@@ -1359,7 +1442,8 @@ function MuiFormBuilder({
1359
1442
  const contextValue = (0, import_react4.useMemo)(() => ({ options: contextOptions }), [contextOptions]);
1360
1443
  const components = (0, import_react4.useMemo)(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1361
1444
  const slots = (0, import_react4.useMemo)(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1362
- const resolvedSectionOrder = sectionOrder ?? contextOptions.layoutOptions?.sectionOrder;
1445
+ const placement = contextOptions.localizationOptions?.placement;
1446
+ const resolvedSectionOrder = sectionOrder ?? (placement === void 0 ? contextOptions.layoutOptions?.sectionOrder : MUI_LOCALIZATION_SECTION_ORDERS[placement]);
1363
1447
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1364
1448
  import_react3.FormBuilder,
1365
1449
  {
@@ -1374,6 +1458,7 @@ function MuiFormBuilder({
1374
1458
  // Annotate the CommonJS export names for ESM import in node:
1375
1459
  0 && (module.exports = {
1376
1460
  DEFAULT_MUI_SECTION_ORDER,
1461
+ MUI_LOCALIZATION_SECTION_ORDERS,
1377
1462
  MuiButtonAdapter,
1378
1463
  MuiCheckboxAdapter,
1379
1464
  MuiErrorMessageAdapter,
package/dist/index.d.cts CHANGED
@@ -1,19 +1,29 @@
1
- import { BuilderActionIconType, 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, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
2
2
  import * as react from 'react';
3
- import { ComponentType, ReactNode } from 'react';
3
+ import { ReactNode, ComponentType } from 'react';
4
4
  import { CardProps, PaperProps, StackProps, AccordionProps } from '@mui/material';
5
5
 
6
6
  type BuilderSectionName = "basicSettings" | "completionMessage" | "questions" | "addQuestion" | "localization";
7
7
  type MuiButtonVariant = "contained" | "outlined" | "text";
8
+ interface LocaleOptionItem {
9
+ readonly value: string;
10
+ readonly label: string;
11
+ }
12
+ type LocalizationSectionPlacement = "top" | "beforeQuestions" | "afterQuestions" | "bottom";
8
13
  interface MuiLayoutOptions {
9
14
  readonly sectionOrder?: readonly BuilderSectionName[];
10
15
  }
11
16
  interface MuiLocalizationOptions {
17
+ readonly availableLocales?: readonly (LocaleOptionItem | string)[];
18
+ readonly placement?: LocalizationSectionPlacement;
12
19
  readonly collapsible?: boolean;
13
20
  readonly defaultExpanded?: boolean | "when-configured" | "always";
14
21
  readonly showSummary?: boolean;
22
+ readonly renderSummary?: (context: LocalizationSummaryContext) => ReactNode;
15
23
  readonly emptyStateMessage?: string;
16
24
  readonly defaultLocaleControl?: "editable" | "readOnly" | "hidden";
25
+ readonly noWrapActions?: boolean;
26
+ readonly autoFocusNewTab?: boolean;
17
27
  }
18
28
  interface MuiFieldEditorOptions {
19
29
  readonly description?: "editable" | "readOnly" | "hidden";
@@ -65,6 +75,7 @@ interface ResolvedMuiAdapterOptions {
65
75
  readonly muiSlotProps?: MuiBuilderSlotProps;
66
76
  }
67
77
  declare const DEFAULT_MUI_SECTION_ORDER: readonly BuilderSectionName[];
78
+ declare const MUI_LOCALIZATION_SECTION_ORDERS: Readonly<Record<LocalizationSectionPlacement, readonly BuilderSectionName[]>>;
68
79
  declare function resolveMuiAdapterOptions(options?: MuiAdapterOptions): ResolvedMuiAdapterOptions;
69
80
 
70
81
  declare function createMuiButtonAdapter(options?: MuiAdapterOptions): ComponentType<BuilderButtonProps>;
@@ -138,4 +149,4 @@ declare const MuiToolbarSlot: NonNullable<FormBuilderSlots["toolbar"]>;
138
149
  declare const muiBuilderSlots: FormBuilderSlots;
139
150
  declare function createMuiBuilderSlots(options?: MuiAdapterOptions, customOverrides?: Partial<FormBuilderSlots>): FormBuilderSlots;
140
151
 
141
- export { type BuilderSectionName, DEFAULT_MUI_SECTION_ORDER, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,29 @@
1
- import { BuilderActionIconType, 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, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
2
2
  import * as react from 'react';
3
- import { ComponentType, ReactNode } from 'react';
3
+ import { ReactNode, ComponentType } from 'react';
4
4
  import { CardProps, PaperProps, StackProps, AccordionProps } from '@mui/material';
5
5
 
6
6
  type BuilderSectionName = "basicSettings" | "completionMessage" | "questions" | "addQuestion" | "localization";
7
7
  type MuiButtonVariant = "contained" | "outlined" | "text";
8
+ interface LocaleOptionItem {
9
+ readonly value: string;
10
+ readonly label: string;
11
+ }
12
+ type LocalizationSectionPlacement = "top" | "beforeQuestions" | "afterQuestions" | "bottom";
8
13
  interface MuiLayoutOptions {
9
14
  readonly sectionOrder?: readonly BuilderSectionName[];
10
15
  }
11
16
  interface MuiLocalizationOptions {
17
+ readonly availableLocales?: readonly (LocaleOptionItem | string)[];
18
+ readonly placement?: LocalizationSectionPlacement;
12
19
  readonly collapsible?: boolean;
13
20
  readonly defaultExpanded?: boolean | "when-configured" | "always";
14
21
  readonly showSummary?: boolean;
22
+ readonly renderSummary?: (context: LocalizationSummaryContext) => ReactNode;
15
23
  readonly emptyStateMessage?: string;
16
24
  readonly defaultLocaleControl?: "editable" | "readOnly" | "hidden";
25
+ readonly noWrapActions?: boolean;
26
+ readonly autoFocusNewTab?: boolean;
17
27
  }
18
28
  interface MuiFieldEditorOptions {
19
29
  readonly description?: "editable" | "readOnly" | "hidden";
@@ -65,6 +75,7 @@ interface ResolvedMuiAdapterOptions {
65
75
  readonly muiSlotProps?: MuiBuilderSlotProps;
66
76
  }
67
77
  declare const DEFAULT_MUI_SECTION_ORDER: readonly BuilderSectionName[];
78
+ declare const MUI_LOCALIZATION_SECTION_ORDERS: Readonly<Record<LocalizationSectionPlacement, readonly BuilderSectionName[]>>;
68
79
  declare function resolveMuiAdapterOptions(options?: MuiAdapterOptions): ResolvedMuiAdapterOptions;
69
80
 
70
81
  declare function createMuiButtonAdapter(options?: MuiAdapterOptions): ComponentType<BuilderButtonProps>;
@@ -138,4 +149,4 @@ declare const MuiToolbarSlot: NonNullable<FormBuilderSlots["toolbar"]>;
138
149
  declare const muiBuilderSlots: FormBuilderSlots;
139
150
  declare function createMuiBuilderSlots(options?: MuiAdapterOptions, customOverrides?: Partial<FormBuilderSlots>): FormBuilderSlots;
140
151
 
141
- export { type BuilderSectionName, DEFAULT_MUI_SECTION_ORDER, 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 };
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 };
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,
@@ -100,7 +113,7 @@ function createMuiButtonAdapter(options) {
100
113
  color: variant === "danger" ? "error" : "primary",
101
114
  variant: resolved.buttonVariants?.[variant ?? "primary"] ?? resolved.buttonVariant,
102
115
  fullWidth: resolved.buttonFullWidth ?? false,
103
- sx: { whiteSpace: "nowrap" },
116
+ sx: { whiteSpace: noWrap === false ? "normal" : "nowrap" },
104
117
  children
105
118
  }
106
119
  );
@@ -344,6 +357,7 @@ function createMuiSelectAdapter(options) {
344
357
  helperText,
345
358
  value,
346
359
  onChange,
360
+ onKeyDown,
347
361
  options: selectOptions
348
362
  }) {
349
363
  const resolved = useResolvedMuiAdapterOptions(options);
@@ -371,7 +385,9 @@ function createMuiSelectAdapter(options) {
371
385
  name,
372
386
  label,
373
387
  value,
388
+ disabled,
374
389
  onChange: handleChange,
390
+ onKeyDown,
375
391
  required,
376
392
  readOnly,
377
393
  inputProps: {
@@ -469,6 +485,7 @@ function createMuiTextInputAdapter(options) {
469
485
  helperText,
470
486
  value,
471
487
  onChange,
488
+ onKeyDown,
472
489
  placeholder,
473
490
  maxLength,
474
491
  inputMode,
@@ -510,7 +527,8 @@ function createMuiTextInputAdapter(options) {
510
527
  fullWidth: resolved.inputFullWidth ?? resolved.fullWidth,
511
528
  size: resolved.size,
512
529
  variant: resolved.variant,
513
- onChange: (event) => onChange(event.currentTarget.value)
530
+ onChange: (event) => onChange(event.currentTarget.value),
531
+ onKeyDown
514
532
  }
515
533
  );
516
534
  };
@@ -1033,10 +1051,32 @@ var MuiFieldEditorSlot = createMuiFieldEditorSlot();
1033
1051
 
1034
1052
  // src/slots/Localization.tsx
1035
1053
  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";
1054
+ import {
1055
+ Accordion,
1056
+ AccordionDetails,
1057
+ AccordionSummary,
1058
+ Alert as Alert2,
1059
+ Box as Box2,
1060
+ Chip,
1061
+ Stack as Stack4,
1062
+ Tab,
1063
+ Tabs,
1064
+ Typography as Typography4
1065
+ } from "@mui/material";
1066
+ import { useEffect, useState } from "react";
1038
1067
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1039
1068
  var DEFAULT_EMPTY_STATE_MESSAGE = "No translation locales have been added yet. Select a language from the dropdown above to add one.";
1069
+ function normalizeLocaleOptions(options, getLocaleLabel) {
1070
+ const seen = /* @__PURE__ */ new Set();
1071
+ const normalized = [];
1072
+ for (const item of options) {
1073
+ const value = typeof item === "string" ? item : item.value;
1074
+ if (value.length === 0 || seen.has(value)) continue;
1075
+ seen.add(value);
1076
+ normalized.push({ value, label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label });
1077
+ }
1078
+ return normalized;
1079
+ }
1040
1080
  function createMuiLocalizationSlot(options) {
1041
1081
  return function MuiLocalization({
1042
1082
  schema,
@@ -1055,6 +1095,7 @@ function createMuiLocalizationSlot(options) {
1055
1095
  const resolved = useResolvedMuiAdapterOptions(options);
1056
1096
  const { Button: Button2, ErrorMessage, Select: Select2, TextArea, TextInput } = components;
1057
1097
  const [newLocale, setNewLocale] = useState("");
1098
+ const [pendingFocusLocale, setPendingFocusLocale] = useState(null);
1058
1099
  const locales = Array.from(
1059
1100
  new Set((schema.supportedLocales ?? []).filter((locale) => locale !== schema.defaultLocale))
1060
1101
  );
@@ -1063,21 +1104,44 @@ function createMuiLocalizationSlot(options) {
1063
1104
  ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1064
1105
  ...schema.supportedLocales ?? []
1065
1106
  ]);
1066
- const availableAllowedLocales = policy?.allowedLocales?.filter((locale) => !registeredLocales.has(locale));
1107
+ const localizationOptions = resolved.localizationOptions ?? {};
1108
+ const availableLocaleSource = localizationOptions.availableLocales;
1109
+ const availableLocaleOptions = availableLocaleSource === void 0 ? policy?.allowedLocales?.map((value) => ({
1110
+ value,
1111
+ label: resolved.getLocaleLabel?.(value) ?? value
1112
+ })) : normalizeLocaleOptions(availableLocaleSource, resolved.getLocaleLabel);
1113
+ const filteredAvailableLocales = availableLocaleOptions?.filter(
1114
+ (locale) => !registeredLocales.has(locale.value) && (policy?.allowedLocales === void 0 || policy.allowedLocales.includes(locale.value))
1115
+ );
1116
+ const hasLocaleSelector = filteredAvailableLocales !== void 0;
1067
1117
  const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1068
1118
  const normalizedLocale = newLocale.trim();
1119
+ const selectedLocaleAvailable = !hasLocaleSelector || filteredAvailableLocales.some((locale) => locale.value === normalizedLocale);
1069
1120
  const localeAllowed = policy?.allowedLocales === void 0 || policy.allowedLocales.includes(normalizedLocale);
1070
- const canAddLocale = !readOnly && normalizedLocale.length > 0 && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
1121
+ const canAddLocale = !readOnly && normalizedLocale.length > 0 && selectedLocaleAvailable && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
1071
1122
  const handleTabChange = (_event, value) => onCurrentLocaleChange(value);
1072
1123
  const addLocale = () => {
1073
1124
  if (!canAddLocale) return;
1074
1125
  const result = actions.addLocale(normalizedLocale);
1075
1126
  if (!result.success) return;
1076
1127
  onCurrentLocaleChange(normalizedLocale);
1128
+ if (localizationOptions.autoFocusNewTab ?? true) setPendingFocusLocale(normalizedLocale);
1077
1129
  setNewLocale("");
1078
1130
  };
1131
+ const handleAddKeyDown = (event) => {
1132
+ if (event.key !== "Enter" || !canAddLocale) return;
1133
+ event.preventDefault();
1134
+ event.stopPropagation();
1135
+ addLocale();
1136
+ };
1137
+ useEffect(() => {
1138
+ if (pendingFocusLocale === null || currentLocale !== pendingFocusLocale) return;
1139
+ const tab = document.getElementById(`mui-builder-locale-tab-${pendingFocusLocale}`);
1140
+ if (tab === null) return;
1141
+ tab.focus();
1142
+ setPendingFocusLocale(null);
1143
+ }, [currentLocale, pendingFocusLocale]);
1079
1144
  const stackProps = resolved.muiSlotProps?.stack;
1080
- const localizationOptions = resolved.localizationOptions ?? {};
1081
1145
  const collapsible = localizationOptions.collapsible ?? false;
1082
1146
  const defaultLocaleControl = localizationOptions.defaultLocaleControl ?? "editable";
1083
1147
  const configuredTranslationLocales = locales.filter((locale) => locale !== schema.defaultLocale);
@@ -1085,24 +1149,35 @@ function createMuiLocalizationSlot(options) {
1085
1149
  ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1086
1150
  ...configuredTranslationLocales
1087
1151
  ];
1088
- const summaryLabel = configuredTranslationLocales.length === 0 ? "Translations not configured" : `${configuredLocales.length} ${configuredLocales.length === 1 ? "language" : "languages"} configured: ${configuredLocales.map(
1152
+ const legacySummaryLabel = configuredTranslationLocales.length === 0 ? "Translations not configured" : `${configuredLocales.length} ${configuredLocales.length === 1 ? "language" : "languages"} configured: ${configuredLocales.map(
1089
1153
  (locale) => locale === schema.defaultLocale ? `${resolved.getLocaleLabel?.(locale) ?? locale} (default)` : resolved.getLocaleLabel?.(locale) ?? locale
1090
1154
  ).join(", ")}`;
1091
1155
  const emptyStateMessage = localizationOptions.emptyStateMessage === void 0 ? DEFAULT_EMPTY_STATE_MESSAGE : translate(localizationOptions.emptyStateMessage);
1156
+ const summaryContext = {
1157
+ defaultLocale: schema.defaultLocale ?? "",
1158
+ supportedLocales: schema.supportedLocales ?? [],
1159
+ totalLocales: registeredLocales.size
1160
+ };
1161
+ const summary = localizationOptions.renderSummary === void 0 ? localizationOptions.showSummary ? configuredTranslationLocales.length > 0 ? /* @__PURE__ */ jsx14(Alert2, { severity: "success", sx: { mb: 2 }, children: translate("builder.localization.localesConfiguredSummary", {
1162
+ count: schema.supportedLocales?.length ?? 0
1163
+ }) }) : /* @__PURE__ */ jsx14(Alert2, { severity: "info", sx: { mb: 2 }, children: translate("builder.localization.noLocalesConfigured") }) : null : localizationOptions.renderSummary(summaryContext);
1092
1164
  const title = /* @__PURE__ */ jsxs8(Stack4, { alignItems: "center", direction: "row", spacing: 1, children: [
1093
1165
  /* @__PURE__ */ jsx14(Typography4, { variant: "subtitle1", fontWeight: "bold", children: translate("builder.localization") }),
1094
- localizationOptions.showSummary ? /* @__PURE__ */ jsx14(
1166
+ localizationOptions.renderSummary === void 0 && localizationOptions.showSummary && configuredTranslationLocales.length > 0 ? /* @__PURE__ */ jsx14(
1095
1167
  Chip,
1096
1168
  {
1097
- label: summaryLabel,
1169
+ label: legacySummaryLabel,
1098
1170
  size: "small",
1099
1171
  color: configuredTranslationLocales.length > 0 ? "primary" : "default",
1100
1172
  variant: "outlined"
1101
1173
  }
1102
1174
  ) : null
1103
1175
  ] });
1176
+ const noCandidateLocales = hasLocaleSelector && filteredAvailableLocales.length === 0;
1177
+ const candidateHelperText = localeLimitReached ? void 0 : noCandidateLocales ? translate("builder.localization.allLocalesAdded") : void 0;
1104
1178
  const content = /* @__PURE__ */ jsxs8(Stack4, { ...stackProps, spacing: resolved.dense ? 1 : 2, children: [
1105
1179
  !collapsible ? title : null,
1180
+ summary,
1106
1181
  /* @__PURE__ */ jsxs8(
1107
1182
  Stack4,
1108
1183
  {
@@ -1134,36 +1209,52 @@ function createMuiLocalizationSlot(options) {
1134
1209
  }
1135
1210
  }
1136
1211
  ) }),
1137
- /* @__PURE__ */ jsx14(Box2, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, children: availableAllowedLocales === void 0 ? /* @__PURE__ */ jsx14(
1138
- TextInput,
1212
+ /* @__PURE__ */ jsx14(Box2, { sx: { flexGrow: 1, minWidth: 0, width: { xs: "100%", sm: "auto" } }, onKeyDownCapture: handleAddKeyDown, children: hasLocaleSelector ? /* @__PURE__ */ jsx14(
1213
+ Select2,
1139
1214
  {
1140
1215
  id: "mui-builder-new-locale",
1141
- label: translate("builder.addLocale"),
1216
+ label: translate("builder.localization.selectLocaleToAdd"),
1142
1217
  value: newLocale,
1143
- disabled: readOnly || localeLimitReached,
1218
+ options: filteredAvailableLocales.map((locale) => ({ value: locale.value, label: locale.label })),
1219
+ ...candidateHelperText === void 0 ? {} : { helperText: candidateHelperText },
1220
+ disabled: readOnly || localeLimitReached || noCandidateLocales,
1144
1221
  onChange: setNewLocale
1145
1222
  }
1146
1223
  ) : /* @__PURE__ */ jsx14(
1147
- Select2,
1224
+ TextInput,
1148
1225
  {
1149
1226
  id: "mui-builder-new-locale",
1150
1227
  label: translate("builder.addLocale"),
1151
1228
  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,
1229
+ disabled: readOnly || localeLimitReached,
1160
1230
  onChange: setNewLocale
1161
1231
  }
1162
1232
  ) }),
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") }) })
1233
+ /* @__PURE__ */ jsx14(
1234
+ Box2,
1235
+ {
1236
+ sx: {
1237
+ flexShrink: 0,
1238
+ minWidth: "max-content",
1239
+ ...localizationOptions.noWrapActions ?? true ? { whiteSpace: "nowrap" } : {}
1240
+ },
1241
+ children: /* @__PURE__ */ jsx14(
1242
+ Button2,
1243
+ {
1244
+ variant: "primary",
1245
+ action: "addLocale",
1246
+ noWrap: localizationOptions.noWrapActions ?? true,
1247
+ disabled: !canAddLocale,
1248
+ onClick: addLocale,
1249
+ children: translate("builder.addLocale")
1250
+ }
1251
+ )
1252
+ }
1253
+ )
1164
1254
  ]
1165
1255
  }
1166
1256
  ),
1257
+ localeLimitReached ? /* @__PURE__ */ jsx14(Typography4, { variant: "body2", color: "text.secondary", children: translate("builder.localization.maxLocalesReached", { max: policy?.maxLocales ?? 0 }) }) : null,
1167
1258
  locales.length === 0 ? null : /* @__PURE__ */ jsx14(
1168
1259
  Tabs,
1169
1260
  {
@@ -1175,6 +1266,7 @@ function createMuiLocalizationSlot(options) {
1175
1266
  children: locales.map((locale) => /* @__PURE__ */ jsx14(
1176
1267
  Tab,
1177
1268
  {
1269
+ id: `mui-builder-locale-tab-${locale}`,
1178
1270
  value: locale,
1179
1271
  label: resolved.getLocaleLabel?.(locale) ?? locale,
1180
1272
  disabled: readOnly && locale !== currentLocale,
@@ -1188,6 +1280,7 @@ function createMuiLocalizationSlot(options) {
1188
1280
  Button2,
1189
1281
  {
1190
1282
  variant: "secondary",
1283
+ noWrap: localizationOptions.noWrapActions ?? true,
1191
1284
  disabled: readOnly || !editingLocaleConfigured || isTranslating,
1192
1285
  onClick: onAutoTranslate,
1193
1286
  children: isTranslating ? translate("builder.translating") : translate("builder.autoTranslate")
@@ -1308,7 +1401,8 @@ function MuiFormBuilder({
1308
1401
  const contextValue = useMemo(() => ({ options: contextOptions }), [contextOptions]);
1309
1402
  const components = useMemo(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1310
1403
  const slots = useMemo(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1311
- const resolvedSectionOrder = sectionOrder ?? contextOptions.layoutOptions?.sectionOrder;
1404
+ const placement = contextOptions.localizationOptions?.placement;
1405
+ const resolvedSectionOrder = sectionOrder ?? (placement === void 0 ? contextOptions.layoutOptions?.sectionOrder : MUI_LOCALIZATION_SECTION_ORDERS[placement]);
1312
1406
  return /* @__PURE__ */ jsx15(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx15(
1313
1407
  FormBuilder,
1314
1408
  {
@@ -1322,6 +1416,7 @@ function MuiFormBuilder({
1322
1416
  }
1323
1417
  export {
1324
1418
  DEFAULT_MUI_SECTION_ORDER,
1419
+ MUI_LOCALIZATION_SECTION_ORDERS,
1325
1420
  MuiButtonAdapter,
1326
1421
  MuiCheckboxAdapter,
1327
1422
  MuiErrorMessageAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/mui",
3
- "version": "2.9.6",
3
+ "version": "3.0.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": "2.9.6",
50
- "@form-engine-ts/react": "2.9.6"
49
+ "@form-engine-ts/core": "3.0.0",
50
+ "@form-engine-ts/react": "3.0.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": "2.9.6",
62
- "@form-engine-ts/react": "2.9.6"
61
+ "@form-engine-ts/core": "3.0.0",
62
+ "@form-engine-ts/react": "3.0.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",