@bpmn-io/form-js-editor 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +0 -3
  2. package/dist/assets/form-js-editor-base.css +486 -0
  3. package/dist/assets/form-js-editor.css +1162 -60
  4. package/dist/index.cjs +1258 -529
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.es.js +1260 -531
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/FormEditor.d.ts +9 -0
  9. package/dist/types/core/FormFieldRegistry.d.ts +1 -1
  10. package/dist/types/core/FormLayoutValidator.d.ts +16 -0
  11. package/dist/types/core/FormLayouter.d.ts +1 -0
  12. package/dist/types/core/index.d.ts +8 -4
  13. package/dist/types/features/dragging/Dragging.d.ts +60 -0
  14. package/dist/types/features/dragging/index.d.ts +6 -0
  15. package/dist/types/features/editor-actions/index.d.ts +1 -1
  16. package/dist/types/features/expression-language/index.d.ts +8 -0
  17. package/dist/types/features/keyboard/index.d.ts +1 -1
  18. package/dist/types/features/modeling/FormLayoutUpdater.d.ts +13 -0
  19. package/dist/types/features/modeling/Modeling.d.ts +7 -7
  20. package/dist/types/features/modeling/behavior/index.d.ts +3 -3
  21. package/dist/types/features/modeling/cmd/Util.d.ts +1 -0
  22. package/dist/types/features/modeling/index.d.ts +3 -1
  23. package/dist/types/features/palette/index.d.ts +1 -1
  24. package/dist/types/features/properties-panel/entries/ColumnsEntry.d.ts +2 -1
  25. package/dist/types/features/properties-panel/entries/DefaultValueEntry.d.ts +1 -0
  26. package/dist/types/features/properties-panel/entries/InputKeyValuesSourceEntry.d.ts +1 -1
  27. package/dist/types/features/properties-panel/entries/SelectEntries.d.ts +1 -0
  28. package/dist/types/features/properties-panel/entries/factories/simpleBoolEntryFactory.d.ts +1 -0
  29. package/dist/types/features/properties-panel/groups/GeneralGroup.d.ts +1 -1
  30. package/dist/types/features/properties-panel/groups/LayoutGroup.d.ts +11 -0
  31. package/dist/types/features/properties-panel/groups/index.d.ts +1 -0
  32. package/dist/types/features/properties-panel/index.d.ts +1 -1
  33. package/dist/types/features/selection/index.d.ts +2 -2
  34. package/dist/types/import/Importer.d.ts +4 -2
  35. package/dist/types/import/index.d.ts +1 -1
  36. package/dist/types/index.d.ts +2 -2
  37. package/dist/types/render/EditorFormFields.d.ts +1 -1
  38. package/dist/types/render/components/FieldDragPreview.d.ts +1 -0
  39. package/dist/types/render/components/editor-form-fields/EditorText.d.ts +1 -1
  40. package/dist/types/render/components/editor-form-fields/index.d.ts +1 -1
  41. package/dist/types/render/components/icons/index.d.ts +1 -1
  42. package/dist/types/render/hooks/index.d.ts +3 -0
  43. package/dist/types/render/hooks/useDebounce.d.ts +1 -0
  44. package/dist/types/render/hooks/usePrevious.d.ts +1 -0
  45. package/dist/types/render/index.d.ts +2 -2
  46. package/package.json +10 -8
  47. package/dist/assets/dragula.css +0 -22
  48. package/dist/assets/properties-panel.css +0 -1016
package/dist/index.cjs CHANGED
@@ -5,12 +5,13 @@ var Ids = require('ids');
5
5
  var minDash = require('min-dash');
6
6
  var classnames = require('classnames');
7
7
  var jsxRuntime = require('preact/jsx-runtime');
8
- var preact = require('preact');
9
8
  var hooks$1 = require('preact/hooks');
10
- var dragula = require('dragula');
9
+ var preact = require('preact');
11
10
  var React = require('preact/compat');
11
+ var dragula = require('dragula');
12
12
  var minDom = require('min-dom');
13
13
  var arrayMove = require('array-move');
14
+ var feelers = require('feelers');
14
15
  var FeelEditor = require('@bpmn-io/feel-editor');
15
16
  var Big = require('big.js');
16
17
 
@@ -570,19 +571,78 @@ class FormFieldRegistry extends formJsViewer.FormFieldRegistry {
570
571
  }
571
572
  }
572
573
 
574
+ class FormLayoutValidator {
575
+ /**
576
+ * @constructor
577
+ *
578
+ * @param { import('./FormLayouter').default } formLayouter
579
+ * @param { import('./FormFieldRegistry').default } formFieldRegistry
580
+ */
581
+ constructor(formLayouter, formFieldRegistry) {
582
+ this._formLayouter = formLayouter;
583
+ this._formFieldRegistry = formFieldRegistry;
584
+ }
585
+ validateField(field = {}, columns, row) {
586
+ // allow empty (auto columns)
587
+ if (columns) {
588
+ // allow minimum 2 cols
589
+ if (columns < 2) {
590
+ return 'Minimum 2 columns are allowed';
591
+ }
592
+
593
+ // allow maximum 16 cols
594
+ if (columns > 16) {
595
+ return 'Maximum 16 columns are allowed';
596
+ }
597
+ }
598
+ if (!row) {
599
+ row = this._formLayouter.getRowForField(field);
600
+ }
601
+
602
+ // calculate columns with and without updated field
603
+ let sumColumns = parseInt(columns) || 0;
604
+ let sumFields = 1;
605
+ let sumAutoCols = columns ? 0 : 1;
606
+ row.components.forEach(id => {
607
+ if (field.id === id) {
608
+ return;
609
+ }
610
+ const component = this._formFieldRegistry.get(id);
611
+ const cols = (component.layout || {}).columns;
612
+ if (!cols) {
613
+ sumAutoCols++;
614
+ }
615
+ sumColumns += parseInt(cols) || 0;
616
+ sumFields++;
617
+ });
618
+
619
+ // do not allow overflows
620
+ if (sumColumns > 16 || sumColumns === 16 && sumAutoCols > 0 || columns === 16 && sumFields > 1) {
621
+ return 'New value exceeds the maximum of 16 columns per row';
622
+ }
623
+ if (sumFields > 4) {
624
+ return 'Maximum 4 fields per row are allowed';
625
+ }
626
+ return null;
627
+ }
628
+ }
629
+ FormLayoutValidator.$inject = ['formLayouter', 'formFieldRegistry'];
630
+
573
631
  class Importer {
574
632
  /**
575
633
  * @constructor
576
634
  * @param { import('../core/FormFieldRegistry').default } formFieldRegistry
577
635
  * @param { import('../core/FieldFactory').default } fieldFactory
636
+ * @param { import('../core/FormLayouter').default } formLayouter
578
637
  */
579
- constructor(formFieldRegistry, fieldFactory) {
638
+ constructor(formFieldRegistry, fieldFactory, formLayouter) {
580
639
  this._formFieldRegistry = formFieldRegistry;
581
640
  this._fieldFactory = fieldFactory;
641
+ this._formLayouter = formLayouter;
582
642
  }
583
643
 
584
644
  /**
585
- * Import schema creating fields, attaching additional
645
+ * Import schema creating rows, fields, attaching additional
586
646
  * information to each field and adding fields to the
587
647
  * field registry.
588
648
  *
@@ -602,6 +662,7 @@ class Importer {
602
662
  const warnings = [];
603
663
  try {
604
664
  const importedSchema = this.importFormField(formJsViewer.clone(schema));
665
+ this._formLayouter.calculateLayout(formJsViewer.clone(importedSchema));
605
666
  return {
606
667
  schema: importedSchema,
607
668
  warnings
@@ -666,7 +727,7 @@ class Importer {
666
727
  });
667
728
  }
668
729
  }
669
- Importer.$inject = ['formFieldRegistry', 'fieldFactory'];
730
+ Importer.$inject = ['formFieldRegistry', 'fieldFactory', 'formLayouter'];
670
731
 
671
732
  var importModule = {
672
733
  importer: ['type', Importer]
@@ -683,11 +744,35 @@ function editorFormFieldClasses(type, {
683
744
  });
684
745
  }
685
746
 
686
- 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); }
747
+ const DragAndDropContext = preact.createContext({
748
+ drake: null
749
+ });
750
+ var DragAndDropContext$1 = DragAndDropContext;
751
+
752
+ /**
753
+ * @param {string} type
754
+ * @param {boolean} [strict]
755
+ *
756
+ * @returns {any}
757
+ */
758
+ function getService$1(type, strict) {}
759
+ const FormEditorContext = preact.createContext({
760
+ getService: getService$1
761
+ });
762
+ var FormEditorContext$1 = FormEditorContext;
763
+
764
+ function useService$1 (type, strict) {
765
+ const {
766
+ getService
767
+ } = hooks$1.useContext(FormEditorContext$1);
768
+ return getService(type, strict);
769
+ }
770
+
771
+ 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); }
687
772
  var CloseIcon = (({
688
773
  styles = {},
689
774
  ...props
690
- }) => /*#__PURE__*/React.createElement("svg", _extends$2({
775
+ }) => /*#__PURE__*/React.createElement("svg", _extends$3({
691
776
  width: "16",
692
777
  height: "16",
693
778
  fill: "currentColor",
@@ -698,6 +783,23 @@ var CloseIcon = (({
698
783
  d: "M12 4.7l-.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"
699
784
  })));
700
785
 
786
+ 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); }
787
+ var DraggableIcon = (({
788
+ styles = {},
789
+ ...props
790
+ }) => /*#__PURE__*/React.createElement("svg", _extends$2({
791
+ xmlns: "http://www.w3.org/2000/svg",
792
+ width: "16",
793
+ height: "16",
794
+ fill: "currentcolor",
795
+ viewBox: "0 0 32 32"
796
+ }, props), /*#__PURE__*/React.createElement("path", {
797
+ d: "M10 6h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4z"
798
+ }), /*#__PURE__*/React.createElement("path", {
799
+ d: "M0 0h32v32H0z",
800
+ fill: "none"
801
+ })));
802
+
701
803
  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); }
702
804
  var SearchIcon = (({
703
805
  styles = {},
@@ -718,6 +820,8 @@ function EditorText(props) {
718
820
  text = ''
719
821
  } = props.field;
720
822
  const Icon = formJsViewer.iconsByType('text');
823
+ const templating = useService$1('templating');
824
+ const expressionLanguage = useService$1('expressionLanguage');
721
825
  if (!text) {
722
826
  return jsxRuntime.jsx("div", {
723
827
  class: editorFormFieldClasses(type),
@@ -729,7 +833,7 @@ function EditorText(props) {
729
833
  })
730
834
  });
731
835
  }
732
- if (formJsViewer.isExpression(text)) {
836
+ if (expressionLanguage.isExpression(text)) {
733
837
  return jsxRuntime.jsx("div", {
734
838
  class: editorFormFieldClasses(type),
735
839
  children: jsxRuntime.jsxs("div", {
@@ -740,6 +844,17 @@ function EditorText(props) {
740
844
  })
741
845
  });
742
846
  }
847
+ if (templating.isTemplate(text)) {
848
+ return jsxRuntime.jsx("div", {
849
+ class: editorFormFieldClasses(type),
850
+ children: jsxRuntime.jsxs("div", {
851
+ class: "fjs-form-field-placeholder",
852
+ children: [jsxRuntime.jsx(Icon, {
853
+ viewBox: "0 0 54 54"
854
+ }), "Text view is templated"]
855
+ })
856
+ });
857
+ }
743
858
  return jsxRuntime.jsx(formJsViewer.Text, {
744
859
  ...props,
745
860
  disableLinks: true
@@ -760,30 +875,6 @@ class EditorFormFields extends formJsViewer.FormFields {
760
875
  }
761
876
  }
762
877
 
763
- const DragAndDropContext = preact.createContext({
764
- drake: null
765
- });
766
- var DragAndDropContext$1 = DragAndDropContext;
767
-
768
- /**
769
- * @param {string} type
770
- * @param {boolean} [strict]
771
- *
772
- * @returns {any}
773
- */
774
- function getService$1(type, strict) {}
775
- const FormEditorContext = preact.createContext({
776
- getService: getService$1
777
- });
778
- var FormEditorContext$1 = FormEditorContext;
779
-
780
- function useService$1 (type, strict) {
781
- const {
782
- getService
783
- } = hooks$1.useContext(FormEditorContext$1);
784
- return getService(type, strict);
785
- }
786
-
787
878
  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); }
788
879
  var ListDeleteIcon = (({
789
880
  styles = {},
@@ -887,7 +978,7 @@ function Palette(props) {
887
978
  return jsxRuntime.jsxs("div", {
888
979
  class: "fjs-palette-field fjs-drag-copy fjs-no-drop",
889
980
  "data-field-type": type,
890
- title: `Create a ${label} element`,
981
+ title: `Create ${getIndefiniteArticle(type)} ${label} element`,
891
982
  children: [Icon ? jsxRuntime.jsx(Icon, {
892
983
  class: "fjs-palette-field-icon",
893
984
  width: "36",
@@ -926,6 +1017,12 @@ function groupEntries(entries) {
926
1017
  });
927
1018
  return groups.filter(g => g.entries.length);
928
1019
  }
1020
+ function getIndefiniteArticle(type) {
1021
+ if (['image'].includes(type)) {
1022
+ return 'an';
1023
+ }
1024
+ return 'a';
1025
+ }
929
1026
 
930
1027
  const CURSOR_CLS_PATTERN = /^fjs-cursor-.*$/;
931
1028
  function set(mode) {
@@ -939,6 +1036,307 @@ function unset() {
939
1036
  set(null);
940
1037
  }
941
1038
 
1039
+ const DRAG_CONTAINER_CLS = 'fjs-drag-container';
1040
+ const DROP_CONTAINER_VERTICAL_CLS = 'fjs-drop-container-vertical';
1041
+ const DROP_CONTAINER_HORIZONTAL_CLS = 'fjs-drop-container-horizontal';
1042
+ const DRAG_MOVE_CLS = 'fjs-drag-move';
1043
+ const DRAG_ROW_MOVE_CLS = 'fjs-drag-row-move';
1044
+ const DRAG_COPY_CLS = 'fjs-drag-copy';
1045
+ const DRAG_NO_DROP_CLS = 'fjs-no-drop';
1046
+ const DRAG_NO_MOVE_CLS = 'fjs-no-move';
1047
+ const ERROR_DROP_CLS = 'fjs-error-drop';
1048
+
1049
+ /**
1050
+ * @typedef { { id: String, components: Array<any> } } FormRow
1051
+ */
1052
+
1053
+ class Dragging {
1054
+ /**
1055
+ * @constructor
1056
+ *
1057
+ * @param { import('../../core/FormFieldRegistry').default } formFieldRegistry
1058
+ * @param { import('../../core/FormLayouter').default } formLayouter
1059
+ * @param { import('../../core/FormLayoutValidator').default } formLayoutValidator
1060
+ * @param { import('../../core/EventBus').default } eventBus
1061
+ * @param { import('../modeling/Modeling').default } modeling
1062
+ */
1063
+ constructor(formFieldRegistry, formLayouter, formLayoutValidator, eventBus, modeling) {
1064
+ this._formFieldRegistry = formFieldRegistry;
1065
+ this._formLayouter = formLayouter;
1066
+ this._formLayoutValidator = formLayoutValidator;
1067
+ this._eventBus = eventBus;
1068
+ this._modeling = modeling;
1069
+ }
1070
+
1071
+ /**
1072
+ * Calculcates position in form schema given the dropped place.
1073
+ *
1074
+ * @param { FormRow } targetRow
1075
+ * @param { any } targetFormField
1076
+ * @param { HTMLElement } sibling
1077
+ * @returns { number }
1078
+ */
1079
+ getTargetIndex(targetRow, targetFormField, sibling) {
1080
+ /** @type HTMLElement */
1081
+ const siblingFormFieldNode = sibling && sibling.querySelector('.fjs-element');
1082
+ const siblingFormField = siblingFormFieldNode && this._formFieldRegistry.get(siblingFormFieldNode.dataset.id);
1083
+
1084
+ // (1) dropped before existing field => place before
1085
+ if (siblingFormField) {
1086
+ return getFormFieldIndex$1(targetFormField, siblingFormField);
1087
+ }
1088
+
1089
+ // (2) dropped in row => place at the end of row (after last field in row)
1090
+ if (targetRow) {
1091
+ return getFormFieldIndex$1(targetFormField, this._formFieldRegistry.get(targetRow.components[targetRow.components.length - 1])) + 1;
1092
+ }
1093
+
1094
+ // (3) dropped as last item
1095
+ return targetFormField.components.length;
1096
+ }
1097
+ validateDrop(element, target) {
1098
+ const formFieldNode = element.querySelector('.fjs-element');
1099
+ const targetRow = this._formLayouter.getRow(target.dataset.rowId);
1100
+ let columns;
1101
+ let formField;
1102
+ if (formFieldNode) {
1103
+ formField = this._formFieldRegistry.get(formFieldNode.dataset.id);
1104
+ columns = (formField.layout || {}).columns;
1105
+ }
1106
+ return this._formLayoutValidator.validateField(formField, columns, targetRow);
1107
+ }
1108
+ moveField(element, source, targetRow, targetFormField, targetIndex) {
1109
+ const formFieldNode = element.querySelector('.fjs-element');
1110
+ const formField = this._formFieldRegistry.get(formFieldNode.dataset.id);
1111
+ const sourceParent = getFormParent(source);
1112
+ const sourceFormField = this._formFieldRegistry.get(sourceParent.dataset.id);
1113
+ const sourceIndex = getFormFieldIndex$1(sourceFormField, formField);
1114
+ const sourceRow = this._formLayouter.getRowForField(formField);
1115
+ this._modeling.moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex, sourceRow, targetRow);
1116
+ }
1117
+ createNewField(element, targetRow, targetFormField, targetIndex) {
1118
+ const type = element.dataset.fieldType;
1119
+ let attrs = {
1120
+ type
1121
+ };
1122
+ attrs = {
1123
+ ...attrs,
1124
+ layout: {
1125
+ row: targetRow ? targetRow.id : this._formLayouter.nextRowId(),
1126
+ // enable auto columns
1127
+ columns: null
1128
+ }
1129
+ };
1130
+ this._modeling.addFormField(attrs, targetFormField, targetIndex);
1131
+ }
1132
+ handleRowDrop(el, target, source, sibling) {
1133
+ const targetFormField = this._formFieldRegistry.get(target.dataset.id);
1134
+ const rowNode = el.querySelector('.fjs-layout-row');
1135
+ const row = this._formLayouter.getRow(rowNode.dataset.rowId);
1136
+
1137
+ // move each field in the row before first field of sibling row
1138
+ row.components.forEach((id, index) => {
1139
+ const formField = this._formFieldRegistry.get(id);
1140
+ const sourceParent = getFormParent(source);
1141
+ const sourceFormField = this._formFieldRegistry.get(sourceParent.dataset.id);
1142
+ const siblingRowNode = sibling && sibling.querySelector('.fjs-layout-row');
1143
+ const siblingRow = siblingRowNode && this._formLayouter.getRow(siblingRowNode.dataset.rowId);
1144
+ const siblingFormField = sibling && this._formFieldRegistry.get(siblingRow.components[0]);
1145
+ const sourceIndex = getFormFieldIndex$1(sourceFormField, formField);
1146
+ const targetIndex = (siblingRowNode ? getFormFieldIndex$1(targetFormField, siblingFormField) : targetFormField.components.length) + index;
1147
+ this._modeling.moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex, row, row);
1148
+ });
1149
+ }
1150
+ handleElementDrop(el, target, source, sibling, drake) {
1151
+ // (1) detect drop target
1152
+ const targetFormField = this._formFieldRegistry.get(getFormParent(target).dataset.id);
1153
+ let targetRow;
1154
+
1155
+ // (2.1) dropped in existing row
1156
+ if (isRow(target)) {
1157
+ unsetDropNotAllowed(target);
1158
+ targetRow = this._formLayouter.getRow(target.dataset.rowId);
1159
+
1160
+ // validate whether drop is allowed
1161
+ const validationError = this.validateDrop(el, target);
1162
+ if (validationError) {
1163
+ return drake.cancel(true);
1164
+ }
1165
+ }
1166
+ drake.remove();
1167
+
1168
+ // (3) detect position to drop field in schema order
1169
+ const targetIndex = this.getTargetIndex(targetRow, targetFormField, sibling);
1170
+
1171
+ // (4) create new field or move existing
1172
+ if (isPalette(source)) {
1173
+ this.createNewField(el, targetRow, targetFormField, targetIndex);
1174
+ } else {
1175
+ this.moveField(el, source, targetRow, targetFormField, targetIndex);
1176
+ }
1177
+ }
1178
+
1179
+ /**
1180
+ * @param { { container: Array<string>, direction: string, mirrorContainer: string } } options
1181
+ */
1182
+ createDragulaInstance(options) {
1183
+ const {
1184
+ container,
1185
+ direction,
1186
+ mirrorContainer
1187
+ } = options || {};
1188
+ const dragulaInstance = dragula({
1189
+ direction,
1190
+ mirrorContainer,
1191
+ isContainer(el) {
1192
+ return container.some(cls => el.classList.contains(cls));
1193
+ },
1194
+ moves(el, source, handle) {
1195
+ return !handle.classList.contains(DRAG_NO_MOVE_CLS) && (el.classList.contains(DRAG_MOVE_CLS) || el.classList.contains(DRAG_COPY_CLS) || el.classList.contains(DRAG_ROW_MOVE_CLS));
1196
+ },
1197
+ copy(el) {
1198
+ return el.classList.contains(DRAG_COPY_CLS);
1199
+ },
1200
+ accepts: (el, target) => {
1201
+ unsetDropNotAllowed(target);
1202
+
1203
+ // allow dropping rows only between rows
1204
+ if (el.classList.contains(DRAG_ROW_MOVE_CLS)) {
1205
+ return !target.classList.contains(DROP_CONTAINER_HORIZONTAL_CLS);
1206
+ }
1207
+
1208
+ // validate field drop in row
1209
+ if (isRow(target)) {
1210
+ const validationError = this.validateDrop(el, target);
1211
+ if (validationError) {
1212
+ // set error feedback to row
1213
+ setDropNotAllowed(target);
1214
+ }
1215
+ }
1216
+ return !target.classList.contains(DRAG_NO_DROP_CLS);
1217
+ },
1218
+ slideFactorX: 10,
1219
+ slideFactorY: 5
1220
+ });
1221
+
1222
+ // bind life cycle events
1223
+ dragulaInstance.on('drag', (element, source) => {
1224
+ this.emit('drag.start', {
1225
+ element,
1226
+ source
1227
+ });
1228
+ });
1229
+ dragulaInstance.on('dragend', element => {
1230
+ this.emit('drag.end', {
1231
+ element
1232
+ });
1233
+ });
1234
+ dragulaInstance.on('drop', (element, target, source, sibling) => {
1235
+ this.emit('drag.drop', {
1236
+ element,
1237
+ target,
1238
+ source,
1239
+ sibling
1240
+ });
1241
+ });
1242
+ dragulaInstance.on('over', (element, container, source) => {
1243
+ this.emit('drag.hover', {
1244
+ element,
1245
+ container,
1246
+ source
1247
+ });
1248
+ });
1249
+ dragulaInstance.on('out', (element, container, source) => {
1250
+ this.emit('drag.out', {
1251
+ element,
1252
+ container,
1253
+ source
1254
+ });
1255
+ });
1256
+ dragulaInstance.on('cancel', (element, container, source) => {
1257
+ this.emit('drag.cancel', {
1258
+ element,
1259
+ container,
1260
+ source
1261
+ });
1262
+ });
1263
+ dragulaInstance.on('drop', (el, target, source, sibling) => {
1264
+ if (!target) {
1265
+ dragulaInstance.remove();
1266
+ return;
1267
+ }
1268
+
1269
+ // (1) handle row drop
1270
+ if (isDragRow(el)) {
1271
+ this.handleRowDrop(el, target, source, sibling);
1272
+ } else {
1273
+ // (2) handle form field drop
1274
+ this.handleElementDrop(el, target, source, sibling, dragulaInstance);
1275
+ }
1276
+ });
1277
+ this.emit('dragula.created', dragulaInstance);
1278
+ return dragulaInstance;
1279
+ }
1280
+ emit(event, context) {
1281
+ this._eventBus.fire(event, context);
1282
+ }
1283
+ }
1284
+ Dragging.$inject = ['formFieldRegistry', 'formLayouter', 'formLayoutValidator', 'eventBus', 'modeling'];
1285
+
1286
+ // helper //////////
1287
+
1288
+ function getFormFieldIndex$1(parent, formField) {
1289
+ let fieldFormIndex = parent.components.length;
1290
+ parent.components.forEach(({
1291
+ id
1292
+ }, index) => {
1293
+ if (id === formField.id) {
1294
+ fieldFormIndex = index;
1295
+ }
1296
+ });
1297
+ return fieldFormIndex;
1298
+ }
1299
+ function isRow(node) {
1300
+ return node.classList.contains('fjs-layout-row');
1301
+ }
1302
+ function isDragRow(node) {
1303
+ return node.classList.contains(DRAG_ROW_MOVE_CLS);
1304
+ }
1305
+ function isPalette(node) {
1306
+ return node.classList.contains('fjs-palette-fields');
1307
+ }
1308
+ function getFormParent(node) {
1309
+ return node.closest('.fjs-element');
1310
+ }
1311
+ function setDropNotAllowed(node) {
1312
+ node.classList.add(ERROR_DROP_CLS);
1313
+ set('not-allowed');
1314
+ }
1315
+ function unsetDropNotAllowed(node) {
1316
+ node.classList.remove(ERROR_DROP_CLS);
1317
+ set('grabbing');
1318
+ }
1319
+
1320
+ function FieldDragPreview(props) {
1321
+ const {
1322
+ class: className,
1323
+ Icon,
1324
+ label
1325
+ } = props;
1326
+ return jsxRuntime.jsxs("div", {
1327
+ class: classnames('fjs-field-preview', className),
1328
+ children: [jsxRuntime.jsx(Icon, {
1329
+ class: "fjs-field-preview-icon",
1330
+ width: "36",
1331
+ height: "36",
1332
+ viewBox: "0 0 54 54"
1333
+ }), jsxRuntime.jsx("span", {
1334
+ class: "fjs-field-preview-text",
1335
+ children: label
1336
+ })]
1337
+ });
1338
+ }
1339
+
942
1340
  function ContextPad(props) {
943
1341
  if (!props.children) {
944
1342
  return null;
@@ -985,7 +1383,7 @@ function Element(props) {
985
1383
  event.stopPropagation();
986
1384
  selection.toggle(field);
987
1385
  }
988
- const classes = ['fjs-element'];
1386
+ const classes = [];
989
1387
  if (props.class) {
990
1388
  classes.push(...props.class.split(' '));
991
1389
  }
@@ -1004,7 +1402,9 @@ function Element(props) {
1004
1402
  "data-field-type": type,
1005
1403
  onClick: onClick,
1006
1404
  ref: ref,
1007
- children: [jsxRuntime.jsx(ContextPad, {
1405
+ children: [jsxRuntime.jsx(DebugColumns, {
1406
+ field: field
1407
+ }), jsxRuntime.jsx(ContextPad, {
1008
1408
  children: selection.isSelected(field) && field.type !== 'default' ? jsxRuntime.jsx("button", {
1009
1409
  class: "fjs-context-pad-item",
1010
1410
  onClick: onRemove,
@@ -1013,6 +1413,20 @@ function Element(props) {
1013
1413
  }), props.children]
1014
1414
  });
1015
1415
  }
1416
+ function DebugColumns(props) {
1417
+ const {
1418
+ field
1419
+ } = props;
1420
+ const debugColumnsConfig = useService$1('config.debugColumns');
1421
+ if (!debugColumnsConfig || field.type == 'default') {
1422
+ return null;
1423
+ }
1424
+ return jsxRuntime.jsx("div", {
1425
+ 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;",
1426
+ class: "fjs-debug-columns",
1427
+ children: (field.layout || {}).columns || 'auto'
1428
+ });
1429
+ }
1016
1430
  function Children(props) {
1017
1431
  const {
1018
1432
  field
@@ -1020,7 +1434,7 @@ function Children(props) {
1020
1434
  const {
1021
1435
  id
1022
1436
  } = field;
1023
- const classes = ['fjs-children', 'fjs-drag-container'];
1437
+ const classes = ['fjs-children', DROP_CONTAINER_VERTICAL_CLS];
1024
1438
  if (props.class) {
1025
1439
  classes.push(...props.class.split(' '));
1026
1440
  }
@@ -1030,12 +1444,51 @@ function Children(props) {
1030
1444
  children: props.children
1031
1445
  });
1032
1446
  }
1447
+ function Row(props) {
1448
+ const {
1449
+ row
1450
+ } = props;
1451
+ const {
1452
+ id
1453
+ } = row;
1454
+ const classes = [DROP_CONTAINER_HORIZONTAL_CLS];
1455
+ if (props.class) {
1456
+ classes.push(...props.class.split(' '));
1457
+ }
1458
+ return jsxRuntime.jsxs("div", {
1459
+ class: classnames(DRAG_ROW_MOVE_CLS),
1460
+ children: [jsxRuntime.jsx("span", {
1461
+ class: "fjs-row-dragger",
1462
+ children: jsxRuntime.jsx(DraggableIcon, {})
1463
+ }), jsxRuntime.jsx("div", {
1464
+ class: classes.join(' '),
1465
+ "data-row-id": id,
1466
+ children: props.children
1467
+ })]
1468
+ });
1469
+ }
1470
+ function Column(props) {
1471
+ const {
1472
+ field
1473
+ } = props;
1474
+ const classes = [DRAG_MOVE_CLS];
1475
+ if (field.type === 'default') {
1476
+ return props.children;
1477
+ }
1478
+ if (props.class) {
1479
+ classes.push(...props.class.split(' '));
1480
+ }
1481
+ return jsxRuntime.jsx("div", {
1482
+ "data-field-type": field.type,
1483
+ class: classes.join(' '),
1484
+ children: props.children
1485
+ });
1486
+ }
1033
1487
  function FormEditor$1(props) {
1034
- const eventBus = useService$1('eventBus'),
1488
+ const dragging = useService$1('dragging'),
1489
+ eventBus = useService$1('eventBus'),
1035
1490
  formEditor = useService$1('formEditor'),
1036
- formFieldRegistry = useService$1('formFieldRegistry'),
1037
1491
  injector = useService$1('injector'),
1038
- modeling = useService$1('modeling'),
1039
1492
  selection = useService$1('selection'),
1040
1493
  palette = useService$1('palette'),
1041
1494
  paletteConfig = useService$1('config.palette'),
@@ -1044,6 +1497,7 @@ function FormEditor$1(props) {
1044
1497
  const {
1045
1498
  schema
1046
1499
  } = formEditor._getState();
1500
+ const formContainerRef = hooks$1.useRef(null);
1047
1501
  const paletteRef = hooks$1.useRef(null);
1048
1502
  const propertiesPanelRef = hooks$1.useRef(null);
1049
1503
  const [, setSelection] = hooks$1.useState(schema);
@@ -1062,98 +1516,12 @@ function FormEditor$1(props) {
1062
1516
  drake
1063
1517
  };
1064
1518
  hooks$1.useEffect(() => {
1065
- const handleDragEvent = (type, context) => {
1066
- return eventBus.fire(type, context);
1067
- };
1068
- const createDragulaInstance = () => {
1069
- const dragulaInstance = dragula({
1070
- isContainer(el) {
1071
- return el.classList.contains('fjs-drag-container');
1072
- },
1073
- copy(el) {
1074
- return el.classList.contains('fjs-drag-copy');
1075
- },
1076
- accepts(el, target) {
1077
- return !target.classList.contains('fjs-no-drop');
1078
- },
1079
- slideFactorX: 10,
1080
- slideFactorY: 5
1081
- });
1082
-
1083
- // bind life cycle events
1084
- dragulaInstance.on('drag', (element, source) => {
1085
- handleDragEvent('drag.start', {
1086
- element,
1087
- source
1088
- });
1089
- });
1090
- dragulaInstance.on('dragend', element => {
1091
- handleDragEvent('drag.end', {
1092
- element
1093
- });
1094
- });
1095
- dragulaInstance.on('drop', (element, target, source, sibling) => {
1096
- handleDragEvent('drag.drop', {
1097
- element,
1098
- target,
1099
- source,
1100
- sibling
1101
- });
1102
- });
1103
- dragulaInstance.on('over', (element, container, source) => {
1104
- handleDragEvent('drag.hover', {
1105
- element,
1106
- container,
1107
- source
1108
- });
1109
- });
1110
- dragulaInstance.on('out', (element, container, source) => {
1111
- handleDragEvent('drag.out', {
1112
- element,
1113
- container,
1114
- source
1115
- });
1116
- });
1117
- dragulaInstance.on('cancel', (element, container, source) => {
1118
- handleDragEvent('drag.cancel', {
1119
- element,
1120
- container,
1121
- source
1122
- });
1123
- });
1124
-
1125
- // set custom styling
1126
- dragulaInstance.on('drag', () => {
1127
- set('grabbing');
1128
- });
1129
- dragulaInstance.on('dragend', () => {
1130
- unset();
1131
- });
1132
- dragulaInstance.on('drop', (el, target, source, sibling) => {
1133
- dragulaInstance.remove();
1134
- if (!target) {
1135
- return;
1136
- }
1137
- const targetFormField = formFieldRegistry.get(target.dataset.id);
1138
- const siblingFormField = sibling && formFieldRegistry.get(sibling.dataset.id),
1139
- targetIndex = siblingFormField ? getFormFieldIndex(targetFormField, siblingFormField) : targetFormField.components.length;
1140
- if (source.classList.contains('fjs-palette-fields')) {
1141
- const type = el.dataset.fieldType;
1142
- modeling.addFormField({
1143
- type
1144
- }, targetFormField, targetIndex);
1145
- } else {
1146
- const formField = formFieldRegistry.get(el.dataset.id),
1147
- sourceFormField = formFieldRegistry.get(source.dataset.id),
1148
- sourceIndex = getFormFieldIndex(sourceFormField, formField);
1149
- modeling.moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex);
1150
- }
1151
- });
1152
- eventBus.fire('dragula.created');
1153
- setDrake(dragulaInstance);
1154
- return dragulaInstance;
1155
- };
1156
- let dragulaInstance = createDragulaInstance();
1519
+ let dragulaInstance = dragging.createDragulaInstance({
1520
+ container: [DRAG_CONTAINER_CLS, DROP_CONTAINER_VERTICAL_CLS, DROP_CONTAINER_HORIZONTAL_CLS],
1521
+ direction: 'vertical',
1522
+ mirrorContainer: formContainerRef.current
1523
+ });
1524
+ setDrake(dragulaInstance);
1157
1525
  const onDetach = () => {
1158
1526
  if (dragulaInstance) {
1159
1527
  dragulaInstance.destroy();
@@ -1162,14 +1530,34 @@ function FormEditor$1(props) {
1162
1530
  };
1163
1531
  const onAttach = () => {
1164
1532
  onDetach();
1165
- dragulaInstance = createDragulaInstance();
1533
+ dragulaInstance = dragging.createDragulaInstance({
1534
+ container: [DRAG_CONTAINER_CLS, DROP_CONTAINER_VERTICAL_CLS, DROP_CONTAINER_HORIZONTAL_CLS],
1535
+ direction: 'vertical',
1536
+ mirrorContainer: formContainerRef.current
1537
+ });
1538
+ setDrake(dragulaInstance);
1539
+ };
1540
+ const onCreate = drake => {
1541
+ setDrake(drake);
1542
+ };
1543
+ const onDragStart = () => {
1544
+ set('grabbing');
1545
+ };
1546
+ const onDragEnd = () => {
1547
+ unset();
1166
1548
  };
1167
1549
  eventBus.on('attach', onAttach);
1168
1550
  eventBus.on('detach', onDetach);
1551
+ eventBus.on('dragula.created', onCreate);
1552
+ eventBus.on('drag.start', onDragStart);
1553
+ eventBus.on('drag.end', onDragEnd);
1169
1554
  return () => {
1170
1555
  onDetach();
1171
1556
  eventBus.off('attach', onAttach);
1172
1557
  eventBus.off('detach', onDetach);
1558
+ eventBus.off('dragula.created', onCreate);
1559
+ eventBus.off('drag.start', onDragStart);
1560
+ eventBus.off('drag.end', onDragEnd);
1173
1561
  };
1174
1562
  }, []);
1175
1563
 
@@ -1179,8 +1567,10 @@ function FormEditor$1(props) {
1179
1567
  }, []);
1180
1568
  const formRenderContext = {
1181
1569
  Children,
1570
+ Column,
1182
1571
  Element,
1183
- Empty
1572
+ Empty,
1573
+ Row
1184
1574
  };
1185
1575
  const formContext = {
1186
1576
  getService(type, strict = true) {
@@ -1229,6 +1619,7 @@ function FormEditor$1(props) {
1229
1619
  class: "fjs-editor-palette-container",
1230
1620
  ref: paletteRef
1231
1621
  }), jsxRuntime.jsx("div", {
1622
+ ref: formContainerRef,
1232
1623
  class: "fjs-form-container",
1233
1624
  children: jsxRuntime.jsx(formJsViewer.FormContext.Provider, {
1234
1625
  value: formContext,
@@ -1264,29 +1655,39 @@ function CreatePreview(props) {
1264
1655
  } = hooks$1.useContext(DragAndDropContext$1);
1265
1656
  function handleCloned(clone, original, type) {
1266
1657
  const fieldType = clone.dataset.fieldType;
1267
- const Icon = formJsViewer.iconsByType(fieldType);
1268
- const {
1269
- label
1270
- } = findPaletteEntry(fieldType);
1658
+
1659
+ // (1) field preview
1271
1660
  if (fieldType) {
1661
+ const {
1662
+ label
1663
+ } = findPaletteEntry(fieldType);
1664
+ const Icon = formJsViewer.iconsByType(fieldType);
1272
1665
  clone.innerHTML = '';
1273
1666
  clone.class = 'gu-mirror';
1667
+ clone.classList.add('fjs-field-preview-container');
1274
1668
  if (original.classList.contains('fjs-palette-field')) {
1275
- preact.render(jsxRuntime.jsxs("div", {
1276
- class: "fjs-palette-field",
1277
- children: [jsxRuntime.jsx(Icon, {
1278
- class: "fjs-palette-field-icon",
1279
- width: "36",
1280
- height: "36",
1281
- viewBox: "0 0 54 54"
1282
- }), jsxRuntime.jsx("span", {
1283
- class: "fjs-palette-field-text",
1284
- children: label
1285
- })]
1286
- }), clone);
1287
- } else {
1288
- preact.render(jsxRuntime.jsx(Icon, {}), clone);
1669
+ // default to auto columns when creating from palette
1670
+ clone.classList.add('cds--col');
1289
1671
  }
1672
+
1673
+ // todo(pinussilvestrus): dragula, how to mitigate cursor position
1674
+ // https://github.com/bevacqua/dragula/issues/285
1675
+ preact.render(jsxRuntime.jsx(FieldDragPreview, {
1676
+ label: label,
1677
+ Icon: Icon
1678
+ }), clone);
1679
+ } else {
1680
+ // (2) row preview
1681
+
1682
+ // remove elements from copy (context pad, row dragger, ...)
1683
+ ['fjs-context-pad', 'fjs-row-dragger', 'fjs-debug-columns'].forEach(cls => {
1684
+ const cloneNode = clone.querySelectorAll('.' + cls);
1685
+ cloneNode.length && cloneNode.forEach(e => e.remove());
1686
+ });
1687
+
1688
+ // mirror grid
1689
+ clone.classList.add('cds--grid');
1690
+ clone.classList.add('cds--grid--condensed');
1290
1691
  }
1291
1692
  }
1292
1693
  hooks$1.useEffect(() => {
@@ -1359,10 +1760,12 @@ var renderModule = {
1359
1760
 
1360
1761
  var core = {
1361
1762
  __depends__: [importModule, renderModule],
1763
+ debounce: ['factory', DebounceFactory],
1362
1764
  eventBus: ['type', EventBus],
1363
1765
  formFieldRegistry: ['type', FormFieldRegistry],
1364
- fieldFactory: ['type', FieldFactory],
1365
- debounce: ['factory', DebounceFactory]
1766
+ formLayouter: ['type', formJsViewer.FormLayouter],
1767
+ formLayoutValidator: ['type', FormLayoutValidator],
1768
+ fieldFactory: ['type', FieldFactory]
1366
1769
  };
1367
1770
 
1368
1771
  var NOT_REGISTERED_ERROR = 'is not a registered action',
@@ -1640,6 +2043,11 @@ var EditorActionsModule = {
1640
2043
  editorActions: ['type', FormEditorActions]
1641
2044
  };
1642
2045
 
2046
+ var DraggingModule = {
2047
+ __init__: ['dragging'],
2048
+ dragging: ['type', Dragging]
2049
+ };
2050
+
1643
2051
  var KEYS_COPY = ['c', 'C', 'KeyC'];
1644
2052
  var KEYS_PASTE = ['v', 'V', 'KeyV'];
1645
2053
  var KEYS_REDO$1 = ['y', 'Y', 'KeyY'];
@@ -2052,6 +2460,13 @@ function updatePath(formFieldRegistry, formField, index) {
2052
2460
  formField._path = [...parent._path, 'components', index];
2053
2461
  return formField;
2054
2462
  }
2463
+ function updateRow(formField, rowId) {
2464
+ formField.layout = {
2465
+ ...(formField.layout || {}),
2466
+ row: rowId
2467
+ };
2468
+ return formField;
2469
+ }
2055
2470
 
2056
2471
  class AddFormFieldHandler {
2057
2472
  /**
@@ -2201,13 +2616,17 @@ class MoveFormFieldHandler {
2201
2616
  sourceFormField,
2202
2617
  targetFormField,
2203
2618
  sourceIndex,
2204
- targetIndex
2619
+ targetIndex,
2620
+ sourceRow,
2621
+ targetRow
2205
2622
  } = context;
2206
2623
  this.moveFormField({
2207
2624
  sourceFormField: targetFormField,
2208
2625
  targetFormField: sourceFormField,
2209
2626
  sourceIndex: targetIndex,
2210
- targetIndex: sourceIndex
2627
+ targetIndex: sourceIndex,
2628
+ sourceRow: targetRow,
2629
+ targetRow: sourceRow
2211
2630
  }, true);
2212
2631
  }
2213
2632
  moveFormField(context, revert) {
@@ -2215,7 +2634,8 @@ class MoveFormFieldHandler {
2215
2634
  sourceFormField,
2216
2635
  targetFormField,
2217
2636
  sourceIndex,
2218
- targetIndex
2637
+ targetIndex,
2638
+ targetRow
2219
2639
  } = context;
2220
2640
  let {
2221
2641
  schema
@@ -2231,11 +2651,15 @@ class MoveFormFieldHandler {
2231
2651
  targetIndex--;
2232
2652
  }
2233
2653
  }
2654
+ const formField = minDash.get(schema, [...sourcePath, sourceIndex]);
2655
+
2656
+ // (1) Add to row
2657
+ updateRow(formField, targetRow ? targetRow.id : null);
2234
2658
 
2235
- // (1) Move form field
2659
+ // (2) Move form field
2236
2660
  arrayMove.mutate(minDash.get(schema, sourcePath), sourceIndex, targetIndex);
2237
2661
 
2238
- // (2) Update paths of new form field and its siblings
2662
+ // (3) Update paths of new form field and its siblings
2239
2663
  minDash.get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
2240
2664
  } else {
2241
2665
  const formField = minDash.get(schema, [...sourcePath, sourceIndex]);
@@ -2248,10 +2672,13 @@ class MoveFormFieldHandler {
2248
2672
  minDash.get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
2249
2673
  const targetPath = [...targetFormField._path, 'components'];
2250
2674
 
2251
- // (3) Add form field
2675
+ // (3) Add to row
2676
+ updateRow(formField, targetRow ? targetRow.id : null);
2677
+
2678
+ // (4) Add form field
2252
2679
  arrayAdd$1(minDash.get(schema, targetPath), targetIndex, formField);
2253
2680
 
2254
- // (4) Update paths of siblings
2681
+ // (5) Update paths of siblings
2255
2682
  minDash.get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
2256
2683
  }
2257
2684
 
@@ -2443,13 +2870,15 @@ class Modeling {
2443
2870
  };
2444
2871
  this._commandStack.execute('formField.edit', context);
2445
2872
  }
2446
- moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex) {
2873
+ moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex, sourceRow, targetRow) {
2447
2874
  const context = {
2448
2875
  formField,
2449
2876
  sourceFormField,
2450
2877
  targetFormField,
2451
2878
  sourceIndex,
2452
- targetIndex
2879
+ targetIndex,
2880
+ sourceRow,
2881
+ targetRow
2453
2882
  };
2454
2883
  this._commandStack.execute('formField.move', context);
2455
2884
  }
@@ -2608,6 +3037,44 @@ minDash.forEach(hooks, function (hook) {
2608
3037
  };
2609
3038
  });
2610
3039
 
3040
+ class FormLayoutUpdater extends CommandInterceptor {
3041
+ constructor(eventBus, formLayouter, modeling, formEditor) {
3042
+ super(eventBus);
3043
+ this._eventBus = eventBus;
3044
+ this._formLayouter = formLayouter;
3045
+ this._modeling = modeling;
3046
+ this._formEditor = formEditor;
3047
+
3048
+ // @ts-ignore
3049
+ this.preExecute(['formField.add', 'formField.remove', 'formField.move', 'id.updateClaim'], event => this.updateRowIds(event));
3050
+
3051
+ // we need that as the state got updates
3052
+ // on the next tick (not in post execute)
3053
+ eventBus.on('changed', context => {
3054
+ const {
3055
+ schema
3056
+ } = context;
3057
+ this.updateLayout(schema);
3058
+ });
3059
+ }
3060
+ updateLayout(schema) {
3061
+ this._formLayouter.clear();
3062
+ this._formLayouter.calculateLayout(formJsViewer.clone(schema));
3063
+ }
3064
+ updateRowIds(event) {
3065
+ const {
3066
+ schema
3067
+ } = this._formEditor._getState();
3068
+
3069
+ // make sure rows are persisted in schema (e.g. for migration case)
3070
+ schema.components.forEach(formField => {
3071
+ const row = this._formLayouter.getRowForField(formField);
3072
+ updateRow(formField, row.id);
3073
+ });
3074
+ }
3075
+ }
3076
+ FormLayoutUpdater.$inject = ['eventBus', 'formLayouter', 'modeling', 'formEditor'];
3077
+
2611
3078
  class IdBehavior extends CommandInterceptor {
2612
3079
  constructor(eventBus, modeling) {
2613
3080
  super(eventBus);
@@ -3129,7 +3596,8 @@ var commandModule = {
3129
3596
 
3130
3597
  var ModelingModule = {
3131
3598
  __depends__: [behaviorModule, commandModule],
3132
- __init__: ['modeling'],
3599
+ __init__: ['formLayoutUpdater', 'modeling'],
3600
+ formLayoutUpdater: ['type', FormLayoutUpdater],
3133
3601
  modeling: ['type', Modeling]
3134
3602
  };
3135
3603
 
@@ -3425,19 +3893,19 @@ const ErrorsContext = preact.createContext({
3425
3893
  errors: {}
3426
3894
  });
3427
3895
 
3428
- /**
3429
- * @typedef {Function} <propertiesPanel.showEntry> callback
3430
- *
3431
- * @example
3432
- *
3433
- * useEvent('propertiesPanel.showEntry', ({ focus = false, ...rest }) => {
3434
- * // ...
3435
- * });
3436
- *
3437
- * @param {Object} context
3438
- * @param {boolean} [context.focus]
3439
- *
3440
- * @returns void
3896
+ /**
3897
+ * @typedef {Function} <propertiesPanel.showEntry> callback
3898
+ *
3899
+ * @example
3900
+ *
3901
+ * useEvent('propertiesPanel.showEntry', ({ focus = false, ...rest }) => {
3902
+ * // ...
3903
+ * });
3904
+ *
3905
+ * @param {Object} context
3906
+ * @param {boolean} [context.focus]
3907
+ *
3908
+ * @returns void
3441
3909
  */
3442
3910
  const EventContext = preact.createContext({
3443
3911
  eventBus: null
@@ -3449,20 +3917,20 @@ const LayoutContext = preact.createContext({
3449
3917
  setLayoutForKey: () => {}
3450
3918
  });
3451
3919
 
3452
- /**
3453
- * Accesses the global DescriptionContext and returns a description for a given id and element.
3454
- *
3455
- * @example
3456
- * ```jsx
3457
- * function TextField(props) {
3458
- * const description = useDescriptionContext('input1', element);
3459
- * }
3460
- * ```
3461
- *
3462
- * @param {string} id
3463
- * @param {object} element
3464
- *
3465
- * @returns {string}
3920
+ /**
3921
+ * Accesses the global DescriptionContext and returns a description for a given id and element.
3922
+ *
3923
+ * @example
3924
+ * ```jsx
3925
+ * function TextField(props) {
3926
+ * const description = useDescriptionContext('input1', element);
3927
+ * }
3928
+ * ```
3929
+ *
3930
+ * @param {string} id
3931
+ * @param {object} element
3932
+ *
3933
+ * @returns {string}
3466
3934
  */
3467
3935
  function useDescriptionContext(id, element) {
3468
3936
  const {
@@ -3477,11 +3945,11 @@ function useError(id) {
3477
3945
  return errors[id];
3478
3946
  }
3479
3947
 
3480
- /**
3481
- * Subscribe to an event immediately. Update subscription after inputs changed.
3482
- *
3483
- * @param {string} event
3484
- * @param {Function} callback
3948
+ /**
3949
+ * Subscribe to an event immediately. Update subscription after inputs changed.
3950
+ *
3951
+ * @param {string} event
3952
+ * @param {Function} callback
3485
3953
  */
3486
3954
  function useEvent(event, callback, eventBus) {
3487
3955
  const eventContext = hooks$1.useContext(EventContext);
@@ -3511,20 +3979,20 @@ function useEvent(event, callback, eventBus) {
3511
3979
  }, [callback, event, eventBus]);
3512
3980
  }
3513
3981
 
3514
- /**
3515
- * Creates a state that persists in the global LayoutContext.
3516
- *
3517
- * @example
3518
- * ```jsx
3519
- * function Group(props) {
3520
- * const [ open, setOpen ] = useLayoutState([ 'groups', 'foo', 'open' ], false);
3521
- * }
3522
- * ```
3523
- *
3524
- * @param {(string|number)[]} path
3525
- * @param {any} [defaultValue]
3526
- *
3527
- * @returns {[ any, Function ]}
3982
+ /**
3983
+ * Creates a state that persists in the global LayoutContext.
3984
+ *
3985
+ * @example
3986
+ * ```jsx
3987
+ * function Group(props) {
3988
+ * const [ open, setOpen ] = useLayoutState([ 'groups', 'foo', 'open' ], false);
3989
+ * }
3990
+ * ```
3991
+ *
3992
+ * @param {(string|number)[]} path
3993
+ * @param {any} [defaultValue]
3994
+ *
3995
+ * @returns {[ any, Function ]}
3528
3996
  */
3529
3997
  function useLayoutState(path, defaultValue) {
3530
3998
  const {
@@ -3532,22 +4000,17 @@ function useLayoutState(path, defaultValue) {
3532
4000
  setLayoutForKey
3533
4001
  } = hooks$1.useContext(LayoutContext);
3534
4002
  const layoutForKey = getLayoutForKey(path, defaultValue);
3535
- const [value, set] = hooks$1.useState(layoutForKey);
3536
- const setState = newValue => {
3537
- // (1) set component state
3538
- set(newValue);
3539
-
3540
- // (2) set context
4003
+ const setState = hooks$1.useCallback(newValue => {
3541
4004
  setLayoutForKey(path, newValue);
3542
- };
3543
- return [value, setState];
4005
+ }, [setLayoutForKey]);
4006
+ return [layoutForKey, setState];
3544
4007
  }
3545
4008
 
3546
- /**
3547
- * @pinussilvestrus: we need to introduce our own hook to persist the previous
3548
- * state on updates.
3549
- *
3550
- * cf. https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
4009
+ /**
4010
+ * @pinussilvestrus: we need to introduce our own hook to persist the previous
4011
+ * state on updates.
4012
+ *
4013
+ * cf. https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
3551
4014
  */
3552
4015
 
3553
4016
  function usePrevious(value) {
@@ -3558,12 +4021,12 @@ function usePrevious(value) {
3558
4021
  return ref.current;
3559
4022
  }
3560
4023
 
3561
- /**
3562
- * Subscribe to `propertiesPanel.showEntry`.
3563
- *
3564
- * @param {string} id
3565
- *
3566
- * @returns {import('preact').Ref}
4024
+ /**
4025
+ * Subscribe to `propertiesPanel.showEntry`.
4026
+ *
4027
+ * @param {string} id
4028
+ *
4029
+ * @returns {import('preact').Ref}
3567
4030
  */
3568
4031
  function useShowEntryEvent(id) {
3569
4032
  const {
@@ -3594,20 +4057,20 @@ function useShowEntryEvent(id) {
3594
4057
  return ref;
3595
4058
  }
3596
4059
 
3597
- /**
3598
- * @callback setSticky
3599
- * @param {boolean} value
4060
+ /**
4061
+ * @callback setSticky
4062
+ * @param {boolean} value
3600
4063
  */
3601
4064
 
3602
- /**
3603
- * Use IntersectionObserver to identify when DOM element is in sticky mode.
3604
- * If sticky is observered setSticky(true) will be called.
3605
- * If sticky mode is left, setSticky(false) will be called.
3606
- *
3607
- *
3608
- * @param {Object} ref
3609
- * @param {string} scrollContainerSelector
3610
- * @param {setSticky} setSticky
4065
+ /**
4066
+ * Use IntersectionObserver to identify when DOM element is in sticky mode.
4067
+ * If sticky is observered setSticky(true) will be called.
4068
+ * If sticky mode is left, setSticky(false) will be called.
4069
+ *
4070
+ *
4071
+ * @param {Object} ref
4072
+ * @param {string} scrollContainerSelector
4073
+ * @param {setSticky} setSticky
3611
4074
  */
3612
4075
  function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky) {
3613
4076
  hooks$1.useEffect(() => {
@@ -3646,19 +4109,19 @@ function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky)
3646
4109
  }, [ref, scrollContainerSelector, setSticky]);
3647
4110
  }
3648
4111
 
3649
- /**
3650
- * Creates a static function reference with changing body.
3651
- * This is necessary when external libraries require a callback function
3652
- * that has references to state variables.
3653
- *
3654
- * Usage:
3655
- * const callback = useStaticCallback((val) => {val === currentState});
3656
- *
3657
- * The `callback` reference is static and can be safely used in external
3658
- * libraries or as a prop that does not cause rerendering of children.
3659
- *
3660
- * @param {Function} callback function with changing reference
3661
- * @returns {Function} static function reference
4112
+ /**
4113
+ * Creates a static function reference with changing body.
4114
+ * This is necessary when external libraries require a callback function
4115
+ * that has references to state variables.
4116
+ *
4117
+ * Usage:
4118
+ * const callback = useStaticCallback((val) => {val === currentState});
4119
+ *
4120
+ * The `callback` reference is static and can be safely used in external
4121
+ * libraries or as a prop that does not cause rerendering of children.
4122
+ *
4123
+ * @param {Function} callback function with changing reference
4124
+ * @returns {Function} static function reference
3662
4125
  */
3663
4126
  function useStaticCallback(callback) {
3664
4127
  const callbackRef = hooks$1.useRef(callback);
@@ -3750,13 +4213,13 @@ function DataMarker() {
3750
4213
  });
3751
4214
  }
3752
4215
 
3753
- /**
3754
- * @typedef { {
3755
- * text: (element: object) => string,
3756
- * icon?: (element: Object) => import('preact').Component
3757
- * } } PlaceholderDefinition
3758
- *
3759
- * @param { PlaceholderDefinition } props
4216
+ /**
4217
+ * @typedef { {
4218
+ * text: (element: object) => string,
4219
+ * icon?: (element: Object) => import('preact').Component
4220
+ * } } PlaceholderDefinition
4221
+ *
4222
+ * @param { PlaceholderDefinition } props
3760
4223
  */
3761
4224
  function Placeholder(props) {
3762
4225
  const {
@@ -3781,72 +4244,72 @@ const DEFAULT_LAYOUT = {
3781
4244
  };
3782
4245
  const DEFAULT_DESCRIPTION = {};
3783
4246
 
3784
- /**
3785
- * @typedef { {
3786
- * component: import('preact').Component,
3787
- * id: String,
3788
- * isEdited?: Function
3789
- * } } EntryDefinition
3790
- *
3791
- * @typedef { {
3792
- * autoFocusEntry: String,
3793
- * autoOpen?: Boolean,
3794
- * entries: Array<EntryDefinition>,
3795
- * id: String,
3796
- * label: String,
3797
- * remove: (event: MouseEvent) => void
3798
- * } } ListItemDefinition
3799
- *
3800
- * @typedef { {
3801
- * add: (event: MouseEvent) => void,
3802
- * component: import('preact').Component,
3803
- * element: Object,
3804
- * id: String,
3805
- * items: Array<ListItemDefinition>,
3806
- * label: String,
3807
- * shouldSort?: Boolean,
3808
- * shouldOpen?: Boolean
3809
- * } } ListGroupDefinition
3810
- *
3811
- * @typedef { {
3812
- * component?: import('preact').Component,
3813
- * entries: Array<EntryDefinition>,
3814
- * id: String,
3815
- * label: String,
3816
- * shouldOpen?: Boolean
3817
- * } } GroupDefinition
3818
- *
3819
- * @typedef { {
3820
- * [id: String]: GetDescriptionFunction
3821
- * } } DescriptionConfig
3822
- *
3823
- * @callback { {
3824
- * @param {string} id
3825
- * @param {Object} element
3826
- * @returns {string}
3827
- * } } GetDescriptionFunction
3828
- *
3829
- * @typedef { {
3830
- * getEmpty: (element: object) => import('./components/Placeholder').PlaceholderDefinition,
3831
- * getMultiple: (element: Object) => import('./components/Placeholder').PlaceholderDefinition
3832
- * } } PlaceholderProvider
3833
- *
4247
+ /**
4248
+ * @typedef { {
4249
+ * component: import('preact').Component,
4250
+ * id: String,
4251
+ * isEdited?: Function
4252
+ * } } EntryDefinition
4253
+ *
4254
+ * @typedef { {
4255
+ * autoFocusEntry: String,
4256
+ * autoOpen?: Boolean,
4257
+ * entries: Array<EntryDefinition>,
4258
+ * id: String,
4259
+ * label: String,
4260
+ * remove: (event: MouseEvent) => void
4261
+ * } } ListItemDefinition
4262
+ *
4263
+ * @typedef { {
4264
+ * add: (event: MouseEvent) => void,
4265
+ * component: import('preact').Component,
4266
+ * element: Object,
4267
+ * id: String,
4268
+ * items: Array<ListItemDefinition>,
4269
+ * label: String,
4270
+ * shouldSort?: Boolean,
4271
+ * shouldOpen?: Boolean
4272
+ * } } ListGroupDefinition
4273
+ *
4274
+ * @typedef { {
4275
+ * component?: import('preact').Component,
4276
+ * entries: Array<EntryDefinition>,
4277
+ * id: String,
4278
+ * label: String,
4279
+ * shouldOpen?: Boolean
4280
+ * } } GroupDefinition
4281
+ *
4282
+ * @typedef { {
4283
+ * [id: String]: GetDescriptionFunction
4284
+ * } } DescriptionConfig
4285
+ *
4286
+ * @callback { {
4287
+ * @param {string} id
4288
+ * @param {Object} element
4289
+ * @returns {string}
4290
+ * } } GetDescriptionFunction
4291
+ *
4292
+ * @typedef { {
4293
+ * getEmpty: (element: object) => import('./components/Placeholder').PlaceholderDefinition,
4294
+ * getMultiple: (element: Object) => import('./components/Placeholder').PlaceholderDefinition
4295
+ * } } PlaceholderProvider
4296
+ *
3834
4297
  */
3835
4298
 
3836
- /**
3837
- * A basic properties panel component. Describes *how* content will be rendered, accepts
3838
- * data from implementor to describe *what* will be rendered.
3839
- *
3840
- * @param {Object} props
3841
- * @param {Object|Array} props.element
3842
- * @param {import('./components/Header').HeaderProvider} props.headerProvider
3843
- * @param {PlaceholderProvider} [props.placeholderProvider]
3844
- * @param {Array<GroupDefinition|ListGroupDefinition>} props.groups
3845
- * @param {Object} [props.layoutConfig]
3846
- * @param {Function} [props.layoutChanged]
3847
- * @param {DescriptionConfig} [props.descriptionConfig]
3848
- * @param {Function} [props.descriptionLoaded]
3849
- * @param {Object} [props.eventBus]
4299
+ /**
4300
+ * A basic properties panel component. Describes *how* content will be rendered, accepts
4301
+ * data from implementor to describe *what* will be rendered.
4302
+ *
4303
+ * @param {Object} props
4304
+ * @param {Object|Array} props.element
4305
+ * @param {import('./components/Header').HeaderProvider} props.headerProvider
4306
+ * @param {PlaceholderProvider} [props.placeholderProvider]
4307
+ * @param {Array<GroupDefinition|ListGroupDefinition>} props.groups
4308
+ * @param {Object} [props.layoutConfig]
4309
+ * @param {Function} [props.layoutChanged]
4310
+ * @param {DescriptionConfig} [props.descriptionConfig]
4311
+ * @param {Function} [props.descriptionLoaded]
4312
+ * @param {Object} [props.eventBus]
3850
4313
  */
3851
4314
  function PropertiesPanel(props) {
3852
4315
  const {
@@ -3854,15 +4317,21 @@ function PropertiesPanel(props) {
3854
4317
  headerProvider,
3855
4318
  placeholderProvider,
3856
4319
  groups,
3857
- layoutConfig = {},
4320
+ layoutConfig,
3858
4321
  layoutChanged,
3859
- descriptionConfig = {},
4322
+ descriptionConfig,
3860
4323
  descriptionLoaded,
3861
4324
  eventBus
3862
4325
  } = props;
3863
4326
 
3864
4327
  // set-up layout context
3865
4328
  const [layout, setLayout] = hooks$1.useState(createLayout(layoutConfig));
4329
+
4330
+ // react to external changes in the layout config
4331
+ useUpdateEffect(() => {
4332
+ const newLayout = createLayout(layoutConfig);
4333
+ setLayout(newLayout);
4334
+ }, [layoutConfig]);
3866
4335
  hooks$1.useEffect(() => {
3867
4336
  if (typeof layoutChanged === 'function') {
3868
4337
  layoutChanged(layout);
@@ -3884,10 +4353,12 @@ function PropertiesPanel(props) {
3884
4353
  };
3885
4354
 
3886
4355
  // set-up description context
3887
- const description = createDescriptionContext(descriptionConfig);
3888
- if (typeof descriptionLoaded === 'function') {
3889
- descriptionLoaded(description);
3890
- }
4356
+ const description = hooks$1.useMemo(() => createDescriptionContext(descriptionConfig), [descriptionConfig]);
4357
+ hooks$1.useEffect(() => {
4358
+ if (typeof descriptionLoaded === 'function') {
4359
+ descriptionLoaded(description);
4360
+ }
4361
+ }, [description, descriptionLoaded]);
3891
4362
  const getDescriptionForId = (id, element) => {
3892
4363
  return description[id] && description[id](element);
3893
4364
  };
@@ -3962,18 +4433,37 @@ function PropertiesPanel(props) {
3962
4433
 
3963
4434
  // helpers //////////////////
3964
4435
 
3965
- function createLayout(overrides) {
4436
+ function createLayout(overrides = {}, defaults = DEFAULT_LAYOUT) {
3966
4437
  return {
3967
- ...DEFAULT_LAYOUT,
4438
+ ...defaults,
3968
4439
  ...overrides
3969
4440
  };
3970
4441
  }
3971
- function createDescriptionContext(overrides) {
4442
+ function createDescriptionContext(overrides = {}) {
3972
4443
  return {
3973
4444
  ...DEFAULT_DESCRIPTION,
3974
4445
  ...overrides
3975
4446
  };
3976
4447
  }
4448
+
4449
+ // hooks //////////////////
4450
+
4451
+ /**
4452
+ * This hook behaves like useEffect, but does not trigger on the first render.
4453
+ *
4454
+ * @param {Function} effect
4455
+ * @param {Array} deps
4456
+ */
4457
+ function useUpdateEffect(effect, deps) {
4458
+ const isMounted = hooks$1.useRef(false);
4459
+ hooks$1.useEffect(() => {
4460
+ if (isMounted.current) {
4461
+ return effect();
4462
+ } else {
4463
+ isMounted.current = true;
4464
+ }
4465
+ }, deps);
4466
+ }
3977
4467
  function CollapsibleEntry(props) {
3978
4468
  const {
3979
4469
  element,
@@ -4069,10 +4559,10 @@ function ListItem(props) {
4069
4559
  })
4070
4560
  });
4071
4561
  }
4072
- const noop$2 = () => {};
4562
+ const noop$3 = () => {};
4073
4563
 
4074
- /**
4075
- * @param {import('../PropertiesPanel').ListGroupDefinition} props
4564
+ /**
4565
+ * @param {import('../PropertiesPanel').ListGroupDefinition} props
4076
4566
  */
4077
4567
  function ListGroup(props) {
4078
4568
  const {
@@ -4090,6 +4580,9 @@ function ListGroup(props) {
4090
4580
  const onShow = hooks$1.useCallback(() => setOpen(true), [setOpen]);
4091
4581
  const [ordering, setOrdering] = hooks$1.useState([]);
4092
4582
  const [newItemAdded, setNewItemAdded] = hooks$1.useState(false);
4583
+
4584
+ // Flag to mark that add button was clicked in the last render cycle
4585
+ const [addTriggered, setAddTriggered] = hooks$1.useState(false);
4093
4586
  const prevItems = usePrevious(items);
4094
4587
  const prevElement = usePrevious(element);
4095
4588
  const elementChanged = element !== prevElement;
@@ -4111,6 +4604,8 @@ function ListGroup(props) {
4111
4604
 
4112
4605
  // (1) items were added
4113
4606
  hooks$1.useEffect(() => {
4607
+ // reset addTriggered flag
4608
+ setAddTriggered(false);
4114
4609
  if (shouldHandleEffects && prevItems && items.length > prevItems.length) {
4115
4610
  let add = [];
4116
4611
  items.forEach(item => {
@@ -4120,14 +4615,18 @@ function ListGroup(props) {
4120
4615
  });
4121
4616
  let newOrdering = ordering;
4122
4617
 
4123
- // open if not open and configured
4124
- if (!open && shouldOpen) {
4618
+ // open if not open, configured and triggered by add button
4619
+ //
4620
+ // TODO(marstamm): remove once we refactor layout handling for listGroups.
4621
+ // Ideally, opening should be handled as part of the `add` callback and
4622
+ // not be a concern for the ListGroup component.
4623
+ if (addTriggered && !open && shouldOpen) {
4125
4624
  toggleOpen();
4625
+ }
4126
4626
 
4127
- // if I opened and I should sort, then sort items
4128
- if (shouldSort) {
4129
- newOrdering = createOrdering(sortItems(items));
4130
- }
4627
+ // filter when not open and configured
4628
+ if (!open && shouldSort) {
4629
+ newOrdering = createOrdering(sortItems(items));
4131
4630
  }
4132
4631
 
4133
4632
  // add new items on top or bottom depending on sorting behavior
@@ -4138,11 +4637,11 @@ function ListGroup(props) {
4138
4637
  newOrdering.push(...add);
4139
4638
  }
4140
4639
  setOrdering(newOrdering);
4141
- setNewItemAdded(true);
4640
+ setNewItemAdded(addTriggered);
4142
4641
  } else {
4143
4642
  setNewItemAdded(false);
4144
4643
  }
4145
- }, [items, open, shouldHandleEffects]);
4644
+ }, [items, open, shouldHandleEffects, addTriggered]);
4146
4645
 
4147
4646
  // (2) sort items on open if shouldSort is set
4148
4647
  hooks$1.useEffect(() => {
@@ -4172,13 +4671,17 @@ function ListGroup(props) {
4172
4671
  ...hooks$1.useContext(LayoutContext),
4173
4672
  onShow
4174
4673
  };
4674
+ const handleAddClick = e => {
4675
+ setAddTriggered(true);
4676
+ add(e);
4677
+ };
4175
4678
  return jsxRuntime.jsxs("div", {
4176
4679
  class: "bio-properties-panel-group",
4177
4680
  "data-group-id": 'group-' + id,
4178
4681
  ref: groupRef,
4179
4682
  children: [jsxRuntime.jsxs("div", {
4180
4683
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
4181
- onClick: hasItems ? toggleOpen : noop$2,
4684
+ onClick: hasItems ? toggleOpen : noop$3,
4182
4685
  children: [jsxRuntime.jsx("div", {
4183
4686
  title: label,
4184
4687
  class: "bio-properties-panel-group-header-title",
@@ -4188,7 +4691,7 @@ function ListGroup(props) {
4188
4691
  children: [add ? jsxRuntime.jsxs("button", {
4189
4692
  title: "Create new list item",
4190
4693
  class: "bio-properties-panel-group-header-button bio-properties-panel-add-entry",
4191
- onClick: add,
4694
+ onClick: handleAddClick,
4192
4695
  children: [jsxRuntime.jsx(CreateIcon, {}), !hasItems ? jsxRuntime.jsx("span", {
4193
4696
  class: "bio-properties-panel-add-entry-label",
4194
4697
  children: "Create"
@@ -4218,8 +4721,9 @@ function ListGroup(props) {
4218
4721
  id
4219
4722
  } = item;
4220
4723
 
4221
- // if item was added, open first or last item based on ordering
4222
- const autoOpen = newItemAdded && (shouldSort ? index === 0 : index === ordering.length - 1);
4724
+ // if item was added, open it
4725
+ // Existing items will not be affected as autoOpen is only applied on first render
4726
+ const autoOpen = newItemAdded;
4223
4727
  return preact.createElement(ListItem, {
4224
4728
  ...item,
4225
4729
  autoOpen: autoOpen,
@@ -4235,8 +4739,8 @@ function ListGroup(props) {
4235
4739
 
4236
4740
  // helpers ////////////////////
4237
4741
 
4238
- /**
4239
- * Sorts given items alphanumeric by label
4742
+ /**
4743
+ * Sorts given items alphanumeric by label
4240
4744
  */
4241
4745
  function sortItems(items) {
4242
4746
  return minDash.sortBy(items, i => i.label.toLowerCase());
@@ -4310,17 +4814,17 @@ function Checkbox(props) {
4310
4814
  });
4311
4815
  }
4312
4816
 
4313
- /**
4314
- * @param {Object} props
4315
- * @param {Object} props.element
4316
- * @param {String} props.id
4317
- * @param {String} props.description
4318
- * @param {String} props.label
4319
- * @param {Function} props.getValue
4320
- * @param {Function} props.setValue
4321
- * @param {Function} props.onFocus
4322
- * @param {Function} props.onBlur
4323
- * @param {boolean} [props.disabled]
4817
+ /**
4818
+ * @param {Object} props
4819
+ * @param {Object} props.element
4820
+ * @param {String} props.id
4821
+ * @param {String} props.description
4822
+ * @param {String} props.label
4823
+ * @param {Function} props.getValue
4824
+ * @param {Function} props.setValue
4825
+ * @param {Function} props.onFocus
4826
+ * @param {Function} props.onBlur
4827
+ * @param {boolean} [props.disabled]
4324
4828
  */
4325
4829
  function CheckboxEntry(props) {
4326
4830
  const {
@@ -4357,7 +4861,7 @@ function CheckboxEntry(props) {
4357
4861
  })]
4358
4862
  });
4359
4863
  }
4360
- function isEdited$7(node) {
4864
+ function isEdited$8(node) {
4361
4865
  return node && !!node.checked;
4362
4866
  }
4363
4867
 
@@ -4366,6 +4870,87 @@ function isEdited$7(node) {
4366
4870
  function prefixId$7(id) {
4367
4871
  return `bio-properties-panel-${id}`;
4368
4872
  }
4873
+ const useBufferedFocus$1 = function (editor, ref) {
4874
+ const [buffer, setBuffer] = hooks$1.useState(undefined);
4875
+ ref.current = hooks$1.useMemo(() => ({
4876
+ focus: offset => {
4877
+ if (editor) {
4878
+ editor.focus(offset);
4879
+ } else {
4880
+ if (typeof offset === 'undefined') {
4881
+ offset = Infinity;
4882
+ }
4883
+ setBuffer(offset);
4884
+ }
4885
+ }
4886
+ }), [editor]);
4887
+ hooks$1.useEffect(() => {
4888
+ if (typeof buffer !== 'undefined' && editor) {
4889
+ editor.focus(buffer);
4890
+ setBuffer(false);
4891
+ }
4892
+ }, [editor, buffer]);
4893
+ };
4894
+ const CodeEditor$1 = React.forwardRef((props, ref) => {
4895
+ const {
4896
+ onInput,
4897
+ disabled,
4898
+ tooltipContainer,
4899
+ enableGutters,
4900
+ value,
4901
+ onLint = () => {},
4902
+ contentAttributes = {},
4903
+ hostLanguage = null,
4904
+ singleLine = false
4905
+ } = props;
4906
+ const inputRef = hooks$1.useRef();
4907
+ const [editor, setEditor] = hooks$1.useState();
4908
+ const [localValue, setLocalValue] = hooks$1.useState(value || '');
4909
+ useBufferedFocus$1(editor, ref);
4910
+ const handleInput = useStaticCallback(newValue => {
4911
+ onInput(newValue);
4912
+ setLocalValue(newValue);
4913
+ });
4914
+ hooks$1.useEffect(() => {
4915
+ let editor;
4916
+ editor = new feelers.FeelersEditor({
4917
+ container: inputRef.current,
4918
+ onChange: handleInput,
4919
+ value: localValue,
4920
+ onLint,
4921
+ contentAttributes,
4922
+ tooltipContainer,
4923
+ enableGutters,
4924
+ hostLanguage,
4925
+ singleLine
4926
+ });
4927
+ setEditor(editor);
4928
+ return () => {
4929
+ onLint([]);
4930
+ inputRef.current.innerHTML = '';
4931
+ setEditor(null);
4932
+ };
4933
+ }, []);
4934
+ hooks$1.useEffect(() => {
4935
+ if (!editor) {
4936
+ return;
4937
+ }
4938
+ if (value === localValue) {
4939
+ return;
4940
+ }
4941
+ editor.setValue(value);
4942
+ setLocalValue(value);
4943
+ }, [value]);
4944
+ const handleClick = () => {
4945
+ ref.current.focus();
4946
+ };
4947
+ return jsxRuntime.jsx("div", {
4948
+ name: props.name,
4949
+ class: classnames('bio-properties-panel-feelers-editor bio-properties-panel-input', localValue ? 'edited' : null, disabled ? 'disabled' : null),
4950
+ ref: inputRef,
4951
+ onClick: handleClick
4952
+ });
4953
+ });
4369
4954
  const useBufferedFocus = function (editor, ref) {
4370
4955
  const [buffer, setBuffer] = hooks$1.useState(undefined);
4371
4956
  ref.current = hooks$1.useMemo(() => ({
@@ -4408,10 +4993,10 @@ const CodeEditor = React.forwardRef((props, ref) => {
4408
4993
  hooks$1.useEffect(() => {
4409
4994
  let editor;
4410
4995
 
4411
- /* Trigger FEEL toggle when
4412
- *
4413
- * - `backspace` is pressed
4414
- * - AND the cursor is at the beginning of the input
4996
+ /* Trigger FEEL toggle when
4997
+ *
4998
+ * - `backspace` is pressed
4999
+ * - AND the cursor is at the beginning of the input
4415
5000
  */
4416
5001
  const onKeyDown = e => {
4417
5002
  if (e.key !== 'Backspace' || !editor) {
@@ -4480,12 +5065,12 @@ function FeelIndicator(props) {
4480
5065
  children: "="
4481
5066
  });
4482
5067
  }
4483
- const noop$1 = () => {};
5068
+ const noop$2 = () => {};
4484
5069
 
4485
- /**
4486
- * @param {Object} props
4487
- * @param {Object} props.label
4488
- * @param {String} props.feel
5070
+ /**
5071
+ * @param {Object} props
5072
+ * @param {Object} props.label
5073
+ * @param {String} props.feel
4489
5074
  */
4490
5075
  function FeelIcon(props) {
4491
5076
  const {
@@ -4493,7 +5078,7 @@ function FeelIcon(props) {
4493
5078
  feel = false,
4494
5079
  active,
4495
5080
  disabled = false,
4496
- onClick = noop$1
5081
+ onClick = noop$2
4497
5082
  } = props;
4498
5083
  const feelRequiredLabel = ' must be a FEEL expression';
4499
5084
  const feelOptionalLabel = ' can optionally be a FEEL expression';
@@ -4513,7 +5098,7 @@ function FeelIcon(props) {
4513
5098
  children: feel === 'required' ? jsxRuntime.jsx(FeelRequiredIcon, {}) : jsxRuntime.jsx(FeelOptionalIcon, {})
4514
5099
  });
4515
5100
  }
4516
- const noop = () => {};
5101
+ const noop$1 = () => {};
4517
5102
  function FeelTextfield(props) {
4518
5103
  const {
4519
5104
  debounce,
@@ -4670,6 +5255,9 @@ function FeelTextfield(props) {
4670
5255
  }) : jsxRuntime.jsx(OptionalComponent, {
4671
5256
  ...props,
4672
5257
  onInput: handleLocalInput,
5258
+ contentAttributes: {
5259
+ 'id': prefixId$6(id)
5260
+ },
4673
5261
  value: localValue,
4674
5262
  ref: editorRef
4675
5263
  })]
@@ -4719,7 +5307,7 @@ const OptionalFeelInput = React.forwardRef((props, ref) => {
4719
5307
  value: value || ''
4720
5308
  });
4721
5309
  });
4722
- const OptionalFeelTextArea = React.forwardRef((props, ref) => {
5310
+ React.forwardRef((props, ref) => {
4723
5311
  const {
4724
5312
  id,
4725
5313
  disabled,
@@ -4759,17 +5347,24 @@ const OptionalFeelTextArea = React.forwardRef((props, ref) => {
4759
5347
  });
4760
5348
  });
4761
5349
 
4762
- /**
4763
- * @param {Object} props
4764
- * @param {Object} props.element
4765
- * @param {String} props.id
4766
- * @param {String} props.description
4767
- * @param {Boolean} props.debounce
4768
- * @param {Boolean} props.disabled
4769
- * @param {String} props.label
4770
- * @param {Function} props.getValue
4771
- * @param {Function} props.setValue
4772
- * @param {Function} props.validate
5350
+ /**
5351
+ * @param {Object} props
5352
+ * @param {Object} props.element
5353
+ * @param {String} props.id
5354
+ * @param {String} props.description
5355
+ * @param {Boolean} props.debounce
5356
+ * @param {Boolean} props.disabled
5357
+ * @param {Boolean} props.feel
5358
+ * @param {String} props.label
5359
+ * @param {Function} props.getValue
5360
+ * @param {Function} props.setValue
5361
+ * @param {Function} props.tooltipContainer
5362
+ * @param {Function} props.validate
5363
+ * @param {Function} props.show
5364
+ * @param {Function} props.example
5365
+ * @param {Function} props.variables
5366
+ * @param {Function} props.onFocus
5367
+ * @param {Function} props.onBlur
4773
5368
  */
4774
5369
  function FeelEntry(props) {
4775
5370
  const {
@@ -4783,8 +5378,10 @@ function FeelEntry(props) {
4783
5378
  getValue,
4784
5379
  setValue,
4785
5380
  tooltipContainer,
5381
+ hostLanguage,
5382
+ singleLine,
4786
5383
  validate,
4787
- show = noop,
5384
+ show = noop$1,
4788
5385
  example,
4789
5386
  variables,
4790
5387
  onFocus,
@@ -4838,6 +5435,8 @@ function FeelEntry(props) {
4838
5435
  onFocus: onFocus,
4839
5436
  onBlur: onBlur,
4840
5437
  example: example,
5438
+ hostLanguage: hostLanguage,
5439
+ singleLine: singleLine,
4841
5440
  show: show,
4842
5441
  value: value,
4843
5442
  variables: variables,
@@ -4854,28 +5453,35 @@ function FeelEntry(props) {
4854
5453
  });
4855
5454
  }
4856
5455
 
4857
- /**
4858
- * @param {Object} props
4859
- * @param {Object} props.element
4860
- * @param {String} props.id
4861
- * @param {String} props.description
4862
- * @param {Boolean} props.debounce
4863
- * @param {Boolean} props.disabled
4864
- * @param {String} props.label
4865
- * @param {Function} props.getValue
4866
- * @param {Function} props.setValue
4867
- * @param {Function} props.onFocus
4868
- * @param {Function} props.onBlur
4869
- * @param {Function} props.validate
5456
+ /**
5457
+ * @param {Object} props
5458
+ * @param {Object} props.element
5459
+ * @param {String} props.id
5460
+ * @param {String} props.description
5461
+ * @param {String} props.hostLanguage
5462
+ * @param {Boolean} props.singleLine
5463
+ * @param {Boolean} props.debounce
5464
+ * @param {Boolean} props.disabled
5465
+ * @param {Boolean} props.feel
5466
+ * @param {String} props.label
5467
+ * @param {Function} props.getValue
5468
+ * @param {Function} props.setValue
5469
+ * @param {Function} props.tooltipContainer
5470
+ * @param {Function} props.validate
5471
+ * @param {Function} props.show
5472
+ * @param {Function} props.example
5473
+ * @param {Function} props.variables
5474
+ * @param {Function} props.onFocus
5475
+ * @param {Function} props.onBlur
4870
5476
  */
4871
- function FeelTextArea(props) {
5477
+ function FeelTemplatingEntry(props) {
4872
5478
  return jsxRuntime.jsx(FeelEntry, {
4873
- class: "bio-properties-panel-feel-textarea",
4874
- OptionalComponent: OptionalFeelTextArea,
5479
+ class: "bio-properties-panel-feel-templating",
5480
+ OptionalComponent: CodeEditor$1,
4875
5481
  ...props
4876
5482
  });
4877
5483
  }
4878
- function isEdited$6(node) {
5484
+ function isEdited$7(node) {
4879
5485
  return node && (!!node.value || node.classList.contains('edited'));
4880
5486
  }
4881
5487
 
@@ -4945,22 +5551,22 @@ function NumberField(props) {
4945
5551
  });
4946
5552
  }
4947
5553
 
4948
- /**
4949
- * @param {Object} props
4950
- * @param {Boolean} props.debounce
4951
- * @param {String} props.description
4952
- * @param {Boolean} props.disabled
4953
- * @param {Object} props.element
4954
- * @param {Function} props.getValue
4955
- * @param {String} props.id
4956
- * @param {String} props.label
4957
- * @param {String} props.max
4958
- * @param {String} props.min
4959
- * @param {Function} props.setValue
4960
- * @param {Function} props.onFocus
4961
- * @param {Function} props.onBlur
4962
- * @param {String} props.step
4963
- * @param {Function} props.validate
5554
+ /**
5555
+ * @param {Object} props
5556
+ * @param {Boolean} props.debounce
5557
+ * @param {String} props.description
5558
+ * @param {Boolean} props.disabled
5559
+ * @param {Object} props.element
5560
+ * @param {Function} props.getValue
5561
+ * @param {String} props.id
5562
+ * @param {String} props.label
5563
+ * @param {String} props.max
5564
+ * @param {String} props.min
5565
+ * @param {Function} props.setValue
5566
+ * @param {Function} props.onFocus
5567
+ * @param {Function} props.onBlur
5568
+ * @param {String} props.step
5569
+ * @param {Function} props.validate
4964
5570
  */
4965
5571
  function NumberFieldEntry(props) {
4966
5572
  const {
@@ -5085,6 +5691,16 @@ function Select(props) {
5085
5691
  value: localValue,
5086
5692
  disabled: disabled,
5087
5693
  children: options.map((option, idx) => {
5694
+ if (option.children) {
5695
+ return jsxRuntime.jsx("optgroup", {
5696
+ label: option.label,
5697
+ children: option.children.map((child, idx) => jsxRuntime.jsx("option", {
5698
+ value: child.value,
5699
+ disabled: child.disabled,
5700
+ children: child.label
5701
+ }, idx))
5702
+ }, idx);
5703
+ }
5088
5704
  return jsxRuntime.jsx("option", {
5089
5705
  value: option.value,
5090
5706
  disabled: option.disabled,
@@ -5095,18 +5711,19 @@ function Select(props) {
5095
5711
  });
5096
5712
  }
5097
5713
 
5098
- /**
5099
- * @param {object} props
5100
- * @param {object} props.element
5101
- * @param {string} props.id
5102
- * @param {string} [props.description]
5103
- * @param {string} props.label
5104
- * @param {Function} props.getValue
5105
- * @param {Function} props.setValue
5106
- * @param {Function} props.onFocus
5107
- * @param {Function} props.onBlur
5108
- * @param {Function} props.getOptions
5109
- * @param {boolean} [props.disabled]
5714
+ /**
5715
+ * @param {object} props
5716
+ * @param {object} props.element
5717
+ * @param {string} props.id
5718
+ * @param {string} [props.description]
5719
+ * @param {string} props.label
5720
+ * @param {Function} props.getValue
5721
+ * @param {Function} props.setValue
5722
+ * @param {Function} props.onFocus
5723
+ * @param {Function} props.onBlur
5724
+ * @param {Function} props.getOptions
5725
+ * @param {boolean} [props.disabled]
5726
+ * @param {Function} [props.validate]
5110
5727
  */
5111
5728
  function SelectEntry(props) {
5112
5729
  const {
@@ -5119,11 +5736,37 @@ function SelectEntry(props) {
5119
5736
  getOptions,
5120
5737
  disabled,
5121
5738
  onFocus,
5122
- onBlur
5739
+ onBlur,
5740
+ validate
5123
5741
  } = props;
5124
- const value = getValue(element);
5125
5742
  const options = getOptions(element);
5126
- const error = useError(id);
5743
+ const [cachedInvalidValue, setCachedInvalidValue] = hooks$1.useState(null);
5744
+ const globalError = useError(id);
5745
+ const [localError, setLocalError] = hooks$1.useState(null);
5746
+ let value = getValue(element);
5747
+ const previousValue = usePrevious(value);
5748
+ hooks$1.useEffect(() => {
5749
+ if (minDash.isFunction(validate)) {
5750
+ const newValidationError = validate(value) || null;
5751
+ setLocalError(newValidationError);
5752
+ }
5753
+ }, [value]);
5754
+ const onChange = newValue => {
5755
+ let newValidationError = null;
5756
+ if (minDash.isFunction(validate)) {
5757
+ newValidationError = validate(newValue) || null;
5758
+ }
5759
+ if (newValidationError) {
5760
+ setCachedInvalidValue(newValue);
5761
+ } else {
5762
+ setValue(newValue);
5763
+ }
5764
+ setLocalError(newValidationError);
5765
+ };
5766
+ if (previousValue === value && localError) {
5767
+ value = cachedInvalidValue;
5768
+ }
5769
+ const error = globalError || localError;
5127
5770
  return jsxRuntime.jsxs("div", {
5128
5771
  class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
5129
5772
  "data-entry-id": id,
@@ -5131,7 +5774,7 @@ function SelectEntry(props) {
5131
5774
  id: id,
5132
5775
  label: label,
5133
5776
  value: value,
5134
- onChange: setValue,
5777
+ onChange: onChange,
5135
5778
  onFocus: onFocus,
5136
5779
  onBlur: onBlur,
5137
5780
  options: options,
@@ -5155,18 +5798,26 @@ function isEdited$4(node) {
5155
5798
  function prefixId$4(id) {
5156
5799
  return `bio-properties-panel-${id}`;
5157
5800
  }
5801
+ function resizeToContents(element) {
5802
+ element.style.height = 'auto';
5803
+
5804
+ // a 2px pixel offset is required to prevent scrollbar from
5805
+ // appearing on OS with a full length scroll bar (Windows/Linux)
5806
+ element.style.height = `${element.scrollHeight + 2}px`;
5807
+ }
5158
5808
  function TextArea(props) {
5159
5809
  const {
5160
5810
  id,
5161
5811
  label,
5162
- rows = 2,
5163
5812
  debounce,
5164
5813
  onInput,
5165
5814
  value = '',
5166
5815
  disabled,
5167
5816
  monospace,
5168
5817
  onFocus,
5169
- onBlur
5818
+ onBlur,
5819
+ autoResize,
5820
+ rows = autoResize ? 1 : 2
5170
5821
  } = props;
5171
5822
  const [localValue, setLocalValue] = hooks$1.useState(value);
5172
5823
  const ref = useShowEntryEvent(id);
@@ -5177,8 +5828,12 @@ function TextArea(props) {
5177
5828
  }, [onInput, debounce]);
5178
5829
  const handleInput = e => {
5179
5830
  handleInputCallback(e);
5831
+ autoResize && resizeToContents(e.target);
5180
5832
  setLocalValue(e.target.value);
5181
5833
  };
5834
+ hooks$1.useLayoutEffect(() => {
5835
+ autoResize && resizeToContents(ref.current);
5836
+ }, []);
5182
5837
  hooks$1.useEffect(() => {
5183
5838
  if (value === localValue) {
5184
5839
  return;
@@ -5196,7 +5851,7 @@ function TextArea(props) {
5196
5851
  id: prefixId$2(id),
5197
5852
  name: id,
5198
5853
  spellCheck: "false",
5199
- class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : ''),
5854
+ class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
5200
5855
  onInput: handleInput,
5201
5856
  onFocus: onFocus,
5202
5857
  onBlur: onBlur,
@@ -5208,20 +5863,20 @@ function TextArea(props) {
5208
5863
  });
5209
5864
  }
5210
5865
 
5211
- /**
5212
- * @param {object} props
5213
- * @param {object} props.element
5214
- * @param {string} props.id
5215
- * @param {string} props.description
5216
- * @param {boolean} props.debounce
5217
- * @param {string} props.label
5218
- * @param {Function} props.getValue
5219
- * @param {Function} props.setValue
5220
- * @param {Function} props.onFocus
5221
- * @param {Function} props.onBlur
5222
- * @param {number} props.rows
5223
- * @param {boolean} props.monospace
5224
- * @param {boolean} [props.disabled]
5866
+ /**
5867
+ * @param {object} props
5868
+ * @param {object} props.element
5869
+ * @param {string} props.id
5870
+ * @param {string} props.description
5871
+ * @param {boolean} props.debounce
5872
+ * @param {string} props.label
5873
+ * @param {Function} props.getValue
5874
+ * @param {Function} props.setValue
5875
+ * @param {Function} props.onFocus
5876
+ * @param {Function} props.onBlur
5877
+ * @param {number} props.rows
5878
+ * @param {boolean} props.monospace
5879
+ * @param {boolean} [props.disabled]
5225
5880
  */
5226
5881
  function TextAreaEntry(props) {
5227
5882
  const {
@@ -5236,7 +5891,8 @@ function TextAreaEntry(props) {
5236
5891
  monospace,
5237
5892
  disabled,
5238
5893
  onFocus,
5239
- onBlur
5894
+ onBlur,
5895
+ autoResize
5240
5896
  } = props;
5241
5897
  const value = getValue(element);
5242
5898
  const error = useError(id);
@@ -5253,7 +5909,8 @@ function TextAreaEntry(props) {
5253
5909
  rows: rows,
5254
5910
  debounce: debounce,
5255
5911
  monospace: monospace,
5256
- disabled: disabled
5912
+ disabled: disabled,
5913
+ autoResize: autoResize
5257
5914
  }, element), error && jsxRuntime.jsx("div", {
5258
5915
  class: "bio-properties-panel-error",
5259
5916
  children: error
@@ -5324,19 +5981,19 @@ function Textfield(props) {
5324
5981
  });
5325
5982
  }
5326
5983
 
5327
- /**
5328
- * @param {Object} props
5329
- * @param {Object} props.element
5330
- * @param {String} props.id
5331
- * @param {String} props.description
5332
- * @param {Boolean} props.debounce
5333
- * @param {Boolean} props.disabled
5334
- * @param {String} props.label
5335
- * @param {Function} props.getValue
5336
- * @param {Function} props.setValue
5337
- * @param {Function} props.onFocus
5338
- * @param {Function} props.onBlur
5339
- * @param {Function} props.validate
5984
+ /**
5985
+ * @param {Object} props
5986
+ * @param {Object} props.element
5987
+ * @param {String} props.id
5988
+ * @param {String} props.description
5989
+ * @param {Boolean} props.debounce
5990
+ * @param {Boolean} props.disabled
5991
+ * @param {String} props.label
5992
+ * @param {Function} props.getValue
5993
+ * @param {Function} props.setValue
5994
+ * @param {Function} props.onFocus
5995
+ * @param {Function} props.onBlur
5996
+ * @param {Function} props.validate
5340
5997
  */
5341
5998
  function TextfieldEntry(props) {
5342
5999
  const {
@@ -5603,7 +6260,7 @@ function AltTextEntry(props) {
5603
6260
  component: AltText,
5604
6261
  editField: editField,
5605
6262
  field: field,
5606
- isEdited: isEdited$6
6263
+ isEdited: isEdited$7
5607
6264
  });
5608
6265
  }
5609
6266
  return entries;
@@ -5637,59 +6294,70 @@ function AltText(props) {
5637
6294
  });
5638
6295
  }
5639
6296
 
6297
+ const AUTO_OPTION_VALUE = '';
5640
6298
  function ColumnsEntry(props) {
5641
6299
  const {
5642
6300
  editField,
5643
6301
  field
5644
6302
  } = props;
5645
- const {
5646
- type
5647
- } = field;
5648
- const entries = [];
5649
- if (type === 'columns') {
5650
- entries.push({
5651
- id: 'columns',
5652
- component: Columns,
5653
- editField: editField,
5654
- field: field,
5655
- isEdited: isEdited$5
5656
- });
5657
- }
6303
+ const entries = [{
6304
+ id: 'columns',
6305
+ component: Columns,
6306
+ field,
6307
+ editField,
6308
+ isEdited: isEdited$4
6309
+ }];
5658
6310
  return entries;
5659
6311
  }
5660
6312
  function Columns(props) {
5661
6313
  const {
5662
- editField,
5663
6314
  field,
6315
+ editField,
5664
6316
  id
5665
6317
  } = props;
5666
6318
  const debounce = useService('debounce');
5667
- const getValue = () => {
5668
- return field.components.length;
6319
+ const formLayoutValidator = useService('formLayoutValidator');
6320
+ const validate = value => {
6321
+ return formLayoutValidator.validateField(field, value ? parseInt(value) : null);
5669
6322
  };
5670
6323
  const setValue = value => {
5671
- let components = field.components.slice();
5672
- if (value > components.length) {
5673
- while (value > components.length) {
5674
- components.push(formJsViewer.Default.create({
5675
- _parent: field.id
5676
- }));
5677
- }
5678
- } else {
5679
- components = components.slice(0, value);
5680
- }
5681
- editField(field, 'components', components);
6324
+ const layout = minDash.get(field, ['layout'], {});
6325
+ const newValue = value ? parseInt(value) : null;
6326
+ editField(field, ['layout'], minDash.set(layout, ['columns'], newValue));
5682
6327
  };
5683
- return NumberFieldEntry({
6328
+ const getValue = () => {
6329
+ return minDash.get(field, ['layout', 'columns']);
6330
+ };
6331
+ const getOptions = () => {
6332
+ return [{
6333
+ label: 'Auto',
6334
+ value: AUTO_OPTION_VALUE
6335
+ },
6336
+ // todo(pinussilvestrus): make options dependant on field type
6337
+ // cf. https://github.com/bpmn-io/form-js/issues/575
6338
+ ...[2, 4, 6, 8, 10, 12, 14, 16].map(asOption)];
6339
+ };
6340
+ return SelectEntry({
5684
6341
  debounce,
5685
6342
  element: field,
5686
- getValue,
5687
6343
  id,
5688
6344
  label: 'Columns',
5689
- setValue
6345
+ getOptions,
6346
+ getValue,
6347
+ setValue,
6348
+ validate
5690
6349
  });
5691
6350
  }
5692
6351
 
6352
+ // helper /////////
6353
+
6354
+ function asOption(number) {
6355
+ return {
6356
+ value: number,
6357
+ label: number.toString()
6358
+ };
6359
+ }
6360
+
5693
6361
  function DescriptionEntry(props) {
5694
6362
  const {
5695
6363
  editField,
@@ -5734,6 +6402,7 @@ function Description(props) {
5734
6402
  });
5735
6403
  }
5736
6404
 
6405
+ const EMPTY_OPTION = null;
5737
6406
  function DefaultOptionEntry(props) {
5738
6407
  const {
5739
6408
  editField,
@@ -5879,13 +6548,14 @@ function DefaultValueSingleSelect(props) {
5879
6548
  label
5880
6549
  } = props;
5881
6550
  const {
5882
- defaultValue,
6551
+ defaultValue = EMPTY_OPTION,
5883
6552
  values = []
5884
6553
  } = field;
5885
6554
  const path = ['defaultValue'];
5886
6555
  const getOptions = () => {
5887
6556
  return [{
5888
- label: '<none>'
6557
+ label: '<none>',
6558
+ value: EMPTY_OPTION
5889
6559
  }, ...values];
5890
6560
  };
5891
6561
  const setValue = value => {
@@ -5982,7 +6652,7 @@ function DisabledEntry(props) {
5982
6652
  component: Disabled,
5983
6653
  editField: editField,
5984
6654
  field: field,
5985
- isEdited: isEdited$7
6655
+ isEdited: isEdited$8
5986
6656
  });
5987
6657
  }
5988
6658
  return entries;
@@ -6192,6 +6862,7 @@ function simpleBoolEntryFactory(options) {
6192
6862
  const {
6193
6863
  id,
6194
6864
  label,
6865
+ description,
6195
6866
  path,
6196
6867
  props
6197
6868
  } = options;
@@ -6205,8 +6876,9 @@ function simpleBoolEntryFactory(options) {
6205
6876
  path,
6206
6877
  field,
6207
6878
  editField,
6879
+ description,
6208
6880
  component: SimpleBoolComponent,
6209
- isEdited: isEdited$7
6881
+ isEdited: isEdited$8
6210
6882
  };
6211
6883
  }
6212
6884
  const SimpleBoolComponent = props => {
@@ -6215,7 +6887,8 @@ const SimpleBoolComponent = props => {
6215
6887
  label,
6216
6888
  path,
6217
6889
  field,
6218
- editField
6890
+ editField,
6891
+ description
6219
6892
  } = props;
6220
6893
  const getValue = () => minDash.get(field, path, '');
6221
6894
  const setValue = value => editField(field, path, value);
@@ -6224,7 +6897,8 @@ const SimpleBoolComponent = props => {
6224
6897
  getValue,
6225
6898
  id,
6226
6899
  label,
6227
- setValue
6900
+ setValue,
6901
+ description
6228
6902
  });
6229
6903
  };
6230
6904
 
@@ -6280,7 +6954,7 @@ function SourceEntry(props) {
6280
6954
  component: Source,
6281
6955
  editField: editField,
6282
6956
  field: field,
6283
- isEdited: isEdited$6
6957
+ isEdited: isEdited$7
6284
6958
  });
6285
6959
  }
6286
6960
  return entries;
@@ -6318,21 +6992,38 @@ function Source(props) {
6318
6992
  function TextEntry(props) {
6319
6993
  const {
6320
6994
  editField,
6995
+ /* getService, */
6321
6996
  field
6322
6997
  } = props;
6323
6998
  const {
6324
6999
  type
6325
7000
  } = field;
7001
+
7002
+ // const templating = getService('templating');
7003
+
6326
7004
  if (type !== 'text') {
6327
7005
  return [];
6328
7006
  }
6329
- return [{
7007
+ const entries = [{
6330
7008
  id: 'text',
6331
7009
  component: Text,
6332
7010
  editField: editField,
6333
7011
  field: field,
6334
- isEdited: isEdited$6
7012
+ isEdited: isEdited$7
6335
7013
  }];
7014
+
7015
+ // todo: skipped to make the release without too much risk
7016
+ // if (templating.isTemplate(field.text)) {
7017
+ // entries.push(simpleBoolEntryFactory({
7018
+ // id: 'strict',
7019
+ // path: [ 'strict' ],
7020
+ // label: 'Strict templating',
7021
+ // description: 'Enforces types to be correct',
7022
+ // props
7023
+ // }));
7024
+ // }
7025
+
7026
+ return entries;
6336
7027
  }
6337
7028
  function Text(props) {
6338
7029
  const {
@@ -6351,15 +7042,21 @@ function Text(props) {
6351
7042
  const setValue = value => {
6352
7043
  return editField(field, path, value);
6353
7044
  };
6354
- return FeelTextArea({
7045
+ const description = hooks$1.useMemo(() => jsxRuntime.jsxs(jsxRuntime.Fragment, {
7046
+ children: ["Supports markdown and templating. ", jsxRuntime.jsx("a", {
7047
+ href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-text/",
7048
+ target: "_blank",
7049
+ children: "Learn more"
7050
+ })]
7051
+ }), []);
7052
+ return FeelTemplatingEntry({
6355
7053
  debounce,
6356
- description: 'Use an Expression, Markdown or basic HTML to format.',
7054
+ description,
6357
7055
  element: field,
6358
- feel: 'optional',
6359
7056
  getValue,
6360
7057
  id,
6361
7058
  label: 'Text',
6362
- rows: 10,
7059
+ hostLanguage: 'markdown',
6363
7060
  setValue,
6364
7061
  variables
6365
7062
  });
@@ -6475,7 +7172,7 @@ function NumberSerializationEntry(props) {
6475
7172
  entries.push({
6476
7173
  id: 'serialize-to-string',
6477
7174
  component: SerializeToString,
6478
- isEdited: isEdited$7,
7175
+ isEdited: isEdited$8,
6479
7176
  editField,
6480
7177
  field
6481
7178
  });
@@ -6534,7 +7231,7 @@ function DateTimeEntry(props) {
6534
7231
  entries.push({
6535
7232
  id: 'use24h',
6536
7233
  component: Use24h,
6537
- isEdited: isEdited$7,
7234
+ isEdited: isEdited$8,
6538
7235
  editField,
6539
7236
  field
6540
7237
  });
@@ -6647,7 +7344,7 @@ function DateTimeConstraintsEntry(props) {
6647
7344
  entries.push({
6648
7345
  id: id + '-disallowPassedDates',
6649
7346
  component: DisallowPassedDates,
6650
- isEdited: isEdited$7,
7347
+ isEdited: isEdited$8,
6651
7348
  editField,
6652
7349
  field
6653
7350
  });
@@ -7022,11 +7719,19 @@ function InputKeyValuesSourceEntry(props) {
7022
7719
  field,
7023
7720
  id
7024
7721
  } = props;
7722
+ const schema = '[\n {\n "label": "dollar",\n "value": "$"\n }\n]';
7723
+ const description = jsxRuntime.jsxs("div", {
7724
+ children: ["Define which input property to populate the values from.", jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), "The input property may be an array of simple values or alternatively follow this schema:", jsxRuntime.jsx("pre", {
7725
+ children: jsxRuntime.jsx("code", {
7726
+ children: schema
7727
+ })
7728
+ })]
7729
+ });
7025
7730
  return [{
7026
7731
  id: id + '-key',
7027
7732
  component: InputValuesKey,
7028
7733
  label: 'Input values key',
7029
- description: 'Define which input property to populate the values from',
7734
+ description,
7030
7735
  isEdited: isEdited$1,
7031
7736
  editField,
7032
7737
  field
@@ -7221,7 +7926,7 @@ function ConditionEntry(props) {
7221
7926
  component: Condition,
7222
7927
  editField: editField,
7223
7928
  field: field,
7224
- isEdited: isEdited$6
7929
+ isEdited: isEdited$7
7225
7930
  }];
7226
7931
  }
7227
7932
  function Condition(props) {
@@ -7259,7 +7964,7 @@ function Condition(props) {
7259
7964
  });
7260
7965
  }
7261
7966
 
7262
- function GeneralGroup(field, editField) {
7967
+ function GeneralGroup(field, editField, getService) {
7263
7968
  const entries = [...IdEntry({
7264
7969
  field,
7265
7970
  editField
@@ -7278,15 +7983,13 @@ function GeneralGroup(field, editField) {
7278
7983
  }), ...ActionEntry({
7279
7984
  field,
7280
7985
  editField
7281
- }), ...ColumnsEntry({
7282
- field,
7283
- editField
7284
7986
  }), ...DateTimeEntry({
7285
7987
  field,
7286
7988
  editField
7287
7989
  }), ...TextEntry({
7288
7990
  field,
7289
- editField
7991
+ editField,
7992
+ getService
7290
7993
  }), ...NumberEntries({
7291
7994
  field,
7292
7995
  editField
@@ -7382,7 +8085,7 @@ function ValidationGroup(field, editField) {
7382
8085
  component: Required,
7383
8086
  getValue,
7384
8087
  field,
7385
- isEdited: isEdited$7,
8088
+ isEdited: isEdited$8,
7386
8089
  onChange
7387
8090
  }];
7388
8091
  if (type === 'textfield') {
@@ -7724,6 +8427,27 @@ function AppearanceGroup(field, editField) {
7724
8427
  };
7725
8428
  }
7726
8429
 
8430
+ function LayoutGroup(field, editField) {
8431
+ const {
8432
+ type
8433
+ } = field;
8434
+ if (type === 'default') {
8435
+ return null;
8436
+ }
8437
+ const entries = [...ColumnsEntry({
8438
+ field,
8439
+ editField
8440
+ })];
8441
+ if (entries.length === 0) {
8442
+ return null;
8443
+ }
8444
+ return {
8445
+ id: 'layout',
8446
+ label: 'Layout',
8447
+ entries
8448
+ };
8449
+ }
8450
+
7727
8451
  function ConditionGroup(field, editField) {
7728
8452
  const {
7729
8453
  type
@@ -7742,11 +8466,11 @@ function ConditionGroup(field, editField) {
7742
8466
  };
7743
8467
  }
7744
8468
 
7745
- function getGroups(field, editField) {
8469
+ function getGroups(field, editField, getService) {
7746
8470
  if (!field) {
7747
8471
  return [];
7748
8472
  }
7749
- const groups = [GeneralGroup(field, editField), ConditionGroup(field, editField), AppearanceGroup(field, editField), SerializationGroup(field, editField), ...ValuesGroups(field, editField), ConstraintsGroup(field, editField), ValidationGroup(field, editField), CustomValuesGroup(field, editField)];
8473
+ const groups = [GeneralGroup(field, editField, getService), ConditionGroup(field, editField), LayoutGroup(field, editField), AppearanceGroup(field, editField), SerializationGroup(field, editField), ...ValuesGroups(field, editField), ConstraintsGroup(field, editField), ValidationGroup(field, editField), CustomValuesGroup(field, editField)];
7750
8474
 
7751
8475
  // contract: if a group returns null, it should not be displayed at all
7752
8476
  return groups.filter(group => group !== null);
@@ -7799,10 +8523,9 @@ function FormPropertiesPanel(props) {
7799
8523
  };
7800
8524
  }, []);
7801
8525
  const selectedFormField = state.selectedFormField;
8526
+ const getService = (type, strict = true) => injector.get(type, strict);
7802
8527
  const propertiesPanelContext = {
7803
- getService(type, strict = true) {
7804
- return injector.get(type, strict);
7805
- }
8528
+ getService
7806
8529
  };
7807
8530
  const onFocus = () => eventBus.fire('propertiesPanel.focusin');
7808
8531
  const onBlur = () => eventBus.fire('propertiesPanel.focusout');
@@ -7817,7 +8540,7 @@ function FormPropertiesPanel(props) {
7817
8540
  children: jsxRuntime.jsx(PropertiesPanel, {
7818
8541
  element: selectedFormField,
7819
8542
  eventBus: eventBus,
7820
- groups: getGroups(selectedFormField, editField),
8543
+ groups: getGroups(selectedFormField, editField, getService),
7821
8544
  headerProvider: PropertiesPanelHeaderProvider,
7822
8545
  placeholderProvider: PropertiesPanelPlaceholderProvider
7823
8546
  })
@@ -7895,6 +8618,12 @@ var PropertiesPanelModule = {
7895
8618
  propertiesPanel: ['type', PropertiesPanelRenderer]
7896
8619
  };
7897
8620
 
8621
+ var ExpressionLanguageModule = {
8622
+ __init__: ['expressionLanguage', 'templating'],
8623
+ expressionLanguage: ['type', formJsViewer.FeelExpressionLanguage],
8624
+ templating: ['type', formJsViewer.FeelersTemplating]
8625
+ };
8626
+
7898
8627
  const ids = new Ids([32, 36, 1]);
7899
8628
 
7900
8629
  /**
@@ -8147,7 +8876,7 @@ class FormEditor {
8147
8876
  * @internal
8148
8877
  */
8149
8878
  _getModules() {
8150
- return [ModelingModule, EditorActionsModule, KeyboardModule, SelectionModule, PaletteModule, PropertiesPanelModule];
8879
+ return [ModelingModule, EditorActionsModule, DraggingModule, KeyboardModule, SelectionModule, PaletteModule, ExpressionLanguageModule, formJsViewer.MarkdownModule, PropertiesPanelModule];
8151
8880
  }
8152
8881
 
8153
8882
  /**