@form-engine-ts/react 2.1.1 → 2.3.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
@@ -2,10 +2,11 @@
2
2
  import {
3
3
  populateSchemaTranslations
4
4
  } from "@form-engine-ts/core";
5
- import { useState } from "react";
5
+ import { createContext, useContext, useState } from "react";
6
6
 
7
7
  // src/hooks/useFormBuilder.ts
8
8
  import {
9
+ collectSchemaLocales,
9
10
  transformFieldType,
10
11
  validateFormSchema
11
12
  } from "@form-engine-ts/core";
@@ -135,10 +136,16 @@ function useFormBuilder({
135
136
  if (pages === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
136
137
  const updated = updater(page);
137
138
  if (updated.id !== pageId) return { success: false, error: { type: "invalid_id", kind: "page", id: updated.id } };
139
+ for (const text of [updated.title, updated.description]) {
140
+ if (text !== void 0) {
141
+ const error = textPolicyError(text);
142
+ if (error !== void 0) return error;
143
+ }
144
+ }
138
145
  onChange({ ...schema, pages: pages.map((candidate) => candidate.id === pageId ? updated : candidate) });
139
146
  return { success: true };
140
147
  },
141
- [onChange, schema]
148
+ [onChange, schema, textPolicyError]
142
149
  );
143
150
  const addField = useCallback(
144
151
  (type, pageId) => {
@@ -482,6 +489,13 @@ function useFormBuilder({
482
489
  const normalized = locale.trim();
483
490
  if (normalized.length === 0)
484
491
  return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
492
+ if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(normalized)) {
493
+ return { success: false, error: { type: "disallowed_locale", locale: normalized } };
494
+ }
495
+ const allUniqueLocales = collectSchemaLocales(schema).allUniqueLocales;
496
+ if (!allUniqueLocales.has(normalized) && policy?.maxLocales !== void 0 && allUniqueLocales.size >= policy.maxLocales) {
497
+ return { success: false, error: { type: "max_locales_exceeded", max: policy.maxLocales } };
498
+ }
485
499
  const error = textPolicyError(text);
486
500
  if (error !== void 0) return error;
487
501
  const supportedLocales = [.../* @__PURE__ */ new Set([...schema.supportedLocales ?? [], normalized])];
@@ -550,13 +564,22 @@ function useFormBuilder({
550
564
  onChange({ ...schema, supportedLocales, fields, ...pages === void 0 ? {} : { pages } });
551
565
  return { success: true };
552
566
  },
553
- [onChange, schema, textPolicyError]
567
+ [onChange, policy?.allowedLocales, policy?.maxLocales, schema, textPolicyError]
554
568
  );
555
569
  const addLocale = useCallback(
556
570
  (locale) => {
557
571
  const normalized = locale.trim();
558
572
  if (normalized.length === 0)
559
573
  return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
574
+ if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(normalized)) {
575
+ return { success: false, error: { type: "disallowed_locale", locale: normalized } };
576
+ }
577
+ const allUniqueLocales = collectSchemaLocales(schema).allUniqueLocales;
578
+ if ((schema.supportedLocales ?? []).includes(normalized) || schema.defaultLocale === normalized)
579
+ return { success: true };
580
+ if (policy?.maxLocales !== void 0 && !allUniqueLocales.has(normalized) && allUniqueLocales.size >= policy.maxLocales) {
581
+ return { success: false, error: { type: "max_locales_exceeded", max: policy.maxLocales } };
582
+ }
560
583
  onChange({
561
584
  ...schema,
562
585
  supportedLocales: [
@@ -569,13 +592,20 @@ function useFormBuilder({
569
592
  });
570
593
  return { success: true };
571
594
  },
572
- [onChange, schema]
595
+ [onChange, policy?.allowedLocales, policy?.maxLocales, schema]
573
596
  );
574
597
  const setDefaultLocale = useCallback(
575
598
  (locale) => {
576
599
  const normalized = locale.trim();
577
600
  if (normalized.length === 0)
578
601
  return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
602
+ if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(normalized)) {
603
+ return { success: false, error: { type: "disallowed_locale", locale: normalized } };
604
+ }
605
+ const allUniqueLocales = collectSchemaLocales(schema).allUniqueLocales;
606
+ if (!allUniqueLocales.has(normalized) && policy?.maxLocales !== void 0 && allUniqueLocales.size >= policy.maxLocales) {
607
+ return { success: false, error: { type: "max_locales_exceeded", max: policy.maxLocales } };
608
+ }
579
609
  onChange({
580
610
  ...schema,
581
611
  defaultLocale: normalized,
@@ -583,7 +613,7 @@ function useFormBuilder({
583
613
  });
584
614
  return { success: true };
585
615
  },
586
- [onChange, schema]
616
+ [onChange, policy?.allowedLocales, policy?.maxLocales, schema]
587
617
  );
588
618
  const validationIssues = useMemo(() => {
589
619
  const result = validateFormSchema(schema, policy === void 0 ? {} : { policy });
@@ -616,6 +646,260 @@ function useFormBuilder({
616
646
 
617
647
  // src/builder.tsx
618
648
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
649
+ function DefaultButton({
650
+ id,
651
+ className,
652
+ disabled,
653
+ "aria-label": ariaLabel,
654
+ onClick,
655
+ children,
656
+ title,
657
+ action,
658
+ targetId
659
+ }) {
660
+ return /* @__PURE__ */ jsx(
661
+ "button",
662
+ {
663
+ id,
664
+ className,
665
+ type: "button",
666
+ disabled,
667
+ "aria-label": ariaLabel,
668
+ title,
669
+ "data-builder-action": action,
670
+ "data-target-id": targetId,
671
+ onClick,
672
+ children
673
+ }
674
+ );
675
+ }
676
+ function DefaultIconButton(props) {
677
+ return /* @__PURE__ */ jsx(
678
+ DefaultButton,
679
+ {
680
+ ...props.id === void 0 ? {} : { id: props.id },
681
+ ...props.className === void 0 ? {} : { className: props.className },
682
+ ...props.disabled === void 0 ? {} : { disabled: props.disabled },
683
+ "aria-label": props["aria-label"] ?? props.title,
684
+ ...props.onClick === void 0 ? {} : { onClick: props.onClick },
685
+ children: props.icon
686
+ }
687
+ );
688
+ }
689
+ function DefaultTextInput({
690
+ id,
691
+ className,
692
+ disabled,
693
+ readOnly,
694
+ "aria-label": ariaLabel,
695
+ value,
696
+ onChange,
697
+ placeholder,
698
+ maxLength,
699
+ inputMode,
700
+ type = "text",
701
+ min,
702
+ max,
703
+ step
704
+ }) {
705
+ return /* @__PURE__ */ jsx(
706
+ "input",
707
+ {
708
+ id,
709
+ className,
710
+ disabled,
711
+ readOnly,
712
+ "aria-label": ariaLabel,
713
+ type: type === "text" ? void 0 : type,
714
+ inputMode,
715
+ min,
716
+ max,
717
+ step,
718
+ value,
719
+ placeholder,
720
+ maxLength,
721
+ onChange: (event) => onChange(event.currentTarget.value)
722
+ }
723
+ );
724
+ }
725
+ function DefaultTextArea({
726
+ id,
727
+ className,
728
+ disabled,
729
+ readOnly,
730
+ "aria-label": ariaLabel,
731
+ value,
732
+ onChange,
733
+ rows,
734
+ placeholder,
735
+ maxLength
736
+ }) {
737
+ return /* @__PURE__ */ jsx(
738
+ "textarea",
739
+ {
740
+ id,
741
+ className,
742
+ disabled,
743
+ readOnly,
744
+ "aria-label": ariaLabel,
745
+ value,
746
+ rows,
747
+ placeholder,
748
+ maxLength,
749
+ onChange: (event) => onChange(event.currentTarget.value)
750
+ }
751
+ );
752
+ }
753
+ function DefaultSelect({
754
+ id,
755
+ className,
756
+ disabled,
757
+ "aria-label": ariaLabel,
758
+ value,
759
+ onChange,
760
+ options
761
+ }) {
762
+ return /* @__PURE__ */ jsx(
763
+ "select",
764
+ {
765
+ id,
766
+ className,
767
+ disabled,
768
+ "aria-label": ariaLabel,
769
+ value,
770
+ onChange: (event) => onChange(event.currentTarget.value),
771
+ children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
772
+ }
773
+ );
774
+ }
775
+ function DefaultCheckbox({
776
+ id,
777
+ className,
778
+ disabled,
779
+ "aria-label": ariaLabel,
780
+ checked,
781
+ onChange,
782
+ label
783
+ }) {
784
+ return /* @__PURE__ */ jsxs("label", { className, children: [
785
+ /* @__PURE__ */ jsx(
786
+ "input",
787
+ {
788
+ id,
789
+ type: "checkbox",
790
+ disabled,
791
+ "aria-label": ariaLabel,
792
+ checked,
793
+ onChange: (event) => onChange(event.currentTarget.checked)
794
+ }
795
+ ),
796
+ label
797
+ ] });
798
+ }
799
+ function DefaultSection({
800
+ id,
801
+ className,
802
+ title,
803
+ description,
804
+ headingId,
805
+ "aria-label": ariaLabel,
806
+ onClickCapture,
807
+ children
808
+ }) {
809
+ return /* @__PURE__ */ jsxs(
810
+ "section",
811
+ {
812
+ id,
813
+ className,
814
+ "aria-label": ariaLabel,
815
+ "aria-labelledby": title === void 0 ? void 0 : headingId,
816
+ onClickCapture,
817
+ children: [
818
+ title === void 0 ? null : /* @__PURE__ */ jsx("h2", { id: headingId, children: title }),
819
+ description === void 0 ? null : /* @__PURE__ */ jsx("p", { children: description }),
820
+ children
821
+ ]
822
+ }
823
+ );
824
+ }
825
+ function DefaultFieldset({ className, legend, disabled, children }) {
826
+ return /* @__PURE__ */ jsxs("fieldset", { className, disabled, children: [
827
+ legend === void 0 ? null : /* @__PURE__ */ jsx("legend", { children: legend }),
828
+ children
829
+ ] });
830
+ }
831
+ function DefaultErrorMessage({ message }) {
832
+ return /* @__PURE__ */ jsx("p", { className: "form-engine-builder__error", children: message });
833
+ }
834
+ var DEFAULT_COMPONENTS = {
835
+ Button: DefaultButton,
836
+ IconButton: DefaultIconButton,
837
+ TextInput: DefaultTextInput,
838
+ TextArea: DefaultTextArea,
839
+ Select: DefaultSelect,
840
+ Checkbox: DefaultCheckbox,
841
+ Section: DefaultSection,
842
+ Fieldset: DefaultFieldset,
843
+ ErrorMessage: DefaultErrorMessage
844
+ };
845
+ var BuilderPrimitiveContext = createContext({
846
+ components: DEFAULT_COMPONENTS,
847
+ readOnly: false
848
+ });
849
+ function BuilderButton(props) {
850
+ const context = useContext(BuilderPrimitiveContext);
851
+ const Component = context.components.Button;
852
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true, readOnly: context.readOnly });
853
+ }
854
+ function BuilderIconButton(props) {
855
+ const context = useContext(BuilderPrimitiveContext);
856
+ const Component = context.components.IconButton;
857
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true, readOnly: context.readOnly });
858
+ }
859
+ function BuilderTextInput(props) {
860
+ const context = useContext(BuilderPrimitiveContext);
861
+ const Component = context.components.TextInput;
862
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true, readOnly: context.readOnly });
863
+ }
864
+ function BuilderTextArea(props) {
865
+ const context = useContext(BuilderPrimitiveContext);
866
+ const Component = context.components.TextArea;
867
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true, readOnly: context.readOnly });
868
+ }
869
+ function BuilderSelect(props) {
870
+ const context = useContext(BuilderPrimitiveContext);
871
+ const Component = context.components.Select;
872
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true, readOnly: context.readOnly });
873
+ }
874
+ function BuilderCheckbox(props) {
875
+ const context = useContext(BuilderPrimitiveContext);
876
+ const Component = context.components.Checkbox;
877
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true, readOnly: context.readOnly });
878
+ }
879
+ function BuilderSection(props) {
880
+ const Component = useContext(BuilderPrimitiveContext).components.Section;
881
+ return /* @__PURE__ */ jsx(Component, { ...props });
882
+ }
883
+ function BuilderFieldset(props) {
884
+ const context = useContext(BuilderPrimitiveContext);
885
+ const Component = context.components.Fieldset;
886
+ return /* @__PURE__ */ jsx(Component, { ...props, disabled: context.readOnly || props.disabled === true });
887
+ }
888
+ function BuilderErrorMessage(props) {
889
+ const Component = useContext(BuilderPrimitiveContext).components.ErrorMessage;
890
+ return /* @__PURE__ */ jsx(Component, { ...props });
891
+ }
892
+ var GUARDED_COMPONENTS = {
893
+ Button: BuilderButton,
894
+ IconButton: BuilderIconButton,
895
+ TextInput: BuilderTextInput,
896
+ TextArea: BuilderTextArea,
897
+ Select: BuilderSelect,
898
+ Checkbox: BuilderCheckbox,
899
+ Section: BuilderSection,
900
+ Fieldset: BuilderFieldset,
901
+ ErrorMessage: BuilderErrorMessage
902
+ };
619
903
  var FIELD_TYPES = [
620
904
  "text",
621
905
  "textarea",
@@ -714,37 +998,52 @@ function ConditionValueEditor({
714
998
  source,
715
999
  condition,
716
1000
  onChange,
717
- translate
1001
+ translate,
1002
+ components
718
1003
  }) {
1004
+ const { Select, TextInput } = components;
719
1005
  if (condition.operator === "not_empty") return null;
720
1006
  const update = (value) => onChange({ ...condition, value });
721
1007
  if (source.type === "checkbox") {
722
- return /* @__PURE__ */ jsxs("select", { value: String(condition.value), onChange: (event) => update(event.currentTarget.value === "true"), children: [
723
- /* @__PURE__ */ jsx("option", { value: "true", children: translate("builder.conditionTrue") }),
724
- /* @__PURE__ */ jsx("option", { value: "false", children: translate("builder.conditionFalse") })
725
- ] });
1008
+ return /* @__PURE__ */ jsx(
1009
+ Select,
1010
+ {
1011
+ value: String(condition.value),
1012
+ onChange: (value) => update(value === "true"),
1013
+ options: [
1014
+ { value: "true", label: translate("builder.conditionTrue") },
1015
+ { value: "false", label: translate("builder.conditionFalse") }
1016
+ ]
1017
+ }
1018
+ );
726
1019
  }
727
1020
  if (source.type === "number" || source.type === "rating") {
728
1021
  return /* @__PURE__ */ jsx(
729
- "input",
1022
+ TextInput,
730
1023
  {
731
1024
  "aria-label": translate("builder.conditionValue"),
732
1025
  type: "number",
733
- value: typeof condition.value === "number" ? condition.value : "",
734
- onChange: (event) => update(event.currentTarget.value === "" ? 0 : event.currentTarget.valueAsNumber)
1026
+ value: typeof condition.value === "number" ? String(condition.value) : "",
1027
+ onChange: (value) => update(value === "" ? 0 : Number(value))
735
1028
  }
736
1029
  );
737
1030
  }
738
1031
  if ("options" in source) {
739
- return /* @__PURE__ */ jsx("select", { value: String(condition.value ?? ""), onChange: (event) => update(event.currentTarget.value), children: source.options.map((option) => /* @__PURE__ */ jsx("option", { value: option.id, children: option.label }, option.id)) });
1032
+ return /* @__PURE__ */ jsx(
1033
+ Select,
1034
+ {
1035
+ value: String(condition.value ?? ""),
1036
+ onChange: update,
1037
+ options: source.options.map((option) => ({ value: option.id, label: option.label }))
1038
+ }
1039
+ );
740
1040
  }
741
1041
  return /* @__PURE__ */ jsx(
742
- "input",
1042
+ TextInput,
743
1043
  {
744
1044
  "aria-label": translate("builder.conditionValue"),
745
- type: "text",
746
1045
  value: typeof condition.value === "string" ? condition.value : "",
747
- onChange: (event) => update(event.currentTarget.value)
1046
+ onChange: update
748
1047
  }
749
1048
  );
750
1049
  }
@@ -770,8 +1069,21 @@ function FormBuilder({
770
1069
  className = "",
771
1070
  defaultFieldType,
772
1071
  onActionError,
773
- createManualTranslationMetadata
1072
+ createManualTranslationMetadata,
1073
+ readOnly = false,
1074
+ features,
1075
+ components: componentOverrides,
1076
+ slots
774
1077
  }) {
1078
+ const resolvedComponents = { ...DEFAULT_COMPONENTS, ...componentOverrides };
1079
+ const components = GUARDED_COMPONENTS;
1080
+ const { Button, Checkbox, ErrorMessage, Fieldset, IconButton, Section, Select, TextInput } = components;
1081
+ const ToolbarSlot = slots?.toolbar;
1082
+ const FieldEditorSlot = slots?.fieldEditor;
1083
+ const OptionEditorSlot = slots?.optionEditor;
1084
+ const PagesSlot = slots?.pages;
1085
+ const LocalizationSlot = slots?.localization;
1086
+ const TranslationActionsSlot = slots?.translationActions;
775
1087
  const headless = useFormBuilder({
776
1088
  schema,
777
1089
  onChange,
@@ -788,28 +1100,33 @@ function FormBuilder({
788
1100
  const translated = translator?.translate(key, locale, params);
789
1101
  return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
790
1102
  };
791
- const executeAction = (result, context) => {
1103
+ const pagesEnabled = features?.pages ?? true;
1104
+ const localizationEnabled = features?.localization ?? true;
1105
+ const conditionsEnabled = features?.conditions ?? true;
1106
+ const executeAction = (run, context) => {
1107
+ if (readOnly) return { success: true };
1108
+ const result = run();
792
1109
  if (!result.success) onActionError?.(result.error, context);
793
1110
  return result;
794
1111
  };
795
- const updateField = (fieldId, updater, params) => executeAction(headless.updateField(fieldId, updater), {
1112
+ const updateField = (fieldId, updater, params) => executeAction(() => headless.updateField(fieldId, updater), {
796
1113
  action: "updateField",
797
1114
  targetId: fieldId,
798
1115
  ...params === void 0 ? {} : { params }
799
1116
  });
800
- const changeType = (fieldId, type) => executeAction(headless.changeFieldType(fieldId, type), {
1117
+ const changeType = (fieldId, type) => executeAction(() => headless.changeFieldType(fieldId, type), {
801
1118
  action: "changeFieldType",
802
1119
  targetId: fieldId,
803
1120
  params: { type }
804
1121
  });
805
- const removeField = (fieldId) => executeAction(headless.removeField(fieldId), { action: "removeField", targetId: fieldId });
1122
+ const removeField = (fieldId) => executeAction(() => headless.removeField(fieldId), { action: "removeField", targetId: fieldId });
806
1123
  const initialFieldType = resolveInitialFieldType(defaultFieldType, policy?.allowedFieldTypes);
807
1124
  const maxFieldsReached = policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields;
808
1125
  const moveField = (index, offset) => {
809
1126
  const target = index + offset;
810
1127
  const field = schema.fields[index];
811
1128
  if (field !== void 0) {
812
- executeAction(headless.moveField(field.id, target), {
1129
+ executeAction(() => headless.moveField(field.id, target), {
813
1130
  action: "moveField",
814
1131
  targetId: field.id,
815
1132
  params: { targetIndex: target }
@@ -818,10 +1135,10 @@ function FormBuilder({
818
1135
  };
819
1136
  const addField = () => {
820
1137
  if (initialFieldType === null) return;
821
- executeAction(headless.addField(initialFieldType), { action: "addField" });
1138
+ executeAction(() => headless.addField(initialFieldType), { action: "addField" });
822
1139
  };
823
1140
  const enablePages = () => {
824
- if (schema.pages === void 0) executeAction(headless.addPage(), { action: "addPage" });
1141
+ if (schema.pages === void 0) executeAction(() => headless.addPage(), { action: "addPage" });
825
1142
  };
826
1143
  const pageForField = (fieldId) => schema.pages?.find((page) => page.questionIds.includes(fieldId));
827
1144
  const movablePageQuestions = schema.pages?.flatMap((page) => page.questionIds.length > 1 ? page.questionIds : []) ?? [];
@@ -832,7 +1149,7 @@ function FormBuilder({
832
1149
  }
833
1150
  const questionId = movablePageQuestions.includes(newPageQuestionId) ? newPageQuestionId : movablePageQuestions[0];
834
1151
  if (questionId === void 0) return;
835
- executeAction(headless.addPage(questionId), {
1152
+ executeAction(() => headless.addPage(questionId), {
836
1153
  action: "addPage",
837
1154
  targetId: questionId,
838
1155
  params: { questionId }
@@ -841,13 +1158,14 @@ function FormBuilder({
841
1158
  };
842
1159
  const removePage = (pageIndex) => {
843
1160
  const page = schema.pages?.[pageIndex];
844
- if (page !== void 0) executeAction(headless.removePage(page.id), { action: "removePage", targetId: page.id });
1161
+ if (page !== void 0)
1162
+ executeAction(() => headless.removePage(page.id), { action: "removePage", targetId: page.id });
845
1163
  };
846
1164
  const movePage = (pageIndex, offset) => {
847
1165
  const target = pageIndex + offset;
848
1166
  const page = schema.pages?.[pageIndex];
849
1167
  if (page !== void 0) {
850
- executeAction(headless.movePage(page.id, target), {
1168
+ executeAction(() => headless.movePage(page.id, target), {
851
1169
  action: "movePage",
852
1170
  targetId: page.id,
853
1171
  params: { targetIndex: target }
@@ -855,30 +1173,44 @@ function FormBuilder({
855
1173
  }
856
1174
  };
857
1175
  const updatePage = (pageId, update) => {
858
- headless.updatePage(pageId, update);
1176
+ executeAction(() => headless.updatePage(pageId, update), {
1177
+ action: "updatePage",
1178
+ targetId: pageId
1179
+ });
859
1180
  };
860
1181
  const assignFieldToPage = (fieldId, pageId) => {
861
- executeAction(headless.assignFieldToPage(fieldId, pageId), {
1182
+ executeAction(() => headless.assignFieldToPage(fieldId, pageId), {
862
1183
  action: "assignFieldToPage",
863
1184
  targetId: fieldId,
864
1185
  params: { pageId }
865
1186
  });
866
1187
  };
867
1188
  const addLocale = () => {
1189
+ if (readOnly) return;
868
1190
  const normalized = newLocale.trim();
869
1191
  if (normalized.length === 0) return;
870
- headless.addLocale(normalized);
1192
+ const result = executeAction(() => headless.addLocale(normalized), {
1193
+ action: "addLocale",
1194
+ params: { locale: normalized }
1195
+ });
1196
+ if (!result.success) return;
871
1197
  setEditingLocale(normalized);
872
1198
  setNewLocale("");
873
1199
  };
1200
+ const setDefaultLocale = (locale2) => executeAction(() => headless.setDefaultLocale(locale2), {
1201
+ action: "setDefaultLocale",
1202
+ params: { locale: locale2 }
1203
+ });
874
1204
  const translateAll = async () => {
875
- if (translationAdapter === void 0 || editingLocale.length === 0) return;
1205
+ if (readOnly || translationAdapter === void 0 || editingLocale.length === 0) return;
876
1206
  setIsTranslating(true);
877
1207
  setTranslationError(null);
878
1208
  try {
1209
+ const translationPolicy = policy ?? translationOptions?.policy;
879
1210
  const populated = await populateSchemaTranslations(schema, [editingLocale], translationAdapter, {
880
1211
  overwrite: "missing-only",
881
- ...translationOptions
1212
+ ...translationOptions,
1213
+ ...translationPolicy === void 0 ? {} : { policy: translationPolicy }
882
1214
  });
883
1215
  onChange(populated.schema);
884
1216
  onTranslationReport?.(populated.report);
@@ -889,10 +1221,11 @@ function FormBuilder({
889
1221
  }
890
1222
  };
891
1223
  const updateManualTranslation = (context) => {
1224
+ if (readOnly) return;
892
1225
  const metadata = createManualTranslationMetadata?.(context);
893
1226
  const target = context.kind === "form" ? { kind: "form" } : { kind: context.kind, id: context.nodeId };
894
1227
  executeAction(
895
- headless.setLocaleTranslation(
1228
+ () => headless.setLocaleTranslation(
896
1229
  context.locale,
897
1230
  target,
898
1231
  context.property,
@@ -918,41 +1251,124 @@ function FormBuilder({
918
1251
  ...schema.translationMetadata?.[editingLocale]?.[property] === void 0 ? {} : { existingTranslationMetadata: schema.translationMetadata[editingLocale]?.[property] }
919
1252
  });
920
1253
  };
921
- const setSourceText = (target, property, text) => executeAction(headless.setSourceText(target, property, text), {
1254
+ const setSourceText = (target, property, text) => executeAction(() => headless.setSourceText(target, property, text), {
922
1255
  action: "setSourceText",
923
1256
  ...target.id === void 0 ? {} : { targetId: target.id },
924
1257
  params: { kind: target.kind, property }
925
1258
  });
926
- const updateOption = (fieldId, optionId, label) => executeAction(
927
- headless.updateOption(fieldId, optionId, (option) => ({ ...option, label })),
928
- { action: "updateOption", targetId: optionId, params: { fieldId } }
929
- );
930
- const addOption = (fieldId) => executeAction(headless.addOption(fieldId), { action: "addOption", targetId: fieldId });
931
- const removeOption = (fieldId, optionId) => executeAction(headless.removeOption(fieldId, optionId), {
1259
+ const updateOption = (fieldId, optionId, label) => executeAction(() => headless.updateOption(fieldId, optionId, (option) => ({ ...option, label })), {
1260
+ action: "updateOption",
1261
+ targetId: optionId,
1262
+ params: { fieldId }
1263
+ });
1264
+ const addOption = (fieldId) => executeAction(() => headless.addOption(fieldId), { action: "addOption", targetId: fieldId });
1265
+ const removeOption = (fieldId, optionId) => executeAction(() => headless.removeOption(fieldId, optionId), {
932
1266
  action: "removeOption",
933
1267
  targetId: optionId,
934
1268
  params: { fieldId }
935
1269
  });
936
- const moveOption = (fieldId, optionId, targetIndex) => executeAction(headless.moveOption(fieldId, optionId, targetIndex), {
1270
+ const moveOption = (fieldId, optionId, targetIndex) => executeAction(() => headless.moveOption(fieldId, optionId, targetIndex), {
937
1271
  action: "moveOption",
938
1272
  targetId: optionId,
939
1273
  params: { fieldId, targetIndex }
940
1274
  });
941
- const setDisplayCondition = (fieldId, condition) => executeAction(headless.setDisplayCondition(fieldId, condition), {
1275
+ const setDisplayCondition = (fieldId, condition) => executeAction(() => headless.setDisplayCondition(fieldId, condition), {
942
1276
  action: "setDisplayCondition",
943
1277
  targetId: fieldId,
944
1278
  ...condition === void 0 ? {} : { params: { condition } }
945
1279
  });
946
- return /* @__PURE__ */ jsxs(
947
- "section",
1280
+ const registeredLocales = /* @__PURE__ */ new Set([
1281
+ ...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
1282
+ ...schema.supportedLocales ?? []
1283
+ ]);
1284
+ const availableAllowedLocales = policy?.allowedLocales?.filter((candidate) => !registeredLocales.has(candidate));
1285
+ const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1286
+ const actions = {
1287
+ addField: (type, pageId) => executeAction(() => headless.addField(type, pageId), {
1288
+ action: "addField",
1289
+ ...pageId === void 0 ? { params: { type } } : { targetId: pageId, params: { type, pageId } }
1290
+ }),
1291
+ removeField: (fieldId) => executeAction(() => headless.removeField(fieldId), { action: "removeField", targetId: fieldId }),
1292
+ moveField: (fieldId, targetIndex) => executeAction(() => headless.moveField(fieldId, targetIndex), {
1293
+ action: "moveField",
1294
+ targetId: fieldId,
1295
+ params: { targetIndex }
1296
+ }),
1297
+ updateField: (fieldId, updater) => executeAction(() => headless.updateField(fieldId, updater), { action: "updateField", targetId: fieldId }),
1298
+ changeFieldType: (fieldId, type) => executeAction(() => headless.changeFieldType(fieldId, type), {
1299
+ action: "changeFieldType",
1300
+ targetId: fieldId,
1301
+ params: { type }
1302
+ }),
1303
+ addOption: (fieldId) => executeAction(() => headless.addOption(fieldId), { action: "addOption", targetId: fieldId }),
1304
+ updateOption: (fieldId, optionId, updater) => executeAction(() => headless.updateOption(fieldId, optionId, updater), {
1305
+ action: "updateOption",
1306
+ targetId: optionId,
1307
+ params: { fieldId }
1308
+ }),
1309
+ removeOption: (fieldId, optionId) => executeAction(() => headless.removeOption(fieldId, optionId), {
1310
+ action: "removeOption",
1311
+ targetId: optionId,
1312
+ params: { fieldId }
1313
+ }),
1314
+ moveOption: (fieldId, optionId, targetIndex) => executeAction(() => headless.moveOption(fieldId, optionId, targetIndex), {
1315
+ action: "moveOption",
1316
+ targetId: optionId,
1317
+ params: { fieldId, targetIndex }
1318
+ }),
1319
+ addPage: (questionId) => executeAction(() => headless.addPage(questionId), {
1320
+ action: "addPage",
1321
+ ...questionId === void 0 ? {} : { targetId: questionId, params: { questionId } }
1322
+ }),
1323
+ updatePage: (pageId, updater) => executeAction(() => headless.updatePage(pageId, updater), { action: "updatePage", targetId: pageId }),
1324
+ removePage: (pageId) => executeAction(() => headless.removePage(pageId), { action: "removePage", targetId: pageId }),
1325
+ movePage: (pageId, targetIndex) => executeAction(() => headless.movePage(pageId, targetIndex), {
1326
+ action: "movePage",
1327
+ targetId: pageId,
1328
+ params: { targetIndex }
1329
+ }),
1330
+ assignFieldToPage: (fieldId, pageId) => executeAction(() => headless.assignFieldToPage(fieldId, pageId), {
1331
+ action: "assignFieldToPage",
1332
+ targetId: fieldId,
1333
+ params: { pageId }
1334
+ }),
1335
+ setDisplayCondition: (fieldId, condition) => executeAction(() => headless.setDisplayCondition(fieldId, condition), {
1336
+ action: "setDisplayCondition",
1337
+ targetId: fieldId,
1338
+ ...condition === void 0 ? {} : { params: { condition } }
1339
+ }),
1340
+ setSourceText: (target, property, text) => executeAction(() => headless.setSourceText(target, property, text), {
1341
+ action: "setSourceText",
1342
+ ...target.id === void 0 ? {} : { targetId: target.id },
1343
+ params: { property, text }
1344
+ }),
1345
+ setLocaleTranslation: (targetLocale, target, property, text, options) => executeAction(() => headless.setLocaleTranslation(targetLocale, target, property, text, options), {
1346
+ action: "setLocaleTranslation",
1347
+ ...target.id === void 0 ? {} : { targetId: target.id },
1348
+ params: { locale: targetLocale, property }
1349
+ }),
1350
+ addLocale: (targetLocale) => executeAction(() => headless.addLocale(targetLocale), {
1351
+ action: "addLocale",
1352
+ params: { locale: targetLocale }
1353
+ }),
1354
+ setDefaultLocale: (targetLocale) => executeAction(() => headless.setDefaultLocale(targetLocale), {
1355
+ action: "setDefaultLocale",
1356
+ params: { locale: targetLocale }
1357
+ })
1358
+ };
1359
+ return /* @__PURE__ */ jsx(BuilderPrimitiveContext.Provider, { value: { components: resolvedComponents, readOnly }, children: /* @__PURE__ */ jsx(
1360
+ Section,
948
1361
  {
949
1362
  className: `form-engine-builder ${className}`.trim(),
950
1363
  "aria-label": translate("builder.formBuilder"),
951
1364
  onClickCapture: (event) => {
1365
+ if (readOnly) return;
952
1366
  if (!(event.target instanceof HTMLElement)) return;
953
1367
  const actionTarget = event.target.closest("[data-builder-action]");
954
1368
  if (actionTarget?.dataset.builderAction === "addField" && maxFieldsReached && initialFieldType !== null)
955
1369
  addField();
1370
+ if (actionTarget?.dataset.builderAction === "addLocale" && localeLimitReached && newLocale.trim().length > 0)
1371
+ addLocale();
956
1372
  if (actionTarget?.dataset.builderAction !== "addOption") return;
957
1373
  const fieldId = actionTarget.dataset.targetId;
958
1374
  const field = schema.fields.find((candidate) => candidate.id === fieldId);
@@ -960,329 +1376,451 @@ function FormBuilder({
960
1376
  addOption(fieldId);
961
1377
  }
962
1378
  },
963
- children: [
964
- /* @__PURE__ */ jsxs("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
965
- /* @__PURE__ */ jsx("h2", { id: "builder-pages-heading", children: translate("builder.pages") }),
966
- schema.pages === void 0 ? /* @__PURE__ */ jsx("button", { type: "button", onClick: enablePages, children: translate("builder.enablePages") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
967
- schema.pages.map((page, pageIndex) => {
968
- const priorQuestionIds = new Set(schema.pages?.slice(0, pageIndex).flatMap((item) => item.questionIds));
969
- const availableSources = schema.fields.filter((field) => priorQuestionIds.has(field.id));
970
- const source = schema.fields.find((field) => field.id === page.displayCondition?.questionId);
971
- return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__page", children: [
972
- /* @__PURE__ */ jsx("legend", { children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }),
973
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__toolbar", children: [
1379
+ children: /* @__PURE__ */ jsxs(Fieldset, { className: "form-engine-builder__controls", disabled: readOnly, children: [
1380
+ pagesEnabled ? PagesSlot === void 0 ? /* @__PURE__ */ jsx(
1381
+ Section,
1382
+ {
1383
+ className: "form-engine-builder__pages",
1384
+ headingId: "builder-pages-heading",
1385
+ title: translate("builder.pages"),
1386
+ children: schema.pages === void 0 ? /* @__PURE__ */ jsx(Button, { onClick: enablePages, children: translate("builder.enablePages") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1387
+ schema.pages.map((page, pageIndex) => {
1388
+ const priorQuestionIds = new Set(
1389
+ schema.pages?.slice(0, pageIndex).flatMap((item) => item.questionIds)
1390
+ );
1391
+ const availableSources = schema.fields.filter((field) => priorQuestionIds.has(field.id));
1392
+ const source = schema.fields.find((field) => field.id === page.displayCondition?.questionId);
1393
+ return /* @__PURE__ */ jsxs(
1394
+ Fieldset,
1395
+ {
1396
+ className: "form-engine-builder__page",
1397
+ legend: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}`,
1398
+ children: [
1399
+ /* @__PURE__ */ jsx("div", { className: "form-engine-builder__toolbar", children: ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1400
+ /* @__PURE__ */ jsx(
1401
+ IconButton,
1402
+ {
1403
+ icon: "\u2191",
1404
+ title: translate("builder.moveUp", { title: page.title ?? page.id }),
1405
+ disabled: pageIndex === 0,
1406
+ onClick: () => movePage(pageIndex, -1)
1407
+ }
1408
+ ),
1409
+ /* @__PURE__ */ jsx(
1410
+ IconButton,
1411
+ {
1412
+ icon: "\u2193",
1413
+ title: translate("builder.moveDown", { title: page.title ?? page.id }),
1414
+ disabled: pageIndex === (schema.pages?.length ?? 0) - 1,
1415
+ onClick: () => movePage(pageIndex, 1)
1416
+ }
1417
+ ),
1418
+ /* @__PURE__ */ jsx(Button, { onClick: () => removePage(pageIndex), variant: "danger", children: translate("builder.deleteAction") })
1419
+ ] }) : /* @__PURE__ */ jsx(
1420
+ ToolbarSlot,
1421
+ {
1422
+ schema,
1423
+ kind: "page",
1424
+ targetId: page.id,
1425
+ index: pageIndex,
1426
+ total: schema.pages?.length ?? 0,
1427
+ title: page.title ?? page.id,
1428
+ onMoveUp: () => movePage(pageIndex, -1),
1429
+ onMoveDown: () => movePage(pageIndex, 1),
1430
+ onRemove: () => removePage(pageIndex),
1431
+ readOnly,
1432
+ actions,
1433
+ components
1434
+ }
1435
+ ) }),
1436
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1437
+ /* @__PURE__ */ jsxs("label", { children: [
1438
+ translate("builder.pageTitle"),
1439
+ /* @__PURE__ */ jsx(
1440
+ TextInput,
1441
+ {
1442
+ value: page.title ?? "",
1443
+ onChange: (value) => {
1444
+ updatePage(page.id, (current) => {
1445
+ if (value.length > 0) return { ...current, title: value };
1446
+ const { title: _title, ...withoutTitle } = current;
1447
+ return withoutTitle;
1448
+ });
1449
+ }
1450
+ }
1451
+ )
1452
+ ] }),
1453
+ /* @__PURE__ */ jsxs("label", { children: [
1454
+ translate("builder.pageDescription"),
1455
+ /* @__PURE__ */ jsx(
1456
+ TextInput,
1457
+ {
1458
+ value: page.description ?? "",
1459
+ onChange: (value) => {
1460
+ updatePage(page.id, (current) => {
1461
+ if (value.length > 0) return { ...current, description: value };
1462
+ const { description: _description, ...withoutDescription } = current;
1463
+ return withoutDescription;
1464
+ });
1465
+ }
1466
+ }
1467
+ )
1468
+ ] })
1469
+ ] }),
1470
+ !localizationEnabled || editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1471
+ /* @__PURE__ */ jsx("strong", { children: editingLocale }),
1472
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1473
+ /* @__PURE__ */ jsxs("label", { children: [
1474
+ translate("builder.pageTitle"),
1475
+ /* @__PURE__ */ jsx(
1476
+ TextInput,
1477
+ {
1478
+ value: page.translations?.[editingLocale]?.title ?? "",
1479
+ onChange: (value) => updateManualTranslation({
1480
+ locale: editingLocale,
1481
+ kind: "page",
1482
+ nodeId: page.id,
1483
+ property: "title",
1484
+ sourceText: page.title ?? "",
1485
+ translatedText: value,
1486
+ ...page.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
1487
+ existingTranslationMetadata: page.translationMetadata[editingLocale]?.title
1488
+ }
1489
+ })
1490
+ }
1491
+ )
1492
+ ] }),
1493
+ /* @__PURE__ */ jsxs("label", { children: [
1494
+ translate("builder.pageDescription"),
1495
+ /* @__PURE__ */ jsx(
1496
+ TextInput,
1497
+ {
1498
+ value: page.translations?.[editingLocale]?.description ?? "",
1499
+ onChange: (value) => updateManualTranslation({
1500
+ locale: editingLocale,
1501
+ kind: "page",
1502
+ nodeId: page.id,
1503
+ property: "description",
1504
+ sourceText: page.description ?? "",
1505
+ translatedText: value,
1506
+ ...page.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
1507
+ existingTranslationMetadata: page.translationMetadata[editingLocale]?.description
1508
+ }
1509
+ })
1510
+ }
1511
+ )
1512
+ ] })
1513
+ ] })
1514
+ ] }),
1515
+ conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
1516
+ /* @__PURE__ */ jsxs("label", { children: [
1517
+ translate("builder.pageCondition"),
1518
+ /* @__PURE__ */ jsx(
1519
+ Select,
1520
+ {
1521
+ value: page.displayCondition?.questionId ?? "",
1522
+ onChange: (value) => {
1523
+ const selected = schema.fields.find((field) => field.id === value);
1524
+ updatePage(page.id, (current) => {
1525
+ if (selected === void 0) {
1526
+ const { displayCondition: _condition, ...withoutCondition } = current;
1527
+ return withoutCondition;
1528
+ }
1529
+ return {
1530
+ ...current,
1531
+ displayCondition: conditionWithValue(
1532
+ selected.id,
1533
+ conditionOperators(selected)[0] ?? "not_empty",
1534
+ defaultConditionValue(selected)
1535
+ )
1536
+ };
1537
+ });
1538
+ },
1539
+ options: [
1540
+ { value: "", label: translate("builder.alwaysVisible") },
1541
+ ...availableSources.map((field) => ({ value: field.id, label: field.title }))
1542
+ ]
1543
+ }
1544
+ )
1545
+ ] }),
1546
+ page.displayCondition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1547
+ /* @__PURE__ */ jsx(
1548
+ Select,
1549
+ {
1550
+ "aria-label": translate("builder.conditionOperator"),
1551
+ value: page.displayCondition.operator,
1552
+ onChange: (value) => {
1553
+ const operator = value;
1554
+ updatePage(page.id, (current) => ({
1555
+ ...current,
1556
+ displayCondition: conditionWithValue(
1557
+ source.id,
1558
+ operator,
1559
+ defaultConditionValue(source)
1560
+ )
1561
+ }));
1562
+ },
1563
+ options: conditionOperators(source).map((operator) => ({
1564
+ value: operator,
1565
+ label: translate(operatorKey(operator))
1566
+ }))
1567
+ }
1568
+ ),
1569
+ /* @__PURE__ */ jsx(
1570
+ ConditionValueEditor,
1571
+ {
1572
+ source,
1573
+ condition: page.displayCondition,
1574
+ onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
1575
+ translate,
1576
+ components
1577
+ }
1578
+ )
1579
+ ] }) : null
1580
+ ] }) : null
1581
+ ]
1582
+ },
1583
+ page.id
1584
+ );
1585
+ }),
1586
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__page-add", children: [
1587
+ /* @__PURE__ */ jsxs("label", { children: [
1588
+ translate("builder.pageQuestion"),
974
1589
  /* @__PURE__ */ jsx(
975
- "button",
1590
+ Select,
976
1591
  {
977
- type: "button",
978
- disabled: pageIndex === 0,
979
- "aria-label": translate("builder.moveUp", { title: page.title ?? page.id }),
980
- onClick: () => movePage(pageIndex, -1),
981
- children: "\u2191"
1592
+ value: newPageQuestionId,
1593
+ disabled: movablePageQuestions.length === 0,
1594
+ onChange: setNewPageQuestionId,
1595
+ options: [
1596
+ { value: "", label: "\u2014" },
1597
+ ...schema.fields.filter((field) => movablePageQuestions.includes(field.id)).map((field) => ({ value: field.id, label: field.title }))
1598
+ ]
982
1599
  }
983
- ),
984
- /* @__PURE__ */ jsx(
985
- "button",
1600
+ )
1601
+ ] }),
1602
+ /* @__PURE__ */ jsx(Button, { disabled: movablePageQuestions.length === 0, onClick: addPage, children: translate("builder.addPage") })
1603
+ ] })
1604
+ ] })
1605
+ }
1606
+ ) : /* @__PURE__ */ jsx(
1607
+ PagesSlot,
1608
+ {
1609
+ schema,
1610
+ currentLocale: editingLocale,
1611
+ readOnly,
1612
+ actions,
1613
+ components
1614
+ }
1615
+ ) : null,
1616
+ localizationEnabled ? LocalizationSlot === void 0 ? /* @__PURE__ */ jsxs(
1617
+ Section,
1618
+ {
1619
+ className: "form-engine-builder__localization",
1620
+ headingId: "builder-localization-heading",
1621
+ title: translate("builder.localization"),
1622
+ children: [
1623
+ /* @__PURE__ */ jsxs("label", { children: [
1624
+ translate("builder.completionMessage"),
1625
+ /* @__PURE__ */ jsx(
1626
+ TextInput,
1627
+ {
1628
+ value: schema.completionMessage ?? "",
1629
+ onChange: (value) => setSourceText({ kind: "form" }, "completionMessage", value)
1630
+ }
1631
+ )
1632
+ ] }),
1633
+ /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1634
+ /* @__PURE__ */ jsxs("label", { children: [
1635
+ translate("builder.defaultLocale"),
1636
+ /* @__PURE__ */ jsx(TextInput, { value: schema.defaultLocale ?? "", onChange: setDefaultLocale })
1637
+ ] }),
1638
+ /* @__PURE__ */ jsxs("label", { htmlFor: "builder-new-locale", children: [
1639
+ translate("builder.addLocale"),
1640
+ availableAllowedLocales === void 0 ? /* @__PURE__ */ jsx(TextInput, { id: "builder-new-locale", value: newLocale, onChange: setNewLocale }) : /* @__PURE__ */ jsx(
1641
+ Select,
986
1642
  {
987
- type: "button",
988
- disabled: pageIndex === (schema.pages?.length ?? 0) - 1,
989
- "aria-label": translate("builder.moveDown", { title: page.title ?? page.id }),
990
- onClick: () => movePage(pageIndex, 1),
991
- children: "\u2193"
1643
+ id: "builder-new-locale",
1644
+ value: newLocale,
1645
+ onChange: setNewLocale,
1646
+ options: [
1647
+ { value: "", label: "\u2014" },
1648
+ ...availableAllowedLocales.map((candidate) => ({ value: candidate, label: candidate }))
1649
+ ]
992
1650
  }
993
- ),
994
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => removePage(pageIndex), children: translate("builder.deleteAction") })
995
- ] }),
996
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
997
- /* @__PURE__ */ jsxs("label", { children: [
998
- translate("builder.pageTitle"),
999
- /* @__PURE__ */ jsx(
1000
- "input",
1001
- {
1002
- value: page.title ?? "",
1003
- onChange: (event) => {
1004
- const value = event.currentTarget.value;
1005
- updatePage(page.id, (current) => {
1006
- if (value.length > 0) return { ...current, title: value };
1007
- const { title: _title, ...withoutTitle } = current;
1008
- return withoutTitle;
1009
- });
1010
- }
1011
- }
1012
- )
1013
- ] }),
1014
- /* @__PURE__ */ jsxs("label", { children: [
1015
- translate("builder.pageDescription"),
1016
- /* @__PURE__ */ jsx(
1017
- "input",
1018
- {
1019
- value: page.description ?? "",
1020
- onChange: (event) => {
1021
- const value = event.currentTarget.value;
1022
- updatePage(page.id, (current) => {
1023
- if (value.length > 0) return { ...current, description: value };
1024
- const { description: _description, ...withoutDescription } = current;
1025
- return withoutDescription;
1026
- });
1027
- }
1028
- }
1029
- )
1030
- ] })
1651
+ )
1031
1652
  ] }),
1032
- editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1033
- /* @__PURE__ */ jsx("strong", { children: editingLocale }),
1034
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1035
- /* @__PURE__ */ jsxs("label", { children: [
1036
- translate("builder.pageTitle"),
1037
- /* @__PURE__ */ jsx(
1038
- "input",
1039
- {
1040
- value: page.translations?.[editingLocale]?.title ?? "",
1041
- onChange: (event) => updateManualTranslation({
1042
- locale: editingLocale,
1043
- kind: "page",
1044
- nodeId: page.id,
1045
- property: "title",
1046
- sourceText: page.title ?? "",
1047
- translatedText: event.currentTarget.value,
1048
- ...page.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
1049
- existingTranslationMetadata: page.translationMetadata[editingLocale]?.title
1050
- }
1051
- })
1052
- }
1053
- )
1054
- ] }),
1055
- /* @__PURE__ */ jsxs("label", { children: [
1056
- translate("builder.pageDescription"),
1057
- /* @__PURE__ */ jsx(
1058
- "input",
1059
- {
1060
- value: page.translations?.[editingLocale]?.description ?? "",
1061
- onChange: (event) => updateManualTranslation({
1062
- locale: editingLocale,
1063
- kind: "page",
1064
- nodeId: page.id,
1065
- property: "description",
1066
- sourceText: page.description ?? "",
1067
- translatedText: event.currentTarget.value,
1068
- ...page.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
1069
- existingTranslationMetadata: page.translationMetadata[editingLocale]?.description
1070
- }
1071
- })
1072
- }
1073
- )
1074
- ] })
1075
- ] })
1653
+ /* @__PURE__ */ jsx(
1654
+ Button,
1655
+ {
1656
+ action: "addLocale",
1657
+ disabled: newLocale.trim().length === 0 || localeLimitReached,
1658
+ onClick: addLocale,
1659
+ children: translate("builder.addLocale")
1660
+ }
1661
+ ),
1662
+ /* @__PURE__ */ jsxs("label", { children: [
1663
+ translate("builder.editLocale"),
1664
+ /* @__PURE__ */ jsx(
1665
+ Select,
1666
+ {
1667
+ value: editingLocale,
1668
+ onChange: setEditingLocale,
1669
+ options: [
1670
+ { value: "", label: "\u2014" },
1671
+ ...(schema.supportedLocales ?? []).filter((item) => item !== schema.defaultLocale).map((item) => ({ value: item, label: item }))
1672
+ ]
1673
+ }
1674
+ )
1076
1675
  ] }),
1077
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
1078
- /* @__PURE__ */ jsxs("label", { children: [
1079
- translate("builder.pageCondition"),
1080
- /* @__PURE__ */ jsxs(
1081
- "select",
1082
- {
1083
- value: page.displayCondition?.questionId ?? "",
1084
- onChange: (event) => {
1085
- const selected = schema.fields.find((field) => field.id === event.currentTarget.value);
1086
- updatePage(page.id, (current) => {
1087
- if (selected === void 0) {
1088
- const { displayCondition: _condition, ...withoutCondition } = current;
1089
- return withoutCondition;
1090
- }
1091
- return {
1092
- ...current,
1093
- displayCondition: conditionWithValue(
1094
- selected.id,
1095
- conditionOperators(selected)[0] ?? "not_empty",
1096
- defaultConditionValue(selected)
1097
- )
1098
- };
1099
- });
1100
- },
1101
- children: [
1102
- /* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
1103
- availableSources.map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
1104
- ]
1105
- }
1106
- )
1107
- ] }),
1108
- page.displayCondition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1109
- /* @__PURE__ */ jsx(
1110
- "select",
1111
- {
1112
- "aria-label": translate("builder.conditionOperator"),
1113
- value: page.displayCondition.operator,
1114
- onChange: (event) => {
1115
- const operator = event.currentTarget.value;
1116
- updatePage(page.id, (current) => ({
1117
- ...current,
1118
- displayCondition: conditionWithValue(source.id, operator, defaultConditionValue(source))
1119
- }));
1120
- },
1121
- children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
1122
- }
1123
- ),
1124
- /* @__PURE__ */ jsx(
1125
- ConditionValueEditor,
1126
- {
1127
- source,
1128
- condition: page.displayCondition,
1129
- onChange: (condition) => updatePage(page.id, (current) => ({ ...current, displayCondition: condition })),
1130
- translate
1131
- }
1132
- )
1133
- ] }) : null
1134
- ] })
1135
- ] }, page.id);
1136
- }),
1137
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__page-add", children: [
1138
- /* @__PURE__ */ jsxs("label", { children: [
1139
- translate("builder.pageQuestion"),
1140
- /* @__PURE__ */ jsxs(
1141
- "select",
1676
+ TranslationActionsSlot === void 0 ? /* @__PURE__ */ jsx(
1677
+ Button,
1142
1678
  {
1143
- value: newPageQuestionId,
1144
- disabled: movablePageQuestions.length === 0,
1145
- onChange: (event) => setNewPageQuestionId(event.currentTarget.value),
1146
- children: [
1147
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1148
- schema.fields.filter((field) => movablePageQuestions.includes(field.id)).map((field) => /* @__PURE__ */ jsx("option", { value: field.id, children: field.title }, field.id))
1149
- ]
1679
+ disabled: translationAdapter === void 0 || editingLocale.length === 0 || isTranslating,
1680
+ onClick: () => void translateAll(),
1681
+ children: translate("builder.autoTranslate")
1682
+ }
1683
+ ) : /* @__PURE__ */ jsx(
1684
+ TranslationActionsSlot,
1685
+ {
1686
+ schema,
1687
+ currentLocale: editingLocale,
1688
+ onAutoTranslate: () => void translateAll(),
1689
+ isTranslating,
1690
+ readOnly,
1691
+ actions,
1692
+ components
1150
1693
  }
1151
1694
  )
1152
1695
  ] }),
1153
- /* @__PURE__ */ jsx("button", { type: "button", disabled: movablePageQuestions.length === 0, onClick: addPage, children: translate("builder.addPage") })
1154
- ] })
1155
- ] })
1156
- ] }),
1157
- /* @__PURE__ */ jsxs("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
1158
- /* @__PURE__ */ jsx("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
1159
- /* @__PURE__ */ jsxs("label", { children: [
1160
- translate("builder.completionMessage"),
1161
- /* @__PURE__ */ jsx(
1162
- "input",
1163
- {
1164
- value: schema.completionMessage ?? "",
1165
- onChange: (event) => setSourceText({ kind: "form" }, "completionMessage", event.currentTarget.value)
1166
- }
1167
- )
1168
- ] }),
1169
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1170
- /* @__PURE__ */ jsxs("label", { children: [
1171
- translate("builder.defaultLocale"),
1172
- /* @__PURE__ */ jsx(
1173
- "input",
1174
- {
1175
- value: schema.defaultLocale ?? "",
1176
- onChange: (event) => headless.setDefaultLocale(event.currentTarget.value)
1177
- }
1178
- )
1179
- ] }),
1180
- /* @__PURE__ */ jsxs("label", { children: [
1181
- translate("builder.addLocale"),
1182
- /* @__PURE__ */ jsx("input", { value: newLocale, onChange: (event) => setNewLocale(event.currentTarget.value) })
1183
- ] }),
1184
- /* @__PURE__ */ jsx("button", { type: "button", onClick: addLocale, children: translate("builder.addLocale") }),
1185
- /* @__PURE__ */ jsxs("label", { children: [
1186
- translate("builder.editLocale"),
1187
- /* @__PURE__ */ jsxs("select", { value: editingLocale, onChange: (event) => setEditingLocale(event.currentTarget.value), children: [
1188
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1189
- (schema.supportedLocales ?? []).filter((item) => item !== schema.defaultLocale).map((item) => /* @__PURE__ */ jsx("option", { value: item, children: item }, item))
1696
+ translationAdapter === void 0 ? /* @__PURE__ */ jsx("p", { children: translate("builder.translationUnavailable") }) : null,
1697
+ translationError === null ? null : /* @__PURE__ */ jsx(ErrorMessage, { message: translationError }),
1698
+ editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1699
+ /* @__PURE__ */ jsxs("label", { children: [
1700
+ translate("builder.questionTitle"),
1701
+ /* @__PURE__ */ jsx(
1702
+ TextInput,
1703
+ {
1704
+ value: schema.translations?.[editingLocale]?.title ?? "",
1705
+ onChange: (value) => updateFormTranslation("title", value)
1706
+ }
1707
+ )
1708
+ ] }),
1709
+ /* @__PURE__ */ jsxs("label", { children: [
1710
+ translate("builder.pageDescription"),
1711
+ /* @__PURE__ */ jsx(
1712
+ TextInput,
1713
+ {
1714
+ value: schema.translations?.[editingLocale]?.description ?? "",
1715
+ onChange: (value) => updateFormTranslation("description", value)
1716
+ }
1717
+ )
1718
+ ] }),
1719
+ /* @__PURE__ */ jsxs("label", { children: [
1720
+ translate("builder.completionMessage"),
1721
+ /* @__PURE__ */ jsx(
1722
+ TextInput,
1723
+ {
1724
+ value: schema.translations?.[editingLocale]?.completionMessage ?? "",
1725
+ onChange: (value) => updateFormTranslation("completionMessage", value)
1726
+ }
1727
+ )
1728
+ ] })
1190
1729
  ] })
1191
- ] }),
1192
- /* @__PURE__ */ jsx(
1193
- "button",
1194
- {
1195
- type: "button",
1196
- disabled: translationAdapter === void 0 || editingLocale.length === 0 || isTranslating,
1197
- onClick: () => void translateAll(),
1198
- children: translate("builder.autoTranslate")
1199
- }
1200
- )
1201
- ] }),
1202
- translationAdapter === void 0 ? /* @__PURE__ */ jsx("p", { children: translate("builder.translationUnavailable") }) : null,
1203
- translationError === null ? null : /* @__PURE__ */ jsx("p", { className: "form-engine-builder__error", children: translationError }),
1204
- editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1205
- /* @__PURE__ */ jsxs("label", { children: [
1206
- translate("builder.questionTitle"),
1207
- /* @__PURE__ */ jsx(
1208
- "input",
1209
- {
1210
- value: schema.translations?.[editingLocale]?.title ?? "",
1211
- onChange: (event) => updateFormTranslation("title", event.currentTarget.value)
1212
- }
1213
- )
1214
- ] }),
1215
- /* @__PURE__ */ jsxs("label", { children: [
1216
- translate("builder.pageDescription"),
1217
- /* @__PURE__ */ jsx(
1218
- "input",
1219
- {
1220
- value: schema.translations?.[editingLocale]?.description ?? "",
1221
- onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
1222
- }
1223
- )
1224
- ] }),
1225
- /* @__PURE__ */ jsxs("label", { children: [
1226
- translate("builder.completionMessage"),
1227
- /* @__PURE__ */ jsx(
1228
- "input",
1229
- {
1230
- value: schema.translations?.[editingLocale]?.completionMessage ?? "",
1231
- onChange: (event) => updateFormTranslation("completionMessage", event.currentTarget.value)
1232
- }
1233
- )
1234
- ] })
1235
- ] })
1236
- ] }),
1730
+ ]
1731
+ }
1732
+ ) : /* @__PURE__ */ jsx(
1733
+ LocalizationSlot,
1734
+ {
1735
+ schema,
1736
+ currentLocale: editingLocale,
1737
+ onCurrentLocaleChange: setEditingLocale,
1738
+ onAutoTranslate: () => void translateAll(),
1739
+ isTranslating,
1740
+ ...translationError === null ? {} : { translationError },
1741
+ readOnly,
1742
+ actions,
1743
+ components
1744
+ }
1745
+ ) : null,
1237
1746
  /* @__PURE__ */ jsx("div", { className: "form-engine-builder__list", children: schema.fields.map((field, index) => {
1238
1747
  const condition = field.displayCondition;
1239
1748
  const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
1240
1749
  const availableSources = schema.fields.slice(0, index);
1241
- return /* @__PURE__ */ jsxs("fieldset", { className: "form-engine-builder__question", children: [
1242
- /* @__PURE__ */ jsx("legend", { children: field.title }),
1243
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__toolbar", children: [
1750
+ if (FieldEditorSlot !== void 0) {
1751
+ return /* @__PURE__ */ jsx(
1752
+ FieldEditorSlot,
1753
+ {
1754
+ schema,
1755
+ field,
1756
+ index,
1757
+ currentLocale: editingLocale,
1758
+ ...policy === void 0 ? {} : { policy },
1759
+ readOnly,
1760
+ actions,
1761
+ components
1762
+ },
1763
+ field.id
1764
+ );
1765
+ }
1766
+ return /* @__PURE__ */ jsxs(Fieldset, { className: "form-engine-builder__question", legend: field.title, children: [
1767
+ /* @__PURE__ */ jsx("div", { className: "form-engine-builder__toolbar", children: ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1244
1768
  /* @__PURE__ */ jsx(
1245
- "button",
1769
+ IconButton,
1246
1770
  {
1247
- type: "button",
1771
+ icon: "\u2191",
1772
+ title: translate("builder.moveUp", { title: field.title }),
1248
1773
  disabled: index === 0,
1249
- onClick: () => moveField(index, -1),
1250
- "aria-label": translate("builder.moveUp", { title: field.title }),
1251
- children: "\u2191"
1774
+ onClick: () => moveField(index, -1)
1252
1775
  }
1253
1776
  ),
1254
1777
  /* @__PURE__ */ jsx(
1255
- "button",
1778
+ IconButton,
1256
1779
  {
1257
- type: "button",
1780
+ icon: "\u2193",
1781
+ title: translate("builder.moveDown", { title: field.title }),
1258
1782
  disabled: index === schema.fields.length - 1,
1259
- onClick: () => moveField(index, 1),
1260
- "aria-label": translate("builder.moveDown", { title: field.title }),
1261
- children: "\u2193"
1783
+ onClick: () => moveField(index, 1)
1262
1784
  }
1263
1785
  ),
1264
1786
  /* @__PURE__ */ jsx(
1265
- "button",
1787
+ Button,
1266
1788
  {
1267
- type: "button",
1268
1789
  disabled: schema.fields.length === 1,
1269
1790
  onClick: () => removeField(field.id),
1270
1791
  "aria-label": translate("builder.delete", { title: field.title }),
1792
+ variant: "danger",
1271
1793
  children: translate("builder.deleteAction")
1272
1794
  }
1273
1795
  )
1274
- ] }),
1796
+ ] }) : /* @__PURE__ */ jsx(
1797
+ ToolbarSlot,
1798
+ {
1799
+ schema,
1800
+ kind: "field",
1801
+ targetId: field.id,
1802
+ index,
1803
+ total: schema.fields.length,
1804
+ title: field.title,
1805
+ onMoveUp: () => moveField(index, -1),
1806
+ onMoveDown: () => moveField(index, 1),
1807
+ onRemove: () => removeField(field.id),
1808
+ readOnly,
1809
+ actions,
1810
+ components
1811
+ }
1812
+ ) }),
1275
1813
  /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1276
1814
  /* @__PURE__ */ jsxs("label", { children: [
1277
1815
  translate("builder.questionTitle"),
1278
1816
  /* @__PURE__ */ jsx(
1279
- "input",
1817
+ TextInput,
1280
1818
  {
1281
1819
  value: field.title,
1282
1820
  placeholder: translate("builder.questionTitlePlaceholder"),
1283
- onChange: (event) => updateField(field.id, (current) => ({
1821
+ onChange: (value) => updateField(field.id, (current) => ({
1284
1822
  ...current,
1285
- title: event.currentTarget.value.trim().length === 0 ? current.title : event.currentTarget.value
1823
+ title: value.trim().length === 0 ? current.title : value
1286
1824
  }))
1287
1825
  }
1288
1826
  )
@@ -1290,55 +1828,56 @@ function FormBuilder({
1290
1828
  /* @__PURE__ */ jsxs("label", { children: [
1291
1829
  translate("builder.type"),
1292
1830
  /* @__PURE__ */ jsx(
1293
- "select",
1831
+ Select,
1294
1832
  {
1295
1833
  value: field.type,
1296
- onChange: (event) => changeType(field.id, event.currentTarget.value),
1297
- children: FIELD_TYPES.filter(
1834
+ onChange: (value) => changeType(field.id, value),
1835
+ options: FIELD_TYPES.filter(
1298
1836
  (type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
1299
- ).map((type) => /* @__PURE__ */ jsx("option", { value: type, children: translate(fieldTypeKey(type)) }, type))
1837
+ ).map((type) => ({ value: type, label: translate(fieldTypeKey(type)) }))
1300
1838
  }
1301
1839
  )
1302
1840
  ] }),
1303
- /* @__PURE__ */ jsxs("label", { className: "form-engine-builder__check", children: [
1304
- /* @__PURE__ */ jsx(
1305
- "input",
1306
- {
1307
- type: "checkbox",
1308
- checked: field.required === true,
1309
- onChange: (event) => updateField(field.id, (current) => ({ ...current, required: event.currentTarget.checked }))
1310
- }
1311
- ),
1312
- translate("builder.required")
1313
- ] })
1841
+ /* @__PURE__ */ jsx(
1842
+ Checkbox,
1843
+ {
1844
+ className: "form-engine-builder__check",
1845
+ checked: field.required === true,
1846
+ onChange: (checked) => updateField(field.id, (current) => ({ ...current, required: checked })),
1847
+ label: translate("builder.required")
1848
+ }
1849
+ )
1314
1850
  ] }),
1315
- schema.pages === void 0 ? null : /* @__PURE__ */ jsxs("label", { children: [
1851
+ !pagesEnabled || schema.pages === void 0 ? null : /* @__PURE__ */ jsxs("label", { children: [
1316
1852
  translate("builder.questionPage"),
1317
1853
  /* @__PURE__ */ jsx(
1318
- "select",
1854
+ Select,
1319
1855
  {
1320
1856
  value: pageForField(field.id)?.id ?? "",
1321
- onChange: (event) => assignFieldToPage(field.id, event.currentTarget.value),
1322
- children: schema.pages.map((page, pageIndex) => /* @__PURE__ */ jsx("option", { value: page.id, children: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}` }, page.id))
1857
+ onChange: (value) => assignFieldToPage(field.id, value),
1858
+ options: schema.pages.map((page, pageIndex) => ({
1859
+ value: page.id,
1860
+ label: page.title ?? `${translate("builder.newPage")} ${pageIndex + 1}`
1861
+ }))
1323
1862
  }
1324
1863
  )
1325
1864
  ] }),
1326
- editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1865
+ !localizationEnabled || editingLocale.length === 0 ? null : /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__translation-editor", children: [
1327
1866
  /* @__PURE__ */ jsx("strong", { children: editingLocale }),
1328
1867
  /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
1329
1868
  /* @__PURE__ */ jsxs("label", { children: [
1330
1869
  translate("builder.questionTitle"),
1331
1870
  /* @__PURE__ */ jsx(
1332
- "input",
1871
+ TextInput,
1333
1872
  {
1334
1873
  value: field.translations?.[editingLocale]?.title ?? "",
1335
- onChange: (event) => updateManualTranslation({
1874
+ onChange: (value) => updateManualTranslation({
1336
1875
  locale: editingLocale,
1337
1876
  kind: "field",
1338
1877
  nodeId: field.id,
1339
1878
  property: "title",
1340
1879
  sourceText: field.title,
1341
- translatedText: event.currentTarget.value,
1880
+ translatedText: value,
1342
1881
  ...field.translationMetadata?.[editingLocale]?.title === void 0 ? {} : {
1343
1882
  existingTranslationMetadata: field.translationMetadata[editingLocale]?.title
1344
1883
  }
@@ -1349,16 +1888,16 @@ function FormBuilder({
1349
1888
  /* @__PURE__ */ jsxs("label", { children: [
1350
1889
  translate("builder.pageDescription"),
1351
1890
  /* @__PURE__ */ jsx(
1352
- "input",
1891
+ TextInput,
1353
1892
  {
1354
1893
  value: field.translations?.[editingLocale]?.description ?? "",
1355
- onChange: (event) => updateManualTranslation({
1894
+ onChange: (value) => updateManualTranslation({
1356
1895
  locale: editingLocale,
1357
1896
  kind: "field",
1358
1897
  nodeId: field.id,
1359
1898
  property: "description",
1360
1899
  sourceText: field.description ?? "",
1361
- translatedText: event.currentTarget.value,
1900
+ translatedText: value,
1362
1901
  ...field.translationMetadata?.[editingLocale]?.description === void 0 ? {} : {
1363
1902
  existingTranslationMetadata: field.translationMetadata[editingLocale]?.description
1364
1903
  }
@@ -1373,16 +1912,16 @@ function FormBuilder({
1373
1912
  editingLocale,
1374
1913
  ")",
1375
1914
  /* @__PURE__ */ jsx(
1376
- "input",
1915
+ TextInput,
1377
1916
  {
1378
1917
  value: option.translations?.[editingLocale] ?? "",
1379
- onChange: (event) => updateManualTranslation({
1918
+ onChange: (value) => updateManualTranslation({
1380
1919
  locale: editingLocale,
1381
1920
  kind: "option",
1382
1921
  nodeId: option.id,
1383
1922
  property: "label",
1384
1923
  sourceText: option.label,
1385
- translatedText: event.currentTarget.value,
1924
+ translatedText: value,
1386
1925
  ...option.translationMetadata?.[editingLocale]?.label === void 0 ? {} : {
1387
1926
  existingTranslationMetadata: option.translationMetadata[editingLocale]?.label
1388
1927
  }
@@ -1395,12 +1934,12 @@ function FormBuilder({
1395
1934
  /* @__PURE__ */ jsxs("label", { children: [
1396
1935
  translate("builder.minimum"),
1397
1936
  /* @__PURE__ */ jsx(
1398
- "input",
1937
+ TextInput,
1399
1938
  {
1400
1939
  type: "number",
1401
- value: field.min ?? 1,
1402
- onChange: (event) => {
1403
- const min = event.currentTarget.valueAsNumber;
1940
+ value: String(field.min ?? 1),
1941
+ onChange: (value) => {
1942
+ const min = Number(value);
1404
1943
  if (!Number.isInteger(min)) return;
1405
1944
  updateField(
1406
1945
  field.id,
@@ -1413,12 +1952,12 @@ function FormBuilder({
1413
1952
  /* @__PURE__ */ jsxs("label", { children: [
1414
1953
  translate("builder.maximum"),
1415
1954
  /* @__PURE__ */ jsx(
1416
- "input",
1955
+ TextInput,
1417
1956
  {
1418
1957
  type: "number",
1419
- value: field.max ?? 5,
1420
- onChange: (event) => {
1421
- const max = event.currentTarget.valueAsNumber;
1958
+ value: String(field.max ?? 5),
1959
+ onChange: (value) => {
1960
+ const max = Number(value);
1422
1961
  if (!Number.isInteger(max)) return;
1423
1962
  updateField(
1424
1963
  field.id,
@@ -1431,67 +1970,97 @@ function FormBuilder({
1431
1970
  ] }) : null,
1432
1971
  "options" in field ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__options", children: [
1433
1972
  /* @__PURE__ */ jsx("strong", { children: translate("builder.options") }),
1434
- field.options.map((option, optionIndex) => /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__option", children: [
1435
- /* @__PURE__ */ jsx(
1436
- "input",
1437
- {
1438
- "aria-label": translate("builder.optionLabel", { index: optionIndex + 1 }),
1439
- value: option.label,
1440
- placeholder: translate("builder.optionLabelPlaceholder"),
1441
- onChange: (event) => event.currentTarget.value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, event.currentTarget.value)
1442
- }
1443
- ),
1444
- /* @__PURE__ */ jsx(
1445
- "button",
1446
- {
1447
- type: "button",
1448
- disabled: optionIndex === 0,
1449
- "aria-label": translate("builder.moveUp", { title: option.label }),
1450
- onClick: () => moveOption(field.id, option.id, optionIndex - 1),
1451
- children: "\u2191"
1452
- }
1453
- ),
1454
- /* @__PURE__ */ jsx(
1455
- "button",
1456
- {
1457
- type: "button",
1458
- disabled: optionIndex === field.options.length - 1,
1459
- "aria-label": translate("builder.moveDown", { title: option.label }),
1460
- onClick: () => moveOption(field.id, option.id, optionIndex + 1),
1461
- children: "\u2193"
1462
- }
1463
- ),
1464
- /* @__PURE__ */ jsx(
1465
- "button",
1973
+ field.options.map(
1974
+ (option, optionIndex) => OptionEditorSlot === void 0 ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__option", children: [
1975
+ /* @__PURE__ */ jsx(
1976
+ TextInput,
1977
+ {
1978
+ "aria-label": translate("builder.optionLabel", { index: optionIndex + 1 }),
1979
+ value: option.label,
1980
+ placeholder: translate("builder.optionLabelPlaceholder"),
1981
+ onChange: (value) => value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, value)
1982
+ }
1983
+ ),
1984
+ ToolbarSlot === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1985
+ /* @__PURE__ */ jsx(
1986
+ IconButton,
1987
+ {
1988
+ icon: "\u2191",
1989
+ title: translate("builder.moveUp", { title: option.label }),
1990
+ disabled: optionIndex === 0,
1991
+ onClick: () => moveOption(field.id, option.id, optionIndex - 1)
1992
+ }
1993
+ ),
1994
+ /* @__PURE__ */ jsx(
1995
+ IconButton,
1996
+ {
1997
+ icon: "\u2193",
1998
+ title: translate("builder.moveDown", { title: option.label }),
1999
+ disabled: optionIndex === field.options.length - 1,
2000
+ onClick: () => moveOption(field.id, option.id, optionIndex + 1)
2001
+ }
2002
+ ),
2003
+ /* @__PURE__ */ jsx(
2004
+ Button,
2005
+ {
2006
+ disabled: field.options.length === 1,
2007
+ onClick: () => removeOption(field.id, option.id),
2008
+ variant: "danger",
2009
+ children: translate("builder.remove")
2010
+ }
2011
+ )
2012
+ ] }) : /* @__PURE__ */ jsx(
2013
+ ToolbarSlot,
2014
+ {
2015
+ schema,
2016
+ kind: "option",
2017
+ targetId: option.id,
2018
+ index: optionIndex,
2019
+ total: field.options.length,
2020
+ title: option.label,
2021
+ onMoveUp: () => moveOption(field.id, option.id, optionIndex - 1),
2022
+ onMoveDown: () => moveOption(field.id, option.id, optionIndex + 1),
2023
+ onRemove: () => removeOption(field.id, option.id),
2024
+ readOnly,
2025
+ actions,
2026
+ components
2027
+ }
2028
+ )
2029
+ ] }, option.id) : /* @__PURE__ */ jsx(
2030
+ OptionEditorSlot,
1466
2031
  {
1467
- type: "button",
1468
- disabled: field.options.length === 1,
1469
- onClick: () => removeOption(field.id, option.id),
1470
- children: translate("builder.remove")
1471
- }
2032
+ schema,
2033
+ field,
2034
+ option,
2035
+ index: optionIndex,
2036
+ currentLocale: editingLocale,
2037
+ readOnly,
2038
+ actions,
2039
+ components
2040
+ },
2041
+ option.id
1472
2042
  )
1473
- ] }, option.id)),
2043
+ ),
1474
2044
  /* @__PURE__ */ jsx(
1475
- "button",
2045
+ Button,
1476
2046
  {
1477
- type: "button",
1478
- "data-builder-action": "addOption",
1479
- "data-target-id": field.id,
2047
+ action: "addOption",
2048
+ targetId: field.id,
1480
2049
  disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
1481
2050
  onClick: () => addOption(field.id),
1482
2051
  children: translate("builder.addOption")
1483
2052
  }
1484
2053
  )
1485
2054
  ] }) : null,
1486
- /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
2055
+ conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: "form-engine-builder__condition", children: [
1487
2056
  /* @__PURE__ */ jsxs("label", { children: [
1488
2057
  translate("builder.displayCondition"),
1489
- /* @__PURE__ */ jsxs(
1490
- "select",
2058
+ /* @__PURE__ */ jsx(
2059
+ Select,
1491
2060
  {
1492
2061
  value: condition?.questionId ?? "",
1493
- onChange: (event) => {
1494
- const selected = schema.fields.find((item) => item.id === event.currentTarget.value);
2062
+ onChange: (value) => {
2063
+ const selected = schema.fields.find((item) => item.id === value);
1495
2064
  setDisplayCondition(
1496
2065
  field.id,
1497
2066
  selected === void 0 ? void 0 : conditionWithValue(
@@ -1501,27 +2070,30 @@ function FormBuilder({
1501
2070
  )
1502
2071
  );
1503
2072
  },
1504
- children: [
1505
- /* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
1506
- availableSources.map((candidate) => /* @__PURE__ */ jsx("option", { value: candidate.id, children: candidate.title }, candidate.id))
2073
+ options: [
2074
+ { value: "", label: translate("builder.alwaysVisible") },
2075
+ ...availableSources.map((candidate) => ({ value: candidate.id, label: candidate.title }))
1507
2076
  ]
1508
2077
  }
1509
2078
  )
1510
2079
  ] }),
1511
2080
  condition !== void 0 && source !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1512
2081
  /* @__PURE__ */ jsx(
1513
- "select",
2082
+ Select,
1514
2083
  {
1515
2084
  "aria-label": translate("builder.conditionOperator"),
1516
2085
  value: condition.operator,
1517
- onChange: (event) => {
1518
- const operator = event.currentTarget.value;
2086
+ onChange: (value) => {
2087
+ const operator = value;
1519
2088
  setDisplayCondition(
1520
2089
  field.id,
1521
2090
  conditionWithValue(source.id, operator, defaultConditionValue(source))
1522
2091
  );
1523
2092
  },
1524
- children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
2093
+ options: conditionOperators(source).map((operator) => ({
2094
+ value: operator,
2095
+ label: translate(operatorKey(operator))
2096
+ }))
1525
2097
  }
1526
2098
  ),
1527
2099
  /* @__PURE__ */ jsx(
@@ -1530,27 +2102,27 @@ function FormBuilder({
1530
2102
  source,
1531
2103
  condition,
1532
2104
  onChange: (next) => setDisplayCondition(field.id, next),
1533
- translate
2105
+ translate,
2106
+ components
1534
2107
  }
1535
2108
  )
1536
2109
  ] }) : null
1537
- ] })
2110
+ ] }) : null
1538
2111
  ] }, field.id);
1539
2112
  }) }),
1540
2113
  /* @__PURE__ */ jsx(
1541
- "button",
2114
+ Button,
1542
2115
  {
1543
2116
  className: "form-engine-builder__add",
1544
- type: "button",
1545
- "data-builder-action": "addField",
2117
+ action: "addField",
1546
2118
  disabled: initialFieldType === null || maxFieldsReached,
1547
2119
  onClick: addField,
1548
2120
  children: translate("builder.addQuestion")
1549
2121
  }
1550
2122
  )
1551
- ]
2123
+ ] })
1552
2124
  }
1553
- );
2125
+ ) });
1554
2126
  }
1555
2127
 
1556
2128
  // src/context.tsx
@@ -1563,9 +2135,9 @@ import {
1563
2135
  validateAnswers,
1564
2136
  validatePageAnswers
1565
2137
  } from "@form-engine-ts/core";
1566
- import { createContext, useCallback as useCallback2, useContext, useEffect, useMemo as useMemo2, useState as useState2 } from "react";
2138
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect, useMemo as useMemo2, useState as useState2 } from "react";
1567
2139
  import { jsx as jsx2 } from "react/jsx-runtime";
1568
- var FormContext = createContext(null);
2140
+ var FormContext = createContext2(null);
1569
2141
  function issuesByField(issues) {
1570
2142
  const result = {};
1571
2143
  for (const issue of issues) result[issue.fieldId] ??= issue;
@@ -1719,7 +2291,7 @@ function FormProvider({
1719
2291
  return /* @__PURE__ */ jsx2(FormContext.Provider, { value: contextValue, children });
1720
2292
  }
1721
2293
  function useForm() {
1722
- const context = useContext(FormContext);
2294
+ const context = useContext2(FormContext);
1723
2295
  if (context === null) throw new Error("useForm must be called inside a FormProvider.");
1724
2296
  return context;
1725
2297
  }