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