@form-engine-ts/react 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -135,10 +135,16 @@ function useFormBuilder({
135
135
  if (pages === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
136
136
  const updated = updater(page);
137
137
  if (updated.id !== pageId) return { success: false, error: { type: "invalid_id", kind: "page", id: updated.id } };
138
+ for (const text of [updated.title, updated.description]) {
139
+ if (text !== void 0) {
140
+ const error = textPolicyError(text);
141
+ if (error !== void 0) return error;
142
+ }
143
+ }
138
144
  onChange({ ...schema, pages: pages.map((candidate) => candidate.id === pageId ? updated : candidate) });
139
145
  return { success: true };
140
146
  },
141
- [onChange, schema]
147
+ [onChange, schema, textPolicyError]
142
148
  );
143
149
  const addField = useCallback(
144
150
  (type, pageId) => {
@@ -557,6 +563,17 @@ function useFormBuilder({
557
563
  const normalized = locale.trim();
558
564
  if (normalized.length === 0)
559
565
  return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
566
+ if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(normalized)) {
567
+ return { success: false, error: { type: "disallowed_locale", locale: normalized } };
568
+ }
569
+ const registeredLocales = /* @__PURE__ */ new Set([
570
+ ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
571
+ ...schema.supportedLocales ?? []
572
+ ]);
573
+ if (registeredLocales.has(normalized)) return { success: true };
574
+ if (policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales) {
575
+ return { success: false, error: { type: "max_locales_exceeded", max: policy.maxLocales } };
576
+ }
560
577
  onChange({
561
578
  ...schema,
562
579
  supportedLocales: [
@@ -569,13 +586,23 @@ function useFormBuilder({
569
586
  });
570
587
  return { success: true };
571
588
  },
572
- [onChange, schema]
589
+ [onChange, policy?.allowedLocales, policy?.maxLocales, schema]
573
590
  );
574
591
  const setDefaultLocale = useCallback(
575
592
  (locale) => {
576
593
  const normalized = locale.trim();
577
594
  if (normalized.length === 0)
578
595
  return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
596
+ if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(normalized)) {
597
+ return { success: false, error: { type: "disallowed_locale", locale: normalized } };
598
+ }
599
+ const registeredLocales = /* @__PURE__ */ new Set([
600
+ ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
601
+ ...schema.supportedLocales ?? []
602
+ ]);
603
+ if (!registeredLocales.has(normalized) && policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales) {
604
+ return { success: false, error: { type: "max_locales_exceeded", max: policy.maxLocales } };
605
+ }
579
606
  onChange({
580
607
  ...schema,
581
608
  defaultLocale: normalized,
@@ -583,7 +610,7 @@ function useFormBuilder({
583
610
  });
584
611
  return { success: true };
585
612
  },
586
- [onChange, schema]
613
+ [onChange, policy?.allowedLocales, policy?.maxLocales, schema]
587
614
  );
588
615
  const validationIssues = useMemo(() => {
589
616
  const result = validateFormSchema(schema, policy === void 0 ? {} : { policy });
@@ -748,6 +775,14 @@ function ConditionValueEditor({
748
775
  }
749
776
  );
750
777
  }
778
+ function resolveInitialFieldType(defaultType, allowedTypes) {
779
+ if (defaultType !== void 0 && (allowedTypes === void 0 || allowedTypes.includes(defaultType))) {
780
+ return defaultType;
781
+ }
782
+ if (allowedTypes !== void 0 && allowedTypes.length > 0) return allowedTypes[0] ?? null;
783
+ if (allowedTypes === void 0 || allowedTypes.includes("text")) return "text";
784
+ return null;
785
+ }
751
786
  function FormBuilder({
752
787
  schema,
753
788
  onChange,
@@ -758,7 +793,13 @@ function FormBuilder({
758
793
  onTranslationReport,
759
794
  policy,
760
795
  idFactory,
761
- factories
796
+ factories,
797
+ className = "",
798
+ defaultFieldType,
799
+ onActionError,
800
+ createManualTranslationMetadata,
801
+ readOnly = false,
802
+ features
762
803
  }) {
763
804
  const headless = useFormBuilder({
764
805
  schema,
@@ -776,17 +817,45 @@ function FormBuilder({
776
817
  const translated = translator?.translate(key, locale, params);
777
818
  return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
778
819
  };
779
- const updateField = headless.updateField;
780
- const changeType = headless.changeFieldType;
781
- const removeField = headless.removeField;
820
+ const pagesEnabled = features?.pages ?? true;
821
+ const localizationEnabled = features?.localization ?? true;
822
+ const conditionsEnabled = features?.conditions ?? true;
823
+ const executeAction = (run, context) => {
824
+ if (readOnly) return { success: true };
825
+ const result = run();
826
+ if (!result.success) onActionError?.(result.error, context);
827
+ return result;
828
+ };
829
+ const updateField = (fieldId, updater, params) => executeAction(() => headless.updateField(fieldId, updater), {
830
+ action: "updateField",
831
+ targetId: fieldId,
832
+ ...params === void 0 ? {} : { params }
833
+ });
834
+ const changeType = (fieldId, type) => executeAction(() => headless.changeFieldType(fieldId, type), {
835
+ action: "changeFieldType",
836
+ targetId: fieldId,
837
+ params: { type }
838
+ });
839
+ const removeField = (fieldId) => executeAction(() => headless.removeField(fieldId), { action: "removeField", targetId: fieldId });
840
+ const initialFieldType = resolveInitialFieldType(defaultFieldType, policy?.allowedFieldTypes);
841
+ const maxFieldsReached = policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields;
782
842
  const moveField = (index, offset) => {
783
843
  const target = index + offset;
784
844
  const field = schema.fields[index];
785
- if (field !== void 0) headless.moveField(field.id, target);
845
+ if (field !== void 0) {
846
+ executeAction(() => headless.moveField(field.id, target), {
847
+ action: "moveField",
848
+ targetId: field.id,
849
+ params: { targetIndex: target }
850
+ });
851
+ }
852
+ };
853
+ const addField = () => {
854
+ if (initialFieldType === null) return;
855
+ executeAction(() => headless.addField(initialFieldType), { action: "addField" });
786
856
  };
787
- const addField = () => headless.addField("text");
788
857
  const enablePages = () => {
789
- if (schema.pages === void 0) headless.addPage();
858
+ if (schema.pages === void 0) executeAction(() => headless.addPage(), { action: "addPage" });
790
859
  };
791
860
  const pageForField = (fieldId) => schema.pages?.find((page) => page.questionIds.includes(fieldId));
792
861
  const movablePageQuestions = schema.pages?.flatMap((page) => page.questionIds.length > 1 ? page.questionIds : []) ?? [];
@@ -797,42 +866,67 @@ function FormBuilder({
797
866
  }
798
867
  const questionId = movablePageQuestions.includes(newPageQuestionId) ? newPageQuestionId : movablePageQuestions[0];
799
868
  if (questionId === void 0) return;
800
- headless.addPage(questionId);
869
+ executeAction(() => headless.addPage(questionId), {
870
+ action: "addPage",
871
+ targetId: questionId,
872
+ params: { questionId }
873
+ });
801
874
  setNewPageQuestionId("");
802
875
  };
803
876
  const removePage = (pageIndex) => {
804
877
  const page = schema.pages?.[pageIndex];
805
- if (page !== void 0) headless.removePage(page.id);
878
+ if (page !== void 0)
879
+ executeAction(() => headless.removePage(page.id), { action: "removePage", targetId: page.id });
806
880
  };
807
881
  const movePage = (pageIndex, offset) => {
808
882
  const target = pageIndex + offset;
809
883
  const page = schema.pages?.[pageIndex];
810
- if (page !== void 0) headless.movePage(page.id, target);
884
+ if (page !== void 0) {
885
+ executeAction(() => headless.movePage(page.id, target), {
886
+ action: "movePage",
887
+ targetId: page.id,
888
+ params: { targetIndex: target }
889
+ });
890
+ }
811
891
  };
812
892
  const updatePage = (pageId, update) => {
813
- headless.updatePage(pageId, update);
893
+ executeAction(() => headless.updatePage(pageId, update), {
894
+ action: "updatePage",
895
+ targetId: pageId
896
+ });
814
897
  };
815
898
  const assignFieldToPage = (fieldId, pageId) => {
816
- headless.assignFieldToPage(fieldId, pageId);
899
+ executeAction(() => headless.assignFieldToPage(fieldId, pageId), {
900
+ action: "assignFieldToPage",
901
+ targetId: fieldId,
902
+ params: { pageId }
903
+ });
817
904
  };
818
905
  const addLocale = () => {
906
+ if (readOnly) return;
819
907
  const normalized = newLocale.trim();
820
908
  if (normalized.length === 0) return;
821
- headless.addLocale(normalized);
909
+ const result = executeAction(() => headless.addLocale(normalized), {
910
+ action: "addLocale",
911
+ params: { locale: normalized }
912
+ });
913
+ if (!result.success) return;
822
914
  setEditingLocale(normalized);
823
915
  setNewLocale("");
824
916
  };
917
+ const setDefaultLocale = (locale2) => executeAction(() => headless.setDefaultLocale(locale2), {
918
+ action: "setDefaultLocale",
919
+ params: { locale: locale2 }
920
+ });
825
921
  const translateAll = async () => {
826
- if (translationAdapter === void 0 || editingLocale.length === 0) return;
922
+ if (readOnly || translationAdapter === void 0 || editingLocale.length === 0) return;
827
923
  setIsTranslating(true);
828
924
  setTranslationError(null);
829
925
  try {
830
- const populated = await populateSchemaTranslations(
831
- schema,
832
- [editingLocale],
833
- translationAdapter,
834
- translationOptions ?? { overwrite: "all" }
835
- );
926
+ const populated = await populateSchemaTranslations(schema, [editingLocale], translationAdapter, {
927
+ overwrite: "missing-only",
928
+ ...translationOptions
929
+ });
836
930
  onChange(populated.schema);
837
931
  onTranslationReport?.(populated.report);
838
932
  } catch (cause) {
@@ -841,28 +935,410 @@ function FormBuilder({
841
935
  setIsTranslating(false);
842
936
  }
843
937
  };
844
- const updateFormTranslation = (key, value) => {
938
+ const updateManualTranslation = (context) => {
939
+ if (readOnly) return;
940
+ const metadata = createManualTranslationMetadata?.(context);
941
+ const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
942
+ executeAction(
943
+ () => headless.setLocaleTranslation(
944
+ context.locale,
945
+ target,
946
+ context.property,
947
+ context.translatedText,
948
+ metadata === void 0 ? void 0 : { metadata }
949
+ ),
950
+ {
951
+ action: "setLocaleTranslation",
952
+ targetId: context.nodeId,
953
+ params: { locale: context.locale, kind: context.kind, property: context.property }
954
+ }
955
+ );
956
+ };
957
+ const updateFormTranslation = (property, translatedText) => {
845
958
  if (editingLocale.length === 0) return;
846
- headless.setLocaleTranslation(editingLocale, { kind: "form" }, key, value);
959
+ updateManualTranslation({
960
+ locale: editingLocale,
961
+ kind: "form",
962
+ nodeId: schema.id,
963
+ property,
964
+ sourceText: schema[property] ?? "",
965
+ translatedText,
966
+ ...schema.translationMetadata?.[editingLocale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[editingLocale]?.[property] }
967
+ });
847
968
  };
848
- return /* @__PURE__ */ jsxs("section", { className: "form-engine-builder", "aria-label": translate("builder.formBuilder"), children: [
849
- /* @__PURE__ */ jsxs("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
850
- /* @__PURE__ */ jsx("h2", { id: "builder-pages-heading", children: translate("builder.pages") }),
851
- schema.pages === void 0 ? /* @__PURE__ */ jsx("button", { type: "button", onClick: enablePages, children: translate("builder.enablePages") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
852
- schema.pages.map((page, pageIndex) => {
853
- const priorQuestionIds = new Set(schema.pages?.slice(0, pageIndex).flatMap((item) => item.questionIds));
854
- const availableSources = schema.fields.filter((field) => priorQuestionIds.has(field.id));
855
- const source = schema.fields.find((field) => field.id === page.displayCondition?.questionId);
856
- return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__page", children: [
857
- /* @__PURE__ */ jsx("legend", { children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }),
969
+ const setSourceText = (target, property, text) => executeAction(() => headless.setSourceText(target, property, text), {
970
+ action: "setSourceText",
971
+ ...target.id === void 0 ? {} : { targetId: target.id },
972
+ params: { kind: target.kind, property }
973
+ });
974
+ const updateOption = (fieldId, optionId, label) => executeAction(() => headless.updateOption(fieldId, optionId, (option) => ({ ...option, label })), {
975
+ action: "updateOption",
976
+ targetId: optionId,
977
+ params: { fieldId }
978
+ });
979
+ const addOption = (fieldId) => executeAction(() => headless.addOption(fieldId), { action: "addOption", targetId: fieldId });
980
+ const removeOption = (fieldId, optionId) => executeAction(() => headless.removeOption(fieldId, optionId), {
981
+ action: "removeOption",
982
+ targetId: optionId,
983
+ params: { fieldId }
984
+ });
985
+ const moveOption = (fieldId, optionId, targetIndex) => executeAction(() => headless.moveOption(fieldId, optionId, targetIndex), {
986
+ action: "moveOption",
987
+ targetId: optionId,
988
+ params: { fieldId, targetIndex }
989
+ });
990
+ const setDisplayCondition = (fieldId, condition) => executeAction(() => headless.setDisplayCondition(fieldId, condition), {
991
+ action: "setDisplayCondition",
992
+ targetId: fieldId,
993
+ ...condition === void 0 ? {} : { params: { condition } }
994
+ });
995
+ const registeredLocales = /* @__PURE__ */ new Set([
996
+ ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
997
+ ...schema.supportedLocales ?? []
998
+ ]);
999
+ const availableAllowedLocales = policy?.allowedLocales?.filter((candidate) => !registeredLocales.has(candidate));
1000
+ const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1001
+ return /* @__PURE__ */ jsx(
1002
+ "section",
1003
+ {
1004
+ className: `form-engine-builder ${className}`.trim(),
1005
+ "aria-label": translate("builder.formBuilder"),
1006
+ onClickCapture: (event) => {
1007
+ if (readOnly) return;
1008
+ if (!(event.target instanceof HTMLElement)) return;
1009
+ const actionTarget = event.target.closest("[data-builder-action]");
1010
+ if (actionTarget?.dataset.builderAction === "addField" && maxFieldsReached && initialFieldType !== null)
1011
+ addField();
1012
+ if (actionTarget?.dataset.builderAction === "addLocale" && localeLimitReached && newLocale.trim().length > 0)
1013
+ addLocale();
1014
+ if (actionTarget?.dataset.builderAction !== "addOption") return;
1015
+ const fieldId = actionTarget.dataset.targetId;
1016
+ const field = schema.fields.find((candidate) => candidate.id === fieldId);
1017
+ if (fieldId !== void 0 && field !== void 0 && "options" in field && policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField) {
1018
+ addOption(fieldId);
1019
+ }
1020
+ },
1021
+ children: /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__controls", disabled: readOnly, children: [
1022
+ pagesEnabled ? /* @__PURE__ */ jsxs("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
1023
+ /* @__PURE__ */ jsx("h2", { id: "builder-pages-heading", children: translate("builder.pages") }),
1024
+ schema.pages === void 0 ? /* @__PURE__ */ jsx("button", { type: "button", onClick: enablePages, children: translate("builder.enablePages") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1025
+ schema.pages.map((page, pageIndex) => {
1026
+ const priorQuestionIds = new Set(
1027
+ schema.pages?.slice(0, pageIndex).flatMap((item) => item.questionIds)
1028
+ );
1029
+ const availableSources = schema.fields.filter((field) => priorQuestionIds.has(field.id));
1030
+ const source = schema.fields.find((field) => field.id === page.displayCondition?.questionId);
1031
+ return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__page", children: [
1032
+ /* @__PURE__ */ jsx("legend", { children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }),
1033
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__toolbar", children: [
1034
+ /* @__PURE__ */ jsx(
1035
+ "button",
1036
+ {
1037
+ type: "button",
1038
+ disabled: pageIndex === 0,
1039
+ "aria-label": translate("builder.moveUp", { title: page.title ?? page.id }),
1040
+ onClick: () => movePage(pageIndex, -1),
1041
+ children: "\u2191"
1042
+ }
1043
+ ),
1044
+ /* @__PURE__ */ jsx(
1045
+ "button",
1046
+ {
1047
+ type: "button",
1048
+ disabled: pageIndex === (schema.pages?.length ?? 0) - 1,
1049
+ "aria-label": translate("builder.moveDown", { title: page.title ?? page.id }),
1050
+ onClick: () => movePage(pageIndex, 1),
1051
+ children: "\u2193"
1052
+ }
1053
+ ),
1054
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => removePage(pageIndex), children: translate("builder.deleteAction") })
1055
+ ] }),
1056
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1057
+ /* @__PURE__ */ jsxs("label", { children: [
1058
+ translate("builder.pageTitle"),
1059
+ /* @__PURE__ */ jsx(
1060
+ "input",
1061
+ {
1062
+ value: page.title ?? "",
1063
+ onChange: (event) => {
1064
+ const value = event.currentTarget.value;
1065
+ updatePage(page.id, (current) => {
1066
+ if (value.length > 0) return { ...current, title: value };
1067
+ const { title: _title, ...withoutTitle } = current;
1068
+ return withoutTitle;
1069
+ });
1070
+ }
1071
+ }
1072
+ )
1073
+ ] }),
1074
+ /* @__PURE__ */ jsxs("label", { children: [
1075
+ translate("builder.pageDescription"),
1076
+ /* @__PURE__ */ jsx(
1077
+ "input",
1078
+ {
1079
+ value: page.description ?? "",
1080
+ onChange: (event) => {
1081
+ const value = event.currentTarget.value;
1082
+ updatePage(page.id, (current) => {
1083
+ if (value.length > 0) return { ...current, description: value };
1084
+ const { description: _description, ...withoutDescription } = current;
1085
+ return withoutDescription;
1086
+ });
1087
+ }
1088
+ }
1089
+ )
1090
+ ] })
1091
+ ] }),
1092
+ !localizationEnabled || editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1093
+ /* @__PURE__ */ jsx("strong", { children: editingLocale }),
1094
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1095
+ /* @__PURE__ */ jsxs("label", { children: [
1096
+ translate("builder.pageTitle"),
1097
+ /* @__PURE__ */ jsx(
1098
+ "input",
1099
+ {
1100
+ value: page.translations?.[editingLocale]?.title ?? "",
1101
+ onChange: (event) => updateManualTranslation({
1102
+ locale: editingLocale,
1103
+ kind: "page",
1104
+ nodeId: page.id,
1105
+ property: "title",
1106
+ sourceText: page.title ?? "",
1107
+ translatedText: event.currentTarget.value,
1108
+ ...page.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
1109
+ existingTranslationMetadata: page.translationMetadata[editingLocale]?.title
1110
+ }
1111
+ })
1112
+ }
1113
+ )
1114
+ ] }),
1115
+ /* @__PURE__ */ jsxs("label", { children: [
1116
+ translate("builder.pageDescription"),
1117
+ /* @__PURE__ */ jsx(
1118
+ "input",
1119
+ {
1120
+ value: page.translations?.[editingLocale]?.description ?? "",
1121
+ onChange: (event) => updateManualTranslation({
1122
+ locale: editingLocale,
1123
+ kind: "page",
1124
+ nodeId: page.id,
1125
+ property: "description",
1126
+ sourceText: page.description ?? "",
1127
+ translatedText: event.currentTarget.value,
1128
+ ...page.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
1129
+ existingTranslationMetadata: page.translationMetadata[editingLocale]?.description
1130
+ }
1131
+ })
1132
+ }
1133
+ )
1134
+ ] })
1135
+ ] })
1136
+ ] }),
1137
+ conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
1138
+ /* @__PURE__ */ jsxs("label", { children: [
1139
+ translate("builder.pageCondition"),
1140
+ /* @__PURE__ */ jsxs(
1141
+ "select",
1142
+ {
1143
+ value: page.displayCondition?.questionId ?? "",
1144
+ onChange: (event) => {
1145
+ const selected = schema.fields.find((field) => field.id === event.currentTarget.value);
1146
+ updatePage(page.id, (current) => {
1147
+ if (selected === void 0) {
1148
+ const { displayCondition: _condition, ...withoutCondition } = current;
1149
+ return withoutCondition;
1150
+ }
1151
+ return {
1152
+ ...current,
1153
+ displayCondition: conditionWithValue(
1154
+ selected.id,
1155
+ conditionOperators(selected)[0] ?? "not_empty",
1156
+ defaultConditionValue(selected)
1157
+ )
1158
+ };
1159
+ });
1160
+ },
1161
+ children: [
1162
+ /* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
1163
+ availableSources.map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
1164
+ ]
1165
+ }
1166
+ )
1167
+ ] }),
1168
+ page.displayCondition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1169
+ /* @__PURE__ */ jsx(
1170
+ "select",
1171
+ {
1172
+ "aria-label": translate("builder.conditionOperator"),
1173
+ value: page.displayCondition.operator,
1174
+ onChange: (event) => {
1175
+ const operator = event.currentTarget.value;
1176
+ updatePage(page.id, (current) => ({
1177
+ ...current,
1178
+ displayCondition: conditionWithValue(
1179
+ source.id,
1180
+ operator,
1181
+ defaultConditionValue(source)
1182
+ )
1183
+ }));
1184
+ },
1185
+ children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ jsx(
1189
+ ConditionValueEditor,
1190
+ {
1191
+ source,
1192
+ condition: page.displayCondition,
1193
+ onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
1194
+ translate
1195
+ }
1196
+ )
1197
+ ] }) : null
1198
+ ] }) : null
1199
+ ] }, page.id);
1200
+ }),
1201
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__page-add", children: [
1202
+ /* @__PURE__ */ jsxs("label", { children: [
1203
+ translate("builder.pageQuestion"),
1204
+ /* @__PURE__ */ jsxs(
1205
+ "select",
1206
+ {
1207
+ value: newPageQuestionId,
1208
+ disabled: movablePageQuestions.length === 0,
1209
+ onChange: (event) => setNewPageQuestionId(event.currentTarget.value),
1210
+ children: [
1211
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1212
+ schema.fields.filter((field) => movablePageQuestions.includes(field.id)).map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
1213
+ ]
1214
+ }
1215
+ )
1216
+ ] }),
1217
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: movablePageQuestions.length === 0, onClick: addPage, children: translate("builder.addPage") })
1218
+ ] })
1219
+ ] })
1220
+ ] }) : null,
1221
+ localizationEnabled ? /* @__PURE__ */ jsxs("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
1222
+ /* @__PURE__ */ jsx("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
1223
+ /* @__PURE__ */ jsxs("label", { children: [
1224
+ translate("builder.completionMessage"),
1225
+ /* @__PURE__ */ jsx(
1226
+ "input",
1227
+ {
1228
+ value: schema.completionMessage ?? "",
1229
+ onChange: (event) => setSourceText({ kind: "form" }, "completionMessage", event.currentTarget.value)
1230
+ }
1231
+ )
1232
+ ] }),
1233
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1234
+ /* @__PURE__ */ jsxs("label", { children: [
1235
+ translate("builder.defaultLocale"),
1236
+ /* @__PURE__ */ jsx(
1237
+ "input",
1238
+ {
1239
+ value: schema.defaultLocale ?? "",
1240
+ onChange: (event) => setDefaultLocale(event.currentTarget.value)
1241
+ }
1242
+ )
1243
+ ] }),
1244
+ /* @__PURE__ */ jsxs("label", { htmlFor: "builder-new-locale", children: [
1245
+ translate("builder.addLocale"),
1246
+ availableAllowedLocales === void 0 ? /* @__PURE__ */ jsx(
1247
+ "input",
1248
+ {
1249
+ id: "builder-new-locale",
1250
+ value: newLocale,
1251
+ onChange: (event) => setNewLocale(event.currentTarget.value)
1252
+ }
1253
+ ) : /* @__PURE__ */ jsxs(
1254
+ "select",
1255
+ {
1256
+ id: "builder-new-locale",
1257
+ value: newLocale,
1258
+ onChange: (event) => setNewLocale(event.currentTarget.value),
1259
+ children: [
1260
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1261
+ availableAllowedLocales.map((candidate) => /* @__PURE__ */ jsx("option", { value: candidate, children: candidate }, candidate))
1262
+ ]
1263
+ }
1264
+ )
1265
+ ] }),
1266
+ /* @__PURE__ */ jsx(
1267
+ "button",
1268
+ {
1269
+ type: "button",
1270
+ "data-builder-action": "addLocale",
1271
+ disabled: newLocale.trim().length === 0 || localeLimitReached,
1272
+ onClick: addLocale,
1273
+ children: translate("builder.addLocale")
1274
+ }
1275
+ ),
1276
+ /* @__PURE__ */ jsxs("label", { children: [
1277
+ translate("builder.editLocale"),
1278
+ /* @__PURE__ */ jsxs("select", { value: editingLocale, onChange: (event) => setEditingLocale(event.currentTarget.value), children: [
1279
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1280
+ (schema.supportedLocales ?? []).filter((item) => item !== schema.defaultLocale).map((item) => /* @__PURE__ */ jsx("option", { value: item, children: item }, item))
1281
+ ] })
1282
+ ] }),
1283
+ /* @__PURE__ */ jsx(
1284
+ "button",
1285
+ {
1286
+ type: "button",
1287
+ disabled: translationAdapter === void 0 || editingLocale.length === 0 || isTranslating,
1288
+ onClick: () => void translateAll(),
1289
+ children: translate("builder.autoTranslate")
1290
+ }
1291
+ )
1292
+ ] }),
1293
+ translationAdapter === void 0 ? /* @__PURE__ */ jsx("p", { children: translate("builder.translationUnavailable") }) : null,
1294
+ translationError === null ? null : /* @__PURE__ */ jsx("p", { className: "form-engine-builder__error", children: translationError }),
1295
+ editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1296
+ /* @__PURE__ */ jsxs("label", { children: [
1297
+ translate("builder.questionTitle"),
1298
+ /* @__PURE__ */ jsx(
1299
+ "input",
1300
+ {
1301
+ value: schema.translations?.[editingLocale]?.title ?? "",
1302
+ onChange: (event) => updateFormTranslation("title", event.currentTarget.value)
1303
+ }
1304
+ )
1305
+ ] }),
1306
+ /* @__PURE__ */ jsxs("label", { children: [
1307
+ translate("builder.pageDescription"),
1308
+ /* @__PURE__ */ jsx(
1309
+ "input",
1310
+ {
1311
+ value: schema.translations?.[editingLocale]?.description ?? "",
1312
+ onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
1313
+ }
1314
+ )
1315
+ ] }),
1316
+ /* @__PURE__ */ jsxs("label", { children: [
1317
+ translate("builder.completionMessage"),
1318
+ /* @__PURE__ */ jsx(
1319
+ "input",
1320
+ {
1321
+ value: schema.translations?.[editingLocale]?.completionMessage ?? "",
1322
+ onChange: (event) => updateFormTranslation("completionMessage", event.currentTarget.value)
1323
+ }
1324
+ )
1325
+ ] })
1326
+ ] })
1327
+ ] }) : null,
1328
+ /* @__PURE__ */ jsx("div", { className: "form-engine-builder__list", children: schema.fields.map((field, index) => {
1329
+ const condition = field.displayCondition;
1330
+ const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
1331
+ const availableSources = schema.fields.slice(0, index);
1332
+ return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__question", children: [
1333
+ /* @__PURE__ */ jsx("legend", { children: field.title }),
858
1334
  /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__toolbar", children: [
859
1335
  /* @__PURE__ */ jsx(
860
1336
  "button",
861
1337
  {
862
1338
  type: "button",
863
- disabled: pageIndex === 0,
864
- "aria-label": translate("builder.moveUp", { title: page.title ?? page.id }),
865
- onClick: () => movePage(pageIndex, -1),
1339
+ disabled: index === 0,
1340
+ onClick: () => moveField(index, -1),
1341
+ "aria-label": translate("builder.moveUp", { title: field.title }),
866
1342
  children: "\u2191"
867
1343
  }
868
1344
  ),
@@ -870,65 +1346,94 @@ function FormBuilder({
870
1346
  "button",
871
1347
  {
872
1348
  type: "button",
873
- disabled: pageIndex === (schema.pages?.length ?? 0) - 1,
874
- "aria-label": translate("builder.moveDown", { title: page.title ?? page.id }),
875
- onClick: () => movePage(pageIndex, 1),
1349
+ disabled: index === schema.fields.length - 1,
1350
+ onClick: () => moveField(index, 1),
1351
+ "aria-label": translate("builder.moveDown", { title: field.title }),
876
1352
  children: "\u2193"
877
1353
  }
878
1354
  ),
879
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => removePage(pageIndex), children: translate("builder.deleteAction") })
1355
+ /* @__PURE__ */ jsx(
1356
+ "button",
1357
+ {
1358
+ type: "button",
1359
+ disabled: schema.fields.length === 1,
1360
+ onClick: () => removeField(field.id),
1361
+ "aria-label": translate("builder.delete", { title: field.title }),
1362
+ children: translate("builder.deleteAction")
1363
+ }
1364
+ )
880
1365
  ] }),
881
1366
  /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
882
1367
  /* @__PURE__ */ jsxs("label", { children: [
883
- translate("builder.pageTitle"),
1368
+ translate("builder.questionTitle"),
884
1369
  /* @__PURE__ */ jsx(
885
1370
  "input",
886
1371
  {
887
- value: page.title ?? "",
888
- onChange: (event) => {
889
- const value = event.currentTarget.value;
890
- updatePage(page.id, (current) => {
891
- if (value.length > 0) return { ...current, title: value };
892
- const { title: _title, ...withoutTitle } = current;
893
- return withoutTitle;
894
- });
895
- }
1372
+ value: field.title,
1373
+ placeholder: translate("builder.questionTitlePlaceholder"),
1374
+ onChange: (event) => updateField(field.id, (current) => ({
1375
+ ...current,
1376
+ title: event.currentTarget.value.trim().length === 0 ? current.title : event.currentTarget.value
1377
+ }))
896
1378
  }
897
1379
  )
898
1380
  ] }),
899
1381
  /* @__PURE__ */ jsxs("label", { children: [
900
- translate("builder.pageDescription"),
1382
+ translate("builder.type"),
901
1383
  /* @__PURE__ */ jsx(
902
- "input",
1384
+ "select",
903
1385
  {
904
- value: page.description ?? "",
905
- onChange: (event) => {
906
- const value = event.currentTarget.value;
907
- updatePage(page.id, (current) => {
908
- if (value.length > 0) return { ...current, description: value };
909
- const { description: _description, ...withoutDescription } = current;
910
- return withoutDescription;
911
- });
912
- }
1386
+ value: field.type,
1387
+ onChange: (event) => changeType(field.id, event.currentTarget.value),
1388
+ children: FIELD_TYPES.filter(
1389
+ (type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
1390
+ ).map((type) => /* @__PURE__ */ jsx("option", { value: type, children: translate(fieldTypeKey(type)) }, type))
913
1391
  }
914
1392
  )
1393
+ ] }),
1394
+ /* @__PURE__ */ jsxs("label", { className: "form-engine-builder__check", children: [
1395
+ /* @__PURE__ */ jsx(
1396
+ "input",
1397
+ {
1398
+ type: "checkbox",
1399
+ checked: field.required === true,
1400
+ onChange: (event) => updateField(field.id, (current) => ({ ...current, required: event.currentTarget.checked }))
1401
+ }
1402
+ ),
1403
+ translate("builder.required")
915
1404
  ] })
916
1405
  ] }),
917
- editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1406
+ !pagesEnabled || schema.pages === void 0 ? null : /* @__PURE__ */ jsxs("label", { children: [
1407
+ translate("builder.questionPage"),
1408
+ /* @__PURE__ */ jsx(
1409
+ "select",
1410
+ {
1411
+ value: pageForField(field.id)?.id ?? "",
1412
+ onChange: (event) => assignFieldToPage(field.id, event.currentTarget.value),
1413
+ children: schema.pages.map((page, pageIndex) => /* @__PURE__ */ jsx("option", { value: page.id, children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }, page.id))
1414
+ }
1415
+ )
1416
+ ] }),
1417
+ !localizationEnabled || editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
918
1418
  /* @__PURE__ */ jsx("strong", { children: editingLocale }),
919
1419
  /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
920
1420
  /* @__PURE__ */ jsxs("label", { children: [
921
- translate("builder.pageTitle"),
1421
+ translate("builder.questionTitle"),
922
1422
  /* @__PURE__ */ jsx(
923
1423
  "input",
924
1424
  {
925
- value: page.translations?.[editingLocale]?.title ?? "",
926
- onChange: (event) => headless.setLocaleTranslation(
927
- editingLocale,
928
- { kind: "page", id: page.id },
929
- "title",
930
- event.currentTarget.value
931
- )
1425
+ value: field.translations?.[editingLocale]?.title ?? "",
1426
+ onChange: (event) => updateManualTranslation({
1427
+ locale: editingLocale,
1428
+ kind: "field",
1429
+ nodeId: field.id,
1430
+ property: "title",
1431
+ sourceText: field.title,
1432
+ translatedText: event.currentTarget.value,
1433
+ ...field.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
1434
+ existingTranslationMetadata: field.translationMetadata[editingLocale]?.title
1435
+ }
1436
+ })
932
1437
  }
933
1438
  )
934
1439
  ] }),
@@ -937,61 +1442,175 @@ function FormBuilder({
937
1442
  /* @__PURE__ */ jsx(
938
1443
  "input",
939
1444
  {
940
- value: page.translations?.[editingLocale]?.description ?? "",
941
- onChange: (event) => headless.setLocaleTranslation(
942
- editingLocale,
943
- { kind: "page", id: page.id },
944
- "description",
945
- event.currentTarget.value
946
- )
1445
+ value: field.translations?.[editingLocale]?.description ?? "",
1446
+ onChange: (event) => updateManualTranslation({
1447
+ locale: editingLocale,
1448
+ kind: "field",
1449
+ nodeId: field.id,
1450
+ property: "description",
1451
+ sourceText: field.description ?? "",
1452
+ translatedText: event.currentTarget.value,
1453
+ ...field.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
1454
+ existingTranslationMetadata: field.translationMetadata[editingLocale]?.description
1455
+ }
1456
+ })
947
1457
  }
948
1458
  )
949
1459
  ] })
950
- ] })
1460
+ ] }),
1461
+ "options" in field ? field.options.map((option, optionIndex) => /* @__PURE__ */ jsxs("label", { children: [
1462
+ translate("builder.optionLabel", { index: optionIndex + 1 }),
1463
+ " (",
1464
+ editingLocale,
1465
+ ")",
1466
+ /* @__PURE__ */ jsx(
1467
+ "input",
1468
+ {
1469
+ value: option.translations?.[editingLocale] ?? "",
1470
+ onChange: (event) => updateManualTranslation({
1471
+ locale: editingLocale,
1472
+ kind: "option",
1473
+ nodeId: option.id,
1474
+ property: "label",
1475
+ sourceText: option.label,
1476
+ translatedText: event.currentTarget.value,
1477
+ ...option.translationMetadata?.[editingLocale]?.label === void 0 ? {} : {
1478
+ existingTranslationMetadata: option.translationMetadata[editingLocale]?.label
1479
+ }
1480
+ })
1481
+ }
1482
+ )
1483
+ ] }, option.id)) : null
951
1484
  ] }),
952
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
1485
+ field.type === "rating" ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1486
+ /* @__PURE__ */ jsxs("label", { children: [
1487
+ translate("builder.minimum"),
1488
+ /* @__PURE__ */ jsx(
1489
+ "input",
1490
+ {
1491
+ type: "number",
1492
+ value: field.min ?? 1,
1493
+ onChange: (event) => {
1494
+ const min = event.currentTarget.valueAsNumber;
1495
+ if (!Number.isInteger(min)) return;
1496
+ updateField(
1497
+ field.id,
1498
+ (current) => current.type === "rating" ? { ...current, min, max: Math.max(min, current.max ?? 5) } : current
1499
+ );
1500
+ }
1501
+ }
1502
+ )
1503
+ ] }),
1504
+ /* @__PURE__ */ jsxs("label", { children: [
1505
+ translate("builder.maximum"),
1506
+ /* @__PURE__ */ jsx(
1507
+ "input",
1508
+ {
1509
+ type: "number",
1510
+ value: field.max ?? 5,
1511
+ onChange: (event) => {
1512
+ const max = event.currentTarget.valueAsNumber;
1513
+ if (!Number.isInteger(max)) return;
1514
+ updateField(
1515
+ field.id,
1516
+ (current) => current.type === "rating" ? { ...current, min: Math.min(current.min ?? 1, max), max } : current
1517
+ );
1518
+ }
1519
+ }
1520
+ )
1521
+ ] })
1522
+ ] }) : null,
1523
+ "options" in field ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__options", children: [
1524
+ /* @__PURE__ */ jsx("strong", { children: translate("builder.options") }),
1525
+ field.options.map((option, optionIndex) => /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__option", children: [
1526
+ /* @__PURE__ */ jsx(
1527
+ "input",
1528
+ {
1529
+ "aria-label": translate("builder.optionLabel", { index: optionIndex + 1 }),
1530
+ value: option.label,
1531
+ placeholder: translate("builder.optionLabelPlaceholder"),
1532
+ onChange: (event) => event.currentTarget.value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, event.currentTarget.value)
1533
+ }
1534
+ ),
1535
+ /* @__PURE__ */ jsx(
1536
+ "button",
1537
+ {
1538
+ type: "button",
1539
+ disabled: optionIndex === 0,
1540
+ "aria-label": translate("builder.moveUp", { title: option.label }),
1541
+ onClick: () => moveOption(field.id, option.id, optionIndex - 1),
1542
+ children: "\u2191"
1543
+ }
1544
+ ),
1545
+ /* @__PURE__ */ jsx(
1546
+ "button",
1547
+ {
1548
+ type: "button",
1549
+ disabled: optionIndex === field.options.length - 1,
1550
+ "aria-label": translate("builder.moveDown", { title: option.label }),
1551
+ onClick: () => moveOption(field.id, option.id, optionIndex + 1),
1552
+ children: "\u2193"
1553
+ }
1554
+ ),
1555
+ /* @__PURE__ */ jsx(
1556
+ "button",
1557
+ {
1558
+ type: "button",
1559
+ disabled: field.options.length === 1,
1560
+ onClick: () => removeOption(field.id, option.id),
1561
+ children: translate("builder.remove")
1562
+ }
1563
+ )
1564
+ ] }, option.id)),
1565
+ /* @__PURE__ */ jsx(
1566
+ "button",
1567
+ {
1568
+ type: "button",
1569
+ "data-builder-action": "addOption",
1570
+ "data-target-id": field.id,
1571
+ disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1572
+ onClick: () => addOption(field.id),
1573
+ children: translate("builder.addOption")
1574
+ }
1575
+ )
1576
+ ] }) : null,
1577
+ conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
953
1578
  /* @__PURE__ */ jsxs("label", { children: [
954
- translate("builder.pageCondition"),
1579
+ translate("builder.displayCondition"),
955
1580
  /* @__PURE__ */ jsxs(
956
1581
  "select",
957
1582
  {
958
- value: page.displayCondition?.questionId ?? "",
1583
+ value: condition?.questionId ?? "",
959
1584
  onChange: (event) => {
960
- const selected = schema.fields.find((field) => field.id === event.currentTarget.value);
961
- updatePage(page.id, (current) => {
962
- if (selected === void 0) {
963
- const { displayCondition: _condition, ...withoutCondition } = current;
964
- return withoutCondition;
965
- }
966
- return {
967
- ...current,
968
- displayCondition: conditionWithValue(
969
- selected.id,
970
- conditionOperators(selected)[0] ?? "not_empty",
971
- defaultConditionValue(selected)
972
- )
973
- };
974
- });
1585
+ const selected = schema.fields.find((item) => item.id === event.currentTarget.value);
1586
+ setDisplayCondition(
1587
+ field.id,
1588
+ selected === void 0 ? void 0 : conditionWithValue(
1589
+ selected.id,
1590
+ conditionOperators(selected)[0] ?? "not_empty",
1591
+ defaultConditionValue(selected)
1592
+ )
1593
+ );
975
1594
  },
976
1595
  children: [
977
1596
  /* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
978
- availableSources.map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
1597
+ availableSources.map((candidate) => /* @__PURE__ */ jsx("option", { value: candidate.id, children: candidate.title }, candidate.id))
979
1598
  ]
980
1599
  }
981
1600
  )
982
1601
  ] }),
983
- page.displayCondition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1602
+ condition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
984
1603
  /* @__PURE__ */ jsx(
985
1604
  "select",
986
1605
  {
987
1606
  "aria-label": translate("builder.conditionOperator"),
988
- value: page.displayCondition.operator,
1607
+ value: condition.operator,
989
1608
  onChange: (event) => {
990
1609
  const operator = event.currentTarget.value;
991
- updatePage(page.id, (current) => ({
992
- ...current,
993
- displayCondition: conditionWithValue(source.id, operator, defaultConditionValue(source))
994
- }));
1610
+ setDisplayCondition(
1611
+ field.id,
1612
+ conditionWithValue(source.id, operator, defaultConditionValue(source))
1613
+ );
995
1614
  },
996
1615
  children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
997
1616
  }
@@ -1000,395 +1619,29 @@ function FormBuilder({
1000
1619
  ConditionValueEditor,
1001
1620
  {
1002
1621
  source,
1003
- condition: page.displayCondition,
1004
- onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
1622
+ condition,
1623
+ onChange: (next) => setDisplayCondition(field.id, next),
1005
1624
  translate
1006
1625
  }
1007
1626
  )
1008
1627
  ] }) : null
1009
- ] })
1010
- ] }, page.id);
1011
- }),
1012
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__page-add", children: [
1013
- /* @__PURE__ */ jsxs("label", { children: [
1014
- translate("builder.pageQuestion"),
1015
- /* @__PURE__ */ jsxs(
1016
- "select",
1017
- {
1018
- value: newPageQuestionId,
1019
- disabled: movablePageQuestions.length === 0,
1020
- onChange: (event) => setNewPageQuestionId(event.currentTarget.value),
1021
- children: [
1022
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1023
- schema.fields.filter((field) => movablePageQuestions.includes(field.id)).map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
1024
- ]
1025
- }
1026
- )
1027
- ] }),
1028
- /* @__PURE__ */ jsx("button", { type: "button", disabled: movablePageQuestions.length === 0, onClick: addPage, children: translate("builder.addPage") })
1029
- ] })
1030
- ] })
1031
- ] }),
1032
- /* @__PURE__ */ jsxs("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
1033
- /* @__PURE__ */ jsx("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
1034
- /* @__PURE__ */ jsxs("label", { children: [
1035
- translate("builder.completionMessage"),
1036
- /* @__PURE__ */ jsx(
1037
- "input",
1038
- {
1039
- value: schema.completionMessage ?? "",
1040
- onChange: (event) => headless.setSourceText({ kind: "form" }, "completionMessage", event.currentTarget.value)
1041
- }
1042
- )
1043
- ] }),
1044
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1045
- /* @__PURE__ */ jsxs("label", { children: [
1046
- translate("builder.defaultLocale"),
1047
- /* @__PURE__ */ jsx(
1048
- "input",
1049
- {
1050
- value: schema.defaultLocale ?? "",
1051
- onChange: (event) => headless.setDefaultLocale(event.currentTarget.value)
1052
- }
1053
- )
1054
- ] }),
1055
- /* @__PURE__ */ jsxs("label", { children: [
1056
- translate("builder.addLocale"),
1057
- /* @__PURE__ */ jsx("input", { value: newLocale, onChange: (event) => setNewLocale(event.currentTarget.value) })
1058
- ] }),
1059
- /* @__PURE__ */ jsx("button", { type: "button", onClick: addLocale, children: translate("builder.addLocale") }),
1060
- /* @__PURE__ */ jsxs("label", { children: [
1061
- translate("builder.editLocale"),
1062
- /* @__PURE__ */ jsxs("select", { value: editingLocale, onChange: (event) => setEditingLocale(event.currentTarget.value), children: [
1063
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1064
- (schema.supportedLocales ?? []).filter((item) => item !== schema.defaultLocale).map((item) => /* @__PURE__ */ jsx("option", { value: item, children: item }, item))
1065
- ] })
1066
- ] }),
1628
+ ] }) : null
1629
+ ] }, field.id);
1630
+ }) }),
1067
1631
  /* @__PURE__ */ jsx(
1068
1632
  "button",
1069
1633
  {
1634
+ className: "form-engine-builder__add",
1070
1635
  type: "button",
1071
- disabled: translationAdapter === void 0 || editingLocale.length === 0 || isTranslating,
1072
- onClick: () => void translateAll(),
1073
- children: translate("builder.autoTranslate")
1636
+ "data-builder-action": "addField",
1637
+ disabled: initialFieldType === null || maxFieldsReached,
1638
+ onClick: addField,
1639
+ children: translate("builder.addQuestion")
1074
1640
  }
1075
1641
  )
1076
- ] }),
1077
- translationAdapter === void 0 ? /* @__PURE__ */ jsx("p", { children: translate("builder.translationUnavailable") }) : null,
1078
- translationError === null ? null : /* @__PURE__ */ jsx("p", { className: "form-engine-builder__error", children: translationError }),
1079
- editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1080
- /* @__PURE__ */ jsxs("label", { children: [
1081
- translate("builder.questionTitle"),
1082
- /* @__PURE__ */ jsx(
1083
- "input",
1084
- {
1085
- value: schema.translations?.[editingLocale]?.title ?? "",
1086
- onChange: (event) => updateFormTranslation("title", event.currentTarget.value)
1087
- }
1088
- )
1089
- ] }),
1090
- /* @__PURE__ */ jsxs("label", { children: [
1091
- translate("builder.pageDescription"),
1092
- /* @__PURE__ */ jsx(
1093
- "input",
1094
- {
1095
- value: schema.translations?.[editingLocale]?.description ?? "",
1096
- onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
1097
- }
1098
- )
1099
- ] }),
1100
- /* @__PURE__ */ jsxs("label", { children: [
1101
- translate("builder.completionMessage"),
1102
- /* @__PURE__ */ jsx(
1103
- "input",
1104
- {
1105
- value: schema.translations?.[editingLocale]?.completionMessage ?? "",
1106
- onChange: (event) => updateFormTranslation("completionMessage", event.currentTarget.value)
1107
- }
1108
- )
1109
- ] })
1110
1642
  ] })
1111
- ] }),
1112
- /* @__PURE__ */ jsx("div", { className: "form-engine-builder__list", children: schema.fields.map((field, index) => {
1113
- const condition = field.displayCondition;
1114
- const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
1115
- const availableSources = schema.fields.slice(0, index);
1116
- return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__question", children: [
1117
- /* @__PURE__ */ jsx("legend", { children: field.title }),
1118
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__toolbar", children: [
1119
- /* @__PURE__ */ jsx(
1120
- "button",
1121
- {
1122
- type: "button",
1123
- disabled: index === 0,
1124
- onClick: () => moveField(index, -1),
1125
- "aria-label": translate("builder.moveUp", { title: field.title }),
1126
- children: "\u2191"
1127
- }
1128
- ),
1129
- /* @__PURE__ */ jsx(
1130
- "button",
1131
- {
1132
- type: "button",
1133
- disabled: index === schema.fields.length - 1,
1134
- onClick: () => moveField(index, 1),
1135
- "aria-label": translate("builder.moveDown", { title: field.title }),
1136
- children: "\u2193"
1137
- }
1138
- ),
1139
- /* @__PURE__ */ jsx(
1140
- "button",
1141
- {
1142
- type: "button",
1143
- disabled: schema.fields.length === 1,
1144
- onClick: () => removeField(field.id),
1145
- "aria-label": translate("builder.delete", { title: field.title }),
1146
- children: translate("builder.deleteAction")
1147
- }
1148
- )
1149
- ] }),
1150
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1151
- /* @__PURE__ */ jsxs("label", { children: [
1152
- translate("builder.questionTitle"),
1153
- /* @__PURE__ */ jsx(
1154
- "input",
1155
- {
1156
- value: field.title,
1157
- placeholder: translate("builder.questionTitlePlaceholder"),
1158
- onChange: (event) => updateField(field.id, (current) => ({
1159
- ...current,
1160
- title: event.currentTarget.value.trim().length === 0 ? current.title : event.currentTarget.value
1161
- }))
1162
- }
1163
- )
1164
- ] }),
1165
- /* @__PURE__ */ jsxs("label", { children: [
1166
- translate("builder.type"),
1167
- /* @__PURE__ */ jsx(
1168
- "select",
1169
- {
1170
- value: field.type,
1171
- onChange: (event) => changeType(field.id, event.currentTarget.value),
1172
- children: FIELD_TYPES.filter(
1173
- (type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
1174
- ).map((type) => /* @__PURE__ */ jsx("option", { value: type, children: translate(fieldTypeKey(type)) }, type))
1175
- }
1176
- )
1177
- ] }),
1178
- /* @__PURE__ */ jsxs("label", { className: "form-engine-builder__check", children: [
1179
- /* @__PURE__ */ jsx(
1180
- "input",
1181
- {
1182
- type: "checkbox",
1183
- checked: field.required === true,
1184
- onChange: (event) => updateField(field.id, (current) => ({ ...current, required: event.currentTarget.checked }))
1185
- }
1186
- ),
1187
- translate("builder.required")
1188
- ] })
1189
- ] }),
1190
- schema.pages === void 0 ? null : /* @__PURE__ */ jsxs("label", { children: [
1191
- translate("builder.questionPage"),
1192
- /* @__PURE__ */ jsx(
1193
- "select",
1194
- {
1195
- value: pageForField(field.id)?.id ?? "",
1196
- onChange: (event) => assignFieldToPage(field.id, event.currentTarget.value),
1197
- children: schema.pages.map((page, pageIndex) => /* @__PURE__ */ jsx("option", { value: page.id, children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }, page.id))
1198
- }
1199
- )
1200
- ] }),
1201
- editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1202
- /* @__PURE__ */ jsx("strong", { children: editingLocale }),
1203
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1204
- /* @__PURE__ */ jsxs("label", { children: [
1205
- translate("builder.questionTitle"),
1206
- /* @__PURE__ */ jsx(
1207
- "input",
1208
- {
1209
- value: field.translations?.[editingLocale]?.title ?? "",
1210
- onChange: (event) => headless.setLocaleTranslation(
1211
- editingLocale,
1212
- { kind: "field", id: field.id },
1213
- "title",
1214
- event.currentTarget.value
1215
- )
1216
- }
1217
- )
1218
- ] }),
1219
- /* @__PURE__ */ jsxs("label", { children: [
1220
- translate("builder.pageDescription"),
1221
- /* @__PURE__ */ jsx(
1222
- "input",
1223
- {
1224
- value: field.translations?.[editingLocale]?.description ?? "",
1225
- onChange: (event) => headless.setLocaleTranslation(
1226
- editingLocale,
1227
- { kind: "field", id: field.id },
1228
- "description",
1229
- event.currentTarget.value
1230
- )
1231
- }
1232
- )
1233
- ] })
1234
- ] }),
1235
- "options" in field ? field.options.map((option, optionIndex) => /* @__PURE__ */ jsxs("label", { children: [
1236
- translate("builder.optionLabel", { index: optionIndex + 1 }),
1237
- " (",
1238
- editingLocale,
1239
- ")",
1240
- /* @__PURE__ */ jsx(
1241
- "input",
1242
- {
1243
- value: option.translations?.[editingLocale] ?? "",
1244
- onChange: (event) => headless.setLocaleTranslation(
1245
- editingLocale,
1246
- { kind: "option", id: option.id },
1247
- "label",
1248
- event.currentTarget.value
1249
- )
1250
- }
1251
- )
1252
- ] }, option.id)) : null
1253
- ] }),
1254
- field.type === "rating" ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1255
- /* @__PURE__ */ jsxs("label", { children: [
1256
- translate("builder.minimum"),
1257
- /* @__PURE__ */ jsx(
1258
- "input",
1259
- {
1260
- type: "number",
1261
- value: field.min ?? 1,
1262
- onChange: (event) => {
1263
- const min = event.currentTarget.valueAsNumber;
1264
- if (!Number.isInteger(min)) return;
1265
- updateField(
1266
- field.id,
1267
- (current) => current.type === "rating" ? { ...current, min, max: Math.max(min, current.max ?? 5) } : current
1268
- );
1269
- }
1270
- }
1271
- )
1272
- ] }),
1273
- /* @__PURE__ */ jsxs("label", { children: [
1274
- translate("builder.maximum"),
1275
- /* @__PURE__ */ jsx(
1276
- "input",
1277
- {
1278
- type: "number",
1279
- value: field.max ?? 5,
1280
- onChange: (event) => {
1281
- const max = event.currentTarget.valueAsNumber;
1282
- if (!Number.isInteger(max)) return;
1283
- updateField(
1284
- field.id,
1285
- (current) => current.type === "rating" ? { ...current, min: Math.min(current.min ?? 1, max), max } : current
1286
- );
1287
- }
1288
- }
1289
- )
1290
- ] })
1291
- ] }) : null,
1292
- "options" in field ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__options", children: [
1293
- /* @__PURE__ */ jsx("strong", { children: translate("builder.options") }),
1294
- field.options.map((option, optionIndex) => /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__option", children: [
1295
- /* @__PURE__ */ jsx(
1296
- "input",
1297
- {
1298
- "aria-label": translate("builder.optionLabel", { index: optionIndex + 1 }),
1299
- value: option.label,
1300
- placeholder: translate("builder.optionLabelPlaceholder"),
1301
- onChange: (event) => event.currentTarget.value.trim().length === 0 ? void 0 : headless.updateOption(field.id, option.id, (item) => ({
1302
- ...item,
1303
- label: event.currentTarget.value
1304
- }))
1305
- }
1306
- ),
1307
- /* @__PURE__ */ jsx(
1308
- "button",
1309
- {
1310
- type: "button",
1311
- disabled: field.options.length === 1,
1312
- onClick: () => headless.removeOption(field.id, option.id),
1313
- children: translate("builder.remove")
1314
- }
1315
- )
1316
- ] }, option.id)),
1317
- /* @__PURE__ */ jsx(
1318
- "button",
1319
- {
1320
- type: "button",
1321
- disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1322
- onClick: () => headless.addOption(field.id),
1323
- children: translate("builder.addOption")
1324
- }
1325
- )
1326
- ] }) : null,
1327
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
1328
- /* @__PURE__ */ jsxs("label", { children: [
1329
- translate("builder.displayCondition"),
1330
- /* @__PURE__ */ jsxs(
1331
- "select",
1332
- {
1333
- value: condition?.questionId ?? "",
1334
- onChange: (event) => {
1335
- const selected = schema.fields.find((item) => item.id === event.currentTarget.value);
1336
- headless.setDisplayCondition(
1337
- field.id,
1338
- selected === void 0 ? void 0 : conditionWithValue(
1339
- selected.id,
1340
- conditionOperators(selected)[0] ?? "not_empty",
1341
- defaultConditionValue(selected)
1342
- )
1343
- );
1344
- },
1345
- children: [
1346
- /* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
1347
- availableSources.map((candidate) => /* @__PURE__ */ jsx("option", { value: candidate.id, children: candidate.title }, candidate.id))
1348
- ]
1349
- }
1350
- )
1351
- ] }),
1352
- condition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1353
- /* @__PURE__ */ jsx(
1354
- "select",
1355
- {
1356
- "aria-label": translate("builder.conditionOperator"),
1357
- value: condition.operator,
1358
- onChange: (event) => {
1359
- const operator = event.currentTarget.value;
1360
- headless.setDisplayCondition(
1361
- field.id,
1362
- conditionWithValue(source.id, operator, defaultConditionValue(source))
1363
- );
1364
- },
1365
- children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
1366
- }
1367
- ),
1368
- /* @__PURE__ */ jsx(
1369
- ConditionValueEditor,
1370
- {
1371
- source,
1372
- condition,
1373
- onChange: (next) => headless.setDisplayCondition(field.id, next),
1374
- translate
1375
- }
1376
- )
1377
- ] }) : null
1378
- ] })
1379
- ] }, field.id);
1380
- }) }),
1381
- /* @__PURE__ */ jsx(
1382
- "button",
1383
- {
1384
- className: "form-engine-builder__add",
1385
- type: "button",
1386
- disabled: policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields,
1387
- onClick: addField,
1388
- children: translate("builder.addQuestion")
1389
- }
1390
- )
1391
- ] });
1643
+ }
1644
+ );
1392
1645
  }
1393
1646
 
1394
1647
  // src/context.tsx
@@ -2045,6 +2298,7 @@ export {
2045
2298
  FormBuilder,
2046
2299
  FormProvider,
2047
2300
  FormRenderer,
2301
+ resolveInitialFieldType,
2048
2302
  useField,
2049
2303
  useForm,
2050
2304
  useFormBuilder