@bpmn-io/form-js-editor 1.5.0 → 1.6.1

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 (50) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +152 -152
  3. package/dist/assets/form-js-editor-base.css +863 -832
  4. package/dist/assets/form-js-editor.css +38 -7
  5. package/dist/index.cjs +1619 -549
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.es.js +1621 -551
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/types/FormEditor.d.ts +36 -4
  10. package/dist/types/features/dragging/Dragging.d.ts +1 -1
  11. package/dist/types/features/modeling/behavior/ColumnsSourceBehavior.d.ts +7 -0
  12. package/dist/types/features/modeling/behavior/OptionsSourceBehavior.d.ts +8 -0
  13. package/dist/types/features/modeling/behavior/TableDataSourceBehavior.d.ts +7 -0
  14. package/dist/types/features/modeling/behavior/index.d.ts +6 -2
  15. package/dist/types/features/modeling/index.d.ts +3 -1
  16. package/dist/types/features/properties-panel/Util.d.ts +11 -3
  17. package/dist/types/features/properties-panel/entries/ColumnEntry.d.ts +11 -0
  18. package/dist/types/features/properties-panel/entries/ColumnsExpressionEntry.d.ts +10 -0
  19. package/dist/types/features/properties-panel/entries/ConditionEntry.d.ts +1 -1
  20. package/dist/types/features/properties-panel/entries/{GroupEntries.d.ts → GroupAppearanceEntry.d.ts} +1 -1
  21. package/dist/types/features/properties-panel/entries/HeadersSourceSelectEntry.d.ts +9 -0
  22. package/dist/types/features/properties-panel/entries/{InputKeyValuesSourceEntry.d.ts → InputKeyOptionsSourceEntry.d.ts} +1 -1
  23. package/dist/types/features/properties-panel/entries/LayouterAppearanceEntry.d.ts +10 -0
  24. package/dist/types/features/properties-panel/entries/OptionsExpressionEntry.d.ts +9 -0
  25. package/dist/types/features/properties-panel/entries/{ValuesSourceSelectEntry.d.ts → OptionsSourceSelectEntry.d.ts} +1 -1
  26. package/dist/types/features/properties-panel/entries/PaginationEntry.d.ts +10 -0
  27. package/dist/types/features/properties-panel/entries/RepeatableEntry.d.ts +24 -0
  28. package/dist/types/features/properties-panel/entries/RowCountEntry.d.ts +10 -0
  29. package/dist/types/features/properties-panel/entries/StaticColumnsSourceEntry.d.ts +5 -0
  30. package/dist/types/features/properties-panel/entries/{StaticValuesSourceEntry.d.ts → StaticOptionsSourceEntry.d.ts} +1 -1
  31. package/dist/types/features/properties-panel/entries/TableDataSourceEntry.d.ts +10 -0
  32. package/dist/types/features/properties-panel/entries/factories/index.d.ts +3 -0
  33. package/dist/types/features/properties-panel/entries/factories/simpleRangeIntegerEntryFactory.d.ts +12 -0
  34. package/dist/types/features/properties-panel/entries/factories/simpleSelectEntryFactory.d.ts +10 -0
  35. package/dist/types/features/properties-panel/entries/factories/zeroPositiveIntegerEntryFactory.d.ts +9 -0
  36. package/dist/types/features/properties-panel/entries/index.d.ts +14 -6
  37. package/dist/types/features/properties-panel/groups/AppearanceGroup.d.ts +24 -3
  38. package/dist/types/features/properties-panel/groups/GeneralGroup.d.ts +11 -0
  39. package/dist/types/features/properties-panel/groups/OptionsGroups.d.ts +1 -0
  40. package/dist/types/features/properties-panel/groups/TableHeaderGroups.d.ts +1 -0
  41. package/dist/types/features/properties-panel/groups/index.d.ts +2 -1
  42. package/dist/types/features/repeat-render/EditorRepeatRenderManager.d.ts +17 -0
  43. package/dist/types/features/repeat-render/index.d.ts +7 -0
  44. package/dist/types/render/components/editor-form-fields/EditorTable.d.ts +10 -0
  45. package/dist/types/render/components/editor-form-fields/index.d.ts +2 -1
  46. package/dist/types/types.d.ts +28 -28
  47. package/package.json +3 -3
  48. package/dist/types/features/modeling/behavior/ValuesSourceBehavior.d.ts +0 -8
  49. package/dist/types/features/properties-panel/entries/ValuesExpressionEntry.d.ts +0 -9
  50. 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),
@@ -537,10 +537,10 @@ function invokeFunction(fn, args) {
537
537
  return fn.apply(null, args);
538
538
  }
539
539
 
540
- /**
541
- * A factory to create a configurable debouncer.
542
- *
543
- * @param {number|boolean} [config=true]
540
+ /**
541
+ * A factory to create a configurable debouncer.
542
+ *
543
+ * @param {number|boolean} [config=true]
544
544
  */
545
545
  function DebounceFactory(config = true) {
546
546
  const timeout = typeof config === 'number' ? config : config ? 300 : 0;
@@ -553,11 +553,11 @@ function DebounceFactory(config = true) {
553
553
  DebounceFactory.$inject = ['config.debounce'];
554
554
 
555
555
  class FormFieldRegistry extends formJsViewer.FormFieldRegistry {
556
- /**
557
- * Updates a form fields id.
558
- *
559
- * @param {Object} formField
560
- * @param {string} newId
556
+ /**
557
+ * Updates a form fields id.
558
+ *
559
+ * @param {Object} formField
560
+ * @param {string} newId
561
561
  */
562
562
  updateId(formField, newId) {
563
563
  this._validateId(newId);
@@ -578,13 +578,13 @@ class FormFieldRegistry extends formJsViewer.FormFieldRegistry {
578
578
  }
579
579
  }
580
580
 
581
- /**
582
- * Validate the suitability of the given id and signals a problem
583
- * with an exception.
584
- *
585
- * @param {string} id
586
- *
587
- * @throws {Error} if id is empty or already assigned
581
+ /**
582
+ * Validate the suitability of the given id and signals a problem
583
+ * with an exception.
584
+ *
585
+ * @param {string} id
586
+ *
587
+ * @throws {Error} if id is empty or already assigned
588
588
  */
589
589
  _validateId(id) {
590
590
  if (!id) {
@@ -601,11 +601,11 @@ const MAX_COLUMNS = 16;
601
601
  const MIN_COLUMNS = 2;
602
602
  const MAX_FIELDS_PER_ROW = 4;
603
603
  class FormLayoutValidator {
604
- /**
605
- * @constructor
606
- *
607
- * @param { import('./FormLayouter').default } formLayouter
608
- * @param { import('./FormFieldRegistry').default } formFieldRegistry
604
+ /**
605
+ * @constructor
606
+ *
607
+ * @param { import('./FormLayouter').default } formLayouter
608
+ * @param { import('./FormFieldRegistry').default } formFieldRegistry
609
609
  */
610
610
  constructor(formLayouter, formFieldRegistry) {
611
611
  this._formLayouter = formLayouter;
@@ -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
@@ -695,21 +676,21 @@ function editorFormFieldClasses(type, {
695
676
  });
696
677
  }
697
678
 
698
- /**
699
- * Add a dragger that calls back the passed function with
700
- * { event, delta } on drag.
701
- *
702
- * @example
703
- *
704
- * function dragMove(event, delta) {
705
- * // we are dragging (!!)
706
- * }
707
- *
708
- * domElement.addEventListener('dragstart', dragger(dragMove));
709
- *
710
- * @param {Function} fn
711
- *
712
- * @return {Function} drag start callback function
679
+ /**
680
+ * Add a dragger that calls back the passed function with
681
+ * { event, delta } on drag.
682
+ *
683
+ * @example
684
+ *
685
+ * function dragMove(event, delta) {
686
+ * // we are dragging (!!)
687
+ * }
688
+ *
689
+ * domElement.addEventListener('dragstart', dragger(dragMove));
690
+ *
691
+ * @param {Function} fn
692
+ *
693
+ * @return {Function} drag start callback function
713
694
  */
714
695
  function createDragger$1(fn) {
715
696
  let self;
@@ -750,12 +731,12 @@ function createDragger$1(fn) {
750
731
  return onDragStart;
751
732
  }
752
733
 
753
- /**
754
- * Throttle function call according UI update cycle.
755
- *
756
- * @param {Function} fn
757
- *
758
- * @return {Function} throttled fn
734
+ /**
735
+ * Throttle function call according UI update cycle.
736
+ *
737
+ * @param {Function} fn
738
+ *
739
+ * @return {Function} throttled fn
759
740
  */
760
741
  function throttle(fn) {
761
742
  let active = false;
@@ -784,16 +765,38 @@ 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
  });
790
793
  var DragAndDropContext$1 = DragAndDropContext;
791
794
 
792
- /**
793
- * @param {string} type
794
- * @param {boolean} [strict]
795
- *
796
- * @returns {any}
795
+ /**
796
+ * @param {string} type
797
+ * @param {boolean} [strict]
798
+ *
799
+ * @returns {any}
797
800
  */
798
801
  function getService$1(type, strict) {}
799
802
  const FormEditorContext = preact.createContext({
@@ -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() {
@@ -1164,23 +1231,23 @@ var Slot = (props => {
1164
1231
  return fillsAndSeparators;
1165
1232
  });
1166
1233
 
1167
- /**
1168
- * Creates a Fragment for a fill.
1169
- *
1170
- * @param {Object} fill Fill to be rendered
1171
- * @returns {Object} Preact Fragment containing fill's children
1234
+ /**
1235
+ * Creates a Fragment for a fill.
1236
+ *
1237
+ * @param {Object} fill Fill to be rendered
1238
+ * @returns {Object} Preact Fragment containing fill's children
1172
1239
  */
1173
1240
  const FillFragment = fill => jsxRuntime.jsx(preact.Fragment, {
1174
1241
  children: fill.children
1175
1242
  }, fill.id);
1176
1243
 
1177
- /**
1178
- * Creates an array of fills, with separators inserted between groups.
1179
- *
1180
- * @param {Array} groups Groups of fills
1181
- * @param {Function} fillRenderer Function to create a fill
1182
- * @param {Function} separatorRenderer Function to create a separator
1183
- * @returns {Array} Array of fills and separators
1244
+ /**
1245
+ * Creates an array of fills, with separators inserted between groups.
1246
+ *
1247
+ * @param {Array} groups Groups of fills
1248
+ * @param {Function} fillRenderer Function to create a fill
1249
+ * @param {Function} separatorRenderer Function to create a separator
1250
+ * @returns {Array} Array of fills and separators
1184
1251
  */
1185
1252
  const buildFills = (groups, fillRenderer, separatorRenderer) => {
1186
1253
  const result = [];
@@ -1198,8 +1265,8 @@ const buildFills = (groups, fillRenderer, separatorRenderer) => {
1198
1265
  return result;
1199
1266
  };
1200
1267
 
1201
- /**
1202
- * Groups fills by group name property.
1268
+ /**
1269
+ * Groups fills by group name property.
1203
1270
  */
1204
1271
  const _groupByGroupName = fills => {
1205
1272
  const groups = [];
@@ -1219,8 +1286,8 @@ const _groupByGroupName = fills => {
1219
1286
  return Object.keys(groupsById).sort().map(id => groupsById[id]);
1220
1287
  };
1221
1288
 
1222
- /**
1223
- * Compares fills by priority.
1289
+ /**
1290
+ * Compares fills by priority.
1224
1291
  */
1225
1292
  const _comparePriority = (a, b) => {
1226
1293
  return (b.priority || 0) - (a.priority || 0);
@@ -1438,11 +1505,11 @@ function groupEntries(entries) {
1438
1505
  return groups.filter(g => g.entries.length);
1439
1506
  }
1440
1507
 
1441
- /**
1442
- * Returns a list of palette entries.
1443
- *
1444
- * @param {FormFields} formFields
1445
- * @returns {Array<PaletteEntry>}
1508
+ /**
1509
+ * Returns a list of palette entries.
1510
+ *
1511
+ * @param {FormFields} formFields
1512
+ * @returns {Array<PaletteEntry>}
1446
1513
  */
1447
1514
  function collectPaletteEntries(formFields) {
1448
1515
  return Object.entries(formFields._formFields).map(([type, formField]) => {
@@ -1461,12 +1528,12 @@ function collectPaletteEntries(formFields) {
1461
1528
  }) => type !== 'default');
1462
1529
  }
1463
1530
 
1464
- /**
1465
- * There are various options to specify an icon for a palette entry.
1466
- *
1467
- * a) via `iconUrl` property in a form field config
1468
- * b) via `icon` property in a form field config
1469
- * c) via statically defined iconsByType (fallback)
1531
+ /**
1532
+ * There are various options to specify an icon for a palette entry.
1533
+ *
1534
+ * a) via `iconUrl` property in a form field config
1535
+ * b) via `icon` property in a form field config
1536
+ * c) via statically defined iconsByType (fallback)
1470
1537
  */
1471
1538
  function getPaletteIcon(entry) {
1472
1539
  const {
@@ -1533,20 +1600,20 @@ const DRAG_NO_DROP_CLS = 'fjs-no-drop';
1533
1600
  const DRAG_NO_MOVE_CLS = 'fjs-no-move';
1534
1601
  const ERROR_DROP_CLS = 'fjs-error-drop';
1535
1602
 
1536
- /**
1537
- * @typedef { { id: String, components: Array<any> } } FormRow
1603
+ /**
1604
+ * @typedef { { id: String, components: Array<any> } } FormRow
1538
1605
  */
1539
1606
 
1540
1607
  class Dragging {
1541
- /**
1542
- * @constructor
1543
- *
1544
- * @param { import('../../core/FormFieldRegistry').default } formFieldRegistry
1545
- * @param { import('../../core/FormLayouter').default } formLayouter
1546
- * @param { import('../../core/FormLayoutValidator').default } formLayoutValidator
1547
- * @param { import('../../core/EventBus').default } eventBus
1548
- * @param { import('../modeling/Modeling').default } modeling
1549
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
1608
+ /**
1609
+ * @constructor
1610
+ *
1611
+ * @param { import('../../core/FormFieldRegistry').default } formFieldRegistry
1612
+ * @param { import('../../core/FormLayouter').default } formLayouter
1613
+ * @param { import('../../core/FormLayoutValidator').default } formLayoutValidator
1614
+ * @param { import('../../core/EventBus').default } eventBus
1615
+ * @param { import('../modeling/Modeling').default } modeling
1616
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
1550
1617
  */
1551
1618
  constructor(formFieldRegistry, formLayouter, formLayoutValidator, eventBus, modeling, pathRegistry) {
1552
1619
  this._formFieldRegistry = formFieldRegistry;
@@ -1557,13 +1624,13 @@ class Dragging {
1557
1624
  this._pathRegistry = pathRegistry;
1558
1625
  }
1559
1626
 
1560
- /**
1561
- * Calculcates position in form schema given the dropped place.
1562
- *
1563
- * @param { FormRow } targetRow
1564
- * @param { any } targetFormField
1565
- * @param { HTMLElement } sibling
1566
- * @returns { number }
1627
+ /**
1628
+ * Calculates position in form schema given the dropped place.
1629
+ *
1630
+ * @param { FormRow } targetRow
1631
+ * @param { any } targetFormField
1632
+ * @param { HTMLElement } sibling
1633
+ * @returns { number }
1567
1634
  */
1568
1635
  getTargetIndex(targetRow, targetFormField, sibling) {
1569
1636
  /** @type HTMLElement */
@@ -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';
@@ -1704,8 +1776,8 @@ class Dragging {
1704
1776
  }
1705
1777
  }
1706
1778
 
1707
- /**
1708
- * @param { { container: Array<string>, direction: string, mirrorContainer: string } } options
1779
+ /**
1780
+ * @param { { container: Array<string>, direction: string, mirrorContainer: string } } options
1709
1781
  */
1710
1782
  createDragulaInstance(options) {
1711
1783
  const {
@@ -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,
@@ -2121,7 +2216,7 @@ function DebugColumns(props) {
2121
2216
  return null;
2122
2217
  }
2123
2218
  return jsxRuntime.jsx("div", {
2124
- style: "width: fit-content; padding: 2px 6px; height: 16px; background: var(--color-blue-205-100-95); display: flex; justify-content: center; align-items: center; position: absolute; bottom: -2px; z-index: 2; font-size: 10px; right: 3px;",
2219
+ style: "width: fit-content;\r padding: 2px 6px;\r height: 16px;\r background: var(--color-blue-205-100-95);\r display: flex;\r justify-content: center;\r align-items: center;\r position: absolute;\r bottom: -2px;\r z-index: 2;\r font-size: 10px;\r right: 3px;",
2125
2220
  class: "fjs-debug-columns",
2126
2221
  children: (field.layout || {}).columns || 'auto'
2127
2222
  });
@@ -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
  };
@@ -3207,10 +3343,10 @@ function updateRow(formField, rowId) {
3207
3343
  }
3208
3344
 
3209
3345
  class AddFormFieldHandler {
3210
- /**
3211
- * @constructor
3212
- * @param { import('../../../FormEditor').default } formEditor
3213
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3346
+ /**
3347
+ * @constructor
3348
+ * @param { import('../../../FormEditor').default } formEditor
3349
+ * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3214
3350
  */
3215
3351
  constructor(formEditor, formFieldRegistry) {
3216
3352
  this._formEditor = formEditor;
@@ -3271,10 +3407,10 @@ class AddFormFieldHandler {
3271
3407
  AddFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
3272
3408
 
3273
3409
  class EditFormFieldHandler {
3274
- /**
3275
- * @constructor
3276
- * @param { import('../../../FormEditor').default } formEditor
3277
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3410
+ /**
3411
+ * @constructor
3412
+ * @param { import('../../../FormEditor').default } formEditor
3413
+ * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3278
3414
  */
3279
3415
  constructor(formEditor, formFieldRegistry) {
3280
3416
  this._formEditor = formEditor;
@@ -3337,12 +3473,12 @@ class EditFormFieldHandler {
3337
3473
  EditFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
3338
3474
 
3339
3475
  class MoveFormFieldHandler {
3340
- /**
3341
- * @constructor
3342
- * @param { import('../../../FormEditor').default } formEditor
3343
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3344
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3345
- * @param { import('@bpmn-io/form-js-viewer').FormLayouter } formLayouter
3476
+ /**
3477
+ * @constructor
3478
+ * @param { import('../../../FormEditor').default } formEditor
3479
+ * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3480
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3481
+ * @param { import('@bpmn-io/form-js-viewer').FormLayouter } formLayouter
3346
3482
  */
3347
3483
  constructor(formEditor, formFieldRegistry, pathRegistry, formLayouter) {
3348
3484
  this._formEditor = formEditor;
@@ -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
 
@@ -3448,10 +3589,10 @@ class MoveFormFieldHandler {
3448
3589
  MoveFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry', 'pathRegistry', 'formLayouter'];
3449
3590
 
3450
3591
  class RemoveFormFieldHandler {
3451
- /**
3452
- * @constructor
3453
- * @param { import('../../../FormEditor').default } formEditor
3454
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3592
+ /**
3593
+ * @constructor
3594
+ * @param { import('../../../FormEditor').default } formEditor
3595
+ * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3455
3596
  */
3456
3597
  constructor(formEditor, formFieldRegistry) {
3457
3598
  this._formEditor = formEditor;
@@ -3511,9 +3652,9 @@ class RemoveFormFieldHandler {
3511
3652
  RemoveFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
3512
3653
 
3513
3654
  class UpdateIdClaimHandler {
3514
- /**
3515
- * @constructor
3516
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3655
+ /**
3656
+ * @constructor
3657
+ * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3517
3658
  */
3518
3659
  constructor(formFieldRegistry) {
3519
3660
  this._formFieldRegistry = formFieldRegistry;
@@ -3546,9 +3687,9 @@ class UpdateIdClaimHandler {
3546
3687
  UpdateIdClaimHandler.$inject = ['formFieldRegistry'];
3547
3688
 
3548
3689
  class UpdateKeyClaimHandler {
3549
- /**
3550
- * @constructor
3551
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3690
+ /**
3691
+ * @constructor
3692
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3552
3693
  */
3553
3694
  constructor(pathRegistry) {
3554
3695
  this._pathRegistry = pathRegistry;
@@ -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,21 +3721,25 @@ 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
  }
3589
3737
  UpdateKeyClaimHandler.$inject = ['pathRegistry'];
3590
3738
 
3591
3739
  class UpdatePathClaimHandler {
3592
- /**
3593
- * @constructor
3594
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3740
+ /**
3741
+ * @constructor
3742
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3595
3743
  */
3596
3744
  constructor(pathRegistry) {
3597
3745
  this._pathRegistry = pathRegistry;
@@ -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
  }
@@ -4162,8 +4326,8 @@ class ValidateBehavior extends CommandInterceptor {
4162
4326
  constructor(eventBus) {
4163
4327
  super(eventBus);
4164
4328
 
4165
- /**
4166
- * Remove custom validation if <validationType> is about to be added.
4329
+ /**
4330
+ * Remove custom validation if <validationType> is about to be added.
4167
4331
  */
4168
4332
  this.preExecute('formField.edit', function (context) {
4169
4333
  const {
@@ -4186,15 +4350,15 @@ 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
 
4193
- /**
4194
- * Cleanup properties on changing the values source.
4195
- *
4196
- * 1) Remove other sources, e.g. set `values` => remove `valuesKey` and `valuesExpression`
4197
- * 2) Remove default values for all other values sources
4357
+ /**
4358
+ * Cleanup properties on changing the values source.
4359
+ *
4360
+ * 1) Remove other sources, e.g. set `values` => remove `valuesKey` and `valuesExpression`
4361
+ * 2) Remove default values for all other values sources
4198
4362
  */
4199
4363
  this.preExecute('formField.edit', function (context) {
4200
4364
  const {
@@ -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
  /**
@@ -4792,22 +5018,22 @@ var SelectionModule = {
4792
5018
  selectionBehavior: ['type', SelectionBehavior]
4793
5019
  };
4794
5020
 
4795
- /**
4796
- * Base class for sectionable UI modules.
4797
- *
4798
- * @property {EventBus} _eventBus - EventBus instance used for event handling.
4799
- * @property {string} managerType - Type of the render manager. Used to form event names.
4800
- *
4801
- * @class SectionModuleBase
5021
+ /**
5022
+ * Base class for sectionable UI modules.
5023
+ *
5024
+ * @property {EventBus} _eventBus - EventBus instance used for event handling.
5025
+ * @property {string} managerType - Type of the render manager. Used to form event names.
5026
+ *
5027
+ * @class SectionModuleBase
4802
5028
  */
4803
5029
  class SectionModuleBase {
4804
- /**
4805
- * Create a SectionModuleBase instance.
4806
- *
4807
- * @param {any} eventBus - The EventBus instance used for event handling.
4808
- * @param {string} sectionKey - The type of render manager. Used to form event names.
4809
- *
4810
- * @constructor
5030
+ /**
5031
+ * Create a SectionModuleBase instance.
5032
+ *
5033
+ * @param {any} eventBus - The EventBus instance used for event handling.
5034
+ * @param {string} sectionKey - The type of render manager. Used to form event names.
5035
+ *
5036
+ * @constructor
4811
5037
  */
4812
5038
  constructor(eventBus, sectionKey) {
4813
5039
  this._eventBus = eventBus;
@@ -4820,10 +5046,10 @@ class SectionModuleBase {
4820
5046
  });
4821
5047
  }
4822
5048
 
4823
- /**
4824
- * Attach the managed section to a parent node.
4825
- *
4826
- * @param {HTMLElement} container - The parent node to attach to.
5049
+ /**
5050
+ * Attach the managed section to a parent node.
5051
+ *
5052
+ * @param {HTMLElement} container - The parent node to attach to.
4827
5053
  */
4828
5054
  attachTo(container) {
4829
5055
  this._onceSectionRendered(() => this._eventBus.fire(`${this._sectionKey}.attach`, {
@@ -4831,22 +5057,22 @@ class SectionModuleBase {
4831
5057
  }));
4832
5058
  }
4833
5059
 
4834
- /**
4835
- * Detach the managed section from its parent node.
5060
+ /**
5061
+ * Detach the managed section from its parent node.
4836
5062
  */
4837
5063
  detach() {
4838
5064
  this._onceSectionRendered(() => this._eventBus.fire(`${this._sectionKey}.detach`));
4839
5065
  }
4840
5066
 
4841
- /**
4842
- * Reset the managed section to its initial state.
5067
+ /**
5068
+ * Reset the managed section to its initial state.
4843
5069
  */
4844
5070
  reset() {
4845
5071
  this._onceSectionRendered(() => this._eventBus.fire(`${this._sectionKey}.reset`));
4846
5072
  }
4847
5073
 
4848
- /**
4849
- * Circumvents timing issues.
5074
+ /**
5075
+ * Circumvents timing issues.
4850
5076
  */
4851
5077
  _onceSectionRendered(callback) {
4852
5078
  if (this.isSectionRendered) {
@@ -8524,11 +8750,11 @@ var index = {
8524
8750
  feelPopup: ['type', FeelPopupModule]
8525
8751
  };
8526
8752
 
8527
- /**
8528
- * @param {string} type
8529
- * @param {boolean} [strict]
8530
- *
8531
- * @returns {any}
8753
+ /**
8754
+ * @param {string} type
8755
+ * @param {boolean} [strict]
8756
+ *
8757
+ * @returns {any}
8532
8758
  */
8533
8759
  function getService(type, strict) {}
8534
8760
  const PropertiesPanelContext = preact.createContext({
@@ -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
@@ -8592,10 +8829,10 @@ function useService (type, strict) {
8592
8829
  return getService(type, strict);
8593
8830
  }
8594
8831
 
8595
- /**
8596
- * Retrieve list of variables from the form schema.
8597
- *
8598
- * @returns { string[] } list of variables used in form schema
8832
+ /**
8833
+ * Retrieve list of variables from the form schema.
8834
+ *
8835
+ * @returns { string[] } list of variables used in form schema
8599
8836
  */
8600
8837
  function useVariables() {
8601
8838
  const form = useService('formEditor');
@@ -8661,8 +8898,8 @@ const PropertiesPanelHeaderProvider = {
8661
8898
  }
8662
8899
  };
8663
8900
 
8664
- /**
8665
- * Provide placeholders for empty and multiple state.
8901
+ /**
8902
+ * Provide placeholders for empty and multiple state.
8666
8903
  */
8667
8904
  const PropertiesPanelPlaceholderProvider = {
8668
8905
  getEmpty: () => {
@@ -8707,9 +8944,9 @@ function FormPropertiesPanel(props) {
8707
8944
  });
8708
8945
  }, [eventBus, formEditor, selectionModule]);
8709
8946
  hooks.useLayoutEffect(() => {
8710
- /**
8711
- * TODO(pinussilvestrus): update with actual updated element,
8712
- * once we have a proper updater/change support
8947
+ /**
8948
+ * TODO(pinussilvestrus): update with actual updated element,
8949
+ * once we have a proper updater/change support
8713
8950
  */
8714
8951
  eventBus.on('changed', refresh);
8715
8952
  eventBus.on('import.done', refresh);
@@ -8761,17 +8998,17 @@ function FormPropertiesPanel(props) {
8761
8998
 
8762
8999
  const DEFAULT_PRIORITY = 1000;
8763
9000
 
8764
- /**
8765
- * @typedef { { parent: Element } } PropertiesPanelConfig
8766
- * @typedef { import('../../core/EventBus').default } EventBus
8767
- * @typedef { import('../../types').Injector } Injector
8768
- * @typedef { { getGroups: ({ formField, editFormField }) => ({ groups}) => Array } } PropertiesProvider
9001
+ /**
9002
+ * @typedef { { parent: Element } } PropertiesPanelConfig
9003
+ * @typedef { import('../../core/EventBus').default } EventBus
9004
+ * @typedef { import('../../types').Injector } Injector
9005
+ * @typedef { { getGroups: ({ formField, editFormField }) => ({ groups}) => Array } } PropertiesProvider
8769
9006
  */
8770
9007
 
8771
- /**
8772
- * @param {PropertiesPanelConfig} propertiesPanelConfig
8773
- * @param {Injector} injector
8774
- * @param {EventBus} eventBus
9008
+ /**
9009
+ * @param {PropertiesPanelConfig} propertiesPanelConfig
9010
+ * @param {Injector} injector
9011
+ * @param {EventBus} eventBus
8775
9012
  */
8776
9013
  class PropertiesPanelRenderer {
8777
9014
  constructor(propertiesPanelConfig, injector, eventBus) {
@@ -8789,10 +9026,10 @@ class PropertiesPanelRenderer {
8789
9026
  });
8790
9027
  }
8791
9028
 
8792
- /**
8793
- * Attach the properties panel to a parent node.
8794
- *
8795
- * @param {HTMLElement} container
9029
+ /**
9030
+ * Attach the properties panel to a parent node.
9031
+ *
9032
+ * @param {HTMLElement} container
8796
9033
  */
8797
9034
  attachTo(container) {
8798
9035
  if (!container) {
@@ -8812,8 +9049,8 @@ class PropertiesPanelRenderer {
8812
9049
  this._eventBus.fire('propertiesPanel.attach');
8813
9050
  }
8814
9051
 
8815
- /**
8816
- * Detach the properties panel from its parent node.
9052
+ /**
9053
+ * Detach the properties panel from its parent node.
8817
9054
  */
8818
9055
  detach() {
8819
9056
  const parentNode = this._container.parentNode;
@@ -8837,11 +9074,11 @@ class PropertiesPanelRenderer {
8837
9074
  }
8838
9075
  }
8839
9076
 
8840
- /**
8841
- * Register a new properties provider to the properties panel.
8842
- *
8843
- * @param {PropertiesProvider} provider
8844
- * @param {Number} [priority]
9077
+ /**
9078
+ * Register a new properties provider to the properties panel.
9079
+ *
9080
+ * @param {PropertiesProvider} provider
9081
+ * @param {Number} [priority]
8845
9082
  */
8846
9083
  registerProvider(provider, priority) {
8847
9084
  if (!priority) {
@@ -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,14 +9911,118 @@ const SimpleBoolComponent = props => {
9637
9911
  });
9638
9912
  };
9639
9913
 
9640
- function GroupEntries(props) {
9914
+ function simpleSelectEntryFactory(options) {
9915
+ const {
9916
+ id,
9917
+ label,
9918
+ path,
9919
+ props,
9920
+ optionsArray
9921
+ } = options;
9922
+ const {
9923
+ editField,
9924
+ field
9925
+ } = props;
9926
+ return {
9927
+ id,
9928
+ label,
9929
+ path,
9930
+ field,
9931
+ editField,
9932
+ optionsArray,
9933
+ component: SimpleSelectComponent,
9934
+ isEdited: isEdited$3
9935
+ };
9936
+ }
9937
+ const SimpleSelectComponent = props => {
9938
+ const {
9939
+ id,
9940
+ label,
9941
+ path,
9942
+ field,
9943
+ editField,
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) {
9641
10019
  const {
9642
10020
  field
9643
10021
  } = props;
9644
10022
  const {
9645
10023
  type
9646
10024
  } = field;
9647
- if (type !== 'group') {
10025
+ if (!['group', 'dynamiclist'].includes(type)) {
9648
10026
  return [];
9649
10027
  }
9650
10028
  const entries = [simpleBoolEntryFactory({
@@ -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,
@@ -10603,14 +10974,14 @@ function Value(props) {
10603
10974
 
10604
10975
  // helpers //////////
10605
10976
 
10606
- /**
10607
- * Returns copy of object with updated value.
10608
- *
10609
- * @param {Object} properties
10610
- * @param {string} key
10611
- * @param {string} value
10612
- *
10613
- * @returns {Object}
10977
+ /**
10978
+ * Returns copy of object with updated value.
10979
+ *
10980
+ * @param {Object} properties
10981
+ * @param {string} key
10982
+ * @param {string} value
10983
+ *
10984
+ * @returns {Object}
10614
10985
  */
10615
10986
  function updateValue(properties, key, value) {
10616
10987
  return {
@@ -10619,14 +10990,14 @@ function updateValue(properties, key, value) {
10619
10990
  };
10620
10991
  }
10621
10992
 
10622
- /**
10623
- * Returns copy of object with updated key.
10624
- *
10625
- * @param {Object} properties
10626
- * @param {string} oldKey
10627
- * @param {string} newKey
10628
- *
10629
- * @returns {Object}
10993
+ /**
10994
+ * Returns copy of object with updated key.
10995
+ *
10996
+ * @param {Object} properties
10997
+ * @param {string} oldKey
10998
+ * @param {string} newKey
10999
+ *
11000
+ * @returns {Object}
10630
11001
  */
10631
11002
  function updateKey(properties, oldKey, newKey) {
10632
11003
  return Object.entries(properties).reduce((newProperties, entry) => {
@@ -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,36 +11528,550 @@ 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 {
11533
+ editField,
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;
11546
+ }
11547
+ function Source(props) {
11548
+ const {
11549
+ editField,
11550
+ field,
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);
11560
+ };
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,
11120
12075
  editField,
11121
12076
  getService
11122
12077
  }), ...IFrameUrlEntry({
@@ -11146,6 +12101,15 @@ function GeneralGroup(field, editField, getService) {
11146
12101
  }), ...ReadonlyEntry({
11147
12102
  field,
11148
12103
  editField
12104
+ }), ...TableDataSourceEntry({
12105
+ field,
12106
+ editField
12107
+ }), ...PaginationEntry({
12108
+ field,
12109
+ editField
12110
+ }), ...RowCountEntry({
12111
+ field,
12112
+ editField
11149
12113
  })];
11150
12114
  if (entries.length === 0) {
11151
12115
  return null;
@@ -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
- /**
11456
- * @type {Array<Group|ListGroup>}
12418
+ /**
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({
@@ -11574,13 +12537,13 @@ function CustomPropertiesGroup(field, editField) {
11574
12537
 
11575
12538
  // helpers //////////
11576
12539
 
11577
- /**
11578
- * Returns copy of object without key.
11579
- *
11580
- * @param {Object} properties
11581
- * @param {string} oldKey
11582
- *
11583
- * @returns {Object}
12540
+ /**
12541
+ * Returns copy of object without key.
12542
+ *
12543
+ * @param {Object} properties
12544
+ * @param {string} oldKey
12545
+ *
12546
+ * @returns {Object}
11584
12547
  */
11585
12548
  function removeKey(properties, oldKey) {
11586
12549
  return Object.entries(properties).reduce((newProperties, entry) => {
@@ -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
@@ -11703,10 +12721,10 @@ var PropertiesPanelModule = {
11703
12721
  propertiesProvider: ['type', PropertiesProvider]
11704
12722
  };
11705
12723
 
11706
- /**
11707
- * Manages the rendering of visual plugins.
11708
- * @constructor
11709
- * @param {Object} eventBus - Event bus for the application.
12724
+ /**
12725
+ * Manages the rendering of visual plugins.
12726
+ * @constructor
12727
+ * @param {Object} eventBus - Event bus for the application.
11710
12728
  */
11711
12729
  class RenderInjector extends SectionModuleBase {
11712
12730
  constructor(eventBus) {
@@ -11715,10 +12733,10 @@ class RenderInjector extends SectionModuleBase {
11715
12733
  this.registeredRenderers = [];
11716
12734
  }
11717
12735
 
11718
- /**
11719
- * Inject a new renderer into the injector.
11720
- * @param {string} identifier - Identifier for the renderer.
11721
- * @param {Function} Renderer - The renderer function.
12736
+ /**
12737
+ * Inject a new renderer into the injector.
12738
+ * @param {string} identifier - Identifier for the renderer.
12739
+ * @param {Function} Renderer - The renderer function.
11722
12740
  */
11723
12741
  attachRenderer(identifier, Renderer) {
11724
12742
  this.registeredRenderers = [...this.registeredRenderers, {
@@ -11727,17 +12745,17 @@ class RenderInjector extends SectionModuleBase {
11727
12745
  }];
11728
12746
  }
11729
12747
 
11730
- /**
11731
- * Detach a renderer from the by key injector.
11732
- * @param {string} identifier - Identifier for the renderer.
12748
+ /**
12749
+ * Detach a renderer from the by key injector.
12750
+ * @param {string} identifier - Identifier for the renderer.
11733
12751
  */
11734
12752
  detachRenderer(identifier) {
11735
12753
  this.registeredRenderers = this.registeredRenderers.filter(r => r.identifier !== identifier);
11736
12754
  }
11737
12755
 
11738
- /**
11739
- * Returns the registered renderers.
11740
- * @returns {Array} Array of registered renderers.
12756
+ /**
12757
+ * Returns the registered renderers.
12758
+ * @returns {Array} Array of registered renderers.
11741
12759
  */
11742
12760
  fetchRenderers() {
11743
12761
  return this.registeredRenderers;
@@ -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) {
@@ -11771,48 +12841,48 @@ var ExpressionLanguageModule = {
11771
12841
 
11772
12842
  const ids = new Ids([32, 36, 1]);
11773
12843
 
11774
- /**
11775
- * @typedef { import('./types').Injector } Injector
11776
- * @typedef { import('./types').Module } Module
11777
- * @typedef { import('./types').Schema } Schema
11778
- *
11779
- * @typedef { import('./types').FormEditorOptions } FormEditorOptions
11780
- * @typedef { import('./types').FormEditorProperties } FormEditorProperties
11781
- *
11782
- * @typedef { {
11783
- * properties: FormEditorProperties,
11784
- * schema: Schema
11785
- * } } State
11786
- *
11787
- * @typedef { (type:string, priority:number, handler:Function) => void } OnEventWithPriority
11788
- * @typedef { (type:string, handler:Function) => void } OnEventWithOutPriority
11789
- * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
12844
+ /**
12845
+ * @typedef { import('./types').Injector } Injector
12846
+ * @typedef { import('./types').Module } Module
12847
+ * @typedef { import('./types').Schema } Schema
12848
+ *
12849
+ * @typedef { import('./types').FormEditorOptions } FormEditorOptions
12850
+ * @typedef { import('./types').FormEditorProperties } FormEditorProperties
12851
+ *
12852
+ * @typedef { {
12853
+ * properties: FormEditorProperties,
12854
+ * schema: Schema
12855
+ * } } State
12856
+ *
12857
+ * @typedef { (type:string, priority:number, handler:Function) => void } OnEventWithPriority
12858
+ * @typedef { (type:string, handler:Function) => void } OnEventWithOutPriority
12859
+ * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
11790
12860
  */
11791
12861
 
11792
- /**
11793
- * The form editor.
12862
+ /**
12863
+ * The form editor.
11794
12864
  */
11795
12865
  class FormEditor {
11796
- /**
11797
- * @constructor
11798
- * @param {FormEditorOptions} options
12866
+ /**
12867
+ * @constructor
12868
+ * @param {FormEditorOptions} options
11799
12869
  */
11800
12870
  constructor(options = {}) {
11801
- /**
11802
- * @public
11803
- * @type {OnEventType}
12871
+ /**
12872
+ * @public
12873
+ * @type {OnEventType}
11804
12874
  */
11805
12875
  this.on = this._onEvent;
11806
12876
 
11807
- /**
11808
- * @public
11809
- * @type {String}
12877
+ /**
12878
+ * @public
12879
+ * @type {String}
11810
12880
  */
11811
12881
  this._id = ids.next();
11812
12882
 
11813
- /**
11814
- * @private
11815
- * @type {Element}
12883
+ /**
12884
+ * @private
12885
+ * @type {Element}
11816
12886
  */
11817
12887
  this._container = formJsViewer.createFormContainer();
11818
12888
  this._container.setAttribute('input-handle-modified-keys', 'z,y');
@@ -11823,15 +12893,15 @@ class FormEditor {
11823
12893
  properties = {}
11824
12894
  } = options;
11825
12895
 
11826
- /**
11827
- * @private
11828
- * @type {any}
12896
+ /**
12897
+ * @private
12898
+ * @type {any}
11829
12899
  */
11830
12900
  this.exporter = exporter;
11831
12901
 
11832
- /**
11833
- * @private
11834
- * @type {State}
12902
+ /**
12903
+ * @private
12904
+ * @type {State}
11835
12905
  */
11836
12906
  this._state = {
11837
12907
  properties,
@@ -11860,10 +12930,10 @@ class FormEditor {
11860
12930
  this._detach(false);
11861
12931
  }
11862
12932
 
11863
- /**
11864
- * @param {Schema} schema
11865
- *
11866
- * @return {Promise<{ warnings: Array<any> }>}
12933
+ /**
12934
+ * @param {Schema} schema
12935
+ *
12936
+ * @return {Promise<{ warnings: Array<any> }>}
11867
12937
  */
11868
12938
  importSchema(schema) {
11869
12939
  return new Promise((resolve, reject) => {
@@ -11892,15 +12962,15 @@ class FormEditor {
11892
12962
  });
11893
12963
  }
11894
12964
 
11895
- /**
11896
- * @returns {Schema}
12965
+ /**
12966
+ * @returns {Schema}
11897
12967
  */
11898
12968
  saveSchema() {
11899
12969
  return this.getSchema();
11900
12970
  }
11901
12971
 
11902
- /**
11903
- * @returns {Schema}
12972
+ /**
12973
+ * @returns {Schema}
11904
12974
  */
11905
12975
  getSchema() {
11906
12976
  const {
@@ -11909,8 +12979,8 @@ class FormEditor {
11909
12979
  return exportSchema(schema, this.exporter, formJsViewer.schemaVersion);
11910
12980
  }
11911
12981
 
11912
- /**
11913
- * @param {Element|string} parentNode
12982
+ /**
12983
+ * @param {Element|string} parentNode
11914
12984
  */
11915
12985
  attachTo(parentNode) {
11916
12986
  if (!parentNode) {
@@ -11928,10 +12998,10 @@ class FormEditor {
11928
12998
  this._detach();
11929
12999
  }
11930
13000
 
11931
- /**
11932
- * @internal
11933
- *
11934
- * @param {boolean} [emit]
13001
+ /**
13002
+ * @internal
13003
+ *
13004
+ * @param {boolean} [emit]
11935
13005
  */
11936
13006
  _detach(emit = true) {
11937
13007
  const container = this._container,
@@ -11945,9 +13015,9 @@ class FormEditor {
11945
13015
  parentNode.removeChild(container);
11946
13016
  }
11947
13017
 
11948
- /**
11949
- * @param {any} property
11950
- * @param {any} value
13018
+ /**
13019
+ * @param {any} property
13020
+ * @param {any} value
11951
13021
  */
11952
13022
  setProperty(property, value) {
11953
13023
  const properties = minDash.set(this._getState().properties, [property], value);
@@ -11956,21 +13026,21 @@ class FormEditor {
11956
13026
  });
11957
13027
  }
11958
13028
 
11959
- /**
11960
- * @param {string} type
11961
- * @param {Function} handler
13029
+ /**
13030
+ * @param {string} type
13031
+ * @param {Function} handler
11962
13032
  */
11963
13033
  off(type, handler) {
11964
13034
  this.get('eventBus').off(type, handler);
11965
13035
  }
11966
13036
 
11967
- /**
11968
- * @internal
11969
- *
11970
- * @param {FormEditorOptions} options
11971
- * @param {Element} container
11972
- *
11973
- * @returns {Injector}
13037
+ /**
13038
+ * @internal
13039
+ *
13040
+ * @param {FormEditorOptions} options
13041
+ * @param {Element} container
13042
+ *
13043
+ * @returns {Injector}
11974
13044
  */
11975
13045
  _createInjector(options, container) {
11976
13046
  const {
@@ -11992,22 +13062,22 @@ class FormEditor {
11992
13062
  }, core, ...modules, ...additionalModules]);
11993
13063
  }
11994
13064
 
11995
- /**
11996
- * @internal
13065
+ /**
13066
+ * @internal
11997
13067
  */
11998
13068
  _emit(type, data) {
11999
13069
  this.get('eventBus').fire(type, data);
12000
13070
  }
12001
13071
 
12002
- /**
12003
- * @internal
13072
+ /**
13073
+ * @internal
12004
13074
  */
12005
13075
  _getState() {
12006
13076
  return this._state;
12007
13077
  }
12008
13078
 
12009
- /**
12010
- * @internal
13079
+ /**
13080
+ * @internal
12011
13081
  */
12012
13082
  _setState(state) {
12013
13083
  this._state = {
@@ -12017,15 +13087,15 @@ class FormEditor {
12017
13087
  this._emit('changed', this._getState());
12018
13088
  }
12019
13089
 
12020
- /**
12021
- * @internal
13090
+ /**
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
- /**
12028
- * @internal
13097
+ /**
13098
+ * @internal
12029
13099
  */
12030
13100
  _onEvent(type, priority, handler) {
12031
13101
  this.get('eventBus').on(type, priority, handler);