@bpmn-io/form-js-editor 0.12.2 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) 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 +1161 -60
  4. package/dist/index.cjs +1247 -526
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.es.js +1249 -528
  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/InputKeyValuesSourceEntry.d.ts +1 -1
  26. package/dist/types/features/properties-panel/entries/SelectEntries.d.ts +1 -0
  27. package/dist/types/features/properties-panel/entries/factories/simpleBoolEntryFactory.d.ts +1 -0
  28. package/dist/types/features/properties-panel/groups/GeneralGroup.d.ts +1 -1
  29. package/dist/types/features/properties-panel/groups/LayoutGroup.d.ts +11 -0
  30. package/dist/types/features/properties-panel/groups/index.d.ts +1 -0
  31. package/dist/types/features/properties-panel/index.d.ts +1 -1
  32. package/dist/types/features/selection/index.d.ts +2 -2
  33. package/dist/types/import/Importer.d.ts +4 -2
  34. package/dist/types/import/index.d.ts +1 -1
  35. package/dist/types/index.d.ts +2 -2
  36. package/dist/types/render/EditorFormFields.d.ts +1 -1
  37. package/dist/types/render/components/FieldDragPreview.d.ts +1 -0
  38. package/dist/types/render/components/editor-form-fields/EditorText.d.ts +1 -1
  39. package/dist/types/render/components/editor-form-fields/index.d.ts +1 -1
  40. package/dist/types/render/components/icons/index.d.ts +1 -1
  41. package/dist/types/render/hooks/index.d.ts +3 -0
  42. package/dist/types/render/hooks/useDebounce.d.ts +1 -0
  43. package/dist/types/render/hooks/usePrevious.d.ts +1 -0
  44. package/dist/types/render/index.d.ts +2 -2
  45. package/package.json +10 -8
  46. package/dist/assets/dragula.css +0 -22
  47. 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 = {},
@@ -945,6 +1036,307 @@ function unset() {
945
1036
  set(null);
946
1037
  }
947
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
+
948
1340
  function ContextPad(props) {
949
1341
  if (!props.children) {
950
1342
  return null;
@@ -991,7 +1383,7 @@ function Element(props) {
991
1383
  event.stopPropagation();
992
1384
  selection.toggle(field);
993
1385
  }
994
- const classes = ['fjs-element'];
1386
+ const classes = [];
995
1387
  if (props.class) {
996
1388
  classes.push(...props.class.split(' '));
997
1389
  }
@@ -1010,7 +1402,9 @@ function Element(props) {
1010
1402
  "data-field-type": type,
1011
1403
  onClick: onClick,
1012
1404
  ref: ref,
1013
- children: [jsxRuntime.jsx(ContextPad, {
1405
+ children: [jsxRuntime.jsx(DebugColumns, {
1406
+ field: field
1407
+ }), jsxRuntime.jsx(ContextPad, {
1014
1408
  children: selection.isSelected(field) && field.type !== 'default' ? jsxRuntime.jsx("button", {
1015
1409
  class: "fjs-context-pad-item",
1016
1410
  onClick: onRemove,
@@ -1019,6 +1413,20 @@ function Element(props) {
1019
1413
  }), props.children]
1020
1414
  });
1021
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
+ }
1022
1430
  function Children(props) {
1023
1431
  const {
1024
1432
  field
@@ -1026,7 +1434,7 @@ function Children(props) {
1026
1434
  const {
1027
1435
  id
1028
1436
  } = field;
1029
- const classes = ['fjs-children', 'fjs-drag-container'];
1437
+ const classes = ['fjs-children', DROP_CONTAINER_VERTICAL_CLS];
1030
1438
  if (props.class) {
1031
1439
  classes.push(...props.class.split(' '));
1032
1440
  }
@@ -1036,12 +1444,51 @@ function Children(props) {
1036
1444
  children: props.children
1037
1445
  });
1038
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
+ }
1039
1487
  function FormEditor$1(props) {
1040
- const eventBus = useService$1('eventBus'),
1488
+ const dragging = useService$1('dragging'),
1489
+ eventBus = useService$1('eventBus'),
1041
1490
  formEditor = useService$1('formEditor'),
1042
- formFieldRegistry = useService$1('formFieldRegistry'),
1043
1491
  injector = useService$1('injector'),
1044
- modeling = useService$1('modeling'),
1045
1492
  selection = useService$1('selection'),
1046
1493
  palette = useService$1('palette'),
1047
1494
  paletteConfig = useService$1('config.palette'),
@@ -1050,6 +1497,7 @@ function FormEditor$1(props) {
1050
1497
  const {
1051
1498
  schema
1052
1499
  } = formEditor._getState();
1500
+ const formContainerRef = hooks$1.useRef(null);
1053
1501
  const paletteRef = hooks$1.useRef(null);
1054
1502
  const propertiesPanelRef = hooks$1.useRef(null);
1055
1503
  const [, setSelection] = hooks$1.useState(schema);
@@ -1068,98 +1516,12 @@ function FormEditor$1(props) {
1068
1516
  drake
1069
1517
  };
1070
1518
  hooks$1.useEffect(() => {
1071
- const handleDragEvent = (type, context) => {
1072
- return eventBus.fire(type, context);
1073
- };
1074
- const createDragulaInstance = () => {
1075
- const dragulaInstance = dragula({
1076
- isContainer(el) {
1077
- return el.classList.contains('fjs-drag-container');
1078
- },
1079
- copy(el) {
1080
- return el.classList.contains('fjs-drag-copy');
1081
- },
1082
- accepts(el, target) {
1083
- return !target.classList.contains('fjs-no-drop');
1084
- },
1085
- slideFactorX: 10,
1086
- slideFactorY: 5
1087
- });
1088
-
1089
- // bind life cycle events
1090
- dragulaInstance.on('drag', (element, source) => {
1091
- handleDragEvent('drag.start', {
1092
- element,
1093
- source
1094
- });
1095
- });
1096
- dragulaInstance.on('dragend', element => {
1097
- handleDragEvent('drag.end', {
1098
- element
1099
- });
1100
- });
1101
- dragulaInstance.on('drop', (element, target, source, sibling) => {
1102
- handleDragEvent('drag.drop', {
1103
- element,
1104
- target,
1105
- source,
1106
- sibling
1107
- });
1108
- });
1109
- dragulaInstance.on('over', (element, container, source) => {
1110
- handleDragEvent('drag.hover', {
1111
- element,
1112
- container,
1113
- source
1114
- });
1115
- });
1116
- dragulaInstance.on('out', (element, container, source) => {
1117
- handleDragEvent('drag.out', {
1118
- element,
1119
- container,
1120
- source
1121
- });
1122
- });
1123
- dragulaInstance.on('cancel', (element, container, source) => {
1124
- handleDragEvent('drag.cancel', {
1125
- element,
1126
- container,
1127
- source
1128
- });
1129
- });
1130
-
1131
- // set custom styling
1132
- dragulaInstance.on('drag', () => {
1133
- set('grabbing');
1134
- });
1135
- dragulaInstance.on('dragend', () => {
1136
- unset();
1137
- });
1138
- dragulaInstance.on('drop', (el, target, source, sibling) => {
1139
- dragulaInstance.remove();
1140
- if (!target) {
1141
- return;
1142
- }
1143
- const targetFormField = formFieldRegistry.get(target.dataset.id);
1144
- const siblingFormField = sibling && formFieldRegistry.get(sibling.dataset.id),
1145
- targetIndex = siblingFormField ? getFormFieldIndex(targetFormField, siblingFormField) : targetFormField.components.length;
1146
- if (source.classList.contains('fjs-palette-fields')) {
1147
- const type = el.dataset.fieldType;
1148
- modeling.addFormField({
1149
- type
1150
- }, targetFormField, targetIndex);
1151
- } else {
1152
- const formField = formFieldRegistry.get(el.dataset.id),
1153
- sourceFormField = formFieldRegistry.get(source.dataset.id),
1154
- sourceIndex = getFormFieldIndex(sourceFormField, formField);
1155
- modeling.moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex);
1156
- }
1157
- });
1158
- eventBus.fire('dragula.created');
1159
- setDrake(dragulaInstance);
1160
- return dragulaInstance;
1161
- };
1162
- 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);
1163
1525
  const onDetach = () => {
1164
1526
  if (dragulaInstance) {
1165
1527
  dragulaInstance.destroy();
@@ -1168,14 +1530,34 @@ function FormEditor$1(props) {
1168
1530
  };
1169
1531
  const onAttach = () => {
1170
1532
  onDetach();
1171
- 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();
1172
1548
  };
1173
1549
  eventBus.on('attach', onAttach);
1174
1550
  eventBus.on('detach', onDetach);
1551
+ eventBus.on('dragula.created', onCreate);
1552
+ eventBus.on('drag.start', onDragStart);
1553
+ eventBus.on('drag.end', onDragEnd);
1175
1554
  return () => {
1176
1555
  onDetach();
1177
1556
  eventBus.off('attach', onAttach);
1178
1557
  eventBus.off('detach', onDetach);
1558
+ eventBus.off('dragula.created', onCreate);
1559
+ eventBus.off('drag.start', onDragStart);
1560
+ eventBus.off('drag.end', onDragEnd);
1179
1561
  };
1180
1562
  }, []);
1181
1563
 
@@ -1185,8 +1567,10 @@ function FormEditor$1(props) {
1185
1567
  }, []);
1186
1568
  const formRenderContext = {
1187
1569
  Children,
1570
+ Column,
1188
1571
  Element,
1189
- Empty
1572
+ Empty,
1573
+ Row
1190
1574
  };
1191
1575
  const formContext = {
1192
1576
  getService(type, strict = true) {
@@ -1235,6 +1619,7 @@ function FormEditor$1(props) {
1235
1619
  class: "fjs-editor-palette-container",
1236
1620
  ref: paletteRef
1237
1621
  }), jsxRuntime.jsx("div", {
1622
+ ref: formContainerRef,
1238
1623
  class: "fjs-form-container",
1239
1624
  children: jsxRuntime.jsx(formJsViewer.FormContext.Provider, {
1240
1625
  value: formContext,
@@ -1270,29 +1655,39 @@ function CreatePreview(props) {
1270
1655
  } = hooks$1.useContext(DragAndDropContext$1);
1271
1656
  function handleCloned(clone, original, type) {
1272
1657
  const fieldType = clone.dataset.fieldType;
1273
- const Icon = formJsViewer.iconsByType(fieldType);
1274
- const {
1275
- label
1276
- } = findPaletteEntry(fieldType);
1658
+
1659
+ // (1) field preview
1277
1660
  if (fieldType) {
1661
+ const {
1662
+ label
1663
+ } = findPaletteEntry(fieldType);
1664
+ const Icon = formJsViewer.iconsByType(fieldType);
1278
1665
  clone.innerHTML = '';
1279
1666
  clone.class = 'gu-mirror';
1667
+ clone.classList.add('fjs-field-preview-container');
1280
1668
  if (original.classList.contains('fjs-palette-field')) {
1281
- preact.render(jsxRuntime.jsxs("div", {
1282
- class: "fjs-palette-field",
1283
- children: [jsxRuntime.jsx(Icon, {
1284
- class: "fjs-palette-field-icon",
1285
- width: "36",
1286
- height: "36",
1287
- viewBox: "0 0 54 54"
1288
- }), jsxRuntime.jsx("span", {
1289
- class: "fjs-palette-field-text",
1290
- children: label
1291
- })]
1292
- }), clone);
1293
- } else {
1294
- preact.render(jsxRuntime.jsx(Icon, {}), clone);
1669
+ // default to auto columns when creating from palette
1670
+ clone.classList.add('cds--col');
1295
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');
1296
1691
  }
1297
1692
  }
1298
1693
  hooks$1.useEffect(() => {
@@ -1365,10 +1760,12 @@ var renderModule = {
1365
1760
 
1366
1761
  var core = {
1367
1762
  __depends__: [importModule, renderModule],
1763
+ debounce: ['factory', DebounceFactory],
1368
1764
  eventBus: ['type', EventBus],
1369
1765
  formFieldRegistry: ['type', FormFieldRegistry],
1370
- fieldFactory: ['type', FieldFactory],
1371
- debounce: ['factory', DebounceFactory]
1766
+ formLayouter: ['type', formJsViewer.FormLayouter],
1767
+ formLayoutValidator: ['type', FormLayoutValidator],
1768
+ fieldFactory: ['type', FieldFactory]
1372
1769
  };
1373
1770
 
1374
1771
  var NOT_REGISTERED_ERROR = 'is not a registered action',
@@ -1646,6 +2043,11 @@ var EditorActionsModule = {
1646
2043
  editorActions: ['type', FormEditorActions]
1647
2044
  };
1648
2045
 
2046
+ var DraggingModule = {
2047
+ __init__: ['dragging'],
2048
+ dragging: ['type', Dragging]
2049
+ };
2050
+
1649
2051
  var KEYS_COPY = ['c', 'C', 'KeyC'];
1650
2052
  var KEYS_PASTE = ['v', 'V', 'KeyV'];
1651
2053
  var KEYS_REDO$1 = ['y', 'Y', 'KeyY'];
@@ -2058,6 +2460,13 @@ function updatePath(formFieldRegistry, formField, index) {
2058
2460
  formField._path = [...parent._path, 'components', index];
2059
2461
  return formField;
2060
2462
  }
2463
+ function updateRow(formField, rowId) {
2464
+ formField.layout = {
2465
+ ...(formField.layout || {}),
2466
+ row: rowId
2467
+ };
2468
+ return formField;
2469
+ }
2061
2470
 
2062
2471
  class AddFormFieldHandler {
2063
2472
  /**
@@ -2207,13 +2616,17 @@ class MoveFormFieldHandler {
2207
2616
  sourceFormField,
2208
2617
  targetFormField,
2209
2618
  sourceIndex,
2210
- targetIndex
2619
+ targetIndex,
2620
+ sourceRow,
2621
+ targetRow
2211
2622
  } = context;
2212
2623
  this.moveFormField({
2213
2624
  sourceFormField: targetFormField,
2214
2625
  targetFormField: sourceFormField,
2215
2626
  sourceIndex: targetIndex,
2216
- targetIndex: sourceIndex
2627
+ targetIndex: sourceIndex,
2628
+ sourceRow: targetRow,
2629
+ targetRow: sourceRow
2217
2630
  }, true);
2218
2631
  }
2219
2632
  moveFormField(context, revert) {
@@ -2221,7 +2634,8 @@ class MoveFormFieldHandler {
2221
2634
  sourceFormField,
2222
2635
  targetFormField,
2223
2636
  sourceIndex,
2224
- targetIndex
2637
+ targetIndex,
2638
+ targetRow
2225
2639
  } = context;
2226
2640
  let {
2227
2641
  schema
@@ -2237,11 +2651,15 @@ class MoveFormFieldHandler {
2237
2651
  targetIndex--;
2238
2652
  }
2239
2653
  }
2654
+ const formField = minDash.get(schema, [...sourcePath, sourceIndex]);
2655
+
2656
+ // (1) Add to row
2657
+ updateRow(formField, targetRow ? targetRow.id : null);
2240
2658
 
2241
- // (1) Move form field
2659
+ // (2) Move form field
2242
2660
  arrayMove.mutate(minDash.get(schema, sourcePath), sourceIndex, targetIndex);
2243
2661
 
2244
- // (2) Update paths of new form field and its siblings
2662
+ // (3) Update paths of new form field and its siblings
2245
2663
  minDash.get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
2246
2664
  } else {
2247
2665
  const formField = minDash.get(schema, [...sourcePath, sourceIndex]);
@@ -2254,10 +2672,13 @@ class MoveFormFieldHandler {
2254
2672
  minDash.get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
2255
2673
  const targetPath = [...targetFormField._path, 'components'];
2256
2674
 
2257
- // (3) Add form field
2675
+ // (3) Add to row
2676
+ updateRow(formField, targetRow ? targetRow.id : null);
2677
+
2678
+ // (4) Add form field
2258
2679
  arrayAdd$1(minDash.get(schema, targetPath), targetIndex, formField);
2259
2680
 
2260
- // (4) Update paths of siblings
2681
+ // (5) Update paths of siblings
2261
2682
  minDash.get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
2262
2683
  }
2263
2684
 
@@ -2449,13 +2870,15 @@ class Modeling {
2449
2870
  };
2450
2871
  this._commandStack.execute('formField.edit', context);
2451
2872
  }
2452
- moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex) {
2873
+ moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex, sourceRow, targetRow) {
2453
2874
  const context = {
2454
2875
  formField,
2455
2876
  sourceFormField,
2456
2877
  targetFormField,
2457
2878
  sourceIndex,
2458
- targetIndex
2879
+ targetIndex,
2880
+ sourceRow,
2881
+ targetRow
2459
2882
  };
2460
2883
  this._commandStack.execute('formField.move', context);
2461
2884
  }
@@ -2614,6 +3037,44 @@ minDash.forEach(hooks, function (hook) {
2614
3037
  };
2615
3038
  });
2616
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
+
2617
3078
  class IdBehavior extends CommandInterceptor {
2618
3079
  constructor(eventBus, modeling) {
2619
3080
  super(eventBus);
@@ -3135,7 +3596,8 @@ var commandModule = {
3135
3596
 
3136
3597
  var ModelingModule = {
3137
3598
  __depends__: [behaviorModule, commandModule],
3138
- __init__: ['modeling'],
3599
+ __init__: ['formLayoutUpdater', 'modeling'],
3600
+ formLayoutUpdater: ['type', FormLayoutUpdater],
3139
3601
  modeling: ['type', Modeling]
3140
3602
  };
3141
3603
 
@@ -3431,19 +3893,19 @@ const ErrorsContext = preact.createContext({
3431
3893
  errors: {}
3432
3894
  });
3433
3895
 
3434
- /**
3435
- * @typedef {Function} <propertiesPanel.showEntry> callback
3436
- *
3437
- * @example
3438
- *
3439
- * useEvent('propertiesPanel.showEntry', ({ focus = false, ...rest }) => {
3440
- * // ...
3441
- * });
3442
- *
3443
- * @param {Object} context
3444
- * @param {boolean} [context.focus]
3445
- *
3446
- * @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
3447
3909
  */
3448
3910
  const EventContext = preact.createContext({
3449
3911
  eventBus: null
@@ -3455,20 +3917,20 @@ const LayoutContext = preact.createContext({
3455
3917
  setLayoutForKey: () => {}
3456
3918
  });
3457
3919
 
3458
- /**
3459
- * Accesses the global DescriptionContext and returns a description for a given id and element.
3460
- *
3461
- * @example
3462
- * ```jsx
3463
- * function TextField(props) {
3464
- * const description = useDescriptionContext('input1', element);
3465
- * }
3466
- * ```
3467
- *
3468
- * @param {string} id
3469
- * @param {object} element
3470
- *
3471
- * @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}
3472
3934
  */
3473
3935
  function useDescriptionContext(id, element) {
3474
3936
  const {
@@ -3483,11 +3945,11 @@ function useError(id) {
3483
3945
  return errors[id];
3484
3946
  }
3485
3947
 
3486
- /**
3487
- * Subscribe to an event immediately. Update subscription after inputs changed.
3488
- *
3489
- * @param {string} event
3490
- * @param {Function} callback
3948
+ /**
3949
+ * Subscribe to an event immediately. Update subscription after inputs changed.
3950
+ *
3951
+ * @param {string} event
3952
+ * @param {Function} callback
3491
3953
  */
3492
3954
  function useEvent(event, callback, eventBus) {
3493
3955
  const eventContext = hooks$1.useContext(EventContext);
@@ -3517,20 +3979,20 @@ function useEvent(event, callback, eventBus) {
3517
3979
  }, [callback, event, eventBus]);
3518
3980
  }
3519
3981
 
3520
- /**
3521
- * Creates a state that persists in the global LayoutContext.
3522
- *
3523
- * @example
3524
- * ```jsx
3525
- * function Group(props) {
3526
- * const [ open, setOpen ] = useLayoutState([ 'groups', 'foo', 'open' ], false);
3527
- * }
3528
- * ```
3529
- *
3530
- * @param {(string|number)[]} path
3531
- * @param {any} [defaultValue]
3532
- *
3533
- * @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 ]}
3534
3996
  */
3535
3997
  function useLayoutState(path, defaultValue) {
3536
3998
  const {
@@ -3538,22 +4000,17 @@ function useLayoutState(path, defaultValue) {
3538
4000
  setLayoutForKey
3539
4001
  } = hooks$1.useContext(LayoutContext);
3540
4002
  const layoutForKey = getLayoutForKey(path, defaultValue);
3541
- const [value, set] = hooks$1.useState(layoutForKey);
3542
- const setState = newValue => {
3543
- // (1) set component state
3544
- set(newValue);
3545
-
3546
- // (2) set context
4003
+ const setState = hooks$1.useCallback(newValue => {
3547
4004
  setLayoutForKey(path, newValue);
3548
- };
3549
- return [value, setState];
4005
+ }, [setLayoutForKey]);
4006
+ return [layoutForKey, setState];
3550
4007
  }
3551
4008
 
3552
- /**
3553
- * @pinussilvestrus: we need to introduce our own hook to persist the previous
3554
- * state on updates.
3555
- *
3556
- * 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
3557
4014
  */
3558
4015
 
3559
4016
  function usePrevious(value) {
@@ -3564,12 +4021,12 @@ function usePrevious(value) {
3564
4021
  return ref.current;
3565
4022
  }
3566
4023
 
3567
- /**
3568
- * Subscribe to `propertiesPanel.showEntry`.
3569
- *
3570
- * @param {string} id
3571
- *
3572
- * @returns {import('preact').Ref}
4024
+ /**
4025
+ * Subscribe to `propertiesPanel.showEntry`.
4026
+ *
4027
+ * @param {string} id
4028
+ *
4029
+ * @returns {import('preact').Ref}
3573
4030
  */
3574
4031
  function useShowEntryEvent(id) {
3575
4032
  const {
@@ -3600,20 +4057,20 @@ function useShowEntryEvent(id) {
3600
4057
  return ref;
3601
4058
  }
3602
4059
 
3603
- /**
3604
- * @callback setSticky
3605
- * @param {boolean} value
4060
+ /**
4061
+ * @callback setSticky
4062
+ * @param {boolean} value
3606
4063
  */
3607
4064
 
3608
- /**
3609
- * Use IntersectionObserver to identify when DOM element is in sticky mode.
3610
- * If sticky is observered setSticky(true) will be called.
3611
- * If sticky mode is left, setSticky(false) will be called.
3612
- *
3613
- *
3614
- * @param {Object} ref
3615
- * @param {string} scrollContainerSelector
3616
- * @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
3617
4074
  */
3618
4075
  function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky) {
3619
4076
  hooks$1.useEffect(() => {
@@ -3652,19 +4109,19 @@ function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky)
3652
4109
  }, [ref, scrollContainerSelector, setSticky]);
3653
4110
  }
3654
4111
 
3655
- /**
3656
- * Creates a static function reference with changing body.
3657
- * This is necessary when external libraries require a callback function
3658
- * that has references to state variables.
3659
- *
3660
- * Usage:
3661
- * const callback = useStaticCallback((val) => {val === currentState});
3662
- *
3663
- * The `callback` reference is static and can be safely used in external
3664
- * libraries or as a prop that does not cause rerendering of children.
3665
- *
3666
- * @param {Function} callback function with changing reference
3667
- * @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
3668
4125
  */
3669
4126
  function useStaticCallback(callback) {
3670
4127
  const callbackRef = hooks$1.useRef(callback);
@@ -3756,13 +4213,13 @@ function DataMarker() {
3756
4213
  });
3757
4214
  }
3758
4215
 
3759
- /**
3760
- * @typedef { {
3761
- * text: (element: object) => string,
3762
- * icon?: (element: Object) => import('preact').Component
3763
- * } } PlaceholderDefinition
3764
- *
3765
- * @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
3766
4223
  */
3767
4224
  function Placeholder(props) {
3768
4225
  const {
@@ -3787,72 +4244,72 @@ const DEFAULT_LAYOUT = {
3787
4244
  };
3788
4245
  const DEFAULT_DESCRIPTION = {};
3789
4246
 
3790
- /**
3791
- * @typedef { {
3792
- * component: import('preact').Component,
3793
- * id: String,
3794
- * isEdited?: Function
3795
- * } } EntryDefinition
3796
- *
3797
- * @typedef { {
3798
- * autoFocusEntry: String,
3799
- * autoOpen?: Boolean,
3800
- * entries: Array<EntryDefinition>,
3801
- * id: String,
3802
- * label: String,
3803
- * remove: (event: MouseEvent) => void
3804
- * } } ListItemDefinition
3805
- *
3806
- * @typedef { {
3807
- * add: (event: MouseEvent) => void,
3808
- * component: import('preact').Component,
3809
- * element: Object,
3810
- * id: String,
3811
- * items: Array<ListItemDefinition>,
3812
- * label: String,
3813
- * shouldSort?: Boolean,
3814
- * shouldOpen?: Boolean
3815
- * } } ListGroupDefinition
3816
- *
3817
- * @typedef { {
3818
- * component?: import('preact').Component,
3819
- * entries: Array<EntryDefinition>,
3820
- * id: String,
3821
- * label: String,
3822
- * shouldOpen?: Boolean
3823
- * } } GroupDefinition
3824
- *
3825
- * @typedef { {
3826
- * [id: String]: GetDescriptionFunction
3827
- * } } DescriptionConfig
3828
- *
3829
- * @callback { {
3830
- * @param {string} id
3831
- * @param {Object} element
3832
- * @returns {string}
3833
- * } } GetDescriptionFunction
3834
- *
3835
- * @typedef { {
3836
- * getEmpty: (element: object) => import('./components/Placeholder').PlaceholderDefinition,
3837
- * getMultiple: (element: Object) => import('./components/Placeholder').PlaceholderDefinition
3838
- * } } PlaceholderProvider
3839
- *
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
+ *
3840
4297
  */
3841
4298
 
3842
- /**
3843
- * A basic properties panel component. Describes *how* content will be rendered, accepts
3844
- * data from implementor to describe *what* will be rendered.
3845
- *
3846
- * @param {Object} props
3847
- * @param {Object|Array} props.element
3848
- * @param {import('./components/Header').HeaderProvider} props.headerProvider
3849
- * @param {PlaceholderProvider} [props.placeholderProvider]
3850
- * @param {Array<GroupDefinition|ListGroupDefinition>} props.groups
3851
- * @param {Object} [props.layoutConfig]
3852
- * @param {Function} [props.layoutChanged]
3853
- * @param {DescriptionConfig} [props.descriptionConfig]
3854
- * @param {Function} [props.descriptionLoaded]
3855
- * @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]
3856
4313
  */
3857
4314
  function PropertiesPanel(props) {
3858
4315
  const {
@@ -3860,15 +4317,21 @@ function PropertiesPanel(props) {
3860
4317
  headerProvider,
3861
4318
  placeholderProvider,
3862
4319
  groups,
3863
- layoutConfig = {},
4320
+ layoutConfig,
3864
4321
  layoutChanged,
3865
- descriptionConfig = {},
4322
+ descriptionConfig,
3866
4323
  descriptionLoaded,
3867
4324
  eventBus
3868
4325
  } = props;
3869
4326
 
3870
4327
  // set-up layout context
3871
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]);
3872
4335
  hooks$1.useEffect(() => {
3873
4336
  if (typeof layoutChanged === 'function') {
3874
4337
  layoutChanged(layout);
@@ -3890,10 +4353,12 @@ function PropertiesPanel(props) {
3890
4353
  };
3891
4354
 
3892
4355
  // set-up description context
3893
- const description = createDescriptionContext(descriptionConfig);
3894
- if (typeof descriptionLoaded === 'function') {
3895
- descriptionLoaded(description);
3896
- }
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]);
3897
4362
  const getDescriptionForId = (id, element) => {
3898
4363
  return description[id] && description[id](element);
3899
4364
  };
@@ -3968,18 +4433,37 @@ function PropertiesPanel(props) {
3968
4433
 
3969
4434
  // helpers //////////////////
3970
4435
 
3971
- function createLayout(overrides) {
4436
+ function createLayout(overrides = {}, defaults = DEFAULT_LAYOUT) {
3972
4437
  return {
3973
- ...DEFAULT_LAYOUT,
4438
+ ...defaults,
3974
4439
  ...overrides
3975
4440
  };
3976
4441
  }
3977
- function createDescriptionContext(overrides) {
4442
+ function createDescriptionContext(overrides = {}) {
3978
4443
  return {
3979
4444
  ...DEFAULT_DESCRIPTION,
3980
4445
  ...overrides
3981
4446
  };
3982
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
+ }
3983
4467
  function CollapsibleEntry(props) {
3984
4468
  const {
3985
4469
  element,
@@ -4075,10 +4559,10 @@ function ListItem(props) {
4075
4559
  })
4076
4560
  });
4077
4561
  }
4078
- const noop$2 = () => {};
4562
+ const noop$3 = () => {};
4079
4563
 
4080
- /**
4081
- * @param {import('../PropertiesPanel').ListGroupDefinition} props
4564
+ /**
4565
+ * @param {import('../PropertiesPanel').ListGroupDefinition} props
4082
4566
  */
4083
4567
  function ListGroup(props) {
4084
4568
  const {
@@ -4096,6 +4580,9 @@ function ListGroup(props) {
4096
4580
  const onShow = hooks$1.useCallback(() => setOpen(true), [setOpen]);
4097
4581
  const [ordering, setOrdering] = hooks$1.useState([]);
4098
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);
4099
4586
  const prevItems = usePrevious(items);
4100
4587
  const prevElement = usePrevious(element);
4101
4588
  const elementChanged = element !== prevElement;
@@ -4117,6 +4604,8 @@ function ListGroup(props) {
4117
4604
 
4118
4605
  // (1) items were added
4119
4606
  hooks$1.useEffect(() => {
4607
+ // reset addTriggered flag
4608
+ setAddTriggered(false);
4120
4609
  if (shouldHandleEffects && prevItems && items.length > prevItems.length) {
4121
4610
  let add = [];
4122
4611
  items.forEach(item => {
@@ -4126,14 +4615,18 @@ function ListGroup(props) {
4126
4615
  });
4127
4616
  let newOrdering = ordering;
4128
4617
 
4129
- // open if not open and configured
4130
- 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) {
4131
4624
  toggleOpen();
4625
+ }
4132
4626
 
4133
- // if I opened and I should sort, then sort items
4134
- if (shouldSort) {
4135
- newOrdering = createOrdering(sortItems(items));
4136
- }
4627
+ // filter when not open and configured
4628
+ if (!open && shouldSort) {
4629
+ newOrdering = createOrdering(sortItems(items));
4137
4630
  }
4138
4631
 
4139
4632
  // add new items on top or bottom depending on sorting behavior
@@ -4144,11 +4637,11 @@ function ListGroup(props) {
4144
4637
  newOrdering.push(...add);
4145
4638
  }
4146
4639
  setOrdering(newOrdering);
4147
- setNewItemAdded(true);
4640
+ setNewItemAdded(addTriggered);
4148
4641
  } else {
4149
4642
  setNewItemAdded(false);
4150
4643
  }
4151
- }, [items, open, shouldHandleEffects]);
4644
+ }, [items, open, shouldHandleEffects, addTriggered]);
4152
4645
 
4153
4646
  // (2) sort items on open if shouldSort is set
4154
4647
  hooks$1.useEffect(() => {
@@ -4178,13 +4671,17 @@ function ListGroup(props) {
4178
4671
  ...hooks$1.useContext(LayoutContext),
4179
4672
  onShow
4180
4673
  };
4674
+ const handleAddClick = e => {
4675
+ setAddTriggered(true);
4676
+ add(e);
4677
+ };
4181
4678
  return jsxRuntime.jsxs("div", {
4182
4679
  class: "bio-properties-panel-group",
4183
4680
  "data-group-id": 'group-' + id,
4184
4681
  ref: groupRef,
4185
4682
  children: [jsxRuntime.jsxs("div", {
4186
4683
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
4187
- onClick: hasItems ? toggleOpen : noop$2,
4684
+ onClick: hasItems ? toggleOpen : noop$3,
4188
4685
  children: [jsxRuntime.jsx("div", {
4189
4686
  title: label,
4190
4687
  class: "bio-properties-panel-group-header-title",
@@ -4194,7 +4691,7 @@ function ListGroup(props) {
4194
4691
  children: [add ? jsxRuntime.jsxs("button", {
4195
4692
  title: "Create new list item",
4196
4693
  class: "bio-properties-panel-group-header-button bio-properties-panel-add-entry",
4197
- onClick: add,
4694
+ onClick: handleAddClick,
4198
4695
  children: [jsxRuntime.jsx(CreateIcon, {}), !hasItems ? jsxRuntime.jsx("span", {
4199
4696
  class: "bio-properties-panel-add-entry-label",
4200
4697
  children: "Create"
@@ -4224,8 +4721,9 @@ function ListGroup(props) {
4224
4721
  id
4225
4722
  } = item;
4226
4723
 
4227
- // if item was added, open first or last item based on ordering
4228
- 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;
4229
4727
  return preact.createElement(ListItem, {
4230
4728
  ...item,
4231
4729
  autoOpen: autoOpen,
@@ -4241,8 +4739,8 @@ function ListGroup(props) {
4241
4739
 
4242
4740
  // helpers ////////////////////
4243
4741
 
4244
- /**
4245
- * Sorts given items alphanumeric by label
4742
+ /**
4743
+ * Sorts given items alphanumeric by label
4246
4744
  */
4247
4745
  function sortItems(items) {
4248
4746
  return minDash.sortBy(items, i => i.label.toLowerCase());
@@ -4316,17 +4814,17 @@ function Checkbox(props) {
4316
4814
  });
4317
4815
  }
4318
4816
 
4319
- /**
4320
- * @param {Object} props
4321
- * @param {Object} props.element
4322
- * @param {String} props.id
4323
- * @param {String} props.description
4324
- * @param {String} props.label
4325
- * @param {Function} props.getValue
4326
- * @param {Function} props.setValue
4327
- * @param {Function} props.onFocus
4328
- * @param {Function} props.onBlur
4329
- * @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]
4330
4828
  */
4331
4829
  function CheckboxEntry(props) {
4332
4830
  const {
@@ -4363,7 +4861,7 @@ function CheckboxEntry(props) {
4363
4861
  })]
4364
4862
  });
4365
4863
  }
4366
- function isEdited$7(node) {
4864
+ function isEdited$8(node) {
4367
4865
  return node && !!node.checked;
4368
4866
  }
4369
4867
 
@@ -4372,6 +4870,87 @@ function isEdited$7(node) {
4372
4870
  function prefixId$7(id) {
4373
4871
  return `bio-properties-panel-${id}`;
4374
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
+ });
4375
4954
  const useBufferedFocus = function (editor, ref) {
4376
4955
  const [buffer, setBuffer] = hooks$1.useState(undefined);
4377
4956
  ref.current = hooks$1.useMemo(() => ({
@@ -4414,10 +4993,10 @@ const CodeEditor = React.forwardRef((props, ref) => {
4414
4993
  hooks$1.useEffect(() => {
4415
4994
  let editor;
4416
4995
 
4417
- /* Trigger FEEL toggle when
4418
- *
4419
- * - `backspace` is pressed
4420
- * - 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
4421
5000
  */
4422
5001
  const onKeyDown = e => {
4423
5002
  if (e.key !== 'Backspace' || !editor) {
@@ -4486,12 +5065,12 @@ function FeelIndicator(props) {
4486
5065
  children: "="
4487
5066
  });
4488
5067
  }
4489
- const noop$1 = () => {};
5068
+ const noop$2 = () => {};
4490
5069
 
4491
- /**
4492
- * @param {Object} props
4493
- * @param {Object} props.label
4494
- * @param {String} props.feel
5070
+ /**
5071
+ * @param {Object} props
5072
+ * @param {Object} props.label
5073
+ * @param {String} props.feel
4495
5074
  */
4496
5075
  function FeelIcon(props) {
4497
5076
  const {
@@ -4499,7 +5078,7 @@ function FeelIcon(props) {
4499
5078
  feel = false,
4500
5079
  active,
4501
5080
  disabled = false,
4502
- onClick = noop$1
5081
+ onClick = noop$2
4503
5082
  } = props;
4504
5083
  const feelRequiredLabel = ' must be a FEEL expression';
4505
5084
  const feelOptionalLabel = ' can optionally be a FEEL expression';
@@ -4519,7 +5098,7 @@ function FeelIcon(props) {
4519
5098
  children: feel === 'required' ? jsxRuntime.jsx(FeelRequiredIcon, {}) : jsxRuntime.jsx(FeelOptionalIcon, {})
4520
5099
  });
4521
5100
  }
4522
- const noop = () => {};
5101
+ const noop$1 = () => {};
4523
5102
  function FeelTextfield(props) {
4524
5103
  const {
4525
5104
  debounce,
@@ -4676,6 +5255,9 @@ function FeelTextfield(props) {
4676
5255
  }) : jsxRuntime.jsx(OptionalComponent, {
4677
5256
  ...props,
4678
5257
  onInput: handleLocalInput,
5258
+ contentAttributes: {
5259
+ 'id': prefixId$6(id)
5260
+ },
4679
5261
  value: localValue,
4680
5262
  ref: editorRef
4681
5263
  })]
@@ -4725,7 +5307,7 @@ const OptionalFeelInput = React.forwardRef((props, ref) => {
4725
5307
  value: value || ''
4726
5308
  });
4727
5309
  });
4728
- const OptionalFeelTextArea = React.forwardRef((props, ref) => {
5310
+ React.forwardRef((props, ref) => {
4729
5311
  const {
4730
5312
  id,
4731
5313
  disabled,
@@ -4765,17 +5347,24 @@ const OptionalFeelTextArea = React.forwardRef((props, ref) => {
4765
5347
  });
4766
5348
  });
4767
5349
 
4768
- /**
4769
- * @param {Object} props
4770
- * @param {Object} props.element
4771
- * @param {String} props.id
4772
- * @param {String} props.description
4773
- * @param {Boolean} props.debounce
4774
- * @param {Boolean} props.disabled
4775
- * @param {String} props.label
4776
- * @param {Function} props.getValue
4777
- * @param {Function} props.setValue
4778
- * @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
4779
5368
  */
4780
5369
  function FeelEntry(props) {
4781
5370
  const {
@@ -4789,8 +5378,10 @@ function FeelEntry(props) {
4789
5378
  getValue,
4790
5379
  setValue,
4791
5380
  tooltipContainer,
5381
+ hostLanguage,
5382
+ singleLine,
4792
5383
  validate,
4793
- show = noop,
5384
+ show = noop$1,
4794
5385
  example,
4795
5386
  variables,
4796
5387
  onFocus,
@@ -4844,6 +5435,8 @@ function FeelEntry(props) {
4844
5435
  onFocus: onFocus,
4845
5436
  onBlur: onBlur,
4846
5437
  example: example,
5438
+ hostLanguage: hostLanguage,
5439
+ singleLine: singleLine,
4847
5440
  show: show,
4848
5441
  value: value,
4849
5442
  variables: variables,
@@ -4860,28 +5453,35 @@ function FeelEntry(props) {
4860
5453
  });
4861
5454
  }
4862
5455
 
4863
- /**
4864
- * @param {Object} props
4865
- * @param {Object} props.element
4866
- * @param {String} props.id
4867
- * @param {String} props.description
4868
- * @param {Boolean} props.debounce
4869
- * @param {Boolean} props.disabled
4870
- * @param {String} props.label
4871
- * @param {Function} props.getValue
4872
- * @param {Function} props.setValue
4873
- * @param {Function} props.onFocus
4874
- * @param {Function} props.onBlur
4875
- * @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
4876
5476
  */
4877
- function FeelTextArea(props) {
5477
+ function FeelTemplatingEntry(props) {
4878
5478
  return jsxRuntime.jsx(FeelEntry, {
4879
- class: "bio-properties-panel-feel-textarea",
4880
- OptionalComponent: OptionalFeelTextArea,
5479
+ class: "bio-properties-panel-feel-templating",
5480
+ OptionalComponent: CodeEditor$1,
4881
5481
  ...props
4882
5482
  });
4883
5483
  }
4884
- function isEdited$6(node) {
5484
+ function isEdited$7(node) {
4885
5485
  return node && (!!node.value || node.classList.contains('edited'));
4886
5486
  }
4887
5487
 
@@ -4951,22 +5551,22 @@ function NumberField(props) {
4951
5551
  });
4952
5552
  }
4953
5553
 
4954
- /**
4955
- * @param {Object} props
4956
- * @param {Boolean} props.debounce
4957
- * @param {String} props.description
4958
- * @param {Boolean} props.disabled
4959
- * @param {Object} props.element
4960
- * @param {Function} props.getValue
4961
- * @param {String} props.id
4962
- * @param {String} props.label
4963
- * @param {String} props.max
4964
- * @param {String} props.min
4965
- * @param {Function} props.setValue
4966
- * @param {Function} props.onFocus
4967
- * @param {Function} props.onBlur
4968
- * @param {String} props.step
4969
- * @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
4970
5570
  */
4971
5571
  function NumberFieldEntry(props) {
4972
5572
  const {
@@ -5091,6 +5691,16 @@ function Select(props) {
5091
5691
  value: localValue,
5092
5692
  disabled: disabled,
5093
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
+ }
5094
5704
  return jsxRuntime.jsx("option", {
5095
5705
  value: option.value,
5096
5706
  disabled: option.disabled,
@@ -5101,18 +5711,19 @@ function Select(props) {
5101
5711
  });
5102
5712
  }
5103
5713
 
5104
- /**
5105
- * @param {object} props
5106
- * @param {object} props.element
5107
- * @param {string} props.id
5108
- * @param {string} [props.description]
5109
- * @param {string} props.label
5110
- * @param {Function} props.getValue
5111
- * @param {Function} props.setValue
5112
- * @param {Function} props.onFocus
5113
- * @param {Function} props.onBlur
5114
- * @param {Function} props.getOptions
5115
- * @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]
5116
5727
  */
5117
5728
  function SelectEntry(props) {
5118
5729
  const {
@@ -5125,11 +5736,37 @@ function SelectEntry(props) {
5125
5736
  getOptions,
5126
5737
  disabled,
5127
5738
  onFocus,
5128
- onBlur
5739
+ onBlur,
5740
+ validate
5129
5741
  } = props;
5130
- const value = getValue(element);
5131
5742
  const options = getOptions(element);
5132
- 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;
5133
5770
  return jsxRuntime.jsxs("div", {
5134
5771
  class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
5135
5772
  "data-entry-id": id,
@@ -5137,7 +5774,7 @@ function SelectEntry(props) {
5137
5774
  id: id,
5138
5775
  label: label,
5139
5776
  value: value,
5140
- onChange: setValue,
5777
+ onChange: onChange,
5141
5778
  onFocus: onFocus,
5142
5779
  onBlur: onBlur,
5143
5780
  options: options,
@@ -5161,18 +5798,26 @@ function isEdited$4(node) {
5161
5798
  function prefixId$4(id) {
5162
5799
  return `bio-properties-panel-${id}`;
5163
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
+ }
5164
5808
  function TextArea(props) {
5165
5809
  const {
5166
5810
  id,
5167
5811
  label,
5168
- rows = 2,
5169
5812
  debounce,
5170
5813
  onInput,
5171
5814
  value = '',
5172
5815
  disabled,
5173
5816
  monospace,
5174
5817
  onFocus,
5175
- onBlur
5818
+ onBlur,
5819
+ autoResize,
5820
+ rows = autoResize ? 1 : 2
5176
5821
  } = props;
5177
5822
  const [localValue, setLocalValue] = hooks$1.useState(value);
5178
5823
  const ref = useShowEntryEvent(id);
@@ -5183,8 +5828,12 @@ function TextArea(props) {
5183
5828
  }, [onInput, debounce]);
5184
5829
  const handleInput = e => {
5185
5830
  handleInputCallback(e);
5831
+ autoResize && resizeToContents(e.target);
5186
5832
  setLocalValue(e.target.value);
5187
5833
  };
5834
+ hooks$1.useLayoutEffect(() => {
5835
+ autoResize && resizeToContents(ref.current);
5836
+ }, []);
5188
5837
  hooks$1.useEffect(() => {
5189
5838
  if (value === localValue) {
5190
5839
  return;
@@ -5202,7 +5851,7 @@ function TextArea(props) {
5202
5851
  id: prefixId$2(id),
5203
5852
  name: id,
5204
5853
  spellCheck: "false",
5205
- 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' : ''),
5206
5855
  onInput: handleInput,
5207
5856
  onFocus: onFocus,
5208
5857
  onBlur: onBlur,
@@ -5214,20 +5863,20 @@ function TextArea(props) {
5214
5863
  });
5215
5864
  }
5216
5865
 
5217
- /**
5218
- * @param {object} props
5219
- * @param {object} props.element
5220
- * @param {string} props.id
5221
- * @param {string} props.description
5222
- * @param {boolean} props.debounce
5223
- * @param {string} props.label
5224
- * @param {Function} props.getValue
5225
- * @param {Function} props.setValue
5226
- * @param {Function} props.onFocus
5227
- * @param {Function} props.onBlur
5228
- * @param {number} props.rows
5229
- * @param {boolean} props.monospace
5230
- * @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]
5231
5880
  */
5232
5881
  function TextAreaEntry(props) {
5233
5882
  const {
@@ -5242,7 +5891,8 @@ function TextAreaEntry(props) {
5242
5891
  monospace,
5243
5892
  disabled,
5244
5893
  onFocus,
5245
- onBlur
5894
+ onBlur,
5895
+ autoResize
5246
5896
  } = props;
5247
5897
  const value = getValue(element);
5248
5898
  const error = useError(id);
@@ -5259,7 +5909,8 @@ function TextAreaEntry(props) {
5259
5909
  rows: rows,
5260
5910
  debounce: debounce,
5261
5911
  monospace: monospace,
5262
- disabled: disabled
5912
+ disabled: disabled,
5913
+ autoResize: autoResize
5263
5914
  }, element), error && jsxRuntime.jsx("div", {
5264
5915
  class: "bio-properties-panel-error",
5265
5916
  children: error
@@ -5330,19 +5981,19 @@ function Textfield(props) {
5330
5981
  });
5331
5982
  }
5332
5983
 
5333
- /**
5334
- * @param {Object} props
5335
- * @param {Object} props.element
5336
- * @param {String} props.id
5337
- * @param {String} props.description
5338
- * @param {Boolean} props.debounce
5339
- * @param {Boolean} props.disabled
5340
- * @param {String} props.label
5341
- * @param {Function} props.getValue
5342
- * @param {Function} props.setValue
5343
- * @param {Function} props.onFocus
5344
- * @param {Function} props.onBlur
5345
- * @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
5346
5997
  */
5347
5998
  function TextfieldEntry(props) {
5348
5999
  const {
@@ -5609,7 +6260,7 @@ function AltTextEntry(props) {
5609
6260
  component: AltText,
5610
6261
  editField: editField,
5611
6262
  field: field,
5612
- isEdited: isEdited$6
6263
+ isEdited: isEdited$7
5613
6264
  });
5614
6265
  }
5615
6266
  return entries;
@@ -5643,59 +6294,70 @@ function AltText(props) {
5643
6294
  });
5644
6295
  }
5645
6296
 
6297
+ const AUTO_OPTION_VALUE = '';
5646
6298
  function ColumnsEntry(props) {
5647
6299
  const {
5648
6300
  editField,
5649
6301
  field
5650
6302
  } = props;
5651
- const {
5652
- type
5653
- } = field;
5654
- const entries = [];
5655
- if (type === 'columns') {
5656
- entries.push({
5657
- id: 'columns',
5658
- component: Columns,
5659
- editField: editField,
5660
- field: field,
5661
- isEdited: isEdited$5
5662
- });
5663
- }
6303
+ const entries = [{
6304
+ id: 'columns',
6305
+ component: Columns,
6306
+ field,
6307
+ editField,
6308
+ isEdited: isEdited$4
6309
+ }];
5664
6310
  return entries;
5665
6311
  }
5666
6312
  function Columns(props) {
5667
6313
  const {
5668
- editField,
5669
6314
  field,
6315
+ editField,
5670
6316
  id
5671
6317
  } = props;
5672
6318
  const debounce = useService('debounce');
5673
- const getValue = () => {
5674
- return field.components.length;
6319
+ const formLayoutValidator = useService('formLayoutValidator');
6320
+ const validate = value => {
6321
+ return formLayoutValidator.validateField(field, value ? parseInt(value) : null);
5675
6322
  };
5676
6323
  const setValue = value => {
5677
- let components = field.components.slice();
5678
- if (value > components.length) {
5679
- while (value > components.length) {
5680
- components.push(formJsViewer.Default.create({
5681
- _parent: field.id
5682
- }));
5683
- }
5684
- } else {
5685
- components = components.slice(0, value);
5686
- }
5687
- 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));
5688
6327
  };
5689
- 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({
5690
6341
  debounce,
5691
6342
  element: field,
5692
- getValue,
5693
6343
  id,
5694
6344
  label: 'Columns',
5695
- setValue
6345
+ getOptions,
6346
+ getValue,
6347
+ setValue,
6348
+ validate
5696
6349
  });
5697
6350
  }
5698
6351
 
6352
+ // helper /////////
6353
+
6354
+ function asOption(number) {
6355
+ return {
6356
+ value: number,
6357
+ label: number.toString()
6358
+ };
6359
+ }
6360
+
5699
6361
  function DescriptionEntry(props) {
5700
6362
  const {
5701
6363
  editField,
@@ -5990,7 +6652,7 @@ function DisabledEntry(props) {
5990
6652
  component: Disabled,
5991
6653
  editField: editField,
5992
6654
  field: field,
5993
- isEdited: isEdited$7
6655
+ isEdited: isEdited$8
5994
6656
  });
5995
6657
  }
5996
6658
  return entries;
@@ -6200,6 +6862,7 @@ function simpleBoolEntryFactory(options) {
6200
6862
  const {
6201
6863
  id,
6202
6864
  label,
6865
+ description,
6203
6866
  path,
6204
6867
  props
6205
6868
  } = options;
@@ -6213,8 +6876,9 @@ function simpleBoolEntryFactory(options) {
6213
6876
  path,
6214
6877
  field,
6215
6878
  editField,
6879
+ description,
6216
6880
  component: SimpleBoolComponent,
6217
- isEdited: isEdited$7
6881
+ isEdited: isEdited$8
6218
6882
  };
6219
6883
  }
6220
6884
  const SimpleBoolComponent = props => {
@@ -6223,7 +6887,8 @@ const SimpleBoolComponent = props => {
6223
6887
  label,
6224
6888
  path,
6225
6889
  field,
6226
- editField
6890
+ editField,
6891
+ description
6227
6892
  } = props;
6228
6893
  const getValue = () => minDash.get(field, path, '');
6229
6894
  const setValue = value => editField(field, path, value);
@@ -6232,7 +6897,8 @@ const SimpleBoolComponent = props => {
6232
6897
  getValue,
6233
6898
  id,
6234
6899
  label,
6235
- setValue
6900
+ setValue,
6901
+ description
6236
6902
  });
6237
6903
  };
6238
6904
 
@@ -6288,7 +6954,7 @@ function SourceEntry(props) {
6288
6954
  component: Source,
6289
6955
  editField: editField,
6290
6956
  field: field,
6291
- isEdited: isEdited$6
6957
+ isEdited: isEdited$7
6292
6958
  });
6293
6959
  }
6294
6960
  return entries;
@@ -6326,21 +6992,38 @@ function Source(props) {
6326
6992
  function TextEntry(props) {
6327
6993
  const {
6328
6994
  editField,
6995
+ /* getService, */
6329
6996
  field
6330
6997
  } = props;
6331
6998
  const {
6332
6999
  type
6333
7000
  } = field;
7001
+
7002
+ // const templating = getService('templating');
7003
+
6334
7004
  if (type !== 'text') {
6335
7005
  return [];
6336
7006
  }
6337
- return [{
7007
+ const entries = [{
6338
7008
  id: 'text',
6339
7009
  component: Text,
6340
7010
  editField: editField,
6341
7011
  field: field,
6342
- isEdited: isEdited$6
7012
+ isEdited: isEdited$7
6343
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;
6344
7027
  }
6345
7028
  function Text(props) {
6346
7029
  const {
@@ -6359,15 +7042,21 @@ function Text(props) {
6359
7042
  const setValue = value => {
6360
7043
  return editField(field, path, value);
6361
7044
  };
6362
- 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({
6363
7053
  debounce,
6364
- description: 'Use an Expression, Markdown or basic HTML to format.',
7054
+ description,
6365
7055
  element: field,
6366
- feel: 'optional',
6367
7056
  getValue,
6368
7057
  id,
6369
7058
  label: 'Text',
6370
- rows: 10,
7059
+ hostLanguage: 'markdown',
6371
7060
  setValue,
6372
7061
  variables
6373
7062
  });
@@ -6483,7 +7172,7 @@ function NumberSerializationEntry(props) {
6483
7172
  entries.push({
6484
7173
  id: 'serialize-to-string',
6485
7174
  component: SerializeToString,
6486
- isEdited: isEdited$7,
7175
+ isEdited: isEdited$8,
6487
7176
  editField,
6488
7177
  field
6489
7178
  });
@@ -6542,7 +7231,7 @@ function DateTimeEntry(props) {
6542
7231
  entries.push({
6543
7232
  id: 'use24h',
6544
7233
  component: Use24h,
6545
- isEdited: isEdited$7,
7234
+ isEdited: isEdited$8,
6546
7235
  editField,
6547
7236
  field
6548
7237
  });
@@ -6655,7 +7344,7 @@ function DateTimeConstraintsEntry(props) {
6655
7344
  entries.push({
6656
7345
  id: id + '-disallowPassedDates',
6657
7346
  component: DisallowPassedDates,
6658
- isEdited: isEdited$7,
7347
+ isEdited: isEdited$8,
6659
7348
  editField,
6660
7349
  field
6661
7350
  });
@@ -7030,11 +7719,19 @@ function InputKeyValuesSourceEntry(props) {
7030
7719
  field,
7031
7720
  id
7032
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
+ });
7033
7730
  return [{
7034
7731
  id: id + '-key',
7035
7732
  component: InputValuesKey,
7036
7733
  label: 'Input values key',
7037
- description: 'Define which input property to populate the values from',
7734
+ description,
7038
7735
  isEdited: isEdited$1,
7039
7736
  editField,
7040
7737
  field
@@ -7229,7 +7926,7 @@ function ConditionEntry(props) {
7229
7926
  component: Condition,
7230
7927
  editField: editField,
7231
7928
  field: field,
7232
- isEdited: isEdited$6
7929
+ isEdited: isEdited$7
7233
7930
  }];
7234
7931
  }
7235
7932
  function Condition(props) {
@@ -7267,7 +7964,7 @@ function Condition(props) {
7267
7964
  });
7268
7965
  }
7269
7966
 
7270
- function GeneralGroup(field, editField) {
7967
+ function GeneralGroup(field, editField, getService) {
7271
7968
  const entries = [...IdEntry({
7272
7969
  field,
7273
7970
  editField
@@ -7286,15 +7983,13 @@ function GeneralGroup(field, editField) {
7286
7983
  }), ...ActionEntry({
7287
7984
  field,
7288
7985
  editField
7289
- }), ...ColumnsEntry({
7290
- field,
7291
- editField
7292
7986
  }), ...DateTimeEntry({
7293
7987
  field,
7294
7988
  editField
7295
7989
  }), ...TextEntry({
7296
7990
  field,
7297
- editField
7991
+ editField,
7992
+ getService
7298
7993
  }), ...NumberEntries({
7299
7994
  field,
7300
7995
  editField
@@ -7390,7 +8085,7 @@ function ValidationGroup(field, editField) {
7390
8085
  component: Required,
7391
8086
  getValue,
7392
8087
  field,
7393
- isEdited: isEdited$7,
8088
+ isEdited: isEdited$8,
7394
8089
  onChange
7395
8090
  }];
7396
8091
  if (type === 'textfield') {
@@ -7732,6 +8427,27 @@ function AppearanceGroup(field, editField) {
7732
8427
  };
7733
8428
  }
7734
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
+
7735
8451
  function ConditionGroup(field, editField) {
7736
8452
  const {
7737
8453
  type
@@ -7750,11 +8466,11 @@ function ConditionGroup(field, editField) {
7750
8466
  };
7751
8467
  }
7752
8468
 
7753
- function getGroups(field, editField) {
8469
+ function getGroups(field, editField, getService) {
7754
8470
  if (!field) {
7755
8471
  return [];
7756
8472
  }
7757
- 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)];
7758
8474
 
7759
8475
  // contract: if a group returns null, it should not be displayed at all
7760
8476
  return groups.filter(group => group !== null);
@@ -7807,10 +8523,9 @@ function FormPropertiesPanel(props) {
7807
8523
  };
7808
8524
  }, []);
7809
8525
  const selectedFormField = state.selectedFormField;
8526
+ const getService = (type, strict = true) => injector.get(type, strict);
7810
8527
  const propertiesPanelContext = {
7811
- getService(type, strict = true) {
7812
- return injector.get(type, strict);
7813
- }
8528
+ getService
7814
8529
  };
7815
8530
  const onFocus = () => eventBus.fire('propertiesPanel.focusin');
7816
8531
  const onBlur = () => eventBus.fire('propertiesPanel.focusout');
@@ -7825,7 +8540,7 @@ function FormPropertiesPanel(props) {
7825
8540
  children: jsxRuntime.jsx(PropertiesPanel, {
7826
8541
  element: selectedFormField,
7827
8542
  eventBus: eventBus,
7828
- groups: getGroups(selectedFormField, editField),
8543
+ groups: getGroups(selectedFormField, editField, getService),
7829
8544
  headerProvider: PropertiesPanelHeaderProvider,
7830
8545
  placeholderProvider: PropertiesPanelPlaceholderProvider
7831
8546
  })
@@ -7903,6 +8618,12 @@ var PropertiesPanelModule = {
7903
8618
  propertiesPanel: ['type', PropertiesPanelRenderer]
7904
8619
  };
7905
8620
 
8621
+ var ExpressionLanguageModule = {
8622
+ __init__: ['expressionLanguage', 'templating'],
8623
+ expressionLanguage: ['type', formJsViewer.FeelExpressionLanguage],
8624
+ templating: ['type', formJsViewer.FeelersTemplating]
8625
+ };
8626
+
7906
8627
  const ids = new Ids([32, 36, 1]);
7907
8628
 
7908
8629
  /**
@@ -8155,7 +8876,7 @@ class FormEditor {
8155
8876
  * @internal
8156
8877
  */
8157
8878
  _getModules() {
8158
- return [ModelingModule, EditorActionsModule, KeyboardModule, SelectionModule, PaletteModule, PropertiesPanelModule];
8879
+ return [ModelingModule, EditorActionsModule, DraggingModule, KeyboardModule, SelectionModule, PaletteModule, ExpressionLanguageModule, formJsViewer.MarkdownModule, PropertiesPanelModule];
8159
8880
  }
8160
8881
 
8161
8882
  /**