@form-engine-ts/react 2.9.3 → 2.9.5

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
@@ -101,7 +101,7 @@ function createLocalStorageSubmissionAttemptStore(options = {}) {
101
101
  import {
102
102
  populateSchemaTranslations
103
103
  } from "@form-engine-ts/core";
104
- import { createContext, useContext, useState } from "react";
104
+ import { Children, createContext, isValidElement, useContext, useState } from "react";
105
105
 
106
106
  // src/hooks/useFormBuilder.ts
107
107
  import {
@@ -1058,7 +1058,7 @@ function BuilderButton(props) {
1058
1058
  ...props,
1059
1059
  className: primitiveClassName(props.className, "feb-button", context.unstyled),
1060
1060
  disabled: context.readOnly || props.disabled === true,
1061
- readOnly: context.readOnly
1061
+ readOnly: context.readOnly || props.readOnly === true
1062
1062
  }
1063
1063
  );
1064
1064
  }
@@ -1073,7 +1073,7 @@ function BuilderIconButton(props) {
1073
1073
  icon,
1074
1074
  className: primitiveClassName(props.className, "feb-icon-button", context.unstyled),
1075
1075
  disabled: context.readOnly || props.disabled === true,
1076
- readOnly: context.readOnly
1076
+ readOnly: context.readOnly || props.readOnly === true
1077
1077
  }
1078
1078
  );
1079
1079
  }
@@ -1086,7 +1086,7 @@ function BuilderTextInput(props) {
1086
1086
  ...props,
1087
1087
  className: primitiveClassName(props.className, "feb-input", context.unstyled),
1088
1088
  disabled: context.readOnly || props.disabled === true,
1089
- readOnly: context.readOnly
1089
+ readOnly: context.readOnly || props.readOnly === true
1090
1090
  }
1091
1091
  );
1092
1092
  }
@@ -1099,7 +1099,7 @@ function BuilderTextArea(props) {
1099
1099
  ...props,
1100
1100
  className: primitiveClassName(props.className, "feb-textarea", context.unstyled),
1101
1101
  disabled: context.readOnly || props.disabled === true,
1102
- readOnly: context.readOnly
1102
+ readOnly: context.readOnly || props.readOnly === true
1103
1103
  }
1104
1104
  );
1105
1105
  }
@@ -1112,7 +1112,7 @@ function BuilderSelect(props) {
1112
1112
  ...props,
1113
1113
  className: primitiveClassName(props.className, "feb-select", context.unstyled),
1114
1114
  disabled: context.readOnly || props.disabled === true,
1115
- readOnly: context.readOnly
1115
+ readOnly: context.readOnly || props.readOnly === true
1116
1116
  }
1117
1117
  );
1118
1118
  }
@@ -1125,7 +1125,7 @@ function BuilderCheckbox(props) {
1125
1125
  ...props,
1126
1126
  className: primitiveClassName(props.className, "feb-checkbox", context.unstyled),
1127
1127
  disabled: context.readOnly || props.disabled === true,
1128
- readOnly: context.readOnly
1128
+ readOnly: context.readOnly || props.readOnly === true
1129
1129
  }
1130
1130
  );
1131
1131
  }
@@ -1169,6 +1169,7 @@ var BUILDER_DEFAULTS = {
1169
1169
  "builder.basicSettings": "Basic settings",
1170
1170
  "builder.formTitle": "Form title",
1171
1171
  "builder.formDescription": "Form description",
1172
+ "builder.description": "Description",
1172
1173
  "builder.moveUp": "Move {{title}} up",
1173
1174
  "builder.moveDown": "Move {{title}} down",
1174
1175
  "builder.delete": "Delete {{title}}",
@@ -1203,12 +1204,31 @@ var BUILDER_DEFAULTS = {
1203
1204
  "builder.pageQuestion": "Question to move to the new page",
1204
1205
  "builder.questionPage": "Page",
1205
1206
  "builder.pageCondition": "Page display condition",
1207
+ "builder.unassigned": "Unassigned",
1206
1208
  "builder.localization": "Localization",
1207
1209
  "builder.defaultLocale": "Default locale",
1208
1210
  "builder.supportedLocales": "Supported locales",
1209
1211
  "builder.addLocale": "Add locale",
1210
1212
  "builder.editLocale": "Edit locale",
1211
1213
  "builder.autoTranslate": "Translate all text",
1214
+ "builder.translating": "Translating\u2026",
1215
+ "builder.translationLocale": "Translation locale",
1216
+ "builder.selectLocale": "Select a locale to edit translations.",
1217
+ "builder.translation": "{{locale}} translation",
1218
+ "builder.translatedFormTitle": "Translated form title",
1219
+ "builder.translatedFormDescription": "Translated form description",
1220
+ "builder.translatedCompletionMessage": "Translated completion message",
1221
+ "builder.translatedQuestionTitle": "Translated question title",
1222
+ "builder.translatedDescription": "Translated description",
1223
+ "builder.actions.moveUp": "Move up",
1224
+ "builder.actions.moveDown": "Move down",
1225
+ "builder.actions.delete": "Delete",
1226
+ "builder.actions.add": "Add",
1227
+ "builder.actions.edit": "Edit",
1228
+ "builder.actions.settings": "Settings",
1229
+ "builder.actions.translate": "Translate",
1230
+ "builder.actions.close": "Close",
1231
+ "builder.actions.dragHandle": "Reorder",
1212
1232
  "builder.translationUnavailable": "Provide an async translation adapter to enable automatic translation.",
1213
1233
  "builder.fieldType.text": "Text",
1214
1234
  "builder.fieldType.textarea": "Textarea",
@@ -1229,12 +1249,81 @@ function interpolate(template, params) {
1229
1249
  (token, name) => Object.hasOwn(params, name) ? String(params[name]) : token
1230
1250
  );
1231
1251
  }
1252
+ function BuilderSectionGroup({ children }) {
1253
+ return children;
1254
+ }
1255
+ function sectionGroupName(node) {
1256
+ if (!isValidElement(node) || node.type !== BuilderSectionGroup) return void 0;
1257
+ return node.props.name;
1258
+ }
1259
+ function OrderedBuilderSections({
1260
+ order,
1261
+ children
1262
+ }) {
1263
+ if (order === void 0) return children;
1264
+ const ranks = new Map(order.map((name, index) => [name, index]));
1265
+ const groups = Children.toArray(children);
1266
+ return groups.map((group, index) => ({ group, index, rank: ranks.get(sectionGroupName(group) ?? "questions") ?? order.length })).sort((left, right) => left.rank - right.rank || left.index - right.index).map(({ group }) => group);
1267
+ }
1232
1268
  function fieldTypeKey(type) {
1233
1269
  return `builder.fieldType.${type}`;
1234
1270
  }
1235
1271
  function operatorKey(operator) {
1236
1272
  return `builder.operator.${operator}`;
1237
1273
  }
1274
+ function manualTranslationContext(schema, locale, target, property, translatedText) {
1275
+ if (target.kind === "form") {
1276
+ if (property !== "title" && property !== "description" && property !== "completionMessage") return void 0;
1277
+ return {
1278
+ locale,
1279
+ kind: "form",
1280
+ nodeId: schema.id,
1281
+ property,
1282
+ sourceText: schema[property] ?? "",
1283
+ translatedText,
1284
+ ...schema.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[locale]?.[property] }
1285
+ };
1286
+ }
1287
+ if (target.id === void 0) return void 0;
1288
+ if (target.kind === "field") {
1289
+ const field = schema.fields.find((candidate) => candidate.id === target.id);
1290
+ if (field === void 0 || property !== "title" && property !== "description") return void 0;
1291
+ return {
1292
+ locale,
1293
+ kind: "field",
1294
+ nodeId: field.id,
1295
+ property,
1296
+ sourceText: field[property] ?? "",
1297
+ translatedText,
1298
+ ...field.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: field.translationMetadata[locale]?.[property] }
1299
+ };
1300
+ }
1301
+ if (target.kind === "page") {
1302
+ const page = schema.pages?.find((candidate) => candidate.id === target.id);
1303
+ if (page === void 0 || property !== "title" && property !== "description") return void 0;
1304
+ return {
1305
+ locale,
1306
+ kind: "page",
1307
+ nodeId: page.id,
1308
+ property,
1309
+ sourceText: page[property] ?? "",
1310
+ translatedText,
1311
+ ...page.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: page.translationMetadata[locale]?.[property] }
1312
+ };
1313
+ }
1314
+ if (property !== "label") return void 0;
1315
+ const option = schema.fields.filter((field) => "options" in field).flatMap((field) => field.options).find((candidate) => candidate.id === target.id);
1316
+ if (option === void 0) return void 0;
1317
+ return {
1318
+ locale,
1319
+ kind: "option",
1320
+ nodeId: option.id,
1321
+ property,
1322
+ sourceText: option.label,
1323
+ translatedText,
1324
+ ...option.translationMetadata?.[locale]?.[property] === void 0 ? {} : { existingTranslationMetadata: option.translationMetadata[locale]?.[property] }
1325
+ };
1326
+ }
1238
1327
  function defaultConditionValue(field) {
1239
1328
  if (field.type === "checkbox") return true;
1240
1329
  if (field.type === "number" || field.type === "rating") return field.min ?? 1;
@@ -1331,6 +1420,7 @@ function FormBuilder({
1331
1420
  features,
1332
1421
  components: componentOverrides,
1333
1422
  slots,
1423
+ sectionOrder,
1334
1424
  disableDefaultStyles = false,
1335
1425
  unstyled = false
1336
1426
  }) {
@@ -1348,6 +1438,7 @@ function FormBuilder({
1348
1438
  const PagesSlot = slots?.pages;
1349
1439
  const LocalizationSlot = slots?.localization;
1350
1440
  const TranslationActionsSlot = slots?.translationActions;
1441
+ const resolvedSectionOrder = sectionOrder ?? slots?.sectionOrder;
1351
1442
  const headless = useFormBuilder({
1352
1443
  schema,
1353
1444
  onChange,
@@ -1362,7 +1453,11 @@ function FormBuilder({
1362
1453
  const [translationError, setTranslationError] = useState(null);
1363
1454
  const [translationReport, setTranslationReport] = useState();
1364
1455
  const translate = (key, params = {}) => {
1365
- const translated = translator?.translate(key, locale, params);
1456
+ const translatorParams = {};
1457
+ for (const [name, value] of Object.entries(params)) {
1458
+ if (typeof value === "string" || typeof value === "number") translatorParams[name] = value;
1459
+ }
1460
+ const translated = translator?.translate(key, locale, translatorParams);
1366
1461
  return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
1367
1462
  };
1368
1463
  const pagesEnabled = features?.pages ?? true;
@@ -1487,25 +1582,30 @@ function FormBuilder({
1487
1582
  setIsTranslating(false);
1488
1583
  }
1489
1584
  };
1490
- const updateManualTranslation = (context) => {
1491
- if (readOnly) return;
1492
- const metadata = createManualTranslationMetadata?.(context);
1493
- const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
1494
- executeAction(
1585
+ const setManualTranslation = (targetLocale, target, property, text) => {
1586
+ if (readOnly) return { success: true };
1587
+ const normalizedLocale = targetLocale.trim();
1588
+ const context = manualTranslationContext(schema, normalizedLocale, target, property, text);
1589
+ const metadata = context === void 0 ? void 0 : createManualTranslationMetadata?.(context);
1590
+ return executeAction(
1495
1591
  () => headless.setLocaleTranslation(
1496
- context.locale,
1592
+ normalizedLocale,
1497
1593
  target,
1498
- context.property,
1499
- context.translatedText,
1594
+ property,
1595
+ text,
1500
1596
  metadata === void 0 ? void 0 : { metadata }
1501
1597
  ),
1502
1598
  {
1503
- action: "setLocaleTranslation",
1504
- targetId: context.nodeId,
1505
- params: { locale: context.locale, kind: context.kind, property: context.property }
1599
+ action: "setManualTranslation",
1600
+ ...target.id === void 0 ? {} : { targetId: target.id },
1601
+ params: { locale: normalizedLocale, kind: target.kind, property }
1506
1602
  }
1507
1603
  );
1508
1604
  };
1605
+ const updateManualTranslation = (context) => {
1606
+ const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
1607
+ setManualTranslation(context.locale, target, context.property, context.translatedText);
1608
+ };
1509
1609
  const updateFormTranslation = (property, translatedText) => {
1510
1610
  if (editingLocale.length === 0) return;
1511
1611
  updateManualTranslation({
@@ -1614,6 +1714,7 @@ function FormBuilder({
1614
1714
  ...target.id === void 0 ? {} : { targetId: target.id },
1615
1715
  params: { locale: targetLocale, property }
1616
1716
  }),
1717
+ setManualTranslation,
1617
1718
  addLocale: (targetLocale) => executeAction(() => headless.addLocale(targetLocale), {
1618
1719
  action: "addLocale",
1619
1720
  params: { locale: targetLocale }
@@ -1647,8 +1748,8 @@ function FormBuilder({
1647
1748
  addOption(fieldId);
1648
1749
  }
1649
1750
  },
1650
- children: /* @__PURE__ */ jsxs(Fieldset, { className: builderClass("form-engine-builder__controls"), disabled: readOnly, children: [
1651
- /* @__PURE__ */ jsx(
1751
+ children: /* @__PURE__ */ jsx(Fieldset, { className: builderClass("form-engine-builder__controls"), disabled: readOnly, children: /* @__PURE__ */ jsxs(OrderedBuilderSections, { ...resolvedSectionOrder === void 0 ? {} : { order: resolvedSectionOrder }, children: [
1752
+ /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "basicSettings", children: /* @__PURE__ */ jsx(
1652
1753
  Section,
1653
1754
  {
1654
1755
  className: builderClass("form-engine-builder__basic-settings"),
@@ -1682,8 +1783,17 @@ function FormBuilder({
1682
1783
  ) })
1683
1784
  ] })
1684
1785
  }
1685
- ),
1686
- pagesEnabled ? PagesSlot === void 0 ? /* @__PURE__ */ jsx(
1786
+ ) }),
1787
+ resolvedSectionOrder === void 0 ? null : /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "completionMessage", children: /* @__PURE__ */ jsx(Section, { headingId: "builder-completion-message-heading", title: translate("builder.completionMessage"), children: /* @__PURE__ */ jsx(
1788
+ TextInput,
1789
+ {
1790
+ id: "builder-completion-message",
1791
+ label: translate("builder.completionMessage"),
1792
+ value: schema.completionMessage ?? "",
1793
+ onChange: (value) => setSourceText({ kind: "form" }, "completionMessage", value)
1794
+ }
1795
+ ) }) }),
1796
+ /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "questions", children: pagesEnabled ? PagesSlot === void 0 ? /* @__PURE__ */ jsx(
1687
1797
  Section,
1688
1798
  {
1689
1799
  className: builderClass("form-engine-builder__pages"),
@@ -1733,6 +1843,7 @@ function FormBuilder({
1733
1843
  ToolbarSlot,
1734
1844
  {
1735
1845
  schema,
1846
+ translate,
1736
1847
  kind: "page",
1737
1848
  targetId: page.id,
1738
1849
  index: pageIndex,
@@ -1879,7 +1990,10 @@ function FormBuilder({
1879
1990
  {
1880
1991
  source,
1881
1992
  condition: page.displayCondition,
1882
- onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
1993
+ onChange: (condition) => updatePage(page.id, (current) => ({
1994
+ ...current,
1995
+ displayCondition: condition
1996
+ })),
1883
1997
  translate,
1884
1998
  components
1885
1999
  }
@@ -1914,20 +2028,22 @@ function FormBuilder({
1914
2028
  PagesSlot,
1915
2029
  {
1916
2030
  schema,
2031
+ translate,
1917
2032
  currentLocale: editingLocale,
2033
+ ...features === void 0 ? {} : { features },
1918
2034
  readOnly,
1919
2035
  actions,
1920
2036
  components
1921
2037
  }
1922
- ) : null,
1923
- localizationEnabled ? LocalizationSlot === void 0 ? /* @__PURE__ */ jsxs(
2038
+ ) : null }),
2039
+ /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "localization", children: localizationEnabled ? LocalizationSlot === void 0 ? /* @__PURE__ */ jsxs(
1924
2040
  Section,
1925
2041
  {
1926
2042
  className: builderClass("form-engine-builder__localization"),
1927
2043
  headingId: "builder-localization-heading",
1928
2044
  title: translate("builder.localization"),
1929
2045
  children: [
1930
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2046
+ resolvedSectionOrder === void 0 ? /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
1931
2047
  TextInput,
1932
2048
  {
1933
2049
  id: "builder-completion-message",
@@ -1935,7 +2051,7 @@ function FormBuilder({
1935
2051
  value: schema.completionMessage ?? "",
1936
2052
  onChange: (value) => setSourceText({ kind: "form" }, "completionMessage", value)
1937
2053
  }
1938
- ) }),
2054
+ ) }) : null,
1939
2055
  /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
1940
2056
  /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
1941
2057
  TextInput,
@@ -2000,12 +2116,14 @@ function FormBuilder({
2000
2116
  TranslationActionsSlot,
2001
2117
  {
2002
2118
  schema,
2119
+ translate,
2003
2120
  currentLocale: editingLocale,
2004
2121
  onAutoTranslate: () => void translateAll(),
2005
2122
  isTranslating,
2006
2123
  ...translationError === null ? {} : { translationError },
2007
2124
  ...translationReport === void 0 ? {} : { translationReport },
2008
2125
  onClearTranslationError: () => setTranslationError(null),
2126
+ translationAdapterAvailable: translationAdapter !== void 0,
2009
2127
  readOnly,
2010
2128
  actions,
2011
2129
  components
@@ -2049,17 +2167,20 @@ function FormBuilder({
2049
2167
  LocalizationSlot,
2050
2168
  {
2051
2169
  schema,
2170
+ translate,
2052
2171
  currentLocale: editingLocale,
2053
2172
  onCurrentLocaleChange: setEditingLocale,
2054
2173
  onAutoTranslate: () => void translateAll(),
2055
2174
  isTranslating,
2056
2175
  ...translationError === null ? {} : { translationError },
2176
+ ...policy === void 0 ? {} : { policy },
2177
+ translationAdapterAvailable: translationAdapter !== void 0,
2057
2178
  readOnly,
2058
2179
  actions,
2059
2180
  components
2060
2181
  }
2061
- ) : null,
2062
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__list"), children: schema.fields.map((field, index) => {
2182
+ ) : null }),
2183
+ /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "questions", children: /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__list"), children: schema.fields.map((field, index) => {
2063
2184
  const condition = field.displayCondition;
2064
2185
  const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
2065
2186
  const availableSources = schema.fields.slice(0, index);
@@ -2071,7 +2192,9 @@ function FormBuilder({
2071
2192
  field,
2072
2193
  index,
2073
2194
  currentLocale: editingLocale,
2195
+ translate,
2074
2196
  ...policy === void 0 ? {} : { policy },
2197
+ ...features === void 0 ? {} : { features },
2075
2198
  readOnly,
2076
2199
  actions,
2077
2200
  components
@@ -2079,348 +2202,362 @@ function FormBuilder({
2079
2202
  field.id
2080
2203
  );
2081
2204
  }
2082
- return /* @__PURE__ */ jsxs(Fieldset, { className: builderClass("form-engine-builder__question"), legend: field.title, children: [
2083
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__toolbar"), children: ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2084
- /* @__PURE__ */ jsx(
2085
- IconButton,
2086
- {
2087
- actionType: "moveUp",
2088
- title: translate("builder.moveUp", { title: field.title }),
2089
- disabled: index === 0,
2090
- onClick: () => moveField(index, -1)
2091
- }
2092
- ),
2093
- /* @__PURE__ */ jsx(
2094
- IconButton,
2095
- {
2096
- actionType: "moveDown",
2097
- title: translate("builder.moveDown", { title: field.title }),
2098
- disabled: index === schema.fields.length - 1,
2099
- onClick: () => moveField(index, 1)
2100
- }
2101
- ),
2102
- /* @__PURE__ */ jsx(
2103
- IconButton,
2104
- {
2105
- actionType: "delete",
2106
- disabled: schema.fields.length === 1,
2107
- onClick: () => removeField(field.id),
2108
- "aria-label": translate("builder.delete", { title: field.title }),
2109
- title: translate("builder.delete", { title: field.title })
2110
- }
2111
- )
2112
- ] }) : /* @__PURE__ */ jsx(
2113
- ToolbarSlot,
2114
- {
2115
- schema,
2116
- kind: "field",
2117
- targetId: field.id,
2118
- index,
2119
- total: schema.fields.length,
2120
- title: field.title,
2121
- onMoveUp: () => moveField(index, -1),
2122
- onMoveDown: () => moveField(index, 1),
2123
- onRemove: () => removeField(field.id),
2124
- readOnly,
2125
- actions,
2126
- components
2127
- }
2128
- ) }),
2129
- /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
2130
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2131
- TextInput,
2132
- {
2133
- id: `builder-field-${field.id}-title`,
2134
- name: `fields.${field.id}.title`,
2135
- label: translate("builder.questionTitle"),
2136
- required: true,
2137
- error: field.title.trim().length === 0,
2138
- helperText: field.title.trim().length === 0 ? translate("builder.required") : "",
2139
- "aria-describedby": field.title.trim().length === 0 ? `builder-field-${field.id}-title-error` : void 0,
2140
- value: field.title,
2141
- placeholder: translate("builder.questionTitlePlaceholder"),
2142
- onChange: (value) => updateField(field.id, (current) => ({
2143
- ...current,
2144
- title: value.trim().length === 0 ? current.title : value
2145
- }))
2146
- }
2147
- ) }),
2148
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2149
- Select,
2150
- {
2151
- id: `builder-field-${field.id}-type`,
2152
- label: translate("builder.type"),
2153
- value: field.type,
2154
- onChange: (value) => changeType(field.id, value),
2155
- options: FIELD_TYPES.filter(
2156
- (type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
2157
- ).map((type) => ({ value: type, label: translate(fieldTypeKey(type)) }))
2158
- }
2159
- ) }),
2160
- /* @__PURE__ */ jsx(
2161
- Checkbox,
2162
- {
2163
- className: builderClass("form-engine-builder__check"),
2164
- checked: field.required === true,
2165
- onChange: (checked) => updateField(field.id, (current) => ({ ...current, required: checked })),
2166
- label: translate("builder.required")
2167
- }
2168
- )
2169
- ] }),
2170
- !pagesEnabled || schema.pages === void 0 ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2171
- Select,
2172
- {
2173
- id: `builder-field-${field.id}-page`,
2174
- label: translate("builder.questionPage"),
2175
- value: pageForField(field.id)?.id ?? "",
2176
- onChange: (value) => assignFieldToPage(field.id, value),
2177
- options: schema.pages.map((page, pageIndex) => ({
2178
- value: page.id,
2179
- label: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}`
2180
- }))
2181
- }
2182
- ) }),
2183
- !localizationEnabled || editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__translation-editor"), children: [
2184
- /* @__PURE__ */ jsx("strong", { children: editingLocale }),
2185
- /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
2186
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2187
- TextInput,
2205
+ return /* @__PURE__ */ jsxs(
2206
+ Fieldset,
2207
+ {
2208
+ className: builderClass("form-engine-builder__question"),
2209
+ legend: field.title,
2210
+ children: [
2211
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__toolbar"), children: ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2212
+ /* @__PURE__ */ jsx(
2213
+ IconButton,
2214
+ {
2215
+ actionType: "moveUp",
2216
+ title: translate("builder.moveUp", { title: field.title }),
2217
+ disabled: index === 0,
2218
+ onClick: () => moveField(index, -1)
2219
+ }
2220
+ ),
2221
+ /* @__PURE__ */ jsx(
2222
+ IconButton,
2223
+ {
2224
+ actionType: "moveDown",
2225
+ title: translate("builder.moveDown", { title: field.title }),
2226
+ disabled: index === schema.fields.length - 1,
2227
+ onClick: () => moveField(index, 1)
2228
+ }
2229
+ ),
2230
+ /* @__PURE__ */ jsx(
2231
+ IconButton,
2232
+ {
2233
+ actionType: "delete",
2234
+ disabled: schema.fields.length === 1,
2235
+ onClick: () => removeField(field.id),
2236
+ "aria-label": translate("builder.delete", { title: field.title }),
2237
+ title: translate("builder.delete", { title: field.title })
2238
+ }
2239
+ )
2240
+ ] }) : /* @__PURE__ */ jsx(
2241
+ ToolbarSlot,
2188
2242
  {
2189
- id: `builder-field-${field.id}-${editingLocale}-title`,
2190
- label: translate("builder.questionTitle"),
2191
- value: field.translations?.[editingLocale]?.title ?? "",
2192
- onChange: (value) => updateManualTranslation({
2193
- locale: editingLocale,
2194
- kind: "field",
2195
- nodeId: field.id,
2196
- property: "title",
2197
- sourceText: field.title,
2198
- translatedText: value,
2199
- ...field.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
2200
- existingTranslationMetadata: field.translationMetadata[editingLocale]?.title
2201
- }
2202
- })
2243
+ schema,
2244
+ translate,
2245
+ kind: "field",
2246
+ targetId: field.id,
2247
+ index,
2248
+ total: schema.fields.length,
2249
+ title: field.title,
2250
+ onMoveUp: () => moveField(index, -1),
2251
+ onMoveDown: () => moveField(index, 1),
2252
+ onRemove: () => removeField(field.id),
2253
+ readOnly,
2254
+ actions,
2255
+ components
2203
2256
  }
2204
2257
  ) }),
2205
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2206
- TextInput,
2258
+ /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
2259
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2260
+ TextInput,
2261
+ {
2262
+ id: `builder-field-${field.id}-title`,
2263
+ name: `fields.${field.id}.title`,
2264
+ label: translate("builder.questionTitle"),
2265
+ required: true,
2266
+ error: field.title.trim().length === 0,
2267
+ helperText: field.title.trim().length === 0 ? translate("builder.required") : "",
2268
+ "aria-describedby": field.title.trim().length === 0 ? `builder-field-${field.id}-title-error` : void 0,
2269
+ value: field.title,
2270
+ placeholder: translate("builder.questionTitlePlaceholder"),
2271
+ onChange: (value) => updateField(field.id, (current) => ({
2272
+ ...current,
2273
+ title: value.trim().length === 0 ? current.title : value
2274
+ }))
2275
+ }
2276
+ ) }),
2277
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2278
+ Select,
2279
+ {
2280
+ id: `builder-field-${field.id}-type`,
2281
+ label: translate("builder.type"),
2282
+ value: field.type,
2283
+ onChange: (value) => changeType(field.id, value),
2284
+ options: FIELD_TYPES.filter(
2285
+ (type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
2286
+ ).map((type) => ({ value: type, label: translate(fieldTypeKey(type)) }))
2287
+ }
2288
+ ) }),
2289
+ /* @__PURE__ */ jsx(
2290
+ Checkbox,
2291
+ {
2292
+ className: builderClass("form-engine-builder__check"),
2293
+ checked: field.required === true,
2294
+ onChange: (checked) => updateField(field.id, (current) => ({ ...current, required: checked })),
2295
+ label: translate("builder.required")
2296
+ }
2297
+ )
2298
+ ] }),
2299
+ !pagesEnabled || schema.pages === void 0 ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2300
+ Select,
2207
2301
  {
2208
- id: `builder-field-${field.id}-${editingLocale}-description`,
2209
- label: translate("builder.pageDescription"),
2210
- value: field.translations?.[editingLocale]?.description ?? "",
2211
- onChange: (value) => updateManualTranslation({
2212
- locale: editingLocale,
2213
- kind: "field",
2214
- nodeId: field.id,
2215
- property: "description",
2216
- sourceText: field.description ?? "",
2217
- translatedText: value,
2218
- ...field.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
2219
- existingTranslationMetadata: field.translationMetadata[editingLocale]?.description
2220
- }
2221
- })
2302
+ id: `builder-field-${field.id}-page`,
2303
+ label: translate("builder.questionPage"),
2304
+ value: pageForField(field.id)?.id ?? "",
2305
+ onChange: (value) => assignFieldToPage(field.id, value),
2306
+ options: schema.pages.map((page, pageIndex) => ({
2307
+ value: page.id,
2308
+ label: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}`
2309
+ }))
2222
2310
  }
2223
- ) })
2224
- ] }),
2225
- "options" in field ? field.options.map((option, optionIndex) => /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2226
- TextInput,
2227
- {
2228
- id: `builder-option-${option.id}-${editingLocale}`,
2229
- label: `${translate("builder.optionLabel", { index: optionIndex + 1 })} (${editingLocale})`,
2230
- value: option.translations?.[editingLocale] ?? "",
2231
- onChange: (value) => updateManualTranslation({
2232
- locale: editingLocale,
2233
- kind: "option",
2234
- nodeId: option.id,
2235
- property: "label",
2236
- sourceText: option.label,
2237
- translatedText: value,
2238
- ...option.translationMetadata?.[editingLocale]?.label === void 0 ? {} : {
2239
- existingTranslationMetadata: option.translationMetadata[editingLocale]?.label
2311
+ ) }),
2312
+ !localizationEnabled || editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__translation-editor"), children: [
2313
+ /* @__PURE__ */ jsx("strong", { children: editingLocale }),
2314
+ /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
2315
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2316
+ TextInput,
2317
+ {
2318
+ id: `builder-field-${field.id}-${editingLocale}-title`,
2319
+ label: translate("builder.questionTitle"),
2320
+ value: field.translations?.[editingLocale]?.title ?? "",
2321
+ onChange: (value) => updateManualTranslation({
2322
+ locale: editingLocale,
2323
+ kind: "field",
2324
+ nodeId: field.id,
2325
+ property: "title",
2326
+ sourceText: field.title,
2327
+ translatedText: value,
2328
+ ...field.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
2329
+ existingTranslationMetadata: field.translationMetadata[editingLocale]?.title
2330
+ }
2331
+ })
2332
+ }
2333
+ ) }),
2334
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2335
+ TextInput,
2336
+ {
2337
+ id: `builder-field-${field.id}-${editingLocale}-description`,
2338
+ label: translate("builder.pageDescription"),
2339
+ value: field.translations?.[editingLocale]?.description ?? "",
2340
+ onChange: (value) => updateManualTranslation({
2341
+ locale: editingLocale,
2342
+ kind: "field",
2343
+ nodeId: field.id,
2344
+ property: "description",
2345
+ sourceText: field.description ?? "",
2346
+ translatedText: value,
2347
+ ...field.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
2348
+ existingTranslationMetadata: field.translationMetadata[editingLocale]?.description
2349
+ }
2350
+ })
2351
+ }
2352
+ ) })
2353
+ ] }),
2354
+ "options" in field ? field.options.map((option, optionIndex) => /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2355
+ TextInput,
2356
+ {
2357
+ id: `builder-option-${option.id}-${editingLocale}`,
2358
+ label: `${translate("builder.optionLabel", { index: optionIndex + 1 })} (${editingLocale})`,
2359
+ value: option.translations?.[editingLocale] ?? "",
2360
+ onChange: (value) => updateManualTranslation({
2361
+ locale: editingLocale,
2362
+ kind: "option",
2363
+ nodeId: option.id,
2364
+ property: "label",
2365
+ sourceText: option.label,
2366
+ translatedText: value,
2367
+ ...option.translationMetadata?.[editingLocale]?.label === void 0 ? {} : {
2368
+ existingTranslationMetadata: option.translationMetadata[editingLocale]?.label
2369
+ }
2370
+ })
2240
2371
  }
2241
- })
2242
- }
2243
- ) }, option.id)) : null
2244
- ] }),
2245
- field.type === "rating" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
2246
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2247
- TextInput,
2248
- {
2249
- id: `builder-field-${field.id}-minimum`,
2250
- label: translate("builder.minimum"),
2251
- type: "number",
2252
- value: String(field.min ?? 1),
2253
- onChange: (value) => {
2254
- const min = Number(value);
2255
- if (!Number.isInteger(min)) return;
2256
- updateField(
2257
- field.id,
2258
- (current) => current.type === "rating" ? { ...current, min, max: Math.max(min, current.max ?? 5) } : current
2259
- );
2260
- }
2261
- }
2262
- ) }),
2263
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2264
- TextInput,
2265
- {
2266
- id: `builder-field-${field.id}-maximum`,
2267
- label: translate("builder.maximum"),
2268
- type: "number",
2269
- value: String(field.max ?? 5),
2270
- onChange: (value) => {
2271
- const max = Number(value);
2272
- if (!Number.isInteger(max)) return;
2273
- updateField(
2274
- field.id,
2275
- (current) => current.type === "rating" ? { ...current, min: Math.min(current.min ?? 1, max), max } : current
2276
- );
2277
- }
2278
- }
2279
- ) })
2280
- ] }) : null,
2281
- "options" in field ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__options"), children: [
2282
- /* @__PURE__ */ jsx("strong", { children: translate("builder.options") }),
2283
- field.options.map(
2284
- (option, optionIndex) => OptionEditorSlot === void 0 ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__option"), children: [
2285
- /* @__PURE__ */ jsx(
2372
+ ) }, option.id)) : null
2373
+ ] }),
2374
+ field.type === "rating" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
2375
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2286
2376
  TextInput,
2287
2377
  {
2288
- id: `builder-option-${option.id}`,
2289
- label: translate("builder.optionLabel", { index: optionIndex + 1 }),
2290
- value: option.label,
2291
- placeholder: translate("builder.optionLabelPlaceholder"),
2292
- onChange: (value) => value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, value)
2378
+ id: `builder-field-${field.id}-minimum`,
2379
+ label: translate("builder.minimum"),
2380
+ type: "number",
2381
+ value: String(field.min ?? 1),
2382
+ onChange: (value) => {
2383
+ const min = Number(value);
2384
+ if (!Number.isInteger(min)) return;
2385
+ updateField(
2386
+ field.id,
2387
+ (current) => current.type === "rating" ? { ...current, min, max: Math.max(min, current.max ?? 5) } : current
2388
+ );
2389
+ }
2293
2390
  }
2294
- ),
2295
- ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2296
- /* @__PURE__ */ jsx(
2297
- IconButton,
2298
- {
2299
- actionType: "moveUp",
2300
- title: translate("builder.moveUp", { title: option.label }),
2301
- disabled: optionIndex === 0,
2302
- onClick: () => moveOption(field.id, option.id, optionIndex - 1)
2391
+ ) }),
2392
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2393
+ TextInput,
2394
+ {
2395
+ id: `builder-field-${field.id}-maximum`,
2396
+ label: translate("builder.maximum"),
2397
+ type: "number",
2398
+ value: String(field.max ?? 5),
2399
+ onChange: (value) => {
2400
+ const max = Number(value);
2401
+ if (!Number.isInteger(max)) return;
2402
+ updateField(
2403
+ field.id,
2404
+ (current) => current.type === "rating" ? { ...current, min: Math.min(current.min ?? 1, max), max } : current
2405
+ );
2303
2406
  }
2304
- ),
2407
+ }
2408
+ ) })
2409
+ ] }) : null,
2410
+ "options" in field ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__options"), children: [
2411
+ /* @__PURE__ */ jsx("strong", { children: translate("builder.options") }),
2412
+ field.options.map(
2413
+ (option, optionIndex) => OptionEditorSlot === void 0 ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__option"), children: [
2414
+ /* @__PURE__ */ jsx(
2415
+ TextInput,
2416
+ {
2417
+ id: `builder-option-${option.id}`,
2418
+ label: translate("builder.optionLabel", { index: optionIndex + 1 }),
2419
+ value: option.label,
2420
+ placeholder: translate("builder.optionLabelPlaceholder"),
2421
+ onChange: (value) => value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, value)
2422
+ }
2423
+ ),
2424
+ ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2425
+ /* @__PURE__ */ jsx(
2426
+ IconButton,
2427
+ {
2428
+ actionType: "moveUp",
2429
+ title: translate("builder.moveUp", { title: option.label }),
2430
+ disabled: optionIndex === 0,
2431
+ onClick: () => moveOption(field.id, option.id, optionIndex - 1)
2432
+ }
2433
+ ),
2434
+ /* @__PURE__ */ jsx(
2435
+ IconButton,
2436
+ {
2437
+ actionType: "moveDown",
2438
+ title: translate("builder.moveDown", { title: option.label }),
2439
+ disabled: optionIndex === field.options.length - 1,
2440
+ onClick: () => moveOption(field.id, option.id, optionIndex + 1)
2441
+ }
2442
+ ),
2443
+ /* @__PURE__ */ jsx(
2444
+ IconButton,
2445
+ {
2446
+ actionType: "delete",
2447
+ disabled: field.options.length === 1,
2448
+ onClick: () => removeOption(field.id, option.id),
2449
+ title: translate("builder.remove")
2450
+ }
2451
+ )
2452
+ ] }) : /* @__PURE__ */ jsx(
2453
+ ToolbarSlot,
2454
+ {
2455
+ schema,
2456
+ translate,
2457
+ kind: "option",
2458
+ targetId: option.id,
2459
+ index: optionIndex,
2460
+ total: field.options.length,
2461
+ title: option.label,
2462
+ onMoveUp: () => moveOption(field.id, option.id, optionIndex - 1),
2463
+ onMoveDown: () => moveOption(field.id, option.id, optionIndex + 1),
2464
+ onRemove: () => removeOption(field.id, option.id),
2465
+ readOnly,
2466
+ actions,
2467
+ components
2468
+ }
2469
+ )
2470
+ ] }, option.id) : /* @__PURE__ */ jsx(
2471
+ OptionEditorSlot,
2472
+ {
2473
+ schema,
2474
+ field,
2475
+ option,
2476
+ index: optionIndex,
2477
+ currentLocale: editingLocale,
2478
+ translate,
2479
+ readOnly,
2480
+ actions,
2481
+ components
2482
+ },
2483
+ option.id
2484
+ )
2485
+ ),
2486
+ /* @__PURE__ */ jsx(
2487
+ Button,
2488
+ {
2489
+ action: "addOption",
2490
+ targetId: field.id,
2491
+ disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
2492
+ onClick: () => addOption(field.id),
2493
+ children: translate("builder.addOption")
2494
+ }
2495
+ )
2496
+ ] }) : null,
2497
+ conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__condition"), children: [
2498
+ /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2499
+ Select,
2500
+ {
2501
+ id: `builder-field-${field.id}-condition`,
2502
+ label: translate("builder.displayCondition"),
2503
+ value: condition?.questionId ?? "",
2504
+ onChange: (value) => {
2505
+ const selected = schema.fields.find((item) => item.id === value);
2506
+ setDisplayCondition(
2507
+ field.id,
2508
+ selected === void 0 ? void 0 : conditionWithValue(
2509
+ selected.id,
2510
+ conditionOperators(selected)[0] ?? "not_empty",
2511
+ defaultConditionValue(selected)
2512
+ )
2513
+ );
2514
+ },
2515
+ options: [
2516
+ { value: "", label: translate("builder.alwaysVisible") },
2517
+ ...availableSources.map((candidate) => ({
2518
+ value: candidate.id,
2519
+ label: candidate.title
2520
+ }))
2521
+ ]
2522
+ }
2523
+ ) }),
2524
+ condition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2305
2525
  /* @__PURE__ */ jsx(
2306
- IconButton,
2526
+ Select,
2307
2527
  {
2308
- actionType: "moveDown",
2309
- title: translate("builder.moveDown", { title: option.label }),
2310
- disabled: optionIndex === field.options.length - 1,
2311
- onClick: () => moveOption(field.id, option.id, optionIndex + 1)
2528
+ "aria-label": translate("builder.conditionOperator"),
2529
+ value: condition.operator,
2530
+ onChange: (value) => {
2531
+ const operator = value;
2532
+ setDisplayCondition(
2533
+ field.id,
2534
+ conditionWithValue(source.id, operator, defaultConditionValue(source))
2535
+ );
2536
+ },
2537
+ options: conditionOperators(source).map((operator) => ({
2538
+ value: operator,
2539
+ label: translate(operatorKey(operator))
2540
+ }))
2312
2541
  }
2313
2542
  ),
2314
2543
  /* @__PURE__ */ jsx(
2315
- IconButton,
2544
+ ConditionValueEditor,
2316
2545
  {
2317
- actionType: "delete",
2318
- disabled: field.options.length === 1,
2319
- onClick: () => removeOption(field.id, option.id),
2320
- title: translate("builder.remove")
2546
+ source,
2547
+ condition,
2548
+ onChange: (next) => setDisplayCondition(field.id, next),
2549
+ translate,
2550
+ components
2321
2551
  }
2322
2552
  )
2323
- ] }) : /* @__PURE__ */ jsx(
2324
- ToolbarSlot,
2325
- {
2326
- schema,
2327
- kind: "option",
2328
- targetId: option.id,
2329
- index: optionIndex,
2330
- total: field.options.length,
2331
- title: option.label,
2332
- onMoveUp: () => moveOption(field.id, option.id, optionIndex - 1),
2333
- onMoveDown: () => moveOption(field.id, option.id, optionIndex + 1),
2334
- onRemove: () => removeOption(field.id, option.id),
2335
- readOnly,
2336
- actions,
2337
- components
2338
- }
2339
- )
2340
- ] }, option.id) : /* @__PURE__ */ jsx(
2341
- OptionEditorSlot,
2342
- {
2343
- schema,
2344
- field,
2345
- option,
2346
- index: optionIndex,
2347
- currentLocale: editingLocale,
2348
- readOnly,
2349
- actions,
2350
- components
2351
- },
2352
- option.id
2353
- )
2354
- ),
2355
- /* @__PURE__ */ jsx(
2356
- Button,
2357
- {
2358
- action: "addOption",
2359
- targetId: field.id,
2360
- disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
2361
- onClick: () => addOption(field.id),
2362
- children: translate("builder.addOption")
2363
- }
2364
- )
2365
- ] }) : null,
2366
- conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__condition"), children: [
2367
- /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
2368
- Select,
2369
- {
2370
- id: `builder-field-${field.id}-condition`,
2371
- label: translate("builder.displayCondition"),
2372
- value: condition?.questionId ?? "",
2373
- onChange: (value) => {
2374
- const selected = schema.fields.find((item) => item.id === value);
2375
- setDisplayCondition(
2376
- field.id,
2377
- selected === void 0 ? void 0 : conditionWithValue(
2378
- selected.id,
2379
- conditionOperators(selected)[0] ?? "not_empty",
2380
- defaultConditionValue(selected)
2381
- )
2382
- );
2383
- },
2384
- options: [
2385
- { value: "", label: translate("builder.alwaysVisible") },
2386
- ...availableSources.map((candidate) => ({ value: candidate.id, label: candidate.title }))
2387
- ]
2388
- }
2389
- ) }),
2390
- condition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2391
- /* @__PURE__ */ jsx(
2392
- Select,
2393
- {
2394
- "aria-label": translate("builder.conditionOperator"),
2395
- value: condition.operator,
2396
- onChange: (value) => {
2397
- const operator = value;
2398
- setDisplayCondition(
2399
- field.id,
2400
- conditionWithValue(source.id, operator, defaultConditionValue(source))
2401
- );
2402
- },
2403
- options: conditionOperators(source).map((operator) => ({
2404
- value: operator,
2405
- label: translate(operatorKey(operator))
2406
- }))
2407
- }
2408
- ),
2409
- /* @__PURE__ */ jsx(
2410
- ConditionValueEditor,
2411
- {
2412
- source,
2413
- condition,
2414
- onChange: (next) => setDisplayCondition(field.id, next),
2415
- translate,
2416
- components
2417
- }
2418
- )
2419
- ] }) : null
2420
- ] }) : null
2421
- ] }, field.id);
2422
- }) }),
2423
- /* @__PURE__ */ jsx(
2553
+ ] }) : null
2554
+ ] }) : null
2555
+ ]
2556
+ },
2557
+ field.id
2558
+ );
2559
+ }) }) }),
2560
+ /* @__PURE__ */ jsx(BuilderSectionGroup, { name: "addQuestion", children: /* @__PURE__ */ jsx(
2424
2561
  Button,
2425
2562
  {
2426
2563
  className: builderClass("form-engine-builder__add"),
@@ -2429,8 +2566,8 @@ function FormBuilder({
2429
2566
  onClick: addField,
2430
2567
  children: translate("builder.addQuestion")
2431
2568
  }
2432
- )
2433
- ] })
2569
+ ) })
2570
+ ] }) })
2434
2571
  }
2435
2572
  )
2436
2573
  }
@@ -2973,6 +3110,8 @@ function ContextFormRenderer({
2973
3110
  autoSaveKey,
2974
3111
  beforeSubmit,
2975
3112
  onDraftSave,
3113
+ successRenderMode = "append",
3114
+ hideFormOnSuccess = false,
2976
3115
  submissionGuards = [],
2977
3116
  receiptStore,
2978
3117
  attemptStore,
@@ -2988,9 +3127,11 @@ function ContextFormRenderer({
2988
3127
  const [focusFieldId, setFocusFieldId] = useState4(null);
2989
3128
  const [confirmation, setConfirmation] = useState4(null);
2990
3129
  const [guardMessage, setGuardMessage] = useState4(null);
3130
+ const [guardsPending, setGuardsPending] = useState4(false);
2991
3131
  const [receipt, setReceipt] = useState4(null);
2992
3132
  const [receiptLoaded, setReceiptLoaded] = useState4(receiptStore === void 0);
2993
3133
  const rendererSubmissionInFlight = useRef2(false);
3134
+ const completionRef = useRef2(null);
2994
3135
  const pages = form.schema.pages;
2995
3136
  const visiblePageIndexes = useMemo4(
2996
3137
  () => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
@@ -3000,8 +3141,9 @@ function ContextFormRenderer({
3000
3141
  const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
3001
3142
  const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
3002
3143
  const visibleValues = useMemo4(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
3003
- const submitState = confirmation === null ? form.submitStatus : "confirming";
3144
+ const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
3004
3145
  const interactionLocked = submitState === "confirming" || submitState === "submitting";
3146
+ const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
3005
3147
  useEffect3(() => {
3006
3148
  let active = true;
3007
3149
  if (receiptStore === void 0) {
@@ -3041,6 +3183,10 @@ function ContextFormRenderer({
3041
3183
  setFocusFieldId(null);
3042
3184
  }
3043
3185
  }, [focusFieldId]);
3186
+ useEffect3(() => {
3187
+ if (!isReplaceMode || form.submitStatus !== "success") return;
3188
+ completionRef.current?.focus();
3189
+ }, [form.submitStatus, isReplaceMode]);
3044
3190
  useEffect3(() => {
3045
3191
  if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
3046
3192
  const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
@@ -3106,13 +3252,14 @@ function ContextFormRenderer({
3106
3252
  };
3107
3253
  };
3108
3254
  const submitValues = async (guardsConfirmed = false) => {
3109
- if (rendererSubmissionInFlight.current || confirmation !== null && !guardsConfirmed) {
3255
+ if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
3110
3256
  return { status: "cancelled" };
3111
3257
  }
3112
3258
  const validation = validateAnswers2(form.schema, form.values);
3113
3259
  const firstInvalidFieldId = validation.issues[0]?.fieldId;
3114
3260
  if (validation.valid && !guardsConfirmed && submissionGuards.length > 0) {
3115
3261
  rendererSubmissionInFlight.current = true;
3262
+ setGuardsPending(true);
3116
3263
  try {
3117
3264
  const guardResult = await runSubmissionGuards(submissionGuards);
3118
3265
  if (guardResult.status === "block") {
@@ -3129,6 +3276,7 @@ function ContextFormRenderer({
3129
3276
  }
3130
3277
  } finally {
3131
3278
  rendererSubmissionInFlight.current = false;
3279
+ setGuardsPending(false);
3132
3280
  }
3133
3281
  }
3134
3282
  setGuardMessage(null);
@@ -3166,7 +3314,6 @@ function ContextFormRenderer({
3166
3314
  };
3167
3315
  try {
3168
3316
  await receiptStore.save(storedReceipt);
3169
- setReceipt(storedReceipt);
3170
3317
  } catch (cause) {
3171
3318
  const error = cause instanceof Error ? cause : new Error(String(cause));
3172
3319
  try {
@@ -3193,7 +3340,7 @@ function ContextFormRenderer({
3193
3340
  };
3194
3341
  const handleSubmit = (event) => {
3195
3342
  event.preventDefault();
3196
- if (interactionLocked) return;
3343
+ if (submitState === "submitting" || submitState === "confirming" || submitState === "success") return;
3197
3344
  void submitValues();
3198
3345
  };
3199
3346
  const confirmSubmission = () => {
@@ -3212,7 +3359,17 @@ function ContextFormRenderer({
3212
3359
  const validationIssues = Object.values(form.errors).filter((issue) => issue !== void 0);
3213
3360
  const canPrev = pages !== void 0 && activeVisibleIndex > 0;
3214
3361
  const canNext = pages !== void 0 && activeVisibleIndex < visiblePageIndexes.length - 1;
3215
- const renderSubmitButton = () => slots.renderSubmitButton?.({ isSubmitting: interactionLocked, onSubmit: () => void submitValues() }) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: interactionLocked, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
3362
+ const renderSubmitButton = () => {
3363
+ const submitButtonProps = {
3364
+ isSubmitting: submitState === "submitting",
3365
+ submitStatus: submitState,
3366
+ disabled: interactionLocked || submitState === "success",
3367
+ onSubmit: () => void submitValues()
3368
+ };
3369
+ return slots.renderSubmitButton?.(submitButtonProps) ?? /* @__PURE__ */ jsx3("button", { className: "fe-submit", type: "submit", disabled: submitButtonProps.disabled, children: form.translate(form.schema.submitLabelKey ?? "form.submit") });
3370
+ };
3371
+ const completionMessage = form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey));
3372
+ const completionRegion = /* @__PURE__ */ jsx3("div", { ref: completionRef, className: "fe-completion", role: "status", "aria-live": "polite", tabIndex: -1, children: slots.renderCompletion?.({ message: completionMessage }) ?? /* @__PURE__ */ jsx3("div", { children: completionMessage }) });
3216
3373
  if (!receiptLoaded) return null;
3217
3374
  if (receipt !== null) {
3218
3375
  return /* @__PURE__ */ jsx3("div", { className: `fe-form fe-already-submitted ${className}`.trim(), children: slots.renderAlreadySubmitted?.({
@@ -3223,6 +3380,9 @@ function ContextFormRenderer({
3223
3380
  receiptStore === void 0 ? null : /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => void resetReceipt(), children: form.translate("form.submitAnother") })
3224
3381
  ] }) });
3225
3382
  }
3383
+ if (form.submitStatus === "success" && isReplaceMode) {
3384
+ return /* @__PURE__ */ jsx3("div", { className: `fe-form ${className}`.trim(), children: completionRegion });
3385
+ }
3226
3386
  return /* @__PURE__ */ jsxs2("form", { ref: formRef, className: `fe-form ${className}`.trim(), noValidate: true, onSubmit: handleSubmit, children: [
3227
3387
  slots.renderHeader?.({
3228
3388
  title: form.schema.title,
@@ -3341,9 +3501,7 @@ function ContextFormRenderer({
3341
3501
  canNext ? null : renderSubmitButton()
3342
3502
  ] }),
3343
3503
  /* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
3344
- form.submitStatus === "success" ? slots.renderCompletion?.({
3345
- message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
3346
- }) ?? /* @__PURE__ */ jsx3("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
3504
+ form.submitStatus === "success" ? completionRegion : null,
3347
3505
  form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (errorMessageKey === void 0 ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
3348
3506
  ] })
3349
3507
  ] });