@bpmn-io/form-js-editor 1.5.0 → 1.6.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.
Files changed (47) hide show
  1. package/dist/assets/form-js-editor-base.css +38 -7
  2. package/dist/assets/form-js-editor.css +38 -7
  3. package/dist/index.cjs +1400 -330
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.es.js +1402 -332
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/types/FormEditor.d.ts +16 -4
  8. package/dist/types/features/dragging/Dragging.d.ts +1 -1
  9. package/dist/types/features/modeling/behavior/ColumnsSourceBehavior.d.ts +7 -0
  10. package/dist/types/features/modeling/behavior/OptionsSourceBehavior.d.ts +8 -0
  11. package/dist/types/features/modeling/behavior/TableDataSourceBehavior.d.ts +7 -0
  12. package/dist/types/features/modeling/behavior/index.d.ts +6 -2
  13. package/dist/types/features/modeling/index.d.ts +3 -1
  14. package/dist/types/features/properties-panel/Util.d.ts +11 -3
  15. package/dist/types/features/properties-panel/entries/ColumnEntry.d.ts +11 -0
  16. package/dist/types/features/properties-panel/entries/ColumnsExpressionEntry.d.ts +10 -0
  17. package/dist/types/features/properties-panel/entries/ConditionEntry.d.ts +1 -1
  18. package/dist/types/features/properties-panel/entries/{GroupEntries.d.ts → GroupAppearanceEntry.d.ts} +1 -1
  19. package/dist/types/features/properties-panel/entries/HeadersSourceSelectEntry.d.ts +9 -0
  20. package/dist/types/features/properties-panel/entries/{InputKeyValuesSourceEntry.d.ts → InputKeyOptionsSourceEntry.d.ts} +1 -1
  21. package/dist/types/features/properties-panel/entries/LayouterAppearanceEntry.d.ts +10 -0
  22. package/dist/types/features/properties-panel/entries/OptionsExpressionEntry.d.ts +9 -0
  23. package/dist/types/features/properties-panel/entries/{ValuesSourceSelectEntry.d.ts → OptionsSourceSelectEntry.d.ts} +1 -1
  24. package/dist/types/features/properties-panel/entries/PaginationEntry.d.ts +10 -0
  25. package/dist/types/features/properties-panel/entries/RepeatableEntry.d.ts +24 -0
  26. package/dist/types/features/properties-panel/entries/RowCountEntry.d.ts +10 -0
  27. package/dist/types/features/properties-panel/entries/StaticColumnsSourceEntry.d.ts +5 -0
  28. package/dist/types/features/properties-panel/entries/{StaticValuesSourceEntry.d.ts → StaticOptionsSourceEntry.d.ts} +1 -1
  29. package/dist/types/features/properties-panel/entries/TableDataSourceEntry.d.ts +10 -0
  30. package/dist/types/features/properties-panel/entries/factories/index.d.ts +3 -0
  31. package/dist/types/features/properties-panel/entries/factories/simpleRangeIntegerEntryFactory.d.ts +12 -0
  32. package/dist/types/features/properties-panel/entries/factories/simpleSelectEntryFactory.d.ts +10 -0
  33. package/dist/types/features/properties-panel/entries/factories/zeroPositiveIntegerEntryFactory.d.ts +9 -0
  34. package/dist/types/features/properties-panel/entries/index.d.ts +14 -6
  35. package/dist/types/features/properties-panel/groups/AppearanceGroup.d.ts +24 -3
  36. package/dist/types/features/properties-panel/groups/GeneralGroup.d.ts +11 -0
  37. package/dist/types/features/properties-panel/groups/OptionsGroups.d.ts +1 -0
  38. package/dist/types/features/properties-panel/groups/TableHeaderGroups.d.ts +1 -0
  39. package/dist/types/features/properties-panel/groups/index.d.ts +2 -1
  40. package/dist/types/features/repeat-render/EditorRepeatRenderManager.d.ts +17 -0
  41. package/dist/types/features/repeat-render/index.d.ts +7 -0
  42. package/dist/types/render/components/editor-form-fields/EditorTable.d.ts +10 -0
  43. package/dist/types/render/components/editor-form-fields/index.d.ts +2 -1
  44. package/package.json +3 -3
  45. package/dist/types/features/modeling/behavior/ValuesSourceBehavior.d.ts +0 -8
  46. package/dist/types/features/properties-panel/entries/ValuesExpressionEntry.d.ts +0 -9
  47. package/dist/types/features/properties-panel/groups/ValuesGroups.d.ts +0 -1
package/dist/index.cjs CHANGED
@@ -3,8 +3,8 @@
3
3
  var formJsViewer = require('@bpmn-io/form-js-viewer');
4
4
  var Ids = require('ids');
5
5
  var minDash = require('min-dash');
6
- var jsxRuntime = require('preact/jsx-runtime');
7
6
  var classnames = require('classnames');
7
+ var jsxRuntime = require('preact/jsx-runtime');
8
8
  var hooks = require('preact/hooks');
9
9
  var preact = require('preact');
10
10
  var React = require('preact/compat');
@@ -437,7 +437,7 @@ EventBus.prototype._invokeListener = function (event, args, listener) {
437
437
  * * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
438
438
  *
439
439
  * @param {string} event
440
- * @param {EventBusListener} listener
440
+ * @param {EventBusListener} newListener
441
441
  */
442
442
  EventBus.prototype._addListener = function (event, newListener) {
443
443
  var listener = this._getListeners(event),
@@ -664,25 +664,6 @@ function calculateMaxColumnsWithAuto(autoCols) {
664
664
  return MAX_COLUMNS_PER_ROW - autoCols * 2;
665
665
  }
666
666
 
667
- function EditorIFrame(props) {
668
- const {
669
- field
670
- } = props;
671
- const Icon = formJsViewer.iconsByType(field.type);
672
- return jsxRuntime.jsx("div", {
673
- class: "fjs-iframe-placeholder",
674
- children: jsxRuntime.jsxs("p", {
675
- class: "fjs-iframe-placeholder-text",
676
- children: [jsxRuntime.jsx(Icon, {
677
- width: "32",
678
- height: "24",
679
- viewBox: "0 0 56 56"
680
- }), "iFrame"]
681
- })
682
- });
683
- }
684
- EditorIFrame.config = formJsViewer.IFrame.config;
685
-
686
667
  const emptyImage = createEmptyImage();
687
668
  function editorFormFieldClasses(type, {
688
669
  disabled = false
@@ -784,6 +765,28 @@ function createEmptyImage() {
784
765
  return img;
785
766
  }
786
767
 
768
+ function EditorIFrame(props) {
769
+ const {
770
+ field
771
+ } = props;
772
+ const Icon = formJsViewer.iconsByType(field.type);
773
+ return jsxRuntime.jsx("div", {
774
+ class: editorFormFieldClasses(field.type),
775
+ children: jsxRuntime.jsx("div", {
776
+ class: "fjs-iframe-placeholder",
777
+ children: jsxRuntime.jsxs("p", {
778
+ class: "fjs-iframe-placeholder-text",
779
+ children: [jsxRuntime.jsx(Icon, {
780
+ width: "32",
781
+ height: "24",
782
+ viewBox: "0 0 56 56"
783
+ }), "iFrame"]
784
+ })
785
+ })
786
+ });
787
+ }
788
+ EditorIFrame.config = formJsViewer.IFrame.config;
789
+
787
790
  const DragAndDropContext = preact.createContext({
788
791
  drake: null
789
792
  });
@@ -829,15 +832,15 @@ function useDebounce(fn, dependencies = []) {
829
832
  return callback;
830
833
  }
831
834
 
832
- var _path$4;
833
- function _extends$4() { _extends$4 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$4.apply(this, arguments); }
835
+ var _path$5;
836
+ function _extends$5() { _extends$5 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$5.apply(this, arguments); }
834
837
  var SvgClose = function SvgClose(props) {
835
- return /*#__PURE__*/React__namespace.createElement("svg", _extends$4({
838
+ return /*#__PURE__*/React__namespace.createElement("svg", _extends$5({
836
839
  xmlns: "http://www.w3.org/2000/svg",
837
840
  width: 16,
838
841
  height: 16,
839
842
  fill: "currentColor"
840
- }, props), _path$4 || (_path$4 = /*#__PURE__*/React__namespace.createElement("path", {
843
+ }, props), _path$5 || (_path$5 = /*#__PURE__*/React__namespace.createElement("path", {
841
844
  fillRule: "evenodd",
842
845
  d: "m12 4.7-.7-.7L8 7.3 4.7 4l-.7.7L7.3 8 4 11.3l.7.7L8 8.7l3.3 3.3.7-.7L8.7 8 12 4.7Z",
843
846
  clipRule: "evenodd"
@@ -845,10 +848,10 @@ var SvgClose = function SvgClose(props) {
845
848
  };
846
849
  var CloseIcon = SvgClose;
847
850
 
848
- var _path$3, _path2$1;
849
- function _extends$3() { _extends$3 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$3.apply(this, arguments); }
851
+ var _path$4, _path2$1;
852
+ function _extends$4() { _extends$4 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$4.apply(this, arguments); }
850
853
  var SvgDelete = function SvgDelete(props) {
851
- return /*#__PURE__*/React__namespace.createElement("svg", _extends$3({
854
+ return /*#__PURE__*/React__namespace.createElement("svg", _extends$4({
852
855
  xmlns: "http://www.w3.org/2000/svg",
853
856
  width: 16,
854
857
  height: 16,
@@ -869,7 +872,7 @@ var SvgDelete = function SvgDelete(props) {
869
872
  mixBlendMode: "multiply"
870
873
  },
871
874
  transform: "translate(.536)"
872
- }), _path$3 || (_path$3 = /*#__PURE__*/React__namespace.createElement("path", {
875
+ }), _path$4 || (_path$4 = /*#__PURE__*/React__namespace.createElement("path", {
873
876
  fill: "currentcolor",
874
877
  d: "M7.536 6h-1v6h1V6Zm3 0h-1v6h1V6Z"
875
878
  })), _path2$1 || (_path2$1 = /*#__PURE__*/React__namespace.createElement("path", {
@@ -879,17 +882,17 @@ var SvgDelete = function SvgDelete(props) {
879
882
  };
880
883
  var DeleteIcon$1 = SvgDelete;
881
884
 
882
- var _path$2;
883
- function _extends$2() { _extends$2 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$2.apply(this, arguments); }
885
+ var _path$3;
886
+ function _extends$3() { _extends$3 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$3.apply(this, arguments); }
884
887
  var SvgDraggable = function SvgDraggable(props) {
885
- return /*#__PURE__*/React__namespace.createElement("svg", _extends$2({
888
+ return /*#__PURE__*/React__namespace.createElement("svg", _extends$3({
886
889
  xmlns: "http://www.w3.org/2000/svg",
887
890
  xmlSpace: "preserve",
888
891
  width: 16,
889
892
  height: 16,
890
893
  fill: "currentcolor",
891
894
  viewBox: "0 0 32 32"
892
- }, props), _path$2 || (_path$2 = /*#__PURE__*/React__namespace.createElement("path", {
895
+ }, props), _path$3 || (_path$3 = /*#__PURE__*/React__namespace.createElement("path", {
893
896
  d: "M10 6h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4z"
894
897
  })), /*#__PURE__*/React__namespace.createElement("path", {
895
898
  d: "M0 0h32v32H0z",
@@ -900,30 +903,30 @@ var SvgDraggable = function SvgDraggable(props) {
900
903
  };
901
904
  var DraggableIcon = SvgDraggable;
902
905
 
903
- var _path$1;
904
- function _extends$1() { _extends$1 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$1.apply(this, arguments); }
906
+ var _path$2;
907
+ function _extends$2() { _extends$2 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$2.apply(this, arguments); }
905
908
  var SvgSearch = function SvgSearch(props) {
906
- return /*#__PURE__*/React__namespace.createElement("svg", _extends$1({
909
+ return /*#__PURE__*/React__namespace.createElement("svg", _extends$2({
907
910
  xmlns: "http://www.w3.org/2000/svg",
908
911
  width: 15,
909
912
  height: 15,
910
913
  fill: "none"
911
- }, props), _path$1 || (_path$1 = /*#__PURE__*/React__namespace.createElement("path", {
914
+ }, props), _path$2 || (_path$2 = /*#__PURE__*/React__namespace.createElement("path", {
912
915
  fill: "currentColor",
913
916
  d: "m14.5 13.793-3.776-3.776a5.508 5.508 0 1 0-.707.707l3.776 3.776.707-.707ZM2 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0Z"
914
917
  })));
915
918
  };
916
919
  var SearchIcon = SvgSearch;
917
920
 
918
- var _path, _rect, _mask, _path2, _path3, _path4, _path5, _path6;
919
- function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
921
+ var _path$1, _rect, _mask, _path2, _path3, _path4, _path5, _path6;
922
+ function _extends$1() { _extends$1 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$1.apply(this, arguments); }
920
923
  var SvgEmptyForm = function SvgEmptyForm(props) {
921
- return /*#__PURE__*/React__namespace.createElement("svg", _extends({
924
+ return /*#__PURE__*/React__namespace.createElement("svg", _extends$1({
922
925
  xmlns: "http://www.w3.org/2000/svg",
923
926
  width: 126,
924
927
  height: 96,
925
928
  fill: "none"
926
- }, props), _path || (_path = /*#__PURE__*/React__namespace.createElement("path", {
929
+ }, props), _path$1 || (_path$1 = /*#__PURE__*/React__namespace.createElement("path", {
927
930
  fill: "#FF832B",
928
931
  fillRule: "evenodd",
929
932
  d: "M70 78v8a3 3 0 0 1-3 3h-8v-5h6v-6h5Zm0-16h-5V46h5v16Zm0-32h-5v-6h-6v-5h8a3 3 0 0 1 3 3v8ZM43 19v5H27v-5h16Zm-32 0v5H5v6H0v-8a3 3 0 0 1 3-3h8ZM0 46h5v16H0V46Zm0 32h5v6h6v5H3a3 3 0 0 1-3-3v-8Zm27 11v-5h16v5H27Z",
@@ -1018,7 +1021,71 @@ function EditorText(props) {
1018
1021
  }
1019
1022
  EditorText.config = formJsViewer.Text.config;
1020
1023
 
1021
- const editorFormFields = [EditorIFrame, EditorText];
1024
+ function EditorTable(props) {
1025
+ const {
1026
+ columnsExpression,
1027
+ columns,
1028
+ id,
1029
+ label
1030
+ } = props.field;
1031
+ const shouldUseMockColumns = typeof columnsExpression === 'string' && columnsExpression.length > 0 || Array.isArray(columns) && columns.length === 0;
1032
+ const editorColumns = shouldUseMockColumns ? [{
1033
+ key: '1',
1034
+ label: 'Column 1'
1035
+ }, {
1036
+ key: '2',
1037
+ label: 'Column 2'
1038
+ }, {
1039
+ key: '3',
1040
+ label: 'Column 3'
1041
+ }] : columns;
1042
+ const prefixId = `fjs-form-${id}`;
1043
+ return jsxRuntime.jsxs("div", {
1044
+ class: editorFormFieldClasses('table', {
1045
+ disabled: true
1046
+ }),
1047
+ children: [jsxRuntime.jsx(formJsViewer.Label, {
1048
+ id: prefixId,
1049
+ label: label
1050
+ }), jsxRuntime.jsx("div", {
1051
+ class: "fjs-table-middle-container",
1052
+ children: jsxRuntime.jsx("div", {
1053
+ class: "fjs-table-inner-container",
1054
+ children: jsxRuntime.jsxs("table", {
1055
+ class: classnames('fjs-table', 'fjs-disabled'),
1056
+ id: prefixId,
1057
+ children: [jsxRuntime.jsx("thead", {
1058
+ class: "fjs-table-head",
1059
+ children: jsxRuntime.jsx("tr", {
1060
+ class: "fjs-table-tr",
1061
+ children: editorColumns.map(({
1062
+ key,
1063
+ label
1064
+ }) => jsxRuntime.jsx("th", {
1065
+ class: "fjs-table-th",
1066
+ children: label
1067
+ }, key))
1068
+ })
1069
+ }), jsxRuntime.jsx("tbody", {
1070
+ class: "fjs-table-body",
1071
+ children: jsxRuntime.jsx("tr", {
1072
+ class: "fjs-table-tr",
1073
+ children: editorColumns.map(({
1074
+ key
1075
+ }) => jsxRuntime.jsx("td", {
1076
+ class: "fjs-table-td",
1077
+ children: "Content"
1078
+ }, key))
1079
+ })
1080
+ })]
1081
+ })
1082
+ })
1083
+ })]
1084
+ });
1085
+ }
1086
+ EditorTable.config = formJsViewer.Table.config;
1087
+
1088
+ const editorFormFields = [EditorIFrame, EditorText, EditorTable];
1022
1089
 
1023
1090
  class EditorFormFields extends formJsViewer.FormFields {
1024
1091
  constructor() {
@@ -1558,7 +1625,7 @@ class Dragging {
1558
1625
  }
1559
1626
 
1560
1627
  /**
1561
- * Calculcates position in form schema given the dropped place.
1628
+ * Calculates position in form schema given the dropped place.
1562
1629
  *
1563
1630
  * @param { FormRow } targetRow
1564
1631
  * @param { any } targetFormField
@@ -1618,13 +1685,18 @@ class Dragging {
1618
1685
  if (targetParentPath.join('.') !== currentParentPath.join('.')) {
1619
1686
  const isDropAllowedByPathRegistry = this._pathRegistry.executeRecursivelyOnFields(formField, ({
1620
1687
  field,
1621
- isClosed
1688
+ isClosed,
1689
+ isRepeatable
1622
1690
  }) => {
1623
1691
  const options = {
1624
1692
  cutoffNode: currentParentFormField.id
1625
1693
  };
1626
1694
  const fieldPath = this._pathRegistry.getValuePath(field, options);
1627
- return this._pathRegistry.canClaimPath([...targetParentPath, ...fieldPath], isClosed);
1695
+ return this._pathRegistry.canClaimPath([...targetParentPath, ...fieldPath], {
1696
+ isClosed,
1697
+ isRepeatable,
1698
+ knownAncestorIds: formJsViewer.getAncestryList(targetParentId, this._formFieldRegistry)
1699
+ });
1628
1700
  });
1629
1701
  if (!isDropAllowedByPathRegistry) {
1630
1702
  return 'Drop not allowed by path registry';
@@ -1990,24 +2062,40 @@ function ContextPad(props) {
1990
2062
  children: props.children
1991
2063
  });
1992
2064
  }
1993
- function Empty() {
2065
+ function Empty(props) {
2066
+ if (props.field.type === 'default') {
2067
+ return jsxRuntime.jsx("div", {
2068
+ class: "fjs-empty-editor",
2069
+ children: jsxRuntime.jsxs("div", {
2070
+ class: "fjs-empty-editor-card",
2071
+ children: [jsxRuntime.jsx(EmptyFormIcon, {}), jsxRuntime.jsx("h2", {
2072
+ children: "Build your form"
2073
+ }), jsxRuntime.jsx("span", {
2074
+ children: "Drag and drop components here to start designing."
2075
+ }), jsxRuntime.jsx("span", {
2076
+ children: "Use the preview window to test your form."
2077
+ })]
2078
+ })
2079
+ });
2080
+ }
2081
+ if (props.field.type === 'group') {
2082
+ return jsxRuntime.jsx("div", {
2083
+ class: "fjs-empty-component",
2084
+ children: jsxRuntime.jsx("span", {
2085
+ children: "Drag and drop components here."
2086
+ })
2087
+ });
2088
+ }
2089
+ if (props.field.type === 'dynamiclist') {
2090
+ return jsxRuntime.jsx("div", {
2091
+ class: "fjs-empty-component",
2092
+ children: jsxRuntime.jsxs("span", {
2093
+ children: ["Drag and drop components here ", jsxRuntime.jsx("br", {}), " to create a repeatable list item."]
2094
+ })
2095
+ });
2096
+ }
1994
2097
  return null;
1995
2098
  }
1996
- function EmptyRoot(props) {
1997
- return jsxRuntime.jsx("div", {
1998
- class: "fjs-empty-editor",
1999
- children: jsxRuntime.jsxs("div", {
2000
- class: "fjs-empty-editor-card",
2001
- children: [jsxRuntime.jsx(EmptyFormIcon, {}), jsxRuntime.jsx("h2", {
2002
- children: "Build your form"
2003
- }), jsxRuntime.jsx("span", {
2004
- children: "Drag and drop components here to start designing."
2005
- }), jsxRuntime.jsx("span", {
2006
- children: "Use the preview window to test your form."
2007
- })]
2008
- })
2009
- });
2010
- }
2011
2099
  function Element$1(props) {
2012
2100
  const eventBus = useService$1('eventBus'),
2013
2101
  formEditor = useService$1('formEditor'),
@@ -2016,8 +2104,7 @@ function Element$1(props) {
2016
2104
  modeling = useService$1('modeling'),
2017
2105
  selection = useService$1('selection');
2018
2106
  const {
2019
- hoveredId,
2020
- setHoveredId
2107
+ hoverInfo
2021
2108
  } = hooks.useContext(formJsViewer.FormRenderContext);
2022
2109
  const {
2023
2110
  field
@@ -2028,6 +2115,7 @@ function Element$1(props) {
2028
2115
  showOutline
2029
2116
  } = field;
2030
2117
  const ref = hooks.useRef();
2118
+ const [hovered, setHovered] = hooks.useState(false);
2031
2119
  function scrollIntoView({
2032
2120
  selection
2033
2121
  }) {
@@ -2056,19 +2144,23 @@ function Element$1(props) {
2056
2144
  // properly focus on field
2057
2145
  ref.current.focus();
2058
2146
  }
2059
- const classes = [];
2060
- if (props.class) {
2061
- classes.push(...props.class.split(' '));
2062
- }
2063
- if (selection.isSelected(field)) {
2064
- classes.push('fjs-editor-selected');
2065
- }
2066
- if (showOutline) {
2067
- classes.push('fjs-outlined');
2068
- }
2069
- if (hoveredId === field.id) {
2070
- classes.push('fjs-editor-hovered');
2071
- }
2147
+ const isSelected = selection.isSelected(field);
2148
+ const classString = hooks.useMemo(() => {
2149
+ const classes = [];
2150
+ if (props.class) {
2151
+ classes.push(...props.class.split(' '));
2152
+ }
2153
+ if (isSelected) {
2154
+ classes.push('fjs-editor-selected');
2155
+ }
2156
+ if (showOutline) {
2157
+ classes.push('fjs-outlined');
2158
+ }
2159
+ if (hovered) {
2160
+ classes.push('fjs-editor-hovered');
2161
+ }
2162
+ return classes.join(' ');
2163
+ }, [hovered, isSelected, props.class, showOutline]);
2072
2164
  const onRemove = event => {
2073
2165
  event.stopPropagation();
2074
2166
  const parentField = formFieldRegistry.get(field._parent);
@@ -2082,15 +2174,18 @@ function Element$1(props) {
2082
2174
  }
2083
2175
  };
2084
2176
  return jsxRuntime.jsxs("div", {
2085
- class: classes.join(' '),
2177
+ class: classString,
2086
2178
  "data-id": id,
2087
2179
  "data-field-type": type,
2088
2180
  tabIndex: type === 'default' ? -1 : 0,
2089
2181
  onClick: onClick,
2090
2182
  onKeyPress: onKeyPress,
2091
2183
  onMouseOver: e => {
2092
- // @ts-ignore
2093
- setHoveredId(field.id);
2184
+ if (hoverInfo.cleanup) {
2185
+ hoverInfo.cleanup();
2186
+ }
2187
+ setHovered(true);
2188
+ hoverInfo.cleanup = () => setHovered(false);
2094
2189
  e.stopPropagation();
2095
2190
  },
2096
2191
  ref: ref,
@@ -2161,6 +2256,7 @@ function Row(props) {
2161
2256
  children: jsxRuntime.jsx(DraggableIcon, {})
2162
2257
  }), jsxRuntime.jsx("div", {
2163
2258
  class: classes.join(' '),
2259
+ style: props.style,
2164
2260
  "data-row-id": id,
2165
2261
  children: props.children
2166
2262
  })]
@@ -2266,17 +2362,14 @@ function FormEditor$1(props) {
2266
2362
  // keep deprecated event to ensure backward compatibility
2267
2363
  eventBus.fire('formEditor.rendered');
2268
2364
  }, []);
2269
- const [hoveredId, setHoveredId] = hooks.useState(null);
2270
2365
  const formRenderContext = hooks.useMemo(() => ({
2271
2366
  Children,
2272
2367
  Column,
2273
2368
  Element: Element$1,
2274
2369
  Empty,
2275
- EmptyRoot,
2276
2370
  Row,
2277
- hoveredId,
2278
- setHoveredId
2279
- }), [hoveredId]);
2371
+ hoverInfo: {}
2372
+ }), []);
2280
2373
  const formContext = hooks.useMemo(() => ({
2281
2374
  getService(type, strict = true) {
2282
2375
  // TODO(philippfromme): clean up
@@ -2637,7 +2730,8 @@ EditorActions.prototype.trigger = function (action, opts) {
2637
2730
  * The key of the object will be the name of the action.
2638
2731
  *
2639
2732
  * @example
2640
- * ´´´
2733
+ *
2734
+ * ```javascript
2641
2735
  * var actions = {
2642
2736
  * spaceTool: function() {
2643
2737
  * spaceTool.activateSelection();
@@ -2650,7 +2744,7 @@ EditorActions.prototype.trigger = function (action, opts) {
2650
2744
  * editorActions.register(actions);
2651
2745
  *
2652
2746
  * editorActions.isRegistered('spaceTool'); // true
2653
- * ´´´
2747
+ * ```
2654
2748
  *
2655
2749
  * @param {Object} actions
2656
2750
  */
@@ -2789,6 +2883,7 @@ function hasModifier(event) {
2789
2883
 
2790
2884
  /**
2791
2885
  * @param {KeyboardEvent} event
2886
+ * @return {boolean}
2792
2887
  */
2793
2888
  function isCmd(event) {
2794
2889
  // ensure we don't react to AltGr
@@ -2804,6 +2899,7 @@ function isCmd(event) {
2804
2899
  *
2805
2900
  * @param {string|string[]} keys
2806
2901
  * @param {KeyboardEvent} event
2902
+ * @return {boolean}
2807
2903
  */
2808
2904
  function isKey(keys, event) {
2809
2905
  keys = minDash.isArray(keys) ? keys : [keys];
@@ -2816,21 +2912,39 @@ function isKey(keys, event) {
2816
2912
  function isShift(event) {
2817
2913
  return event.shiftKey;
2818
2914
  }
2915
+
2916
+ /**
2917
+ * @param {KeyboardEvent} event
2918
+ */
2819
2919
  function isCopy(event) {
2820
2920
  return isCmd(event) && isKey(KEYS_COPY, event);
2821
2921
  }
2922
+
2923
+ /**
2924
+ * @param {KeyboardEvent} event
2925
+ */
2822
2926
  function isPaste(event) {
2823
2927
  return isCmd(event) && isKey(KEYS_PASTE, event);
2824
2928
  }
2929
+
2930
+ /**
2931
+ * @param {KeyboardEvent} event
2932
+ */
2825
2933
  function isUndo(event) {
2826
2934
  return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
2827
2935
  }
2936
+
2937
+ /**
2938
+ * @param {KeyboardEvent} event
2939
+ */
2828
2940
  function isRedo(event) {
2829
2941
  return isCmd(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
2830
2942
  }
2831
2943
 
2832
2944
  /**
2833
2945
  * @typedef {import('../../core/EventBus').default} EventBus
2946
+ *
2947
+ * @typedef {({ keyEvent: KeyboardEvent }) => any} Listener
2834
2948
  */
2835
2949
 
2836
2950
  var KEYDOWN_EVENT = 'keyboard.keydown',
@@ -2860,6 +2974,7 @@ var DEFAULT_PRIORITY$2 = 1000;
2860
2974
  * `keyboard.bindTo` configuration option.
2861
2975
  *
2862
2976
  * @param {Object} config
2977
+ * @param {EventTarget} [config.bindTo]
2863
2978
  * @param {EventBus} eventBus
2864
2979
  */
2865
2980
  function Keyboard(config, eventBus) {
@@ -2926,6 +3041,12 @@ Keyboard.prototype._getAllowedModifiers = function (element) {
2926
3041
  }
2927
3042
  return modifierContainer.getAttribute(HANDLE_MODIFIER_ATTRIBUTE).split(',');
2928
3043
  };
3044
+
3045
+ /**
3046
+ * Bind keyboard events to the given DOM node.
3047
+ *
3048
+ * @param {EventTarget} node
3049
+ */
2929
3050
  Keyboard.prototype.bind = function (node) {
2930
3051
  // make sure that the keyboard is only bound once to the DOM
2931
3052
  this.unbind();
@@ -2936,6 +3057,10 @@ Keyboard.prototype.bind = function (node) {
2936
3057
  minDom.event.bind(node, 'keyup', this._keyupHandler);
2937
3058
  this._fire('bind');
2938
3059
  };
3060
+
3061
+ /**
3062
+ * @return {EventTarget}
3063
+ */
2939
3064
  Keyboard.prototype.getBinding = function () {
2940
3065
  return this._node;
2941
3066
  };
@@ -2950,6 +3075,10 @@ Keyboard.prototype.unbind = function () {
2950
3075
  }
2951
3076
  this._node = null;
2952
3077
  };
3078
+
3079
+ /**
3080
+ * @param {string} event
3081
+ */
2953
3082
  Keyboard.prototype._fire = function (event) {
2954
3083
  this._eventBus.fire('keyboard.' + event, {
2955
3084
  node: this._node
@@ -2962,8 +3091,8 @@ Keyboard.prototype._fire = function (event) {
2962
3091
  * provided, the default value of 1000 is used.
2963
3092
  *
2964
3093
  * @param {number} [priority]
2965
- * @param {Function} listener
2966
- * @param {string} type
3094
+ * @param {Listener} listener
3095
+ * @param {string} [type='keyboard.keydown']
2967
3096
  */
2968
3097
  Keyboard.prototype.addListener = function (priority, listener, type) {
2969
3098
  if (minDash.isFunction(priority)) {
@@ -2973,6 +3102,13 @@ Keyboard.prototype.addListener = function (priority, listener, type) {
2973
3102
  }
2974
3103
  this._eventBus.on(type || KEYDOWN_EVENT, priority, listener);
2975
3104
  };
3105
+
3106
+ /**
3107
+ * Remove a listener function.
3108
+ *
3109
+ * @param {Listener} listener
3110
+ * @param {string} [type='keyboard.keydown']
3111
+ */
2976
3112
  Keyboard.prototype.removeListener = function (listener, type) {
2977
3113
  this._eventBus.off(type || KEYDOWN_EVENT, listener);
2978
3114
  };
@@ -3433,9 +3569,14 @@ class MoveFormFieldHandler {
3433
3569
  // (7) Reregister form field (and children) from path registry
3434
3570
  this._pathRegistry.executeRecursivelyOnFields(formField, ({
3435
3571
  field,
3436
- isClosed
3572
+ isClosed,
3573
+ isRepeatable
3437
3574
  }) => {
3438
- this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), isClosed);
3575
+ this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), {
3576
+ isClosed,
3577
+ isRepeatable,
3578
+ claimerId: field.id
3579
+ });
3439
3580
  });
3440
3581
  }
3441
3582
 
@@ -3566,7 +3707,10 @@ class UpdateKeyClaimHandler {
3566
3707
  };
3567
3708
  const valuePath = this._pathRegistry.getValuePath(formField, options);
3568
3709
  if (claiming) {
3569
- this._pathRegistry.claimPath(valuePath, true);
3710
+ this._pathRegistry.claimPath(valuePath, {
3711
+ isClosed: true,
3712
+ claimerId: formField.id
3713
+ });
3570
3714
  } else {
3571
3715
  this._pathRegistry.unclaimPath(valuePath);
3572
3716
  }
@@ -3577,12 +3721,16 @@ class UpdateKeyClaimHandler {
3577
3721
  revert(context) {
3578
3722
  const {
3579
3723
  claiming,
3724
+ formField,
3580
3725
  valuePath
3581
3726
  } = context;
3582
3727
  if (claiming) {
3583
3728
  this._pathRegistry.unclaimPath(valuePath);
3584
3729
  } else {
3585
- this._pathRegistry.claimPath(valuePath, true);
3730
+ this._pathRegistry.claimPath(valuePath, {
3731
+ isClosed: true,
3732
+ claimerId: formField.id
3733
+ });
3586
3734
  }
3587
3735
  }
3588
3736
  }
@@ -3611,24 +3759,34 @@ class UpdatePathClaimHandler {
3611
3759
  if (claiming) {
3612
3760
  this._pathRegistry.executeRecursivelyOnFields(formField, ({
3613
3761
  field,
3614
- isClosed
3762
+ isClosed,
3763
+ isRepeatable
3615
3764
  }) => {
3616
3765
  const valuePath = this._pathRegistry.getValuePath(field, options);
3617
3766
  valuePaths.push({
3618
3767
  valuePath,
3619
- isClosed
3768
+ isClosed,
3769
+ isRepeatable,
3770
+ claimerId: field.id
3771
+ });
3772
+ this._pathRegistry.claimPath(valuePath, {
3773
+ isClosed,
3774
+ isRepeatable,
3775
+ claimerId: field.id
3620
3776
  });
3621
- this._pathRegistry.claimPath(valuePath, isClosed);
3622
3777
  });
3623
3778
  } else {
3624
3779
  this._pathRegistry.executeRecursivelyOnFields(formField, ({
3625
3780
  field,
3626
- isClosed
3781
+ isClosed,
3782
+ isRepeatable
3627
3783
  }) => {
3628
3784
  const valuePath = this._pathRegistry.getValuePath(field, options);
3629
3785
  valuePaths.push({
3630
3786
  valuePath,
3631
- isClosed
3787
+ isClosed,
3788
+ isRepeatable,
3789
+ claimerId: field.id
3632
3790
  });
3633
3791
  this._pathRegistry.unclaimPath(valuePath);
3634
3792
  });
@@ -3651,9 +3809,15 @@ class UpdatePathClaimHandler {
3651
3809
  } else {
3652
3810
  valuePaths.forEach(({
3653
3811
  valuePath,
3654
- isClosed
3812
+ isClosed,
3813
+ isRepeatable,
3814
+ claimerId
3655
3815
  }) => {
3656
- this._pathRegistry.claimPath(valuePath, isClosed);
3816
+ this._pathRegistry.claimPath(valuePath, {
3817
+ isClosed,
3818
+ isRepeatable,
3819
+ claimerId
3820
+ });
3657
3821
  });
3658
3822
  }
3659
3823
  }
@@ -4186,7 +4350,7 @@ class ValidateBehavior extends CommandInterceptor {
4186
4350
  }
4187
4351
  ValidateBehavior.$inject = ['eventBus'];
4188
4352
 
4189
- class ValuesSourceBehavior extends CommandInterceptor {
4353
+ class OptionsSourceBehavior extends CommandInterceptor {
4190
4354
  constructor(eventBus) {
4191
4355
  super(eventBus);
4192
4356
 
@@ -4206,15 +4370,15 @@ class ValuesSourceBehavior extends CommandInterceptor {
4206
4370
  }
4207
4371
 
4208
4372
  // clean up value sources that are not to going to be set
4209
- Object.values(formJsViewer.VALUES_SOURCES).forEach(source => {
4210
- const path = formJsViewer.VALUES_SOURCES_PATHS[source];
4373
+ Object.values(formJsViewer.OPTIONS_SOURCES).forEach(source => {
4374
+ const path = formJsViewer.OPTIONS_SOURCES_PATHS[source];
4211
4375
  if (minDash.get(properties, path) == undefined) {
4212
- newProperties[formJsViewer.VALUES_SOURCES_PATHS[source]] = undefined;
4376
+ newProperties[formJsViewer.OPTIONS_SOURCES_PATHS[source]] = undefined;
4213
4377
  }
4214
4378
  });
4215
4379
 
4216
4380
  // clean up default value
4217
- if (minDash.get(properties, formJsViewer.VALUES_SOURCES_PATHS[formJsViewer.VALUES_SOURCES.EXPRESSION]) !== undefined || minDash.get(properties, formJsViewer.VALUES_SOURCES_PATHS[formJsViewer.VALUES_SOURCES.INPUT]) !== undefined) {
4381
+ if (minDash.get(properties, formJsViewer.OPTIONS_SOURCES_PATHS[formJsViewer.OPTIONS_SOURCES.EXPRESSION]) !== undefined || minDash.get(properties, formJsViewer.OPTIONS_SOURCES_PATHS[formJsViewer.OPTIONS_SOURCES.INPUT]) !== undefined) {
4218
4382
  newProperties['defaultValue'] = undefined;
4219
4383
  }
4220
4384
  context.properties = {
@@ -4224,23 +4388,85 @@ class ValuesSourceBehavior extends CommandInterceptor {
4224
4388
  }, true);
4225
4389
  }
4226
4390
  }
4227
- ValuesSourceBehavior.$inject = ['eventBus'];
4391
+ OptionsSourceBehavior.$inject = ['eventBus'];
4228
4392
 
4229
4393
  // helper ///////////////////
4230
4394
 
4231
4395
  function isValuesSourceUpdate(properties) {
4232
- return Object.values(formJsViewer.VALUES_SOURCES_PATHS).some(path => {
4396
+ return Object.values(formJsViewer.OPTIONS_SOURCES_PATHS).some(path => {
4233
4397
  return minDash.get(properties, path) !== undefined;
4234
4398
  });
4235
4399
  }
4236
4400
 
4401
+ const COLUMNS_SOURCE_PROPERTIES = {
4402
+ columns: 'columns',
4403
+ columnsExpression: 'columnsExpression'
4404
+ };
4405
+ class ColumnsSourceBehavior extends CommandInterceptor {
4406
+ constructor(eventBus) {
4407
+ super(eventBus);
4408
+ this.preExecute('formField.edit', function (context) {
4409
+ const {
4410
+ properties,
4411
+ oldProperties
4412
+ } = context;
4413
+ const isColumnSourceUpdate = Object.values(COLUMNS_SOURCE_PROPERTIES).some(path => {
4414
+ return minDash.get(properties, [path]) !== undefined;
4415
+ });
4416
+ if (!isColumnSourceUpdate) {
4417
+ return;
4418
+ }
4419
+ const columns = minDash.get(properties, [COLUMNS_SOURCE_PROPERTIES.columns]);
4420
+ const oldColumns = minDash.get(oldProperties, [COLUMNS_SOURCE_PROPERTIES.columns]);
4421
+ const columnsExpression = minDash.get(properties, [COLUMNS_SOURCE_PROPERTIES.columnsExpression]);
4422
+ const oldColumnsExpression = minDash.get(oldProperties, [COLUMNS_SOURCE_PROPERTIES.columnsExpression]);
4423
+ if (minDash.isArray(columns) && !minDash.isDefined(oldColumns)) {
4424
+ context.properties = {
4425
+ ...properties,
4426
+ columnsExpression: undefined
4427
+ };
4428
+ return;
4429
+ }
4430
+ if (minDash.isString(columnsExpression) && !minDash.isString(oldColumnsExpression)) {
4431
+ context.properties = {
4432
+ ...properties,
4433
+ columns: undefined
4434
+ };
4435
+ return;
4436
+ }
4437
+ }, true);
4438
+ }
4439
+ }
4440
+ ColumnsSourceBehavior.$inject = ['eventBus'];
4441
+
4442
+ class TableDataSourceBehavior extends CommandInterceptor {
4443
+ constructor(eventBus) {
4444
+ super(eventBus);
4445
+ this.preExecute('formField.add', function (context) {
4446
+ const {
4447
+ formField
4448
+ } = context;
4449
+ if (minDash.get(formField, ['type']) !== 'table') {
4450
+ return;
4451
+ }
4452
+ context.formField = {
4453
+ ...formField,
4454
+ dataSource: `=${formField.id}`
4455
+ };
4456
+ }, true);
4457
+ }
4458
+ }
4459
+ TableDataSourceBehavior.$inject = ['eventBus'];
4460
+
4237
4461
  var behaviorModule = {
4238
- __init__: ['idBehavior', 'keyBehavior', 'pathBehavior', 'validateBehavior', 'valuesSourceBehavior'],
4462
+ __init__: ['idBehavior', 'keyBehavior', 'pathBehavior', 'validateBehavior', 'optionsSourceBehavior', 'columnsSourceBehavior', 'tableDataSourceBehavior'],
4239
4463
  idBehavior: ['type', IdBehavior],
4240
4464
  keyBehavior: ['type', KeyBehavior],
4241
4465
  pathBehavior: ['type', PathBehavior],
4242
4466
  validateBehavior: ['type', ValidateBehavior],
4243
- valuesSourceBehavior: ['type', ValuesSourceBehavior]
4467
+ optionsSourceBehavior: ['type', OptionsSourceBehavior],
4468
+ columnsSourceBehavior: ['type', ColumnsSourceBehavior],
4469
+ tableDataSourceBehavior: ['type', TableDataSourceBehavior]
4244
4470
  };
4245
4471
 
4246
4472
  /**
@@ -8561,11 +8787,15 @@ function textToLabel(text) {
8561
8787
  }
8562
8788
  return null;
8563
8789
  }
8790
+
8791
+ /**
8792
+ * @param {string} path
8793
+ */
8564
8794
  function isValidDotPath(path) {
8565
8795
  return /^\w+(\.\w+)*$/.test(path);
8566
8796
  }
8567
8797
  const INPUTS = ['checkbox', 'checklist', 'datetime', 'number', 'radio', 'select', 'taglist', 'textfield', 'textarea'];
8568
- const VALUES_INPUTS = ['checklist', 'radio', 'select', 'taglist'];
8798
+ const OPTIONS_INPUTS = ['checklist', 'radio', 'select', 'taglist'];
8569
8799
  function hasEntryConfigured(formFieldDefinition, entryId) {
8570
8800
  const {
8571
8801
  propertiesPanelEntries = []
@@ -8575,7 +8805,7 @@ function hasEntryConfigured(formFieldDefinition, entryId) {
8575
8805
  }
8576
8806
  return propertiesPanelEntries.some(id => id === entryId);
8577
8807
  }
8578
- function hasValuesGroupsConfigured(formFieldDefinition) {
8808
+ function hasOptionsGroupsConfigured(formFieldDefinition) {
8579
8809
  const {
8580
8810
  propertiesPanelEntries = []
8581
8811
  } = formFieldDefinition;
@@ -8585,6 +8815,13 @@ function hasValuesGroupsConfigured(formFieldDefinition) {
8585
8815
  return propertiesPanelEntries.some(id => id === 'values');
8586
8816
  }
8587
8817
 
8818
+ /**
8819
+ * @param {string} path
8820
+ */
8821
+ function hasIntegerPathSegment(path) {
8822
+ return path.split('.').some(segment => /^\d+$/.test(segment));
8823
+ }
8824
+
8588
8825
  function useService (type, strict) {
8589
8826
  const {
8590
8827
  getService
@@ -9090,7 +9327,7 @@ function DefaultOptionEntry(props) {
9090
9327
  function isDefaultVisible(matchers) {
9091
9328
  return field => {
9092
9329
  // Only make default values available when they are statically defined
9093
- if (!INPUTS.includes(type) || VALUES_INPUTS.includes(type) && !field.values) {
9330
+ if (!INPUTS.includes(type) || OPTIONS_INPUTS.includes(type) && !field.values) {
9094
9331
  return false;
9095
9332
  }
9096
9333
  return matchers(field);
@@ -9436,20 +9673,27 @@ function containsSpace(value) {
9436
9673
  function KeyEntry(props) {
9437
9674
  const {
9438
9675
  editField,
9439
- field
9676
+ field,
9677
+ getService
9440
9678
  } = props;
9441
9679
  const entries = [];
9442
9680
  entries.push({
9443
9681
  id: 'key',
9444
- component: Key$1,
9682
+ component: Key$2,
9445
9683
  editField: editField,
9446
9684
  field: field,
9447
9685
  isEdited: isEdited,
9448
- isDefaultVisible: field => INPUTS.includes(field.type)
9686
+ isDefaultVisible: field => {
9687
+ const formFields = getService('formFields');
9688
+ const {
9689
+ config
9690
+ } = formFields.get(field.type);
9691
+ return config.keyed;
9692
+ }
9449
9693
  });
9450
9694
  return entries;
9451
9695
  }
9452
- function Key$1(props) {
9696
+ function Key$2(props) {
9453
9697
  const {
9454
9698
  editField,
9455
9699
  field,
@@ -9471,14 +9715,13 @@ function Key$1(props) {
9471
9715
  if (value === field.key) {
9472
9716
  return null;
9473
9717
  }
9474
- if (minDash.isUndefined(value) || !value.length) {
9718
+ if (!minDash.isString(value) || value.length === 0) {
9475
9719
  return 'Must not be empty.';
9476
9720
  }
9477
- if (value && !isValidDotPath(value)) {
9721
+ if (!isValidDotPath(value)) {
9478
9722
  return 'Must be a variable or a dot separated path.';
9479
9723
  }
9480
- const hasIntegerPathSegment = value.split('.').some(segment => /^\d+$/.test(segment));
9481
- if (hasIntegerPathSegment) {
9724
+ if (hasIntegerPathSegment(value)) {
9482
9725
  return 'Must not contain numerical path segments.';
9483
9726
  }
9484
9727
  const replacements = {
@@ -9491,8 +9734,14 @@ function Key$1(props) {
9491
9734
 
9492
9735
  // unclaim temporarily to avoid self-conflicts
9493
9736
  pathRegistry.unclaimPath(oldPath);
9494
- const canClaim = pathRegistry.canClaimPath(newPath, true);
9495
- pathRegistry.claimPath(oldPath, true);
9737
+ const canClaim = pathRegistry.canClaimPath(newPath, {
9738
+ isClosed: true,
9739
+ claimerId: field.id
9740
+ });
9741
+ pathRegistry.claimPath(oldPath, {
9742
+ isClosed: true,
9743
+ claimerId: field.id
9744
+ });
9496
9745
  return canClaim ? null : 'Must not conflict with other key/path assignments.';
9497
9746
  };
9498
9747
  return TextfieldEntry({
@@ -9511,13 +9760,15 @@ function Key$1(props) {
9511
9760
  function PathEntry(props) {
9512
9761
  const {
9513
9762
  editField,
9514
- field
9763
+ field,
9764
+ getService
9515
9765
  } = props;
9516
9766
  const {
9517
9767
  type
9518
9768
  } = field;
9519
9769
  const entries = [];
9520
- if (type === 'group') {
9770
+ const formFieldDefinition = getService('formFields').get(type);
9771
+ if (formFieldDefinition && formFieldDefinition.config.pathed) {
9521
9772
  entries.push({
9522
9773
  id: 'path',
9523
9774
  component: Path,
@@ -9536,6 +9787,8 @@ function Path(props) {
9536
9787
  } = props;
9537
9788
  const debounce = useService('debounce');
9538
9789
  const pathRegistry = useService('pathRegistry');
9790
+ const fieldConfig = useService('formFields').get(field.type).config;
9791
+ const isRepeating = fieldConfig.repeatable && field.isRepeating;
9539
9792
  const path = ['path'];
9540
9793
  const getValue = () => {
9541
9794
  return minDash.get(field, path, '');
@@ -9547,32 +9800,53 @@ function Path(props) {
9547
9800
  return editField(field, path, value);
9548
9801
  };
9549
9802
  const validate = value => {
9550
- if (!value || value === field.path) {
9803
+ if (!value && isRepeating) {
9804
+ return 'Must not be empty';
9805
+ }
9806
+
9807
+ // Early return for empty value in non-repeating cases or if the field path hasn't changed
9808
+ if (!value && !isRepeating || value === field.path) {
9551
9809
  return null;
9552
9810
  }
9553
- if (value && !isValidDotPath(value)) {
9554
- return 'Must be empty, a variable or a dot separated path';
9811
+
9812
+ // Validate dot-separated path format
9813
+ if (!isValidDotPath(value)) {
9814
+ const msg = isRepeating ? 'Must be a variable or a dot-separated path' : 'Must be empty, a variable or a dot-separated path';
9815
+ return msg;
9555
9816
  }
9556
- const hasIntegerPathSegment = value && value.split('.').some(segment => /^\d+$/.test(segment));
9817
+
9818
+ // Check for integer segments in the path
9819
+ const hasIntegerPathSegment = value.split('.').some(segment => /^\d+$/.test(segment));
9557
9820
  if (hasIntegerPathSegment) {
9558
9821
  return 'Must not contain numerical path segments.';
9559
9822
  }
9560
- const options = value && {
9823
+
9824
+ // Check for path collisions
9825
+ const options = {
9561
9826
  replacements: {
9562
- [field.id]: [value]
9827
+ [field.id]: value.split('.')
9563
9828
  }
9564
- } || {};
9829
+ };
9565
9830
  const canClaim = pathRegistry.executeRecursivelyOnFields(field, ({
9566
9831
  field,
9567
- isClosed
9832
+ isClosed,
9833
+ isRepeatable
9568
9834
  }) => {
9569
9835
  const path = pathRegistry.getValuePath(field, options);
9570
- return pathRegistry.canClaimPath(path, isClosed);
9836
+ return pathRegistry.canClaimPath(path, {
9837
+ isClosed,
9838
+ isRepeatable,
9839
+ claimerId: field.id
9840
+ });
9571
9841
  });
9572
9842
  if (!canClaim) {
9573
- return 'Must not cause two binding paths to colide';
9843
+ return 'Must not cause two binding paths to collide';
9574
9844
  }
9845
+
9846
+ // If all checks pass
9847
+ return null;
9575
9848
  };
9849
+ const tooltip = isRepeating ? 'Routes the children of this component into a form variable, may be left empty to route at the root level.' : 'Routes the children of this component into a form variable.';
9576
9850
  return TextfieldEntry({
9577
9851
  debounce,
9578
9852
  description: 'Where the child variables of this component are pathed to.',
@@ -9580,7 +9854,7 @@ function Path(props) {
9580
9854
  getValue,
9581
9855
  id,
9582
9856
  label: 'Path',
9583
- tooltip: 'Routes the children of this component into a form variable, may be left empty to route at the root level.',
9857
+ tooltip,
9584
9858
  setValue,
9585
9859
  validate
9586
9860
  });
@@ -9637,37 +9911,141 @@ const SimpleBoolComponent = props => {
9637
9911
  });
9638
9912
  };
9639
9913
 
9640
- function GroupEntries(props) {
9914
+ function simpleSelectEntryFactory(options) {
9641
9915
  const {
9916
+ id,
9917
+ label,
9918
+ path,
9919
+ props,
9920
+ optionsArray
9921
+ } = options;
9922
+ const {
9923
+ editField,
9642
9924
  field
9643
9925
  } = props;
9644
- const {
9645
- type
9646
- } = field;
9647
- if (type !== 'group') {
9648
- return [];
9649
- }
9650
- const entries = [simpleBoolEntryFactory({
9651
- id: 'showOutline',
9652
- path: ['showOutline'],
9653
- label: 'Show outline',
9654
- props
9655
- })];
9656
- return entries;
9926
+ return {
9927
+ id,
9928
+ label,
9929
+ path,
9930
+ field,
9931
+ editField,
9932
+ optionsArray,
9933
+ component: SimpleSelectComponent,
9934
+ isEdited: isEdited$3
9935
+ };
9657
9936
  }
9658
-
9659
- function LabelEntry(props) {
9937
+ const SimpleSelectComponent = props => {
9660
9938
  const {
9939
+ id,
9940
+ label,
9941
+ path,
9661
9942
  field,
9662
- editField
9663
- } = props;
9664
- const entries = [];
9665
- entries.push({
9666
- id: 'date-label',
9667
- component: DateLabel,
9668
9943
  editField,
9669
- field,
9670
- isEdited: isEdited$6,
9944
+ optionsArray
9945
+ } = props;
9946
+ const getValue = () => minDash.get(field, path, '');
9947
+ const setValue = value => editField(field, path, value);
9948
+ const getOptions = () => optionsArray;
9949
+ return SelectEntry({
9950
+ label,
9951
+ element: field,
9952
+ getOptions,
9953
+ getValue,
9954
+ id,
9955
+ setValue
9956
+ });
9957
+ };
9958
+
9959
+ function simpleRangeIntegerEntryFactory(options) {
9960
+ const {
9961
+ id,
9962
+ label,
9963
+ path,
9964
+ props,
9965
+ min,
9966
+ max,
9967
+ defaultValue
9968
+ } = options;
9969
+ const {
9970
+ editField,
9971
+ field
9972
+ } = props;
9973
+ return {
9974
+ id,
9975
+ label,
9976
+ path,
9977
+ field,
9978
+ editField,
9979
+ min,
9980
+ max,
9981
+ defaultValue,
9982
+ component: SimpleRangeIntegerEntry,
9983
+ isEdited: isEdited$7
9984
+ };
9985
+ }
9986
+ const SimpleRangeIntegerEntry = props => {
9987
+ const {
9988
+ id,
9989
+ label,
9990
+ path,
9991
+ field,
9992
+ editField,
9993
+ min,
9994
+ max,
9995
+ defaultValue
9996
+ } = props;
9997
+ const debounce = useService('debounce');
9998
+ const getValue = () => {
9999
+ const value = minDash.get(field, path, defaultValue);
10000
+ return Number.isInteger(value) ? value : defaultValue;
10001
+ };
10002
+ const setValue = value => {
10003
+ editField(field, path, value);
10004
+ };
10005
+ return NumberFieldEntry({
10006
+ debounce,
10007
+ label,
10008
+ element: field,
10009
+ step: 1,
10010
+ min,
10011
+ max,
10012
+ getValue,
10013
+ id,
10014
+ setValue
10015
+ });
10016
+ };
10017
+
10018
+ function GroupAppearanceEntry(props) {
10019
+ const {
10020
+ field
10021
+ } = props;
10022
+ const {
10023
+ type
10024
+ } = field;
10025
+ if (!['group', 'dynamiclist'].includes(type)) {
10026
+ return [];
10027
+ }
10028
+ const entries = [simpleBoolEntryFactory({
10029
+ id: 'showOutline',
10030
+ path: ['showOutline'],
10031
+ label: 'Show outline',
10032
+ props
10033
+ })];
10034
+ return entries;
10035
+ }
10036
+
10037
+ function LabelEntry(props) {
10038
+ const {
10039
+ field,
10040
+ editField
10041
+ } = props;
10042
+ const entries = [];
10043
+ entries.push({
10044
+ id: 'date-label',
10045
+ component: DateLabel,
10046
+ editField,
10047
+ field,
10048
+ isEdited: isEdited$6,
9671
10049
  isDefaultVisible: function (field) {
9672
10050
  return field.type === 'datetime' && (field.subtype === formJsViewer.DATETIME_SUBTYPES.DATE || field.subtype === formJsViewer.DATETIME_SUBTYPES.DATETIME);
9673
10051
  }
@@ -9684,15 +10062,15 @@ function LabelEntry(props) {
9684
10062
  });
9685
10063
  entries.push({
9686
10064
  id: 'label',
9687
- component: Label$1,
10065
+ component: Label$2,
9688
10066
  editField,
9689
10067
  field,
9690
10068
  isEdited: isEdited$6,
9691
- isDefaultVisible: field => INPUTS.includes(field.type) || field.type === 'button' || field.type === 'group' || field.type === 'iframe'
10069
+ isDefaultVisible: field => [...INPUTS, 'button', 'group', 'table', 'iframe', 'dynamiclist'].includes(field.type)
9692
10070
  });
9693
10071
  return entries;
9694
10072
  }
9695
- function Label$1(props) {
10073
+ function Label$2(props) {
9696
10074
  const {
9697
10075
  editField,
9698
10076
  field,
@@ -9709,7 +10087,7 @@ function Label$1(props) {
9709
10087
  const setValue = value => {
9710
10088
  return editField(field, path, value || '');
9711
10089
  };
9712
- const label = getLabelText(field);
10090
+ const label = getLabelText(field.type);
9713
10091
  return FeelTemplatingEntry({
9714
10092
  debounce,
9715
10093
  element: field,
@@ -9780,17 +10158,22 @@ function TimeLabel(props) {
9780
10158
 
9781
10159
  // helpers //////////
9782
10160
 
9783
- function getLabelText(field) {
9784
- const {
9785
- type
9786
- } = field;
9787
- if (type === 'group') {
9788
- return 'Group label';
9789
- }
9790
- if (type === 'iframe') {
9791
- return 'Title';
10161
+ /**
10162
+ * @param {string} type
10163
+ * @returns {string}
10164
+ */
10165
+ function getLabelText(type) {
10166
+ switch (type) {
10167
+ case 'group':
10168
+ case 'dynamiclist':
10169
+ return 'Group label';
10170
+ case 'table':
10171
+ return 'Table label';
10172
+ case 'iframe':
10173
+ return 'Title';
10174
+ default:
10175
+ return 'Field label';
9792
10176
  }
9793
- return 'Field label';
9794
10177
  }
9795
10178
 
9796
10179
  function HeightEntry(props) {
@@ -9945,7 +10328,7 @@ function SourceEntry(props) {
9945
10328
  const entries = [];
9946
10329
  entries.push({
9947
10330
  id: 'source',
9948
- component: Source,
10331
+ component: Source$1,
9949
10332
  editField: editField,
9950
10333
  field: field,
9951
10334
  isEdited: isEdited$6,
@@ -9953,7 +10336,7 @@ function SourceEntry(props) {
9953
10336
  });
9954
10337
  return entries;
9955
10338
  }
9956
- function Source(props) {
10339
+ function Source$1(props) {
9957
10340
  const {
9958
10341
  editField,
9959
10342
  field,
@@ -9998,18 +10381,6 @@ function TextEntry(props) {
9998
10381
  isEdited: isEdited$6,
9999
10382
  isDefaultVisible: field => field.type === 'text'
10000
10383
  }];
10001
-
10002
- // todo: skipped to make the release without too much risk
10003
- // if (templating.isTemplate(field.text)) {
10004
- // entries.push(simpleBoolEntryFactory({
10005
- // id: 'strict',
10006
- // path: [ 'strict' ],
10007
- // label: 'Strict templating',
10008
- // description: 'Enforces types to be correct',
10009
- // props
10010
- // }));
10011
- // }
10012
-
10013
10384
  return entries;
10014
10385
  }
10015
10386
  function Text(props) {
@@ -10437,7 +10808,7 @@ function ValueEntry(props) {
10437
10808
  validateFactory
10438
10809
  } = props;
10439
10810
  const entries = [{
10440
- component: Label,
10811
+ component: Label$1,
10441
10812
  editField,
10442
10813
  field,
10443
10814
  id: idPrefix + '-label',
@@ -10455,7 +10826,7 @@ function ValueEntry(props) {
10455
10826
  }];
10456
10827
  return entries;
10457
10828
  }
10458
- function Label(props) {
10829
+ function Label$1(props) {
10459
10830
  const {
10460
10831
  editField,
10461
10832
  field,
@@ -10523,7 +10894,7 @@ function CustomValueEntry(props) {
10523
10894
  validateFactory
10524
10895
  } = props;
10525
10896
  const entries = [{
10526
- component: Key,
10897
+ component: Key$1,
10527
10898
  editField,
10528
10899
  field,
10529
10900
  id: idPrefix + '-key',
@@ -10541,7 +10912,7 @@ function CustomValueEntry(props) {
10541
10912
  }];
10542
10913
  return entries;
10543
10914
  }
10544
- function Key(props) {
10915
+ function Key$1(props) {
10545
10916
  const {
10546
10917
  editField,
10547
10918
  field,
@@ -10666,7 +11037,7 @@ function AutoFocusSelectEntry(props) {
10666
11037
  });
10667
11038
  }
10668
11039
 
10669
- function ValuesSourceSelectEntry(props) {
11040
+ function OptionsSourceSelectEntry(props) {
10670
11041
  const {
10671
11042
  editField,
10672
11043
  field,
@@ -10686,25 +11057,25 @@ function ValuesSourceSelect(props) {
10686
11057
  field,
10687
11058
  id
10688
11059
  } = props;
10689
- const getValue = formJsViewer.getValuesSource;
11060
+ const getValue = formJsViewer.getOptionsSource;
10690
11061
  const setValue = value => {
10691
11062
  let newField = field;
10692
11063
  const newProperties = {};
10693
- newProperties[formJsViewer.VALUES_SOURCES_PATHS[value]] = formJsViewer.VALUES_SOURCES_DEFAULTS[value];
11064
+ newProperties[formJsViewer.OPTIONS_SOURCES_PATHS[value]] = formJsViewer.OPTIONS_SOURCES_DEFAULTS[value];
10694
11065
  newField = editField(field, newProperties);
10695
11066
  return newField;
10696
11067
  };
10697
- const getValuesSourceOptions = () => {
10698
- return Object.values(formJsViewer.VALUES_SOURCES).map(valueSource => ({
10699
- label: formJsViewer.VALUES_SOURCES_LABELS[valueSource],
11068
+ const getOptionsSourceOptions = () => {
11069
+ return Object.values(formJsViewer.OPTIONS_SOURCES).map(valueSource => ({
11070
+ label: formJsViewer.OPTIONS_SOURCES_LABELS[valueSource],
10700
11071
  value: valueSource
10701
11072
  }));
10702
11073
  };
10703
11074
  return AutoFocusSelectEntry({
10704
- autoFocusEntry: getAutoFocusEntryId(field),
11075
+ autoFocusEntry: getAutoFocusEntryId$1(field),
10705
11076
  label: 'Type',
10706
11077
  element: field,
10707
- getOptions: getValuesSourceOptions,
11078
+ getOptions: getOptionsSourceOptions,
10708
11079
  getValue,
10709
11080
  id,
10710
11081
  setValue
@@ -10713,19 +11084,19 @@ function ValuesSourceSelect(props) {
10713
11084
 
10714
11085
  // helpers //////////
10715
11086
 
10716
- function getAutoFocusEntryId(field) {
10717
- const valuesSource = formJsViewer.getValuesSource(field);
10718
- if (valuesSource === formJsViewer.VALUES_SOURCES.EXPRESSION) {
10719
- return `${field.id}-valuesExpression-expression`;
10720
- } else if (valuesSource === formJsViewer.VALUES_SOURCES.INPUT) {
10721
- return `${field.id}-dynamicValues-key`;
10722
- } else if (valuesSource === formJsViewer.VALUES_SOURCES.STATIC) {
10723
- return `${field.id}-staticValues-0-label`;
11087
+ function getAutoFocusEntryId$1(field) {
11088
+ const valuesSource = formJsViewer.getOptionsSource(field);
11089
+ if (valuesSource === formJsViewer.OPTIONS_SOURCES.EXPRESSION) {
11090
+ return 'optionsExpression-expression';
11091
+ } else if (valuesSource === formJsViewer.OPTIONS_SOURCES.INPUT) {
11092
+ return 'dynamicOptions-key';
11093
+ } else if (valuesSource === formJsViewer.OPTIONS_SOURCES.STATIC) {
11094
+ return 'staticOptions-0-label';
10724
11095
  }
10725
11096
  return null;
10726
11097
  }
10727
11098
 
10728
- function InputKeyValuesSourceEntry(props) {
11099
+ function InputKeyOptionsSourceEntry(props) {
10729
11100
  const {
10730
11101
  editField,
10731
11102
  field,
@@ -10746,7 +11117,7 @@ function InputValuesKey(props) {
10746
11117
  id
10747
11118
  } = props;
10748
11119
  const debounce = useService('debounce');
10749
- const path = formJsViewer.VALUES_SOURCES_PATHS[formJsViewer.VALUES_SOURCES.INPUT];
11120
+ const path = formJsViewer.OPTIONS_SOURCES_PATHS[formJsViewer.OPTIONS_SOURCES.INPUT];
10750
11121
  const schema = '[\n {\n "label": "dollar",\n "value": "$"\n }\n]';
10751
11122
  const tooltip = jsxRuntime.jsxs("div", {
10752
11123
  children: ["The input property may be an array of simple values or alternatively follow this schema:", jsxRuntime.jsx("pre", {
@@ -10784,7 +11155,7 @@ function InputValuesKey(props) {
10784
11155
  });
10785
11156
  }
10786
11157
 
10787
- function StaticValuesSourceEntry(props) {
11158
+ function StaticOptionsSourceEntry(props) {
10788
11159
  const {
10789
11160
  editField,
10790
11161
  field,
@@ -10797,10 +11168,10 @@ function StaticValuesSourceEntry(props) {
10797
11168
  e.stopPropagation();
10798
11169
  const index = values.length + 1;
10799
11170
  const entry = getIndexedEntry(index, values);
10800
- editField(field, formJsViewer.VALUES_SOURCES_PATHS[formJsViewer.VALUES_SOURCES.STATIC], arrayAdd(values, values.length, entry));
11171
+ editField(field, formJsViewer.OPTIONS_SOURCES_PATHS[formJsViewer.OPTIONS_SOURCES.STATIC], arrayAdd(values, values.length, entry));
10801
11172
  };
10802
11173
  const removeEntry = entry => {
10803
- editField(field, formJsViewer.VALUES_SOURCES_PATHS[formJsViewer.VALUES_SOURCES.STATIC], minDash.without(values, entry));
11174
+ editField(field, formJsViewer.OPTIONS_SOURCES_PATHS[formJsViewer.OPTIONS_SOURCES.STATIC], minDash.without(values, entry));
10804
11175
  };
10805
11176
  const validateFactory = (key, getValue) => {
10806
11177
  return value => {
@@ -10990,6 +11361,76 @@ function Readonly(props) {
10990
11361
  });
10991
11362
  }
10992
11363
 
11364
+ function LayouterAppearanceEntry(props) {
11365
+ const {
11366
+ field
11367
+ } = props;
11368
+ if (!['group', 'dynamiclist'].includes(field.type)) {
11369
+ return [];
11370
+ }
11371
+ const entries = [simpleSelectEntryFactory({
11372
+ id: 'verticalAlignment',
11373
+ path: ['verticalAlignment'],
11374
+ label: 'Vertical alignment',
11375
+ optionsArray: [{
11376
+ value: 'start',
11377
+ label: 'Top'
11378
+ }, {
11379
+ value: 'center',
11380
+ label: 'Center'
11381
+ }, {
11382
+ value: 'end',
11383
+ label: 'Bottom'
11384
+ }],
11385
+ props
11386
+ })];
11387
+ return entries;
11388
+ }
11389
+
11390
+ function RepeatableEntry(props) {
11391
+ const {
11392
+ field,
11393
+ getService
11394
+ } = props;
11395
+ const {
11396
+ type
11397
+ } = field;
11398
+ const formFieldDefinition = getService('formFields').get(type);
11399
+ if (!formFieldDefinition || !formFieldDefinition.config.repeatable) {
11400
+ return [];
11401
+ }
11402
+ const entries = [simpleRangeIntegerEntryFactory({
11403
+ id: 'defaultRepetitions',
11404
+ path: ['defaultRepetitions'],
11405
+ label: 'Default number of items',
11406
+ min: 0,
11407
+ max: 20,
11408
+ props
11409
+ }), simpleBoolEntryFactory({
11410
+ id: 'allowAddRemove',
11411
+ path: ['allowAddRemove'],
11412
+ label: 'Allow add/delete items',
11413
+ props
11414
+ }), simpleBoolEntryFactory({
11415
+ id: 'disableCollapse',
11416
+ path: ['disableCollapse'],
11417
+ label: 'Disable collapse',
11418
+ props
11419
+ })];
11420
+ if (!field.disableCollapse) {
11421
+ const nonCollapseItemsEntry = simpleRangeIntegerEntryFactory({
11422
+ id: 'nonCollapsedItems',
11423
+ path: ['nonCollapsedItems'],
11424
+ label: 'Number of non-collapsing items',
11425
+ min: 1,
11426
+ defaultValue: 5,
11427
+ props
11428
+ });
11429
+ entries.push(nonCollapseItemsEntry);
11430
+ }
11431
+ return entries;
11432
+ }
11433
+
10993
11434
  function ConditionEntry(props) {
10994
11435
  const {
10995
11436
  editField,
@@ -11038,7 +11479,7 @@ function Condition(props) {
11038
11479
  });
11039
11480
  }
11040
11481
 
11041
- function ValuesExpressionEntry(props) {
11482
+ function OptionsExpressionEntry(props) {
11042
11483
  const {
11043
11484
  editField,
11044
11485
  field,
@@ -11046,13 +11487,13 @@ function ValuesExpressionEntry(props) {
11046
11487
  } = props;
11047
11488
  return [{
11048
11489
  id: id + '-expression',
11049
- component: ValuesExpression,
11490
+ component: OptionsExpression,
11050
11491
  isEdited: isEdited$6,
11051
11492
  editField,
11052
11493
  field
11053
11494
  }];
11054
11495
  }
11055
- function ValuesExpression(props) {
11496
+ function OptionsExpression(props) {
11056
11497
  const {
11057
11498
  editField,
11058
11499
  field,
@@ -11062,7 +11503,7 @@ function ValuesExpression(props) {
11062
11503
  const variables = useVariables().map(name => ({
11063
11504
  name
11064
11505
  }));
11065
- const path = formJsViewer.VALUES_SOURCES_PATHS[formJsViewer.VALUES_SOURCES.EXPRESSION];
11506
+ const path = formJsViewer.OPTIONS_SOURCES_PATHS[formJsViewer.OPTIONS_SOURCES.EXPRESSION];
11066
11507
  const schema = '[\n {\n "label": "dollar",\n "value": "$"\n }\n]';
11067
11508
  const tooltip = jsxRuntime.jsxs("div", {
11068
11509
  children: ["The expression may result in an array of simple values or alternatively follow this schema:", jsxRuntime.jsx("pre", {
@@ -11087,97 +11528,620 @@ function ValuesExpression(props) {
11087
11528
  });
11088
11529
  }
11089
11530
 
11090
- function GeneralGroup(field, editField, getService) {
11091
- const entries = [...IdEntry({
11092
- field,
11093
- editField
11094
- }), ...LabelEntry({
11095
- field,
11096
- editField
11097
- }), ...DescriptionEntry({
11098
- field,
11099
- editField
11100
- }), ...KeyEntry({
11101
- field,
11102
- editField
11103
- }), ...PathEntry({
11104
- field,
11105
- editField
11106
- }), ...GroupEntries({
11107
- field,
11108
- editField
11109
- }), ...DefaultOptionEntry({
11110
- field,
11111
- editField
11112
- }), ...ActionEntry({
11113
- field,
11114
- editField
11115
- }), ...DateTimeEntry({
11116
- field,
11117
- editField
11118
- }), ...TextEntry({
11119
- field,
11531
+ function TableDataSourceEntry(props) {
11532
+ const {
11120
11533
  editField,
11121
- getService
11122
- }), ...IFrameUrlEntry({
11123
- field,
11124
- editField
11125
- }), ...IFrameHeightEntry({
11126
- field,
11127
- editField
11128
- }), ...HeightEntry({
11129
- field,
11130
- editField
11131
- }), ...NumberEntries({
11132
- field,
11133
- editField
11134
- }), ...SourceEntry({
11135
- field,
11136
- editField
11137
- }), ...AltTextEntry({
11138
- field,
11139
- editField
11140
- }), ...SelectEntries({
11141
- field,
11142
- editField
11143
- }), ...DisabledEntry({
11144
- field,
11145
- editField
11146
- }), ...ReadonlyEntry({
11147
- field,
11148
- editField
11149
- })];
11150
- if (entries.length === 0) {
11151
- return null;
11152
- }
11153
- return {
11154
- id: 'general',
11155
- label: 'General',
11156
- entries
11157
- };
11534
+ field
11535
+ } = props;
11536
+ const entries = [];
11537
+ entries.push({
11538
+ id: 'dataSource',
11539
+ component: Source,
11540
+ editField: editField,
11541
+ field: field,
11542
+ isEdited: isEdited$6,
11543
+ isDefaultVisible: field => field.type === 'table'
11544
+ });
11545
+ return entries;
11158
11546
  }
11159
-
11160
- function SerializationGroup(field, editField) {
11161
- const entries = [...NumberSerializationEntry({
11162
- field,
11163
- editField
11164
- }), ...DateTimeFormatEntry({
11547
+ function Source(props) {
11548
+ const {
11549
+ editField,
11165
11550
  field,
11166
- editField
11167
- })];
11168
- if (!entries.length) {
11169
- return null;
11170
- }
11171
- return {
11172
- id: 'serialization',
11173
- label: 'Serialization',
11174
- entries
11551
+ id
11552
+ } = props;
11553
+ const debounce = useService('debounce');
11554
+ const variables = useVariables().map(name => ({
11555
+ name
11556
+ }));
11557
+ const path = ['dataSource'];
11558
+ const getValue = () => {
11559
+ return minDash.get(field, path, field.id);
11175
11560
  };
11176
- }
11177
-
11178
- function ConstraintsGroup(field, editField) {
11179
- const entries = [...DateTimeConstraintsEntry({
11180
- field,
11561
+ const setValue = (value, error) => {
11562
+ if (error) {
11563
+ return;
11564
+ }
11565
+ editField(field, path, value);
11566
+ };
11567
+
11568
+ /**
11569
+ * @param {string|void} value
11570
+ * @returns {string|null}
11571
+ */
11572
+ const validate = value => {
11573
+ if (!minDash.isString(value) || value.length === 0) {
11574
+ return 'Must not be empty.';
11575
+ }
11576
+ if (value.startsWith('=')) {
11577
+ return null;
11578
+ }
11579
+ if (!isValidDotPath(value)) {
11580
+ return 'Must be a variable or a dot separated path.';
11581
+ }
11582
+ if (hasIntegerPathSegment(value)) {
11583
+ return 'Must not contain numerical path segments.';
11584
+ }
11585
+ return null;
11586
+ };
11587
+ return FeelTemplatingEntry({
11588
+ debounce,
11589
+ description: 'Specify the source from which to populate the table',
11590
+ element: field,
11591
+ feel: 'required',
11592
+ getValue,
11593
+ id,
11594
+ label: 'Data source',
11595
+ tooltip: 'Enter a form input variable that contains the data for the table or define an expression to populate the data dynamically.',
11596
+ setValue,
11597
+ singleLine: true,
11598
+ variables,
11599
+ validate
11600
+ });
11601
+ }
11602
+
11603
+ function PaginationEntry(props) {
11604
+ const {
11605
+ editField,
11606
+ field
11607
+ } = props;
11608
+ const entries = [];
11609
+ entries.push({
11610
+ id: 'pagination',
11611
+ component: Pagination,
11612
+ editField: editField,
11613
+ field: field,
11614
+ isEdited: isEdited$8,
11615
+ isDefaultVisible: field => field.type === 'table'
11616
+ });
11617
+ return entries;
11618
+ }
11619
+ function Pagination(props) {
11620
+ const {
11621
+ editField,
11622
+ field,
11623
+ id
11624
+ } = props;
11625
+ const defaultRowCount = 10;
11626
+ const path = ['rowCount'];
11627
+ const getValue = () => {
11628
+ return minDash.isNumber(minDash.get(field, path));
11629
+ };
11630
+
11631
+ /**
11632
+ * @param {boolean} value
11633
+ */
11634
+ const setValue = value => {
11635
+ value ? editField(field, path, defaultRowCount) : editField(field, path, undefined);
11636
+ };
11637
+ return ToggleSwitchEntry({
11638
+ element: field,
11639
+ getValue,
11640
+ id,
11641
+ label: 'Pagination',
11642
+ inline: true,
11643
+ setValue
11644
+ });
11645
+ }
11646
+
11647
+ const path$2 = ['rowCount'];
11648
+ function RowCountEntry(props) {
11649
+ const {
11650
+ editField,
11651
+ field
11652
+ } = props;
11653
+ const entries = [];
11654
+ entries.push({
11655
+ id: 'rowCount',
11656
+ component: RowCount,
11657
+ isEdited: isEdited$7,
11658
+ editField,
11659
+ field,
11660
+ isDefaultVisible: field => field.type === 'table' && minDash.isNumber(minDash.get(field, path$2))
11661
+ });
11662
+ return entries;
11663
+ }
11664
+ function RowCount(props) {
11665
+ const {
11666
+ editField,
11667
+ field,
11668
+ id
11669
+ } = props;
11670
+ const debounce = useService('debounce');
11671
+ const getValue = () => minDash.get(field, path$2);
11672
+
11673
+ /**
11674
+ * @param {number|void} value
11675
+ * @param {string|null} error
11676
+ * @returns {void}
11677
+ */
11678
+ const setValue = (value, error) => {
11679
+ if (error) {
11680
+ return;
11681
+ }
11682
+ editField(field, path$2, value);
11683
+ };
11684
+
11685
+ /**
11686
+ * @param {string|void} value
11687
+ * @returns {string|null}
11688
+ */
11689
+ const validate = value => {
11690
+ if (minDash.isNil(value)) {
11691
+ return null;
11692
+ }
11693
+ if (!minDash.isNumber(value)) {
11694
+ return 'Must be number';
11695
+ }
11696
+ if (!Number.isInteger(value)) {
11697
+ return 'Should be an integer.';
11698
+ }
11699
+ if (value < 1) {
11700
+ return 'Should be greater than zero.';
11701
+ }
11702
+ return null;
11703
+ };
11704
+ return NumberFieldEntry({
11705
+ debounce,
11706
+ label: 'Number of rows per page',
11707
+ element: field,
11708
+ id,
11709
+ getValue,
11710
+ setValue,
11711
+ validate
11712
+ });
11713
+ }
11714
+
11715
+ const OPTIONS = {
11716
+ static: {
11717
+ label: 'List of items',
11718
+ value: 'static'
11719
+ },
11720
+ expression: {
11721
+ label: 'Expression',
11722
+ value: 'expression'
11723
+ }
11724
+ };
11725
+ const SELECT_OPTIONS = Object.values(OPTIONS);
11726
+ const COLUMNS_PATH = ['columns'];
11727
+ const COLUMNS_EXPRESSION_PATH = ['columnsExpression'];
11728
+ function HeadersSourceSelectEntry(props) {
11729
+ const {
11730
+ editField,
11731
+ field,
11732
+ id
11733
+ } = props;
11734
+ return [{
11735
+ id: id + '-select',
11736
+ component: HeadersSourceSelect,
11737
+ isEdited: isEdited$3,
11738
+ editField,
11739
+ field
11740
+ }];
11741
+ }
11742
+ function HeadersSourceSelect(props) {
11743
+ const {
11744
+ editField,
11745
+ field,
11746
+ id
11747
+ } = props;
11748
+
11749
+ /**
11750
+ * @returns {string|void}
11751
+ */
11752
+ const getValue = () => {
11753
+ const columns = minDash.get(field, COLUMNS_PATH);
11754
+ const columnsExpression = minDash.get(field, COLUMNS_EXPRESSION_PATH);
11755
+ if (minDash.isString(columnsExpression)) {
11756
+ return OPTIONS.expression.value;
11757
+ }
11758
+ if (minDash.isArray(columns)) {
11759
+ return OPTIONS.static.value;
11760
+ }
11761
+ };
11762
+
11763
+ /**
11764
+ * @param {string|void} value
11765
+ */
11766
+ const setValue = value => {
11767
+ switch (value) {
11768
+ case OPTIONS.static.value:
11769
+ editField(field, {
11770
+ columns: [{
11771
+ label: 'Column',
11772
+ key: 'inputVariable'
11773
+ }]
11774
+ });
11775
+ break;
11776
+ case OPTIONS.expression.value:
11777
+ editField(field, {
11778
+ columnsExpression: '='
11779
+ });
11780
+ break;
11781
+ }
11782
+ };
11783
+ const getValuesSourceOptions = () => {
11784
+ return SELECT_OPTIONS;
11785
+ };
11786
+ return AutoFocusSelectEntry({
11787
+ autoFocusEntry: getAutoFocusEntryId(field),
11788
+ label: 'Type',
11789
+ element: field,
11790
+ getOptions: getValuesSourceOptions,
11791
+ getValue,
11792
+ id,
11793
+ setValue
11794
+ });
11795
+ }
11796
+
11797
+ // helpers //////////
11798
+
11799
+ function getAutoFocusEntryId(field) {
11800
+ const columns = minDash.get(field, COLUMNS_PATH);
11801
+ const columnsExpression = minDash.get(field, COLUMNS_EXPRESSION_PATH);
11802
+ if (minDash.isString(columnsExpression)) {
11803
+ return `${field.id}-columnsExpression`;
11804
+ }
11805
+ if (minDash.isArray(columns)) {
11806
+ return `${field.id}-columns-0-label`;
11807
+ }
11808
+ return null;
11809
+ }
11810
+
11811
+ const PATH = ['columnsExpression'];
11812
+ function ColumnsExpressionEntry(props) {
11813
+ const {
11814
+ editField,
11815
+ field
11816
+ } = props;
11817
+ const entries = [];
11818
+ entries.push({
11819
+ id: `${field.id}-columnsExpression`,
11820
+ component: ColumnsExpression,
11821
+ editField: editField,
11822
+ field: field,
11823
+ isEdited: isEdited$6,
11824
+ isDefaultVisible: field => field.type === 'table' && minDash.isString(minDash.get(field, PATH))
11825
+ });
11826
+ return entries;
11827
+ }
11828
+ function ColumnsExpression(props) {
11829
+ const {
11830
+ editField,
11831
+ field,
11832
+ id
11833
+ } = props;
11834
+ const debounce = useService('debounce');
11835
+ const variables = useVariables().map(name => ({
11836
+ name
11837
+ }));
11838
+ const getValue = () => {
11839
+ return minDash.get(field, PATH);
11840
+ };
11841
+
11842
+ /**
11843
+ * @param {string|void} value
11844
+ * @param {string|void} error
11845
+ * @returns {void}
11846
+ */
11847
+ const setValue = (value, error) => {
11848
+ if (error) {
11849
+ return;
11850
+ }
11851
+ editField(field, PATH, value);
11852
+ };
11853
+
11854
+ /**
11855
+ * @param {string|void} value
11856
+ * @returns {string|null}
11857
+ */
11858
+ const validate = value => {
11859
+ if (!minDash.isString(value) || value.length === 0 || value === '=') {
11860
+ return 'Must not be empty.';
11861
+ }
11862
+ return null;
11863
+ };
11864
+ const schema = '[\n {\n "key": "column_1",\n "label": "Column 1"\n }\n]';
11865
+ const tooltip = jsxRuntime.jsxs("div", {
11866
+ children: ["The expression may result in an array of simple values or alternatively follow this schema:", jsxRuntime.jsx("pre", {
11867
+ children: jsxRuntime.jsx("code", {
11868
+ children: schema
11869
+ })
11870
+ })]
11871
+ });
11872
+ return FeelTemplatingEntry({
11873
+ debounce,
11874
+ description: 'Specify an expression to populate column items',
11875
+ element: field,
11876
+ feel: 'required',
11877
+ getValue,
11878
+ id,
11879
+ label: 'Expression',
11880
+ tooltip,
11881
+ setValue,
11882
+ singleLine: true,
11883
+ variables,
11884
+ validate
11885
+ });
11886
+ }
11887
+
11888
+ const path$1 = 'columns';
11889
+ const labelPath = 'label';
11890
+ const keyPath = 'key';
11891
+ function ColumnEntry(props) {
11892
+ const {
11893
+ editField,
11894
+ field,
11895
+ idPrefix,
11896
+ index,
11897
+ validateFactory
11898
+ } = props;
11899
+ const entries = [{
11900
+ component: Label,
11901
+ editField,
11902
+ field,
11903
+ id: idPrefix + '-label',
11904
+ idPrefix,
11905
+ index,
11906
+ validateFactory
11907
+ }, {
11908
+ component: Key,
11909
+ editField,
11910
+ field,
11911
+ id: idPrefix + '-key',
11912
+ idPrefix,
11913
+ index,
11914
+ validateFactory
11915
+ }];
11916
+ return entries;
11917
+ }
11918
+ function Label(props) {
11919
+ const {
11920
+ editField,
11921
+ field,
11922
+ id,
11923
+ index
11924
+ } = props;
11925
+ const debounce = useService('debounce');
11926
+
11927
+ /**
11928
+ * @param {string|void} value
11929
+ * @param {string|void} error
11930
+ * @returns {void}
11931
+ */
11932
+ const setValue = (value, error) => {
11933
+ if (error) {
11934
+ return;
11935
+ }
11936
+ const columns = minDash.get(field, [path$1]);
11937
+ editField(field, path$1, minDash.set(columns, [index, labelPath], value));
11938
+ };
11939
+ const getValue = () => {
11940
+ return minDash.get(field, [path$1, index, labelPath]);
11941
+ };
11942
+ return TextfieldEntry({
11943
+ debounce,
11944
+ element: field,
11945
+ getValue,
11946
+ id,
11947
+ label: 'Label',
11948
+ setValue
11949
+ });
11950
+ }
11951
+ function Key(props) {
11952
+ const {
11953
+ editField,
11954
+ field,
11955
+ id,
11956
+ index
11957
+ } = props;
11958
+ const debounce = useService('debounce');
11959
+
11960
+ /**
11961
+ * @param {string|void} value
11962
+ * @param {string|void} error
11963
+ * @returns {void}
11964
+ */
11965
+ const setValue = (value, error) => {
11966
+ if (error) {
11967
+ return;
11968
+ }
11969
+ const columns = minDash.get(field, [path$1]);
11970
+ editField(field, path$1, minDash.set(columns, [index, keyPath], value));
11971
+ };
11972
+ const getValue = () => {
11973
+ return minDash.get(field, [path$1, index, keyPath]);
11974
+ };
11975
+ return TextfieldEntry({
11976
+ debounce,
11977
+ element: field,
11978
+ getValue,
11979
+ id,
11980
+ label: 'Key',
11981
+ setValue,
11982
+ validate
11983
+ });
11984
+ }
11985
+
11986
+ // helpers //////////////////////
11987
+
11988
+ /**
11989
+ * @param {string|void} value
11990
+ * @returns {string|null}
11991
+ */
11992
+ function validate(value) {
11993
+ if (!minDash.isString(value) || value.length === 0) {
11994
+ return 'Must not be empty.';
11995
+ }
11996
+ return null;
11997
+ }
11998
+
11999
+ const path = ['columns'];
12000
+ function StaticColumnsSourceEntry(props) {
12001
+ const {
12002
+ editField,
12003
+ field,
12004
+ id: idPrefix
12005
+ } = props;
12006
+ const {
12007
+ columns
12008
+ } = field;
12009
+ const addEntry = event => {
12010
+ event.stopPropagation();
12011
+ const entry = {
12012
+ label: 'Column',
12013
+ key: 'inputVariable'
12014
+ };
12015
+ editField(field, path, arrayAdd(columns, columns.length, entry));
12016
+ };
12017
+ const removeEntry = entry => {
12018
+ editField(field, path, minDash.without(columns, entry));
12019
+ };
12020
+ const items = columns.map((entry, index) => {
12021
+ const id = `${idPrefix}-${index}`;
12022
+ return {
12023
+ id,
12024
+ label: entry.label || entry.key,
12025
+ entries: ColumnEntry({
12026
+ editField,
12027
+ field,
12028
+ idPrefix: id,
12029
+ index
12030
+ }),
12031
+ autoFocusEntry: `${id}-label`,
12032
+ remove: () => removeEntry(entry)
12033
+ };
12034
+ });
12035
+ return {
12036
+ items,
12037
+ add: addEntry,
12038
+ shouldSort: false
12039
+ };
12040
+ }
12041
+
12042
+ function GeneralGroup(field, editField, getService) {
12043
+ const entries = [...IdEntry({
12044
+ field,
12045
+ editField
12046
+ }), ...LabelEntry({
12047
+ field,
12048
+ editField
12049
+ }), ...DescriptionEntry({
12050
+ field,
12051
+ editField
12052
+ }), ...KeyEntry({
12053
+ field,
12054
+ editField,
12055
+ getService
12056
+ }), ...PathEntry({
12057
+ field,
12058
+ editField,
12059
+ getService
12060
+ }), ...RepeatableEntry({
12061
+ field,
12062
+ editField,
12063
+ getService
12064
+ }), ...DefaultOptionEntry({
12065
+ field,
12066
+ editField
12067
+ }), ...ActionEntry({
12068
+ field,
12069
+ editField
12070
+ }), ...DateTimeEntry({
12071
+ field,
12072
+ editField
12073
+ }), ...TextEntry({
12074
+ field,
12075
+ editField,
12076
+ getService
12077
+ }), ...IFrameUrlEntry({
12078
+ field,
12079
+ editField
12080
+ }), ...IFrameHeightEntry({
12081
+ field,
12082
+ editField
12083
+ }), ...HeightEntry({
12084
+ field,
12085
+ editField
12086
+ }), ...NumberEntries({
12087
+ field,
12088
+ editField
12089
+ }), ...SourceEntry({
12090
+ field,
12091
+ editField
12092
+ }), ...AltTextEntry({
12093
+ field,
12094
+ editField
12095
+ }), ...SelectEntries({
12096
+ field,
12097
+ editField
12098
+ }), ...DisabledEntry({
12099
+ field,
12100
+ editField
12101
+ }), ...ReadonlyEntry({
12102
+ field,
12103
+ editField
12104
+ }), ...TableDataSourceEntry({
12105
+ field,
12106
+ editField
12107
+ }), ...PaginationEntry({
12108
+ field,
12109
+ editField
12110
+ }), ...RowCountEntry({
12111
+ field,
12112
+ editField
12113
+ })];
12114
+ if (entries.length === 0) {
12115
+ return null;
12116
+ }
12117
+ return {
12118
+ id: 'general',
12119
+ label: 'General',
12120
+ entries
12121
+ };
12122
+ }
12123
+
12124
+ function SerializationGroup(field, editField) {
12125
+ const entries = [...NumberSerializationEntry({
12126
+ field,
12127
+ editField
12128
+ }), ...DateTimeFormatEntry({
12129
+ field,
12130
+ editField
12131
+ })];
12132
+ if (!entries.length) {
12133
+ return null;
12134
+ }
12135
+ return {
12136
+ id: 'serialization',
12137
+ label: 'Serialization',
12138
+ entries
12139
+ };
12140
+ }
12141
+
12142
+ function ConstraintsGroup(field, editField) {
12143
+ const entries = [...DateTimeConstraintsEntry({
12144
+ field,
11181
12145
  editField
11182
12146
  })];
11183
12147
  if (!entries.length) {
@@ -11436,67 +12400,66 @@ function ValidationType(props) {
11436
12400
  });
11437
12401
  }
11438
12402
 
11439
- function ValuesGroups(field, editField, getService) {
12403
+ function OptionsGroups(field, editField, getService) {
11440
12404
  const {
11441
- type,
11442
- id: fieldId
12405
+ type
11443
12406
  } = field;
11444
12407
  const formFields = getService('formFields');
11445
12408
  const fieldDefinition = formFields.get(type).config;
11446
- if (!VALUES_INPUTS.includes(type) && !hasValuesGroupsConfigured(fieldDefinition)) {
12409
+ if (!OPTIONS_INPUTS.includes(type) && !hasOptionsGroupsConfigured(fieldDefinition)) {
11447
12410
  return [];
11448
12411
  }
11449
12412
  const context = {
11450
12413
  editField,
11451
12414
  field
11452
12415
  };
11453
- const valuesSourceId = `${fieldId}-valuesSource`;
12416
+ const id = 'valuesSource';
11454
12417
 
11455
12418
  /**
11456
12419
  * @type {Array<Group|ListGroup>}
11457
12420
  */
11458
12421
  const groups = [{
11459
- id: valuesSourceId,
12422
+ id,
11460
12423
  label: 'Options source',
11461
12424
  tooltip: getValuesTooltip(),
11462
12425
  component: Group,
11463
- entries: ValuesSourceSelectEntry({
12426
+ entries: OptionsSourceSelectEntry({
11464
12427
  ...context,
11465
- id: valuesSourceId
12428
+ id
11466
12429
  })
11467
12430
  }];
11468
- const valuesSource = formJsViewer.getValuesSource(field);
11469
- if (valuesSource === formJsViewer.VALUES_SOURCES.INPUT) {
11470
- const dynamicValuesId = `${fieldId}-dynamicValues`;
12431
+ const valuesSource = formJsViewer.getOptionsSource(field);
12432
+ if (valuesSource === formJsViewer.OPTIONS_SOURCES.INPUT) {
12433
+ const id = 'dynamicOptions';
11471
12434
  groups.push({
11472
- id: dynamicValuesId,
12435
+ id,
11473
12436
  label: 'Dynamic options',
11474
12437
  component: Group,
11475
- entries: InputKeyValuesSourceEntry({
12438
+ entries: InputKeyOptionsSourceEntry({
11476
12439
  ...context,
11477
- id: dynamicValuesId
12440
+ id
11478
12441
  })
11479
12442
  });
11480
- } else if (valuesSource === formJsViewer.VALUES_SOURCES.STATIC) {
11481
- const staticValuesId = `${fieldId}-staticValues`;
12443
+ } else if (valuesSource === formJsViewer.OPTIONS_SOURCES.STATIC) {
12444
+ const id = 'staticOptions';
11482
12445
  groups.push({
11483
- id: staticValuesId,
12446
+ id,
11484
12447
  label: 'Static options',
11485
12448
  component: ListGroup,
11486
- ...StaticValuesSourceEntry({
12449
+ ...StaticOptionsSourceEntry({
11487
12450
  ...context,
11488
- id: staticValuesId
12451
+ id
11489
12452
  })
11490
12453
  });
11491
- } else if (valuesSource === formJsViewer.VALUES_SOURCES.EXPRESSION) {
11492
- const valuesExpressionId = `${fieldId}-valuesExpression`;
12454
+ } else if (valuesSource === formJsViewer.OPTIONS_SOURCES.EXPRESSION) {
12455
+ const id = 'optionsExpression';
11493
12456
  groups.push({
11494
- id: valuesExpressionId,
12457
+ id,
11495
12458
  label: 'Options expression',
11496
12459
  component: Group,
11497
- entries: ValuesExpressionEntry({
12460
+ entries: OptionsExpressionEntry({
11498
12461
  ...context,
11499
- id: valuesExpressionId
12462
+ id
11500
12463
  })
11501
12464
  });
11502
12465
  }
@@ -11546,7 +12509,7 @@ function CustomPropertiesGroup(field, editField) {
11546
12509
  event.stopPropagation();
11547
12510
  return editField(field, ['properties'], removeKey(properties, key));
11548
12511
  };
11549
- const id = `${field.id}-property-${index}`;
12512
+ const id = `property-${index}`;
11550
12513
  return {
11551
12514
  autoFocusEntry: id + '-key',
11552
12515
  entries: CustomValueEntry({
@@ -11595,10 +12558,16 @@ function removeKey(properties, oldKey) {
11595
12558
  }, {});
11596
12559
  }
11597
12560
 
11598
- function AppearanceGroup(field, editField) {
12561
+ function AppearanceGroup(field, editField, getService) {
11599
12562
  const entries = [...AdornerEntry({
11600
12563
  field,
11601
12564
  editField
12565
+ }), ...GroupAppearanceEntry({
12566
+ field,
12567
+ editField
12568
+ }), ...LayouterAppearanceEntry({
12569
+ field,
12570
+ editField
11602
12571
  })];
11603
12572
  if (!entries.length) {
11604
12573
  return null;
@@ -11649,6 +12618,55 @@ function ConditionGroup(field, editField) {
11649
12618
  };
11650
12619
  }
11651
12620
 
12621
+ function TableHeaderGroups(field, editField) {
12622
+ const {
12623
+ type,
12624
+ id: fieldId
12625
+ } = field;
12626
+ if (type !== 'table') {
12627
+ return [];
12628
+ }
12629
+ const areStaticColumnsEnabled = minDash.isArray(minDash.get(field, ['columns']));
12630
+
12631
+ /**
12632
+ * @type {Array<Group>}
12633
+ */
12634
+ const groups = [{
12635
+ id: `${fieldId}-columnsSource`,
12636
+ label: 'Headers source',
12637
+ tooltip: TOOLTIP_TEXT,
12638
+ component: Group,
12639
+ entries: [...HeadersSourceSelectEntry({
12640
+ field,
12641
+ editField
12642
+ }), ...ColumnsExpressionEntry({
12643
+ field,
12644
+ editField
12645
+ })]
12646
+ }];
12647
+ if (areStaticColumnsEnabled) {
12648
+ const id = `${fieldId}-columns`;
12649
+ groups.push({
12650
+ id,
12651
+ label: 'Header items',
12652
+ component: ListGroup,
12653
+ ...StaticColumnsSourceEntry({
12654
+ field,
12655
+ editField,
12656
+ id
12657
+ })
12658
+ });
12659
+ }
12660
+ return groups;
12661
+ }
12662
+
12663
+ // helpers //////////
12664
+
12665
+ const TOOLTIP_TEXT = `"List of items" defines a constant, predefined set of form options.
12666
+
12667
+ "Expression" defines options that are populated from a FEEL expression.
12668
+ `;
12669
+
11652
12670
  class PropertiesProvider {
11653
12671
  constructor(propertiesPanel, injector) {
11654
12672
  this._injector = injector;
@@ -11684,7 +12702,7 @@ class PropertiesProvider {
11684
12702
  return groups;
11685
12703
  }
11686
12704
  const getService = (type, strict = true) => this._injector.get(type, strict);
11687
- groups = [...groups, GeneralGroup(field, editField, getService), ConditionGroup(field, editField), LayoutGroup(field, editField), AppearanceGroup(field, editField), SerializationGroup(field, editField), ...ValuesGroups(field, editField, getService), ConstraintsGroup(field, editField), ValidationGroup(field, editField), CustomPropertiesGroup(field, editField)].filter(group => group != null);
12705
+ groups = [...groups, GeneralGroup(field, editField, getService), ...TableHeaderGroups(field, editField), ConditionGroup(field, editField), LayoutGroup(field, editField), AppearanceGroup(field, editField), SerializationGroup(field, editField), ...OptionsGroups(field, editField, getService), ConstraintsGroup(field, editField), ValidationGroup(field, editField), CustomPropertiesGroup(field, editField)].filter(group => group != null);
11688
12706
  this._filterVisibleEntries(groups, field, getService);
11689
12707
 
11690
12708
  // contract: if a group has no entries or items, it should not be displayed at all
@@ -11750,6 +12768,58 @@ var RenderInjectionModule = {
11750
12768
  renderInjector: ['type', RenderInjector]
11751
12769
  };
11752
12770
 
12771
+ var _path;
12772
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
12773
+ var SvgRepeat = function SvgRepeat(props) {
12774
+ return /*#__PURE__*/React__namespace.createElement("svg", _extends({
12775
+ xmlns: "http://www.w3.org/2000/svg",
12776
+ width: 16,
12777
+ height: 16,
12778
+ fill: "none"
12779
+ }, props), _path || (_path = /*#__PURE__*/React__namespace.createElement("path", {
12780
+ fill: "currentColor",
12781
+ d: "M3 3h10.086l-1.793-1.793L12 .5l3 3-3 3-.707-.707L13.086 4H3v3.5H2V4a1.001 1.001 0 0 1 1-1ZM4.707 10.207 2.914 12H13V8.5h1V12a1.002 1.002 0 0 1-1 1H2.914l1.793 1.793L4 15.5l-3-3 3-3 .707.707Z"
12782
+ })));
12783
+ };
12784
+ var RepeatSvg = SvgRepeat;
12785
+
12786
+ class RepeatRenderManager {
12787
+ constructor(formFields, formFieldRegistry) {
12788
+ this._formFields = formFields;
12789
+ this._formFieldRegistry = formFieldRegistry;
12790
+ this.RepeatFooter = this.RepeatFooter.bind(this);
12791
+ }
12792
+
12793
+ /**
12794
+ * Checks whether a field should be repeatable.
12795
+ *
12796
+ * @param {string} id - The id of the field to check
12797
+ * @returns {boolean} - True if repeatable, false otherwise
12798
+ */
12799
+ isFieldRepeating(id) {
12800
+ if (!id) {
12801
+ return false;
12802
+ }
12803
+ const formField = this._formFieldRegistry.get(id);
12804
+ const formFieldDefinition = this._formFields.get(formField.type);
12805
+ return formFieldDefinition.config.repeatable && formField.isRepeating;
12806
+ }
12807
+ RepeatFooter() {
12808
+ return jsxRuntime.jsxs("div", {
12809
+ className: "fjs-repeat-render-footer",
12810
+ children: [jsxRuntime.jsx(RepeatSvg, {}), jsxRuntime.jsx("span", {
12811
+ children: "Repeatable"
12812
+ })]
12813
+ });
12814
+ }
12815
+ }
12816
+ RepeatRenderManager.$inject = ['formFields', 'formFieldRegistry'];
12817
+
12818
+ var RepeatRenderManagerModule = {
12819
+ __init__: ['repeatRenderManager'],
12820
+ repeatRenderManager: ['type', RepeatRenderManager]
12821
+ };
12822
+
11753
12823
  class EditorTemplating {
11754
12824
  // same rules as viewer templating
11755
12825
  isTemplate(value) {
@@ -12021,7 +13091,7 @@ class FormEditor {
12021
13091
  * @internal
12022
13092
  */
12023
13093
  _getModules() {
12024
- return [ModelingModule, EditorActionsModule, DraggingModule, KeyboardModule, SelectionModule, PaletteModule, ExpressionLanguageModule, formJsViewer.MarkdownModule, PropertiesPanelModule, RenderInjectionModule];
13094
+ return [ModelingModule, EditorActionsModule, DraggingModule, KeyboardModule, SelectionModule, PaletteModule, ExpressionLanguageModule, formJsViewer.MarkdownModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderManagerModule];
12025
13095
  }
12026
13096
 
12027
13097
  /**