@bpmn-io/form-js-editor 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/assets/form-js-editor-base.css +23 -2
  2. package/dist/assets/form-js-editor.css +60 -21
  3. package/dist/assets/properties-panel.css +40 -21
  4. package/dist/index.cjs +1770 -1432
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.es.js +1774 -1436
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/FormEditor.d.ts +29 -29
  9. package/dist/types/core/Debounce.d.ts +2 -3
  10. package/dist/types/core/EventBus.d.ts +1 -1
  11. package/dist/types/core/FormFieldRegistry.d.ts +1 -1
  12. package/dist/types/core/FormLayoutValidator.d.ts +7 -8
  13. package/dist/types/core/FormLayouter.d.ts +1 -1
  14. package/dist/types/core/index.d.ts +8 -9
  15. package/dist/types/features/SectionModuleBase.d.ts +1 -1
  16. package/dist/types/features/dragging/Dragging.d.ts +13 -14
  17. package/dist/types/features/dragging/index.d.ts +2 -3
  18. package/dist/types/features/editor-actions/FormEditorActions.d.ts +2 -3
  19. package/dist/types/features/editor-actions/index.d.ts +2 -3
  20. package/dist/types/features/expression-language/EditorTemplating.d.ts +2 -3
  21. package/dist/types/features/expression-language/index.d.ts +2 -3
  22. package/dist/types/features/keyboard/FormEditorKeyboardBindings.d.ts +2 -3
  23. package/dist/types/features/keyboard/index.d.ts +2 -3
  24. package/dist/types/features/modeling/FormLayoutUpdater.d.ts +2 -3
  25. package/dist/types/features/modeling/Modeling.d.ts +9 -10
  26. package/dist/types/features/modeling/behavior/IdBehavior.d.ts +2 -3
  27. package/dist/types/features/modeling/behavior/KeyBehavior.d.ts +2 -3
  28. package/dist/types/features/modeling/behavior/OptionsSourceBehavior.d.ts +2 -3
  29. package/dist/types/features/modeling/behavior/PathBehavior.d.ts +2 -3
  30. package/dist/types/features/modeling/behavior/ValidateBehavior.d.ts +2 -3
  31. package/dist/types/features/modeling/behavior/index.d.ts +6 -7
  32. package/dist/types/features/modeling/cmd/AddFormFieldHandler.d.ts +7 -8
  33. package/dist/types/features/modeling/cmd/EditFormFieldHandler.d.ts +7 -8
  34. package/dist/types/features/modeling/cmd/MoveFormFieldHandler.d.ts +9 -10
  35. package/dist/types/features/modeling/cmd/RemoveFormFieldHandler.d.ts +7 -8
  36. package/dist/types/features/modeling/cmd/UpdateIdClaimHandler.d.ts +5 -6
  37. package/dist/types/features/modeling/cmd/UpdateKeyClaimHandler.d.ts +3 -4
  38. package/dist/types/features/modeling/cmd/UpdatePathClaimHandler.d.ts +3 -4
  39. package/dist/types/features/modeling/index.d.ts +8 -9
  40. package/dist/types/features/palette/PaletteRenderer.d.ts +7 -0
  41. package/dist/types/features/palette/components/Palette.d.ts +2 -2
  42. package/dist/types/features/palette/components/PaletteEntry.d.ts +1 -1
  43. package/dist/types/features/palette/index.d.ts +3 -4
  44. package/dist/types/features/properties-panel/PropertiesPanel.d.ts +1 -1
  45. package/dist/types/features/properties-panel/PropertiesPanelRenderer.d.ts +4 -5
  46. package/dist/types/features/properties-panel/PropertiesProvider.d.ts +2 -3
  47. package/dist/types/features/properties-panel/Util.d.ts +6 -1
  48. package/dist/types/features/properties-panel/components/AutoFocusSelect.d.ts +1 -1
  49. package/dist/types/features/properties-panel/components/index.d.ts +1 -1
  50. package/dist/types/features/properties-panel/context/FormPropertiesPanelContext.d.ts +2 -2
  51. package/dist/types/features/properties-panel/context/index.d.ts +1 -1
  52. package/dist/types/features/properties-panel/entries/ActionEntry.d.ts +1 -1
  53. package/dist/types/features/properties-panel/entries/AdornerEntry.d.ts +1 -1
  54. package/dist/types/features/properties-panel/entries/AltTextEntry.d.ts +1 -1
  55. package/dist/types/features/properties-panel/entries/ColumnEntry.d.ts +0 -1
  56. package/dist/types/features/properties-panel/entries/ColumnsEntry.d.ts +1 -1
  57. package/dist/types/features/properties-panel/entries/ConditionEntry.d.ts +1 -1
  58. package/dist/types/features/properties-panel/entries/CustomValueEntry.d.ts +1 -1
  59. package/dist/types/features/properties-panel/entries/DateTimeConstraintsEntry.d.ts +1 -1
  60. package/dist/types/features/properties-panel/entries/DateTimeEntry.d.ts +1 -1
  61. package/dist/types/features/properties-panel/entries/{DateTimeSerializationEntry.d.ts → DateTimeFormatEntry.d.ts} +1 -1
  62. package/dist/types/features/properties-panel/entries/DefaultValueEntry.d.ts +1 -1
  63. package/dist/types/features/properties-panel/entries/DescriptionEntry.d.ts +1 -1
  64. package/dist/types/features/properties-panel/entries/DisabledEntry.d.ts +1 -1
  65. package/dist/types/features/properties-panel/entries/GroupAppearanceEntry.d.ts +1 -1
  66. package/dist/types/features/properties-panel/entries/HeightEntry.d.ts +1 -1
  67. package/dist/types/features/properties-panel/entries/HtmlEntry.d.ts +10 -0
  68. package/dist/types/features/properties-panel/entries/IFrameHeightEntry.d.ts +1 -1
  69. package/dist/types/features/properties-panel/entries/IFrameUrlEntry.d.ts +1 -1
  70. package/dist/types/features/properties-panel/entries/IdEntry.d.ts +1 -1
  71. package/dist/types/features/properties-panel/entries/ImageSourceEntry.d.ts +1 -1
  72. package/dist/types/features/properties-panel/entries/InputKeyOptionsSourceEntry.d.ts +1 -1
  73. package/dist/types/features/properties-panel/entries/KeyEntry.d.ts +1 -1
  74. package/dist/types/features/properties-panel/entries/LabelEntry.d.ts +1 -1
  75. package/dist/types/features/properties-panel/entries/LayouterAppearanceEntry.d.ts +1 -1
  76. package/dist/types/features/properties-panel/entries/NumberEntries.d.ts +1 -1
  77. package/dist/types/features/properties-panel/entries/NumberSerializationEntry.d.ts +1 -1
  78. package/dist/types/features/properties-panel/entries/OptionsExpressionEntry.d.ts +1 -1
  79. package/dist/types/features/properties-panel/entries/OptionsSourceSelectEntry.d.ts +1 -1
  80. package/dist/types/features/properties-panel/entries/PathEntry.d.ts +1 -1
  81. package/dist/types/features/properties-panel/entries/ReadonlyEntry.d.ts +1 -1
  82. package/dist/types/features/properties-panel/entries/RepeatableEntry.d.ts +1 -2
  83. package/dist/types/features/properties-panel/entries/SelectEntries.d.ts +1 -1
  84. package/dist/types/features/properties-panel/entries/StaticOptionsSourceEntry.d.ts +1 -1
  85. package/dist/types/features/properties-panel/entries/TextEntry.d.ts +1 -1
  86. package/dist/types/features/properties-panel/entries/ValueEntry.d.ts +1 -1
  87. package/dist/types/features/properties-panel/entries/factories/index.d.ts +4 -4
  88. package/dist/types/features/properties-panel/entries/factories/simpleBoolEntryFactory.d.ts +1 -1
  89. package/dist/types/features/properties-panel/entries/factories/simpleRangeIntegerEntryFactory.d.ts +0 -1
  90. package/dist/types/features/properties-panel/entries/factories/simpleSelectEntryFactory.d.ts +1 -1
  91. package/dist/types/features/properties-panel/entries/factories/simpleStringEntryFactory.d.ts +1 -1
  92. package/dist/types/features/properties-panel/entries/factories/zeroPositiveIntegerEntryFactory.d.ts +1 -1
  93. package/dist/types/features/properties-panel/entries/index.d.ts +34 -33
  94. package/dist/types/features/properties-panel/groups/AppearanceGroup.d.ts +1 -1
  95. package/dist/types/features/properties-panel/groups/ConstraintsGroup.d.ts +1 -1
  96. package/dist/types/features/properties-panel/groups/CustomPropertiesGroup.d.ts +1 -1
  97. package/dist/types/features/properties-panel/groups/GeneralGroup.d.ts +1 -2
  98. package/dist/types/features/properties-panel/groups/LayoutGroup.d.ts +1 -1
  99. package/dist/types/features/properties-panel/groups/OptionsGroups.d.ts +1 -1
  100. package/dist/types/features/properties-panel/groups/SecurityAttributesGroup.d.ts +20 -0
  101. package/dist/types/features/properties-panel/groups/SerializationGroup.d.ts +1 -1
  102. package/dist/types/features/properties-panel/groups/ValidationGroup.d.ts +1 -1
  103. package/dist/types/features/properties-panel/groups/index.d.ts +9 -8
  104. package/dist/types/features/properties-panel/hooks/index.d.ts +1 -1
  105. package/dist/types/features/properties-panel/hooks/usePropertiesPanelService.d.ts +1 -1
  106. package/dist/types/features/properties-panel/index.d.ts +4 -5
  107. package/dist/types/features/render-injection/RenderInjector.d.ts +3 -4
  108. package/dist/types/features/render-injection/components/InjectedRendersRoot.d.ts +1 -2
  109. package/dist/types/features/render-injection/index.d.ts +2 -3
  110. package/dist/types/features/render-injection/slot-fill/Fill.d.ts +1 -2
  111. package/dist/types/features/render-injection/slot-fill/FillContext.d.ts +1 -2
  112. package/dist/types/features/render-injection/slot-fill/Slot.d.ts +1 -2
  113. package/dist/types/features/render-injection/slot-fill/SlotContext.d.ts +1 -2
  114. package/dist/types/features/render-injection/slot-fill/SlotFillRoot.d.ts +1 -2
  115. package/dist/types/features/render-injection/slot-fill/index.d.ts +5 -5
  116. package/dist/types/features/repeat-render/EditorRepeatRenderManager.d.ts +2 -3
  117. package/dist/types/features/repeat-render/index.d.ts +2 -3
  118. package/dist/types/features/selection/Selection.d.ts +2 -3
  119. package/dist/types/features/selection/SelectionBehavior.d.ts +2 -3
  120. package/dist/types/features/selection/index.d.ts +3 -4
  121. package/dist/types/index.d.ts +1 -1
  122. package/dist/types/render/EditorFormFields.d.ts +1 -1
  123. package/dist/types/render/Renderer.d.ts +6 -7
  124. package/dist/types/render/components/FormEditor.d.ts +1 -1
  125. package/dist/types/render/components/ModularSection.d.ts +1 -2
  126. package/dist/types/render/components/editor-form-fields/EditorHtml.d.ts +5 -0
  127. package/dist/types/render/components/editor-form-fields/EditorIFrame.d.ts +2 -3
  128. package/dist/types/render/components/editor-form-fields/EditorTable.d.ts +2 -3
  129. package/dist/types/render/components/editor-form-fields/EditorText.d.ts +2 -3
  130. package/dist/types/render/components/editor-form-fields/index.d.ts +5 -4
  131. package/dist/types/render/context/DragAndDropContext.d.ts +1 -2
  132. package/dist/types/render/context/FormEditorContext.d.ts +2 -2
  133. package/dist/types/render/context/index.d.ts +2 -2
  134. package/dist/types/render/hooks/index.d.ts +3 -3
  135. package/dist/types/render/hooks/useDebounce.d.ts +4 -1
  136. package/dist/types/render/hooks/usePrevious.d.ts +1 -1
  137. package/dist/types/render/hooks/useService.d.ts +1 -1
  138. package/dist/types/render/index.d.ts +3 -4
  139. package/package.json +6 -6
  140. package/dist/types/features/palette/PaletteModule.d.ts +0 -8
package/dist/index.es.js CHANGED
@@ -1,9 +1,9 @@
1
- import { FormFieldRegistry as FormFieldRegistry$1, iconsByType, IFrame, Text as Text$1, Label as Label$3, Table, FormFields, sanitizeImageSource, getAncestryList, FormContext, FormRenderContext, FormComponent, Importer, PathRegistry, FormLayouter, FieldFactory, runRecursively, clone, OPTIONS_SOURCES, OPTIONS_SOURCES_PATHS, getSchemaVariables, DATETIME_SUBTYPES, DATE_LABEL_PATH, TIME_LABEL_PATH, DATETIME_SUBTYPE_PATH, DATETIME_SUBTYPES_LABELS, TIME_SERIALISING_FORMAT_PATH, TIME_SERIALISING_FORMATS, TIME_INTERVAL_PATH, TIME_USE24H_PATH, DATE_DISALLOW_PAST_PATH, TIME_SERIALISINGFORMAT_LABELS, getOptionsSource, OPTIONS_SOURCES_DEFAULTS, OPTIONS_SOURCES_LABELS, FeelExpressionLanguage, createFormContainer, createInjector, MarkdownModule, schemaVersion } from '@bpmn-io/form-js-viewer';
2
- export { schemaVersion } from '@bpmn-io/form-js-viewer';
3
1
  import Ids from 'ids';
4
- import { isArray, isFunction, isNumber, bind, assign, debounce, forEach, get, isObject, isDefined, isString, uniqueBy, sortBy, find, set as set$1, reduce, isUndefined, without, isNil, has } from 'min-dash';
2
+ import { FormFieldRegistry as FormFieldRegistry$1, iconsByType, Label as Label$3, IFrame, Text as Text$1, Html, Table, FormFields, sanitizeImageSource, getAncestryList, FormContext, FormRenderContext, FormComponent, Importer, PathRegistry, FormLayouter, FieldFactory, FeelExpressionLanguage, OPTIONS_SOURCES, OPTIONS_SOURCES_PATHS, clone, runRecursively, getSchemaVariables, DATETIME_SUBTYPES, DATE_LABEL_PATH, TIME_LABEL_PATH, DATETIME_SUBTYPE_PATH, DATETIME_SUBTYPES_LABELS, TIME_SERIALISING_FORMAT_PATH, TIME_SERIALISING_FORMATS, TIME_INTERVAL_PATH, TIME_USE24H_PATH, DATE_DISALLOW_PAST_PATH, TIME_SERIALISINGFORMAT_LABELS, getOptionsSource, OPTIONS_SOURCES_DEFAULTS, OPTIONS_SOURCES_LABELS, SECURITY_ATTRIBUTES_DEFINITIONS, createFormContainer, createInjector, MarkdownRendererModule, schemaVersion } from '@bpmn-io/form-js-viewer';
3
+ export { schemaVersion } from '@bpmn-io/form-js-viewer';
4
+ import { isArray, isFunction, isNumber, bind, assign, debounce, forEach, isString, uniqueBy, isObject, get, isDefined, sortBy, find, set as set$1, reduce, without, isNil, has } from 'min-dash';
5
5
  import classnames from 'classnames';
6
- import { jsx, jsxs, Fragment as Fragment$1 } from 'preact/jsx-runtime';
6
+ import { jsxs, jsx, Fragment as Fragment$1 } from 'preact/jsx-runtime';
7
7
  import { useContext, useRef, useEffect, useMemo, useState, useCallback, useLayoutEffect } from 'preact/hooks';
8
8
  import { createContext, Fragment, render, createElement } from 'preact';
9
9
  import * as React from 'preact/compat';
@@ -13,7 +13,7 @@ import { classes, query, closest, event, matches, domify } from 'min-dom';
13
13
  import { mutate } from 'array-move';
14
14
  import { FeelersEditor } from 'feelers';
15
15
  import FeelEditor from '@bpmn-io/feel-editor';
16
- import { lineNumbers } from '@codemirror/view';
16
+ import { lineNumbers, EditorView } from '@codemirror/view';
17
17
  import * as focusTrap from 'focus-trap';
18
18
  import Big from 'big.js';
19
19
 
@@ -584,8 +584,8 @@ class FormLayoutValidator {
584
584
  /**
585
585
  * @constructor
586
586
  *
587
- * @param { import('./FormLayouter').default } formLayouter
588
- * @param { import('./FormFieldRegistry').default } formFieldRegistry
587
+ * @param { import('./FormLayouter').FormLayouter } formLayouter
588
+ * @param { import('./FormFieldRegistry').FormFieldRegistry } formFieldRegistry
589
589
  */
590
590
  constructor(formLayouter, formFieldRegistry) {
591
591
  this._formLayouter = formLayouter;
@@ -747,13 +747,21 @@ function createEmptyImage() {
747
747
 
748
748
  function EditorIFrame(props) {
749
749
  const {
750
- field
750
+ field,
751
+ domId
751
752
  } = props;
753
+ const {
754
+ label
755
+ } = field;
752
756
  const Icon = iconsByType(field.type);
753
- return jsx("div", {
757
+ return jsxs("div", {
754
758
  class: editorFormFieldClasses(field.type),
755
- children: jsx("div", {
759
+ children: [jsx(Label$3, {
760
+ id: domId,
761
+ label: label
762
+ }), jsx("div", {
756
763
  class: "fjs-iframe-placeholder",
764
+ id: domId,
757
765
  children: jsxs("p", {
758
766
  class: "fjs-iframe-placeholder-text",
759
767
  children: [jsx(Icon, {
@@ -762,7 +770,7 @@ function EditorIFrame(props) {
762
770
  viewBox: "0 0 56 56"
763
771
  }), "iFrame"]
764
772
  })
765
- })
773
+ })]
766
774
  });
767
775
  }
768
776
  EditorIFrame.config = IFrame.config;
@@ -770,7 +778,6 @@ EditorIFrame.config = IFrame.config;
770
778
  const DragAndDropContext = createContext({
771
779
  drake: null
772
780
  });
773
- var DragAndDropContext$1 = DragAndDropContext;
774
781
 
775
782
  /**
776
783
  * @param {string} type
@@ -782,26 +789,28 @@ function getService$1(type, strict) {}
782
789
  const FormEditorContext = createContext({
783
790
  getService: getService$1
784
791
  });
785
- var FormEditorContext$1 = FormEditorContext;
786
792
 
787
- function useService$1 (type, strict) {
793
+ function useService$1(type, strict) {
788
794
  const {
789
795
  getService
790
- } = useContext(FormEditorContext$1);
796
+ } = useContext(FormEditorContext);
791
797
  return getService(type, strict);
792
798
  }
793
799
 
794
- function usePrevious$1(value) {
795
- const ref = useRef();
796
- useEffect(() => ref.current = value);
800
+ function usePrevious$1(value, defaultValue = null) {
801
+ const ref = useRef(defaultValue);
802
+ useEffect(() => ref.current = value, [value]);
797
803
  return ref.current;
798
804
  }
799
805
 
800
- function useDebounce(fn, dependencies = []) {
806
+ /**
807
+ * @param {Function} fn - function to debounce
808
+ */
809
+ function useDebounce(fn) {
801
810
  const debounce = useService$1('debounce');
802
811
  const callback = useMemo(() => {
803
812
  return debounce(fn);
804
- }, dependencies);
813
+ }, [debounce, fn]);
805
814
 
806
815
  // cleanup async side-effect if callback #flush is provided.
807
816
  useEffect(() => {
@@ -1001,6 +1010,54 @@ function EditorText(props) {
1001
1010
  }
1002
1011
  EditorText.config = Text$1.config;
1003
1012
 
1013
+ function EditorHtml(props) {
1014
+ const {
1015
+ type,
1016
+ content = ''
1017
+ } = props.field;
1018
+ const Icon = iconsByType(type);
1019
+ const templating = useService$1('templating');
1020
+ const expressionLanguage = useService$1('expressionLanguage');
1021
+ if (!content || !content.trim()) {
1022
+ return jsx("div", {
1023
+ class: editorFormFieldClasses(type),
1024
+ children: jsxs("div", {
1025
+ class: "fjs-form-field-placeholder",
1026
+ children: [jsx(Icon, {
1027
+ viewBox: "0 0 54 54"
1028
+ }), "Html is empty"]
1029
+ })
1030
+ });
1031
+ }
1032
+ if (expressionLanguage.isExpression(content)) {
1033
+ return jsx("div", {
1034
+ class: editorFormFieldClasses(type),
1035
+ children: jsxs("div", {
1036
+ class: "fjs-form-field-placeholder",
1037
+ children: [jsx(Icon, {
1038
+ viewBox: "0 0 54 54"
1039
+ }), "Html is populated by an expression"]
1040
+ })
1041
+ });
1042
+ }
1043
+ if (templating.isTemplate(content)) {
1044
+ return jsx("div", {
1045
+ class: editorFormFieldClasses(type),
1046
+ children: jsxs("div", {
1047
+ class: "fjs-form-field-placeholder",
1048
+ children: [jsx(Icon, {
1049
+ viewBox: "0 0 54 54"
1050
+ }), "Html is templated"]
1051
+ })
1052
+ });
1053
+ }
1054
+ return jsx(Html, {
1055
+ ...props,
1056
+ disableLinks: true
1057
+ });
1058
+ }
1059
+ EditorHtml.config = Html.config;
1060
+
1004
1061
  function EditorTable(props) {
1005
1062
  const {
1006
1063
  columnsExpression,
@@ -1065,7 +1122,7 @@ function EditorTable(props) {
1065
1122
  }
1066
1123
  EditorTable.config = Table.config;
1067
1124
 
1068
- const editorFormFields = [EditorIFrame, EditorText, EditorTable];
1125
+ const editorFormFields = [EditorIFrame, EditorText, EditorHtml, EditorTable];
1069
1126
 
1070
1127
  class EditorFormFields extends FormFields {
1071
1128
  constructor() {
@@ -1076,7 +1133,7 @@ class EditorFormFields extends FormFields {
1076
1133
  }
1077
1134
  }
1078
1135
 
1079
- var ModularSection = (props => {
1136
+ const ModularSection = props => {
1080
1137
  const {
1081
1138
  rootClass,
1082
1139
  RootElement,
@@ -1156,7 +1213,7 @@ var ModularSection = (props => {
1156
1213
  }), ParentElement) : jsx(Root, {
1157
1214
  children: children
1158
1215
  }) : null;
1159
- });
1216
+ };
1160
1217
 
1161
1218
  const FillContext = createContext({
1162
1219
  addFill(uid, props) {
@@ -1166,11 +1223,10 @@ const FillContext = createContext({
1166
1223
  throw new Error('FillContext.addFill() uninitialized');
1167
1224
  }
1168
1225
  });
1169
- var FillContext$1 = FillContext;
1170
1226
 
1171
- var Fill = (props => {
1227
+ const Fill = props => {
1172
1228
  const uid = useRef$1(Symbol('fill_uid'));
1173
- const fillContext = useContext$1(FillContext$1);
1229
+ const fillContext = useContext$1(FillContext);
1174
1230
  useEffect$1(() => {
1175
1231
  if (!fillContext) {
1176
1232
  return;
@@ -1184,14 +1240,13 @@ var Fill = (props => {
1184
1240
  };
1185
1241
  }, [fillContext, props]);
1186
1242
  return null;
1187
- });
1243
+ };
1188
1244
 
1189
1245
  const SlotContext = createContext({
1190
1246
  fills: []
1191
1247
  });
1192
- var SlotContext$1 = SlotContext;
1193
1248
 
1194
- var Slot = (props => {
1249
+ const Slot = props => {
1195
1250
  const {
1196
1251
  name,
1197
1252
  fillRoot = FillFragment,
@@ -1201,7 +1256,7 @@ var Slot = (props => {
1201
1256
  } = props;
1202
1257
  const {
1203
1258
  fills
1204
- } = useContext(SlotContext$1);
1259
+ } = useContext(SlotContext);
1205
1260
  const filtered = useMemo(() => fills.filter(fill => fill.slot === name), [fills, name]);
1206
1261
  const cropped = useMemo(() => limit ? filtered.slice(0, limit) : filtered, [filtered, limit]);
1207
1262
  const groups = useMemo(() => groupFn(cropped), [cropped, groupFn]);
@@ -1209,7 +1264,7 @@ var Slot = (props => {
1209
1264
  return buildFills(groups, fillRoot, separatorFn);
1210
1265
  }, [groups, fillRoot, separatorFn]);
1211
1266
  return fillsAndSeparators;
1212
- });
1267
+ };
1213
1268
 
1214
1269
  /**
1215
1270
  * Creates a Fragment for a fill.
@@ -1273,30 +1328,35 @@ const _comparePriority = (a, b) => {
1273
1328
  return (b.priority || 0) - (a.priority || 0);
1274
1329
  };
1275
1330
 
1276
- var SlotFillRoot = (props => {
1331
+ const noop = () => {};
1332
+ const SlotFillRoot = props => {
1277
1333
  const [fills, setFills] = useState([]);
1334
+ const {
1335
+ onSetFill = noop,
1336
+ onRemoveFill = noop
1337
+ } = props;
1278
1338
  const fillContext = useMemo(() => ({
1279
1339
  addFill: fill => {
1280
1340
  setFills(fills => [...fills.filter(f => f.id !== fill.id), fill]);
1281
- props.onSetFill && props.onSetFill(fill);
1341
+ onSetFill(fill);
1282
1342
  },
1283
1343
  removeFill: id => {
1284
1344
  setFills(fills => fills.filter(f => f.id !== id));
1285
- props.onRemoveFill && props.onRemoveFill(id);
1345
+ onRemoveFill(id);
1286
1346
  }
1287
- }), []);
1347
+ }), [onRemoveFill, onSetFill]);
1288
1348
  const slotContext = useMemo(() => ({
1289
1349
  fills
1290
1350
  }), [fills]);
1291
- return jsx(SlotContext$1.Provider, {
1351
+ return jsx(SlotContext.Provider, {
1292
1352
  value: slotContext,
1293
- children: jsx(FillContext$1.Provider, {
1353
+ children: jsx(FillContext.Provider, {
1294
1354
  /* @ts-expect-error */
1295
1355
  value: fillContext,
1296
1356
  children: props.children
1297
1357
  })
1298
1358
  });
1299
- });
1359
+ };
1300
1360
 
1301
1361
  function PaletteEntry(props) {
1302
1362
  const {
@@ -1539,7 +1599,7 @@ function getPaletteIcon(entry) {
1539
1599
  return Icon;
1540
1600
  }
1541
1601
 
1542
- var InjectedRendersRoot = (() => {
1602
+ const InjectedRendersRoot = () => {
1543
1603
  const renderInjector = useService$1('renderInjector');
1544
1604
  const injectedRenderers = renderInjector.fetchRenderers();
1545
1605
  const injectedProps = useMemo(() => ({
@@ -1556,7 +1616,7 @@ var InjectedRendersRoot = (() => {
1556
1616
  ...injectedProps
1557
1617
  }))
1558
1618
  });
1559
- });
1619
+ };
1560
1620
 
1561
1621
  const CURSOR_CLS_PATTERN = /^fjs-cursor-.*$/;
1562
1622
  function set(mode) {
@@ -1588,11 +1648,11 @@ class Dragging {
1588
1648
  /**
1589
1649
  * @constructor
1590
1650
  *
1591
- * @param { import('../../core/FormFieldRegistry').default } formFieldRegistry
1592
- * @param { import('../../core/FormLayouter').default } formLayouter
1593
- * @param { import('../../core/FormLayoutValidator').default } formLayoutValidator
1594
- * @param { import('../../core/EventBus').default } eventBus
1595
- * @param { import('../modeling/Modeling').default } modeling
1651
+ * @param { import('../../core/FormFieldRegistry').FormFieldRegistry } formFieldRegistry
1652
+ * @param { import('../../core/FormLayouter').FormLayouter } formLayouter
1653
+ * @param { import('../../core/FormLayoutValidator').FormLayoutValidator } formLayoutValidator
1654
+ * @param { import('../../core/EventBus').EventBus } eventBus
1655
+ * @param { import('../modeling/Modeling').Modeling } modeling
1596
1656
  * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
1597
1657
  */
1598
1658
  constructor(formFieldRegistry, formLayouter, formLayoutValidator, eventBus, modeling, pathRegistry) {
@@ -2042,37 +2102,36 @@ function ContextPad(props) {
2042
2102
  children: props.children
2043
2103
  });
2044
2104
  }
2105
+ function EmptyGroup() {
2106
+ return jsx("div", {
2107
+ class: "fjs-empty-component",
2108
+ children: jsx("span", {
2109
+ class: "fjs-empty-component-text",
2110
+ children: "Drag and drop components here."
2111
+ })
2112
+ });
2113
+ }
2114
+ function EmptyForm() {
2115
+ return jsx("div", {
2116
+ class: "fjs-empty-editor",
2117
+ children: jsxs("div", {
2118
+ class: "fjs-empty-editor-card",
2119
+ children: [jsx(EmptyFormIcon, {}), jsx("h2", {
2120
+ children: "Build your form"
2121
+ }), jsx("span", {
2122
+ children: "Drag and drop components here to start designing."
2123
+ }), jsx("span", {
2124
+ children: "Use the preview window to test your form."
2125
+ })]
2126
+ })
2127
+ });
2128
+ }
2045
2129
  function Empty(props) {
2046
- if (props.field.type === 'default') {
2047
- return jsx("div", {
2048
- class: "fjs-empty-editor",
2049
- children: jsxs("div", {
2050
- class: "fjs-empty-editor-card",
2051
- children: [jsx(EmptyFormIcon, {}), jsx("h2", {
2052
- children: "Build your form"
2053
- }), jsx("span", {
2054
- children: "Drag and drop components here to start designing."
2055
- }), jsx("span", {
2056
- children: "Use the preview window to test your form."
2057
- })]
2058
- })
2059
- });
2060
- }
2061
- if (props.field.type === 'group') {
2062
- return jsx("div", {
2063
- class: "fjs-empty-component",
2064
- children: jsx("span", {
2065
- children: "Drag and drop components here."
2066
- })
2067
- });
2130
+ if (['group', 'dynamiclist'].includes(props.field.type)) {
2131
+ return jsx(EmptyGroup, {});
2068
2132
  }
2069
- if (props.field.type === 'dynamiclist') {
2070
- return jsx("div", {
2071
- class: "fjs-empty-component",
2072
- children: jsxs("span", {
2073
- children: ["Drag and drop components here ", jsx("br", {}), " to create a repeatable list item."]
2074
- })
2075
- });
2133
+ if (props.field.type === 'default') {
2134
+ return jsx(EmptyForm, {});
2076
2135
  }
2077
2136
  return null;
2078
2137
  }
@@ -2096,22 +2155,22 @@ function Element$1(props) {
2096
2155
  } = field;
2097
2156
  const ref = useRef();
2098
2157
  const [hovered, setHovered] = useState(false);
2099
- function scrollIntoView({
2100
- selection
2101
- }) {
2102
- if (!selection || selection.id !== id || !ref.current) {
2103
- return;
2104
- }
2105
- const elementBounds = ref.current.getBoundingClientRect(),
2106
- containerBounds = formEditor._container.getBoundingClientRect();
2107
- if (elementBounds.top < 0 || elementBounds.top > containerBounds.bottom) {
2108
- ref.current.scrollIntoView();
2109
- }
2110
- }
2111
2158
  useEffect(() => {
2159
+ function scrollIntoView({
2160
+ selection
2161
+ }) {
2162
+ if (!selection || selection.id !== id || !ref.current) {
2163
+ return;
2164
+ }
2165
+ const elementBounds = ref.current.getBoundingClientRect(),
2166
+ containerBounds = formEditor._container.getBoundingClientRect();
2167
+ if (elementBounds.top < 0 || elementBounds.top > containerBounds.bottom) {
2168
+ ref.current.scrollIntoView();
2169
+ }
2170
+ }
2112
2171
  eventBus.on('selection.changed', scrollIntoView);
2113
2172
  return () => eventBus.off('selection.changed', scrollIntoView);
2114
- }, [id]);
2173
+ }, [eventBus, formEditor._container, id]);
2115
2174
  useLayoutEffect(() => {
2116
2175
  if (selection.isSelected(field)) {
2117
2176
  ref.current.focus();
@@ -2259,7 +2318,7 @@ function Column(props) {
2259
2318
  children: props.children
2260
2319
  });
2261
2320
  }
2262
- function FormEditor$1(props) {
2321
+ function FormEditor$1() {
2263
2322
  const dragging = useService$1('dragging'),
2264
2323
  eventBus = useService$1('eventBus'),
2265
2324
  formEditor = useService$1('formEditor'),
@@ -2277,16 +2336,19 @@ function FormEditor$1(props) {
2277
2336
  const formContainerRef = useRef(null);
2278
2337
  const propertiesPanelRef = useRef(null);
2279
2338
  const [, setSelection] = useState(schema);
2339
+ const [hasInitialized, setHasInitialized] = useState(false);
2280
2340
  useEffect(() => {
2281
2341
  function handleSelectionChanged(event) {
2282
2342
  setSelection(event.selection || schema);
2283
2343
  }
2284
2344
  eventBus.on('selection.changed', handleSelectionChanged);
2285
- setSelection(selection.get() || schema);
2286
2345
  return () => {
2287
2346
  eventBus.off('selection.changed', handleSelectionChanged);
2288
2347
  };
2289
- }, [schema, selection]);
2348
+ }, [eventBus, schema]);
2349
+ useEffect(() => {
2350
+ setSelection(selection.get() || schema);
2351
+ }, [selection, schema]);
2290
2352
  const [drake, setDrake] = useState(null);
2291
2353
  const dragAndDropContext = {
2292
2354
  drake
@@ -2337,11 +2399,15 @@ function FormEditor$1(props) {
2337
2399
 
2338
2400
  // fire event after render to notify interested parties
2339
2401
  useEffect(() => {
2402
+ if (hasInitialized) {
2403
+ return;
2404
+ }
2405
+ setHasInitialized(true);
2340
2406
  eventBus.fire('rendered');
2341
2407
 
2342
2408
  // keep deprecated event to ensure backward compatibility
2343
2409
  eventBus.fire('formEditor.rendered');
2344
- }, []);
2410
+ }, [eventBus, hasInitialized]);
2345
2411
  const formRenderContext = useMemo(() => ({
2346
2412
  Children,
2347
2413
  Column,
@@ -2385,7 +2451,7 @@ function FormEditor$1(props) {
2385
2451
  return jsx("div", {
2386
2452
  class: "fjs-form-editor",
2387
2453
  children: jsxs(SlotFillRoot, {
2388
- children: [jsxs(DragAndDropContext$1.Provider, {
2454
+ children: [jsxs(DragAndDropContext.Provider, {
2389
2455
  value: dragAndDropContext,
2390
2456
  children: [jsx(ModularSection, {
2391
2457
  rootClass: "fjs-palette-container",
@@ -2431,56 +2497,56 @@ function getFormFieldIndex(parent, formField) {
2431
2497
  function CreatePreview(props) {
2432
2498
  const {
2433
2499
  drake
2434
- } = useContext(DragAndDropContext$1);
2500
+ } = useContext(DragAndDropContext);
2435
2501
  const formFields = useService$1('formFields');
2436
- function handleCloned(clone, original, type) {
2437
- const fieldType = clone.dataset.fieldType;
2502
+ useEffect(() => {
2503
+ if (!drake) {
2504
+ return;
2505
+ }
2506
+ function handleCloned(clone, original, type) {
2507
+ const fieldType = clone.dataset.fieldType;
2438
2508
 
2439
- // (1) field preview
2440
- if (fieldType) {
2441
- const paletteEntry = findPaletteEntry(fieldType, formFields);
2442
- if (!paletteEntry) {
2443
- return;
2444
- }
2445
- const {
2446
- label
2447
- } = paletteEntry;
2448
- const Icon = getPaletteIcon(paletteEntry);
2449
- clone.innerHTML = '';
2450
- clone.class = 'gu-mirror';
2451
- clone.classList.add('fjs-field-preview-container');
2452
- if (original.classList.contains('fjs-palette-field')) {
2453
- // default to auto columns when creating from palette
2454
- clone.classList.add('cds--col');
2455
- }
2509
+ // (1) field preview
2510
+ if (fieldType) {
2511
+ const paletteEntry = findPaletteEntry(fieldType, formFields);
2512
+ if (!paletteEntry) {
2513
+ return;
2514
+ }
2515
+ const {
2516
+ label
2517
+ } = paletteEntry;
2518
+ const Icon = getPaletteIcon(paletteEntry);
2519
+ clone.innerHTML = '';
2520
+ clone.class = 'gu-mirror';
2521
+ clone.classList.add('fjs-field-preview-container');
2522
+ if (original.classList.contains('fjs-palette-field')) {
2523
+ // default to auto columns when creating from palette
2524
+ clone.classList.add('cds--col');
2525
+ }
2456
2526
 
2457
- // todo(pinussilvestrus): dragula, how to mitigate cursor position
2458
- // https://github.com/bevacqua/dragula/issues/285
2459
- render(jsx(FieldDragPreview, {
2460
- label: label,
2461
- Icon: Icon
2462
- }), clone);
2463
- } else {
2464
- // (2) row preview
2527
+ // todo(pinussilvestrus): dragula, how to mitigate cursor position
2528
+ // https://github.com/bevacqua/dragula/issues/285
2529
+ render(jsx(FieldDragPreview, {
2530
+ label: label,
2531
+ Icon: Icon
2532
+ }), clone);
2533
+ } else {
2534
+ // (2) row preview
2465
2535
 
2466
- // remove elements from copy (context pad, row dragger, ...)
2467
- ['fjs-context-pad', 'fjs-row-dragger', 'fjs-debug-columns'].forEach(cls => {
2468
- const cloneNode = clone.querySelectorAll('.' + cls);
2469
- cloneNode.length && cloneNode.forEach(e => e.remove());
2470
- });
2536
+ // remove elements from copy (context pad, row dragger, ...)
2537
+ ['fjs-context-pad', 'fjs-row-dragger', 'fjs-debug-columns'].forEach(cls => {
2538
+ const cloneNode = clone.querySelectorAll('.' + cls);
2539
+ cloneNode.length && cloneNode.forEach(e => e.remove());
2540
+ });
2471
2541
 
2472
- // mirror grid
2473
- clone.classList.add('cds--grid');
2474
- clone.classList.add('cds--grid--condensed');
2475
- }
2476
- }
2477
- useEffect(() => {
2478
- if (!drake) {
2479
- return;
2542
+ // mirror grid
2543
+ clone.classList.add('cds--grid');
2544
+ clone.classList.add('cds--grid--condensed');
2545
+ }
2480
2546
  }
2481
2547
  drake.on('cloned', handleCloned);
2482
2548
  return () => drake.off('cloned', handleCloned);
2483
- }, [drake]);
2549
+ }, [drake, formFields]);
2484
2550
  return null;
2485
2551
  }
2486
2552
 
@@ -2524,7 +2590,7 @@ class Renderer {
2524
2590
  }
2525
2591
  return jsx("div", {
2526
2592
  class: `fjs-container fjs-editor-container ${compact ? 'fjs-editor-compact' : ''}`,
2527
- children: jsx(FormEditorContext$1.Provider, {
2593
+ children: jsx(FormEditorContext.Provider, {
2528
2594
  value: formEditorContext,
2529
2595
  children: jsx(FormEditor$1, {})
2530
2596
  })
@@ -2540,14 +2606,14 @@ class Renderer {
2540
2606
  }
2541
2607
  Renderer.$inject = ['config.renderer', 'eventBus', 'formEditor', 'injector'];
2542
2608
 
2543
- var renderModule = {
2609
+ const RenderModule = {
2544
2610
  __init__: ['formFields', 'renderer'],
2545
2611
  formFields: ['type', EditorFormFields],
2546
2612
  renderer: ['type', Renderer]
2547
2613
  };
2548
2614
 
2549
- var core = {
2550
- __depends__: [renderModule],
2615
+ const CoreModule = {
2616
+ __depends__: [RenderModule],
2551
2617
  debounce: ['factory', DebounceFactory],
2552
2618
  eventBus: ['type', EventBus],
2553
2619
  importer: ['type', Importer],
@@ -2789,7 +2855,7 @@ function error(action, message) {
2789
2855
  /**
2790
2856
  * @type { import('didi').ModuleDeclaration }
2791
2857
  */
2792
- var EditorActionsModule$1 = {
2858
+ var BaseEditorActionsModule = {
2793
2859
  __init__: ['editorActions'],
2794
2860
  editorActions: ['type', EditorActions]
2795
2861
  };
@@ -2838,14 +2904,28 @@ class FormEditorActions extends EditorActions {
2838
2904
  }
2839
2905
  FormEditorActions.$inject = ['eventBus', 'injector'];
2840
2906
 
2841
- var EditorActionsModule = {
2842
- __depends__: [EditorActionsModule$1],
2907
+ const EditorActionsModule = {
2908
+ __depends__: [BaseEditorActionsModule],
2843
2909
  editorActions: ['type', FormEditorActions]
2844
2910
  };
2845
2911
 
2846
- var DraggingModule = {
2847
- __init__: ['dragging'],
2848
- dragging: ['type', Dragging]
2912
+ class EditorTemplating {
2913
+ // same rules as viewer templating
2914
+ isTemplate(value) {
2915
+ return isString(value) && (value.startsWith('=') || /{{/.test(value));
2916
+ }
2917
+
2918
+ // return the template raw, as we usually just want to display that
2919
+ evaluate(template) {
2920
+ return template;
2921
+ }
2922
+ }
2923
+ EditorTemplating.$inject = [];
2924
+
2925
+ const EditorExpressionLanguageModule = {
2926
+ __init__: ['expressionLanguage', 'templating'],
2927
+ expressionLanguage: ['type', FeelExpressionLanguage],
2928
+ templating: ['type', EditorTemplating]
2849
2929
  };
2850
2930
 
2851
2931
  var KEYS_COPY = ['c', 'C'];
@@ -3237,7 +3317,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3237
3317
  /**
3238
3318
  * @type { import('didi').ModuleDeclaration }
3239
3319
  */
3240
- var KeyboardModule$1 = {
3320
+ var KeyboardModule = {
3241
3321
  __init__: ['keyboard', 'keyboardBindings'],
3242
3322
  keyboard: ['type', Keyboard],
3243
3323
  keyboardBindings: ['type', KeyboardBindings]
@@ -3288,640 +3368,487 @@ class FormEditorKeyboardBindings {
3288
3368
  }
3289
3369
  FormEditorKeyboardBindings.$inject = ['eventBus', 'keyboard'];
3290
3370
 
3291
- var KeyboardModule = {
3292
- __depends__: [KeyboardModule$1],
3371
+ const FormEditorKeyboardModule = {
3372
+ __depends__: [KeyboardModule],
3293
3373
  __init__: ['keyboardBindings'],
3294
3374
  keyboardBindings: ['type', FormEditorKeyboardBindings]
3295
3375
  };
3296
3376
 
3297
- function arrayAdd$1(array, index, item) {
3298
- array.splice(index, 0, item);
3299
- return array;
3300
- }
3301
- function arrayRemove(array, index) {
3302
- array.splice(index, 1);
3303
- return array;
3304
- }
3305
- function updatePath(formFieldRegistry, formField, index) {
3306
- const parent = formFieldRegistry.get(formField._parent);
3307
- refreshPathsRecursively(formField, [...parent._path, 'components', index]);
3308
- return formField;
3309
- }
3310
- function refreshPathsRecursively(formField, path) {
3311
- formField._path = path;
3312
- const components = formField.components || [];
3313
- components.forEach((component, index) => {
3314
- refreshPathsRecursively(component, [...path, 'components', index]);
3315
- });
3316
- }
3317
- function updateRow(formField, rowId) {
3318
- formField.layout = {
3319
- ...(formField.layout || {}),
3320
- row: rowId
3321
- };
3322
- return formField;
3323
- }
3324
-
3325
- class AddFormFieldHandler {
3326
- /**
3327
- * @constructor
3328
- * @param { import('../../../FormEditor').default } formEditor
3329
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3330
- */
3331
- constructor(formEditor, formFieldRegistry) {
3332
- this._formEditor = formEditor;
3333
- this._formFieldRegistry = formFieldRegistry;
3334
- }
3335
- execute(context) {
3336
- const {
3337
- formField,
3338
- targetFormField,
3339
- targetIndex
3340
- } = context;
3341
- const {
3342
- schema
3343
- } = this._formEditor._getState();
3344
- const targetPath = [...targetFormField._path, 'components'];
3345
- formField._parent = targetFormField.id;
3346
-
3347
- // (1) Add new form field
3348
- arrayAdd$1(get(schema, targetPath), targetIndex, formField);
3349
-
3350
- // (2) Update internal paths of new form field and its siblings (and their children)
3351
- get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3352
-
3353
- // (3) Add new form field to form field registry
3354
- this._formFieldRegistry.add(formField);
3355
-
3356
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3357
- this._formEditor._setState({
3358
- schema
3359
- });
3360
- }
3361
- revert(context) {
3362
- const {
3363
- formField,
3364
- targetFormField,
3365
- targetIndex
3366
- } = context;
3367
- const {
3368
- schema
3369
- } = this._formEditor._getState();
3370
- const targetPath = [...targetFormField._path, 'components'];
3377
+ const DraggingModule = {
3378
+ __init__: ['dragging'],
3379
+ dragging: ['type', Dragging]
3380
+ };
3371
3381
 
3372
- // (1) Remove new form field
3373
- arrayRemove(get(schema, targetPath), targetIndex);
3382
+ /**
3383
+ * @typedef {import('didi').Injector} Injector
3384
+ *
3385
+ * @typedef {import('../core/Types').ElementLike} ElementLike
3386
+ *
3387
+ * @typedef {import('../core/EventBus').default} EventBus
3388
+ * @typedef {import('./CommandHandler').default} CommandHandler
3389
+ *
3390
+ * @typedef { any } CommandContext
3391
+ * @typedef { {
3392
+ * new (...args: any[]) : CommandHandler
3393
+ * } } CommandHandlerConstructor
3394
+ * @typedef { {
3395
+ * [key: string]: CommandHandler;
3396
+ * } } CommandHandlerMap
3397
+ * @typedef { {
3398
+ * command: string;
3399
+ * context: any;
3400
+ * id?: any;
3401
+ * } } CommandStackAction
3402
+ * @typedef { {
3403
+ * actions: CommandStackAction[];
3404
+ * dirty: ElementLike[];
3405
+ * trigger: 'execute' | 'undo' | 'redo' | 'clear' | null;
3406
+ * atomic?: boolean;
3407
+ * } } CurrentExecution
3408
+ */
3374
3409
 
3375
- // (2) Update internal paths of new form field and its siblings (and their children)
3376
- get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3410
+ /**
3411
+ * A service that offers un- and redoable execution of commands.
3412
+ *
3413
+ * The command stack is responsible for executing modeling actions
3414
+ * in a un- and redoable manner. To do this it delegates the actual
3415
+ * command execution to {@link CommandHandler}s.
3416
+ *
3417
+ * Command handlers provide {@link CommandHandler#execute(ctx)} and
3418
+ * {@link CommandHandler#revert(ctx)} methods to un- and redo a command
3419
+ * identified by a command context.
3420
+ *
3421
+ *
3422
+ * ## Life-Cycle events
3423
+ *
3424
+ * In the process the command stack fires a number of life-cycle events
3425
+ * that other components to participate in the command execution.
3426
+ *
3427
+ * * preExecute
3428
+ * * preExecuted
3429
+ * * execute
3430
+ * * executed
3431
+ * * postExecute
3432
+ * * postExecuted
3433
+ * * revert
3434
+ * * reverted
3435
+ *
3436
+ * A special event is used for validating, whether a command can be
3437
+ * performed prior to its execution.
3438
+ *
3439
+ * * canExecute
3440
+ *
3441
+ * Each of the events is fired as `commandStack.{eventName}` and
3442
+ * `commandStack.{commandName}.{eventName}`, respectively. This gives
3443
+ * components fine grained control on where to hook into.
3444
+ *
3445
+ * The event object fired transports `command`, the name of the
3446
+ * command and `context`, the command context.
3447
+ *
3448
+ *
3449
+ * ## Creating Command Handlers
3450
+ *
3451
+ * Command handlers should provide the {@link CommandHandler#execute(ctx)}
3452
+ * and {@link CommandHandler#revert(ctx)} methods to implement
3453
+ * redoing and undoing of a command.
3454
+ *
3455
+ * A command handler _must_ ensure undo is performed properly in order
3456
+ * not to break the undo chain. It must also return the shapes that
3457
+ * got changed during the `execute` and `revert` operations.
3458
+ *
3459
+ * Command handlers may execute other modeling operations (and thus
3460
+ * commands) in their `preExecute(d)` and `postExecute(d)` phases. The command
3461
+ * stack will properly group all commands together into a logical unit
3462
+ * that may be re- and undone atomically.
3463
+ *
3464
+ * Command handlers must not execute other commands from within their
3465
+ * core implementation (`execute`, `revert`).
3466
+ *
3467
+ *
3468
+ * ## Change Tracking
3469
+ *
3470
+ * During the execution of the CommandStack it will keep track of all
3471
+ * elements that have been touched during the command's execution.
3472
+ *
3473
+ * At the end of the CommandStack execution it will notify interested
3474
+ * components via an 'elements.changed' event with all the dirty
3475
+ * elements.
3476
+ *
3477
+ * The event can be picked up by components that are interested in the fact
3478
+ * that elements have been changed. One use case for this is updating
3479
+ * their graphical representation after moving / resizing or deletion.
3480
+ *
3481
+ * @see CommandHandler
3482
+ *
3483
+ * @param {EventBus} eventBus
3484
+ * @param {Injector} injector
3485
+ */
3486
+ function CommandStack(eventBus, injector) {
3487
+ /**
3488
+ * A map of all registered command handlers.
3489
+ *
3490
+ * @type {CommandHandlerMap}
3491
+ */
3492
+ this._handlerMap = {};
3377
3493
 
3378
- // (3) Remove new form field from form field registry
3379
- this._formFieldRegistry.remove(formField);
3494
+ /**
3495
+ * A stack containing all re/undoable actions on the diagram
3496
+ *
3497
+ * @type {CommandStackAction[]}
3498
+ */
3499
+ this._stack = [];
3380
3500
 
3381
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3382
- this._formEditor._setState({
3383
- schema
3384
- });
3385
- }
3386
- }
3387
- AddFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
3501
+ /**
3502
+ * The current index on the stack
3503
+ *
3504
+ * @type {number}
3505
+ */
3506
+ this._stackIdx = -1;
3388
3507
 
3389
- class EditFormFieldHandler {
3390
3508
  /**
3391
- * @constructor
3392
- * @param { import('../../../FormEditor').default } formEditor
3393
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3509
+ * Current active commandStack execution
3510
+ *
3511
+ * @type {CurrentExecution}
3394
3512
  */
3395
- constructor(formEditor, formFieldRegistry) {
3396
- this._formEditor = formEditor;
3397
- this._formFieldRegistry = formFieldRegistry;
3398
- }
3399
- execute(context) {
3400
- const {
3401
- formField,
3402
- properties
3403
- } = context;
3404
- let {
3405
- schema
3406
- } = this._formEditor._getState();
3407
- const oldProperties = {};
3408
- for (let key in properties) {
3409
- oldProperties[key] = formField[key];
3410
- const property = properties[key];
3411
- if (key === 'id') {
3412
- if (property !== formField.id) {
3413
- this._formFieldRegistry.updateId(formField, property);
3414
- }
3415
- } else {
3416
- formField[key] = property;
3417
- }
3418
- }
3419
- context.oldProperties = oldProperties;
3513
+ this._currentExecution = {
3514
+ actions: [],
3515
+ dirty: [],
3516
+ trigger: null
3517
+ };
3420
3518
 
3421
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3422
- this._formEditor._setState({
3423
- schema
3424
- });
3425
- return formField;
3426
- }
3427
- revert(context) {
3428
- const {
3429
- formField,
3430
- oldProperties
3431
- } = context;
3432
- let {
3433
- schema
3434
- } = this._formEditor._getState();
3435
- for (let key in oldProperties) {
3436
- const property = oldProperties[key];
3437
- if (key === 'id') {
3438
- if (property !== formField.id) {
3439
- this._formFieldRegistry.updateId(formField, property);
3440
- }
3441
- } else {
3442
- formField[key] = property;
3443
- }
3444
- }
3519
+ /**
3520
+ * @type {Injector}
3521
+ */
3522
+ this._injector = injector;
3445
3523
 
3446
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3447
- this._formEditor._setState({
3448
- schema
3449
- });
3450
- return formField;
3451
- }
3452
- }
3453
- EditFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
3524
+ /**
3525
+ * @type EventBus
3526
+ */
3527
+ this._eventBus = eventBus;
3454
3528
 
3455
- class MoveFormFieldHandler {
3456
3529
  /**
3457
- * @constructor
3458
- * @param { import('../../../FormEditor').default } formEditor
3459
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3460
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3461
- * @param { import('@bpmn-io/form-js-viewer').FormLayouter } formLayouter
3530
+ * @type { number }
3462
3531
  */
3463
- constructor(formEditor, formFieldRegistry, pathRegistry, formLayouter) {
3464
- this._formEditor = formEditor;
3465
- this._formFieldRegistry = formFieldRegistry;
3466
- this._pathRegistry = pathRegistry;
3467
- this._formLayouter = formLayouter;
3532
+ this._uid = 1;
3533
+ eventBus.on(['diagram.destroy', 'diagram.clear'], function () {
3534
+ this.clear(false);
3535
+ }, this);
3536
+ }
3537
+ CommandStack.$inject = ['eventBus', 'injector'];
3538
+
3539
+ /**
3540
+ * Execute a command.
3541
+ *
3542
+ * @param {string} command The command to execute.
3543
+ * @param {CommandContext} context The context with which to execute the command.
3544
+ */
3545
+ CommandStack.prototype.execute = function (command, context) {
3546
+ if (!command) {
3547
+ throw new Error('command required');
3468
3548
  }
3469
- execute(context) {
3470
- this.moveFormField(context);
3549
+ this._currentExecution.trigger = 'execute';
3550
+ const action = {
3551
+ command: command,
3552
+ context: context
3553
+ };
3554
+ this._pushAction(action);
3555
+ this._internalExecute(action);
3556
+ this._popAction();
3557
+ };
3558
+
3559
+ /**
3560
+ * Check whether a command can be executed.
3561
+ *
3562
+ * Implementors may hook into the mechanism on two ways:
3563
+ *
3564
+ * * in event listeners:
3565
+ *
3566
+ * Users may prevent the execution via an event listener.
3567
+ * It must prevent the default action for `commandStack.(<command>.)canExecute` events.
3568
+ *
3569
+ * * in command handlers:
3570
+ *
3571
+ * If the method {@link CommandHandler#canExecute} is implemented in a handler
3572
+ * it will be called to figure out whether the execution is allowed.
3573
+ *
3574
+ * @param {string} command The command to execute.
3575
+ * @param {CommandContext} context The context with which to execute the command.
3576
+ *
3577
+ * @return {boolean} Whether the command can be executed with the given context.
3578
+ */
3579
+ CommandStack.prototype.canExecute = function (command, context) {
3580
+ const action = {
3581
+ command: command,
3582
+ context: context
3583
+ };
3584
+ const handler = this._getHandler(command);
3585
+ let result = this._fire(command, 'canExecute', action);
3586
+
3587
+ // handler#canExecute will only be called if no listener
3588
+ // decided on a result already
3589
+ if (result === undefined) {
3590
+ if (!handler) {
3591
+ return false;
3592
+ }
3593
+ if (handler.canExecute) {
3594
+ result = handler.canExecute(context);
3595
+ }
3471
3596
  }
3472
- revert(context) {
3473
- let {
3474
- sourceFormField,
3475
- targetFormField,
3476
- sourceIndex,
3477
- targetIndex,
3478
- sourceRow,
3479
- targetRow
3480
- } = context;
3481
- this.moveFormField({
3482
- sourceFormField: targetFormField,
3483
- targetFormField: sourceFormField,
3484
- sourceIndex: targetIndex,
3485
- targetIndex: sourceIndex,
3486
- sourceRow: targetRow,
3487
- targetRow: sourceRow
3488
- }, true);
3597
+ return result;
3598
+ };
3599
+
3600
+ /**
3601
+ * Clear the command stack, erasing all undo / redo history.
3602
+ *
3603
+ * @param {boolean} [emit=true] Whether to fire an event. Defaults to `true`.
3604
+ */
3605
+ CommandStack.prototype.clear = function (emit) {
3606
+ this._stack.length = 0;
3607
+ this._stackIdx = -1;
3608
+ if (emit !== false) {
3609
+ this._fire('changed', {
3610
+ trigger: 'clear'
3611
+ });
3489
3612
  }
3490
- moveFormField(context, revert) {
3491
- let {
3492
- sourceFormField,
3493
- targetFormField,
3494
- sourceIndex,
3495
- targetIndex,
3496
- targetRow
3497
- } = context;
3498
- let {
3499
- schema
3500
- } = this._formEditor._getState();
3501
- const sourcePath = [...sourceFormField._path, 'components'];
3502
- if (sourceFormField.id === targetFormField.id) {
3503
- if (revert) {
3504
- if (sourceIndex > targetIndex) {
3505
- sourceIndex--;
3506
- }
3507
- } else {
3508
- if (sourceIndex < targetIndex) {
3509
- targetIndex--;
3510
- }
3613
+ };
3614
+
3615
+ /**
3616
+ * Undo last command(s)
3617
+ */
3618
+ CommandStack.prototype.undo = function () {
3619
+ let action = this._getUndoAction(),
3620
+ next;
3621
+ if (action) {
3622
+ this._currentExecution.trigger = 'undo';
3623
+ this._pushAction(action);
3624
+ while (action) {
3625
+ this._internalUndo(action);
3626
+ next = this._getUndoAction();
3627
+ if (!next || next.id !== action.id) {
3628
+ break;
3511
3629
  }
3512
- const formField = get(schema, [...sourcePath, sourceIndex]);
3630
+ action = next;
3631
+ }
3632
+ this._popAction();
3633
+ }
3634
+ };
3513
3635
 
3514
- // (1) Add to row or create new one
3515
- updateRow(formField, targetRow ? targetRow.id : this._formLayouter.nextRowId());
3636
+ /**
3637
+ * Redo last command(s)
3638
+ */
3639
+ CommandStack.prototype.redo = function () {
3640
+ let action = this._getRedoAction(),
3641
+ next;
3642
+ if (action) {
3643
+ this._currentExecution.trigger = 'redo';
3644
+ this._pushAction(action);
3645
+ while (action) {
3646
+ this._internalExecute(action, true);
3647
+ next = this._getRedoAction();
3648
+ if (!next || next.id !== action.id) {
3649
+ break;
3650
+ }
3651
+ action = next;
3652
+ }
3653
+ this._popAction();
3654
+ }
3655
+ };
3516
3656
 
3517
- // (2) Move form field
3518
- mutate(get(schema, sourcePath), sourceIndex, targetIndex);
3657
+ /**
3658
+ * Register a handler instance with the command stack.
3659
+ *
3660
+ * @param {string} command Command to be executed.
3661
+ * @param {CommandHandler} handler Handler to execute the command.
3662
+ */
3663
+ CommandStack.prototype.register = function (command, handler) {
3664
+ this._setHandler(command, handler);
3665
+ };
3519
3666
 
3520
- // (3) Update internal paths of new form field and its siblings (and their children)
3521
- get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3522
- } else {
3523
- const formField = get(schema, [...sourcePath, sourceIndex]);
3667
+ /**
3668
+ * Register a handler type with the command stack by instantiating it and
3669
+ * injecting its dependencies.
3670
+ *
3671
+ * @param {string} command Command to be executed.
3672
+ * @param {CommandHandlerConstructor} handlerCls Constructor to instantiate a {@link CommandHandler}.
3673
+ */
3674
+ CommandStack.prototype.registerHandler = function (command, handlerCls) {
3675
+ if (!command || !handlerCls) {
3676
+ throw new Error('command and handlerCls must be defined');
3677
+ }
3678
+ const handler = this._injector.instantiate(handlerCls);
3679
+ this.register(command, handler);
3680
+ };
3524
3681
 
3525
- // (1) Deregister form field (and children) from path registry
3526
- this._pathRegistry.executeRecursivelyOnFields(formField, ({
3527
- field
3528
- }) => {
3529
- this._pathRegistry.unclaimPath(this._pathRegistry.getValuePath(field));
3530
- });
3531
- formField._parent = targetFormField.id;
3682
+ /**
3683
+ * @return {boolean}
3684
+ */
3685
+ CommandStack.prototype.canUndo = function () {
3686
+ return !!this._getUndoAction();
3687
+ };
3532
3688
 
3533
- // (2) Remove form field
3534
- arrayRemove(get(schema, sourcePath), sourceIndex);
3689
+ /**
3690
+ * @return {boolean}
3691
+ */
3692
+ CommandStack.prototype.canRedo = function () {
3693
+ return !!this._getRedoAction();
3694
+ };
3535
3695
 
3536
- // (3) Update internal paths of siblings (and their children)
3537
- get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3538
- const targetPath = [...targetFormField._path, 'components'];
3696
+ // stack access //////////////////////
3539
3697
 
3540
- // (4) Add to row or create new one
3541
- updateRow(formField, targetRow ? targetRow.id : this._formLayouter.nextRowId());
3698
+ CommandStack.prototype._getRedoAction = function () {
3699
+ return this._stack[this._stackIdx + 1];
3700
+ };
3701
+ CommandStack.prototype._getUndoAction = function () {
3702
+ return this._stack[this._stackIdx];
3703
+ };
3542
3704
 
3543
- // (5) Add form field
3544
- arrayAdd$1(get(schema, targetPath), targetIndex, formField);
3705
+ // internal functionality //////////////////////
3545
3706
 
3546
- // (6) Update internal paths of siblings (and their children)
3547
- get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3707
+ CommandStack.prototype._internalUndo = function (action) {
3708
+ const command = action.command,
3709
+ context = action.context;
3710
+ const handler = this._getHandler(command);
3548
3711
 
3549
- // (7) Reregister form field (and children) from path registry
3550
- this._pathRegistry.executeRecursivelyOnFields(formField, ({
3551
- field,
3552
- isClosed,
3553
- isRepeatable
3554
- }) => {
3555
- this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), {
3556
- isClosed,
3557
- isRepeatable,
3558
- claimerId: field.id
3559
- });
3560
- });
3712
+ // guard against illegal nested command stack invocations
3713
+ this._atomicDo(() => {
3714
+ this._fire(command, 'revert', action);
3715
+ if (handler.revert) {
3716
+ this._markDirty(handler.revert(context));
3561
3717
  }
3562
-
3563
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3564
- this._formEditor._setState({
3565
- schema
3566
- });
3718
+ this._revertedAction(action);
3719
+ this._fire(command, 'reverted', action);
3720
+ });
3721
+ };
3722
+ CommandStack.prototype._fire = function (command, qualifier, event) {
3723
+ if (arguments.length < 3) {
3724
+ event = qualifier;
3725
+ qualifier = null;
3567
3726
  }
3568
- }
3569
- MoveFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry', 'pathRegistry', 'formLayouter'];
3570
-
3571
- class RemoveFormFieldHandler {
3572
- /**
3573
- * @constructor
3574
- * @param { import('../../../FormEditor').default } formEditor
3575
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3576
- */
3577
- constructor(formEditor, formFieldRegistry) {
3578
- this._formEditor = formEditor;
3579
- this._formFieldRegistry = formFieldRegistry;
3727
+ const names = qualifier ? [command + '.' + qualifier, qualifier] : [command];
3728
+ let result;
3729
+ event = this._eventBus.createEvent(event);
3730
+ for (const name of names) {
3731
+ result = this._eventBus.fire('commandStack.' + name, event);
3732
+ if (event.cancelBubble) {
3733
+ break;
3734
+ }
3580
3735
  }
3581
- execute(context) {
3582
- const {
3583
- sourceFormField,
3584
- sourceIndex
3585
- } = context;
3586
- let {
3587
- schema
3588
- } = this._formEditor._getState();
3589
- const sourcePath = [...sourceFormField._path, 'components'];
3590
- const formField = context.formField = get(schema, [...sourcePath, sourceIndex]);
3591
-
3592
- // (1) Remove form field
3593
- arrayRemove(get(schema, sourcePath), sourceIndex);
3594
-
3595
- // (2) Update internal paths of its siblings (and their children)
3596
- get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3597
-
3598
- // (3) Remove form field and children from form field registry
3599
- runRecursively(formField, formField => this._formFieldRegistry.remove(formField));
3600
-
3601
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3602
- this._formEditor._setState({
3603
- schema
3604
- });
3736
+ return result;
3737
+ };
3738
+ CommandStack.prototype._createId = function () {
3739
+ return this._uid++;
3740
+ };
3741
+ CommandStack.prototype._atomicDo = function (fn) {
3742
+ const execution = this._currentExecution;
3743
+ execution.atomic = true;
3744
+ try {
3745
+ fn();
3746
+ } finally {
3747
+ execution.atomic = false;
3605
3748
  }
3606
- revert(context) {
3607
- const {
3608
- formField,
3609
- sourceFormField,
3610
- sourceIndex
3611
- } = context;
3612
- let {
3613
- schema
3614
- } = this._formEditor._getState();
3615
- const sourcePath = [...sourceFormField._path, 'components'];
3616
-
3617
- // (1) Add form field
3618
- arrayAdd$1(get(schema, sourcePath), sourceIndex, formField);
3619
-
3620
- // (2) Update internal paths of its siblings (and their children)
3621
- get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
3622
-
3623
- // (3) Add form field and children to form field registry
3624
- runRecursively(formField, formField => this._formFieldRegistry.add(formField));
3625
-
3626
- // TODO: Create updater/change support that automatically updates paths and schema on command execution
3627
- this._formEditor._setState({
3628
- schema
3629
- });
3630
- }
3631
- }
3632
- RemoveFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
3633
-
3634
- class UpdateIdClaimHandler {
3635
- /**
3636
- * @constructor
3637
- * @param { import('../../../core/FormFieldRegistry').default } formFieldRegistry
3638
- */
3639
- constructor(formFieldRegistry) {
3640
- this._formFieldRegistry = formFieldRegistry;
3641
- }
3642
- execute(context) {
3643
- const {
3644
- claiming,
3645
- formField,
3646
- id
3647
- } = context;
3648
- if (claiming) {
3649
- this._formFieldRegistry._ids.claim(id, formField);
3650
- } else {
3651
- this._formFieldRegistry._ids.unclaim(id);
3652
- }
3749
+ };
3750
+ CommandStack.prototype._internalExecute = function (action, redo) {
3751
+ const command = action.command,
3752
+ context = action.context;
3753
+ const handler = this._getHandler(command);
3754
+ if (!handler) {
3755
+ throw new Error('no command handler registered for <' + command + '>');
3653
3756
  }
3654
- revert(context) {
3655
- const {
3656
- claiming,
3657
- formField,
3658
- id
3659
- } = context;
3660
- if (claiming) {
3661
- this._formFieldRegistry._ids.unclaim(id);
3662
- } else {
3663
- this._formFieldRegistry._ids.claim(id, formField);
3757
+ this._pushAction(action);
3758
+ if (!redo) {
3759
+ this._fire(command, 'preExecute', action);
3760
+ if (handler.preExecute) {
3761
+ handler.preExecute(context);
3664
3762
  }
3763
+ this._fire(command, 'preExecuted', action);
3665
3764
  }
3666
- }
3667
- UpdateIdClaimHandler.$inject = ['formFieldRegistry'];
3668
3765
 
3669
- class UpdateKeyClaimHandler {
3670
- /**
3671
- * @constructor
3672
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3673
- */
3674
- constructor(pathRegistry) {
3675
- this._pathRegistry = pathRegistry;
3676
- }
3677
- execute(context) {
3678
- const {
3679
- claiming,
3680
- formField,
3681
- key
3682
- } = context;
3683
- const options = {
3684
- replacements: {
3685
- [formField.id]: key
3686
- }
3687
- };
3688
- const valuePath = this._pathRegistry.getValuePath(formField, options);
3689
- if (claiming) {
3690
- this._pathRegistry.claimPath(valuePath, {
3691
- isClosed: true,
3692
- claimerId: formField.id
3693
- });
3694
- } else {
3695
- this._pathRegistry.unclaimPath(valuePath);
3766
+ // guard against illegal nested command stack invocations
3767
+ this._atomicDo(() => {
3768
+ this._fire(command, 'execute', action);
3769
+ if (handler.execute) {
3770
+ // actual execute + mark return results as dirty
3771
+ this._markDirty(handler.execute(context));
3696
3772
  }
3697
3773
 
3698
- // cache path for revert
3699
- context.valuePath = valuePath;
3700
- }
3701
- revert(context) {
3702
- const {
3703
- claiming,
3704
- formField,
3705
- valuePath
3706
- } = context;
3707
- if (claiming) {
3708
- this._pathRegistry.unclaimPath(valuePath);
3709
- } else {
3710
- this._pathRegistry.claimPath(valuePath, {
3711
- isClosed: true,
3712
- claimerId: formField.id
3713
- });
3774
+ // log to stack
3775
+ this._executedAction(action, redo);
3776
+ this._fire(command, 'executed', action);
3777
+ });
3778
+ if (!redo) {
3779
+ this._fire(command, 'postExecute', action);
3780
+ if (handler.postExecute) {
3781
+ handler.postExecute(context);
3714
3782
  }
3783
+ this._fire(command, 'postExecuted', action);
3715
3784
  }
3716
- }
3717
- UpdateKeyClaimHandler.$inject = ['pathRegistry'];
3718
-
3719
- class UpdatePathClaimHandler {
3720
- /**
3721
- * @constructor
3722
- * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
3723
- */
3724
- constructor(pathRegistry) {
3725
- this._pathRegistry = pathRegistry;
3726
- }
3727
- execute(context) {
3728
- const {
3729
- claiming,
3730
- formField,
3731
- path
3732
- } = context;
3733
- const options = {
3734
- replacements: {
3735
- [formField.id]: path
3736
- }
3737
- };
3738
- const valuePaths = [];
3739
- if (claiming) {
3740
- this._pathRegistry.executeRecursivelyOnFields(formField, ({
3741
- field,
3742
- isClosed,
3743
- isRepeatable
3744
- }) => {
3745
- const valuePath = this._pathRegistry.getValuePath(field, options);
3746
- valuePaths.push({
3747
- valuePath,
3748
- isClosed,
3749
- isRepeatable,
3750
- claimerId: field.id
3751
- });
3752
- this._pathRegistry.claimPath(valuePath, {
3753
- isClosed,
3754
- isRepeatable,
3755
- claimerId: field.id
3756
- });
3757
- });
3758
- } else {
3759
- this._pathRegistry.executeRecursivelyOnFields(formField, ({
3760
- field,
3761
- isClosed,
3762
- isRepeatable
3763
- }) => {
3764
- const valuePath = this._pathRegistry.getValuePath(field, options);
3765
- valuePaths.push({
3766
- valuePath,
3767
- isClosed,
3768
- isRepeatable,
3769
- claimerId: field.id
3770
- });
3771
- this._pathRegistry.unclaimPath(valuePath);
3772
- });
3773
- }
3774
-
3775
- // cache path info for revert
3776
- context.valuePaths = valuePaths;
3785
+ this._popAction();
3786
+ };
3787
+ CommandStack.prototype._pushAction = function (action) {
3788
+ const execution = this._currentExecution,
3789
+ actions = execution.actions;
3790
+ const baseAction = actions[0];
3791
+ if (execution.atomic) {
3792
+ throw new Error('illegal invocation in <execute> or <revert> phase (action: ' + action.command + ')');
3777
3793
  }
3778
- revert(context) {
3779
- const {
3780
- claiming,
3781
- valuePaths
3782
- } = context;
3783
- if (claiming) {
3784
- valuePaths.forEach(({
3785
- valuePath
3786
- }) => {
3787
- this._pathRegistry.unclaimPath(valuePath);
3788
- });
3789
- } else {
3790
- valuePaths.forEach(({
3791
- valuePath,
3792
- isClosed,
3793
- isRepeatable,
3794
- claimerId
3795
- }) => {
3796
- this._pathRegistry.claimPath(valuePath, {
3797
- isClosed,
3798
- isRepeatable,
3799
- claimerId
3800
- });
3801
- });
3802
- }
3794
+ if (!action.id) {
3795
+ action.id = baseAction && baseAction.id || this._createId();
3803
3796
  }
3804
- }
3805
- UpdatePathClaimHandler.$inject = ['pathRegistry'];
3806
-
3807
- class Modeling {
3808
- constructor(commandStack, eventBus, formEditor, formFieldRegistry, fieldFactory) {
3809
- this._commandStack = commandStack;
3810
- this._formEditor = formEditor;
3811
- this._formFieldRegistry = formFieldRegistry;
3812
- this._fieldFactory = fieldFactory;
3813
- eventBus.on('form.init', () => {
3814
- this.registerHandlers();
3797
+ actions.push(action);
3798
+ };
3799
+ CommandStack.prototype._popAction = function () {
3800
+ const execution = this._currentExecution,
3801
+ trigger = execution.trigger,
3802
+ actions = execution.actions,
3803
+ dirty = execution.dirty;
3804
+ actions.pop();
3805
+ if (!actions.length) {
3806
+ this._eventBus.fire('elements.changed', {
3807
+ elements: uniqueBy('id', dirty.reverse())
3815
3808
  });
3816
- }
3817
- registerHandlers() {
3818
- Object.entries(this.getHandlers()).forEach(([id, handler]) => {
3819
- this._commandStack.registerHandler(id, handler);
3809
+ dirty.length = 0;
3810
+ this._fire('changed', {
3811
+ trigger: trigger
3820
3812
  });
3813
+ execution.trigger = null;
3821
3814
  }
3822
- getHandlers() {
3823
- return {
3824
- 'formField.add': AddFormFieldHandler,
3825
- 'formField.edit': EditFormFieldHandler,
3826
- 'formField.move': MoveFormFieldHandler,
3827
- 'formField.remove': RemoveFormFieldHandler,
3828
- 'id.updateClaim': UpdateIdClaimHandler,
3829
- 'key.updateClaim': UpdateKeyClaimHandler,
3830
- 'path.updateClaim': UpdatePathClaimHandler
3831
- };
3832
- }
3833
- addFormField(attrs, targetFormField, targetIndex) {
3834
- const formField = this._fieldFactory.create(attrs);
3835
- const context = {
3836
- formField,
3837
- targetFormField,
3838
- targetIndex
3839
- };
3840
- this._commandStack.execute('formField.add', context);
3841
- return formField;
3842
- }
3843
- editFormField(formField, properties, value) {
3844
- if (!isObject(properties)) {
3845
- properties = {
3846
- [properties]: value
3847
- };
3848
- }
3849
- const context = {
3850
- formField,
3851
- properties
3852
- };
3853
- this._commandStack.execute('formField.edit', context);
3854
- }
3855
- moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex, sourceRow, targetRow) {
3856
- const context = {
3857
- formField,
3858
- sourceFormField,
3859
- targetFormField,
3860
- sourceIndex,
3861
- targetIndex,
3862
- sourceRow,
3863
- targetRow
3864
- };
3865
- this._commandStack.execute('formField.move', context);
3866
- }
3867
- removeFormField(formField, sourceFormField, sourceIndex) {
3868
- const context = {
3869
- formField,
3870
- sourceFormField,
3871
- sourceIndex
3872
- };
3873
- this._commandStack.execute('formField.remove', context);
3874
- }
3875
- claimId(formField, id) {
3876
- const context = {
3877
- formField,
3878
- id,
3879
- claiming: true
3880
- };
3881
- this._commandStack.execute('id.updateClaim', context);
3882
- }
3883
- unclaimId(formField, id) {
3884
- const context = {
3885
- formField,
3886
- id,
3887
- claiming: false
3888
- };
3889
- this._commandStack.execute('id.updateClaim', context);
3890
- }
3891
- claimKey(formField, key) {
3892
- const context = {
3893
- formField,
3894
- key,
3895
- claiming: true
3896
- };
3897
- this._commandStack.execute('key.updateClaim', context);
3815
+ };
3816
+ CommandStack.prototype._markDirty = function (elements) {
3817
+ const execution = this._currentExecution;
3818
+ if (!elements) {
3819
+ return;
3898
3820
  }
3899
- unclaimKey(formField, key) {
3900
- const context = {
3901
- formField,
3902
- key,
3903
- claiming: false
3904
- };
3905
- this._commandStack.execute('key.updateClaim', context);
3821
+ elements = isArray(elements) ? elements : [elements];
3822
+ execution.dirty = execution.dirty.concat(elements);
3823
+ };
3824
+ CommandStack.prototype._executedAction = function (action, redo) {
3825
+ const stackIdx = ++this._stackIdx;
3826
+ if (!redo) {
3827
+ this._stack.splice(stackIdx, this._stack.length, action);
3906
3828
  }
3907
- claimPath(formField, path) {
3908
- const context = {
3909
- formField,
3910
- path,
3911
- claiming: true
3912
- };
3913
- this._commandStack.execute('path.updateClaim', context);
3829
+ };
3830
+ CommandStack.prototype._revertedAction = function (action) {
3831
+ this._stackIdx--;
3832
+ };
3833
+ CommandStack.prototype._getHandler = function (command) {
3834
+ return this._handlerMap[command];
3835
+ };
3836
+ CommandStack.prototype._setHandler = function (command, handler) {
3837
+ if (!command || !handler) {
3838
+ throw new Error('command and handler required');
3914
3839
  }
3915
- unclaimPath(formField, path) {
3916
- const context = {
3917
- formField,
3918
- path,
3919
- claiming: false
3920
- };
3921
- this._commandStack.execute('path.updateClaim', context);
3840
+ if (this._handlerMap[command]) {
3841
+ throw new Error('overriding handler for command <' + command + '>');
3922
3842
  }
3923
- }
3924
- Modeling.$inject = ['commandStack', 'eventBus', 'formEditor', 'formFieldRegistry', 'fieldFactory'];
3843
+ this._handlerMap[command] = handler;
3844
+ };
3845
+
3846
+ /**
3847
+ * @type { import('didi').ModuleDeclaration }
3848
+ */
3849
+ var commandModule = {
3850
+ commandStack: ['type', CommandStack]
3851
+ };
3925
3852
 
3926
3853
  /**
3927
3854
  * @typedef {import('../core/Types').ElementLike} ElementLike
@@ -4151,53 +4078,6 @@ function createHook(hook) {
4151
4078
  return hookFn;
4152
4079
  }
4153
4080
 
4154
- class FormLayoutUpdater extends CommandInterceptor {
4155
- constructor(eventBus, formLayouter, modeling, formEditor) {
4156
- super(eventBus);
4157
- this._eventBus = eventBus;
4158
- this._formLayouter = formLayouter;
4159
- this._modeling = modeling;
4160
- this._formEditor = formEditor;
4161
-
4162
- // @ts-ignore
4163
- this.preExecute(['formField.add', 'formField.remove', 'formField.move', 'id.updateClaim'], event => this.updateRowIds(event));
4164
-
4165
- // we need that as the state got updates
4166
- // on the next tick (not in post execute)
4167
- eventBus.on('changed', context => {
4168
- const {
4169
- schema
4170
- } = context;
4171
- this.updateLayout(schema);
4172
- });
4173
- }
4174
- updateLayout(schema) {
4175
- this._formLayouter.clear();
4176
- this._formLayouter.calculateLayout(clone(schema));
4177
- }
4178
- updateRowIds(event) {
4179
- const {
4180
- schema
4181
- } = this._formEditor._getState();
4182
- const setRowIds = parent => {
4183
- if (!parent.components || !parent.components.length) {
4184
- return;
4185
- }
4186
- parent.components.forEach(formField => {
4187
- const row = this._formLayouter.getRowForField(formField);
4188
- updateRow(formField, row.id);
4189
-
4190
- // handle children recursively
4191
- setRowIds(formField);
4192
- });
4193
- };
4194
-
4195
- // make sure rows are persisted in schema (e.g. for migration case)
4196
- setRowIds(schema);
4197
- }
4198
- }
4199
- FormLayoutUpdater.$inject = ['eventBus', 'formLayouter', 'modeling', 'formEditor'];
4200
-
4201
4081
  class IdBehavior extends CommandInterceptor {
4202
4082
  constructor(eventBus, modeling) {
4203
4083
  super(eventBus);
@@ -4438,7 +4318,7 @@ class TableDataSourceBehavior extends CommandInterceptor {
4438
4318
  }
4439
4319
  TableDataSourceBehavior.$inject = ['eventBus'];
4440
4320
 
4441
- var behaviorModule = {
4321
+ const BehaviorModule = {
4442
4322
  __init__: ['idBehavior', 'keyBehavior', 'pathBehavior', 'validateBehavior', 'optionsSourceBehavior', 'columnsSourceBehavior', 'tableDataSourceBehavior'],
4443
4323
  idBehavior: ['type', IdBehavior],
4444
4324
  keyBehavior: ['type', KeyBehavior],
@@ -4449,479 +4329,684 @@ var behaviorModule = {
4449
4329
  tableDataSourceBehavior: ['type', TableDataSourceBehavior]
4450
4330
  };
4451
4331
 
4452
- /**
4453
- * @typedef {import('didi').Injector} Injector
4454
- *
4455
- * @typedef {import('../core/Types').ElementLike} ElementLike
4456
- *
4457
- * @typedef {import('../core/EventBus').default} EventBus
4458
- * @typedef {import('./CommandHandler').default} CommandHandler
4459
- *
4460
- * @typedef { any } CommandContext
4461
- * @typedef { {
4462
- * new (...args: any[]) : CommandHandler
4463
- * } } CommandHandlerConstructor
4464
- * @typedef { {
4465
- * [key: string]: CommandHandler;
4466
- * } } CommandHandlerMap
4467
- * @typedef { {
4468
- * command: string;
4469
- * context: any;
4470
- * id?: any;
4471
- * } } CommandStackAction
4472
- * @typedef { {
4473
- * actions: CommandStackAction[];
4474
- * dirty: ElementLike[];
4475
- * trigger: 'execute' | 'undo' | 'redo' | 'clear' | null;
4476
- * atomic?: boolean;
4477
- * } } CurrentExecution
4478
- */
4332
+ function arrayAdd$1(array, index, item) {
4333
+ array.splice(index, 0, item);
4334
+ return array;
4335
+ }
4336
+ function arrayRemove(array, index) {
4337
+ array.splice(index, 1);
4338
+ return array;
4339
+ }
4340
+ function updatePath(formFieldRegistry, formField, index) {
4341
+ const parent = formFieldRegistry.get(formField._parent);
4342
+ refreshPathsRecursively(formField, [...parent._path, 'components', index]);
4343
+ return formField;
4344
+ }
4345
+ function refreshPathsRecursively(formField, path) {
4346
+ formField._path = path;
4347
+ const components = formField.components || [];
4348
+ components.forEach((component, index) => {
4349
+ refreshPathsRecursively(component, [...path, 'components', index]);
4350
+ });
4351
+ }
4352
+ function updateRow(formField, rowId) {
4353
+ formField.layout = {
4354
+ ...(formField.layout || {}),
4355
+ row: rowId
4356
+ };
4357
+ return formField;
4358
+ }
4479
4359
 
4480
- /**
4481
- * A service that offers un- and redoable execution of commands.
4482
- *
4483
- * The command stack is responsible for executing modeling actions
4484
- * in a un- and redoable manner. To do this it delegates the actual
4485
- * command execution to {@link CommandHandler}s.
4486
- *
4487
- * Command handlers provide {@link CommandHandler#execute(ctx)} and
4488
- * {@link CommandHandler#revert(ctx)} methods to un- and redo a command
4489
- * identified by a command context.
4490
- *
4491
- *
4492
- * ## Life-Cycle events
4493
- *
4494
- * In the process the command stack fires a number of life-cycle events
4495
- * that other components to participate in the command execution.
4496
- *
4497
- * * preExecute
4498
- * * preExecuted
4499
- * * execute
4500
- * * executed
4501
- * * postExecute
4502
- * * postExecuted
4503
- * * revert
4504
- * * reverted
4505
- *
4506
- * A special event is used for validating, whether a command can be
4507
- * performed prior to its execution.
4508
- *
4509
- * * canExecute
4510
- *
4511
- * Each of the events is fired as `commandStack.{eventName}` and
4512
- * `commandStack.{commandName}.{eventName}`, respectively. This gives
4513
- * components fine grained control on where to hook into.
4514
- *
4515
- * The event object fired transports `command`, the name of the
4516
- * command and `context`, the command context.
4517
- *
4518
- *
4519
- * ## Creating Command Handlers
4520
- *
4521
- * Command handlers should provide the {@link CommandHandler#execute(ctx)}
4522
- * and {@link CommandHandler#revert(ctx)} methods to implement
4523
- * redoing and undoing of a command.
4524
- *
4525
- * A command handler _must_ ensure undo is performed properly in order
4526
- * not to break the undo chain. It must also return the shapes that
4527
- * got changed during the `execute` and `revert` operations.
4528
- *
4529
- * Command handlers may execute other modeling operations (and thus
4530
- * commands) in their `preExecute(d)` and `postExecute(d)` phases. The command
4531
- * stack will properly group all commands together into a logical unit
4532
- * that may be re- and undone atomically.
4533
- *
4534
- * Command handlers must not execute other commands from within their
4535
- * core implementation (`execute`, `revert`).
4536
- *
4537
- *
4538
- * ## Change Tracking
4539
- *
4540
- * During the execution of the CommandStack it will keep track of all
4541
- * elements that have been touched during the command's execution.
4542
- *
4543
- * At the end of the CommandStack execution it will notify interested
4544
- * components via an 'elements.changed' event with all the dirty
4545
- * elements.
4546
- *
4547
- * The event can be picked up by components that are interested in the fact
4548
- * that elements have been changed. One use case for this is updating
4549
- * their graphical representation after moving / resizing or deletion.
4550
- *
4551
- * @see CommandHandler
4552
- *
4553
- * @param {EventBus} eventBus
4554
- * @param {Injector} injector
4555
- */
4556
- function CommandStack(eventBus, injector) {
4360
+ class FormLayoutUpdater extends CommandInterceptor {
4361
+ constructor(eventBus, formLayouter, modeling, formEditor) {
4362
+ super(eventBus);
4363
+ this._eventBus = eventBus;
4364
+ this._formLayouter = formLayouter;
4365
+ this._modeling = modeling;
4366
+ this._formEditor = formEditor;
4367
+
4368
+ // @ts-ignore
4369
+ this.preExecute(['formField.add', 'formField.remove', 'formField.move', 'id.updateClaim'], event => this.updateRowIds(event));
4370
+
4371
+ // we need that as the state got updates
4372
+ // on the next tick (not in post execute)
4373
+ eventBus.on('changed', context => {
4374
+ const {
4375
+ schema
4376
+ } = context;
4377
+ this.updateLayout(schema);
4378
+ });
4379
+ }
4380
+ updateLayout(schema) {
4381
+ this._formLayouter.clear();
4382
+ this._formLayouter.calculateLayout(clone(schema));
4383
+ }
4384
+ updateRowIds(event) {
4385
+ const {
4386
+ schema
4387
+ } = this._formEditor._getState();
4388
+ const setRowIds = parent => {
4389
+ if (!parent.components || !parent.components.length) {
4390
+ return;
4391
+ }
4392
+ parent.components.forEach(formField => {
4393
+ const row = this._formLayouter.getRowForField(formField);
4394
+ updateRow(formField, row.id);
4395
+
4396
+ // handle children recursively
4397
+ setRowIds(formField);
4398
+ });
4399
+ };
4400
+
4401
+ // make sure rows are persisted in schema (e.g. for migration case)
4402
+ setRowIds(schema);
4403
+ }
4404
+ }
4405
+ FormLayoutUpdater.$inject = ['eventBus', 'formLayouter', 'modeling', 'formEditor'];
4406
+
4407
+ class AddFormFieldHandler {
4557
4408
  /**
4558
- * A map of all registered command handlers.
4559
- *
4560
- * @type {CommandHandlerMap}
4409
+ * @constructor
4410
+ * @param { import('../../../FormEditor').FormEditor } formEditor
4411
+ * @param { import('../../../core/FormFieldRegistry').FormFieldRegistry } formFieldRegistry
4561
4412
  */
4562
- this._handlerMap = {};
4413
+ constructor(formEditor, formFieldRegistry) {
4414
+ this._formEditor = formEditor;
4415
+ this._formFieldRegistry = formFieldRegistry;
4416
+ }
4417
+ execute(context) {
4418
+ const {
4419
+ formField,
4420
+ targetFormField,
4421
+ targetIndex
4422
+ } = context;
4423
+ const {
4424
+ schema
4425
+ } = this._formEditor._getState();
4426
+ const targetPath = [...targetFormField._path, 'components'];
4427
+ formField._parent = targetFormField.id;
4428
+
4429
+ // (1) Add new form field
4430
+ arrayAdd$1(get(schema, targetPath), targetIndex, formField);
4431
+
4432
+ // (2) Update internal paths of new form field and its siblings (and their children)
4433
+ get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4434
+
4435
+ // (3) Add new form field to form field registry
4436
+ this._formFieldRegistry.add(formField);
4437
+
4438
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4439
+ this._formEditor._setState({
4440
+ schema
4441
+ });
4442
+ }
4443
+ revert(context) {
4444
+ const {
4445
+ formField,
4446
+ targetFormField,
4447
+ targetIndex
4448
+ } = context;
4449
+ const {
4450
+ schema
4451
+ } = this._formEditor._getState();
4452
+ const targetPath = [...targetFormField._path, 'components'];
4453
+
4454
+ // (1) Remove new form field
4455
+ arrayRemove(get(schema, targetPath), targetIndex);
4456
+
4457
+ // (2) Update internal paths of new form field and its siblings (and their children)
4458
+ get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4459
+
4460
+ // (3) Remove new form field from form field registry
4461
+ this._formFieldRegistry.remove(formField);
4462
+
4463
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4464
+ this._formEditor._setState({
4465
+ schema
4466
+ });
4467
+ }
4468
+ }
4469
+ AddFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
4470
+
4471
+ class EditFormFieldHandler {
4472
+ /**
4473
+ * @constructor
4474
+ * @param { import('../../../FormEditor').FormEditor } formEditor
4475
+ * @param { import('../../../core/FormFieldRegistry').FormFieldRegistry } formFieldRegistry
4476
+ */
4477
+ constructor(formEditor, formFieldRegistry) {
4478
+ this._formEditor = formEditor;
4479
+ this._formFieldRegistry = formFieldRegistry;
4480
+ }
4481
+ execute(context) {
4482
+ const {
4483
+ formField,
4484
+ properties
4485
+ } = context;
4486
+ let {
4487
+ schema
4488
+ } = this._formEditor._getState();
4489
+ const oldProperties = {};
4490
+ for (let key in properties) {
4491
+ oldProperties[key] = formField[key];
4492
+ const property = properties[key];
4493
+ if (key === 'id') {
4494
+ if (property !== formField.id) {
4495
+ this._formFieldRegistry.updateId(formField, property);
4496
+ }
4497
+ } else {
4498
+ formField[key] = property;
4499
+ }
4500
+ }
4501
+ context.oldProperties = oldProperties;
4502
+
4503
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4504
+ this._formEditor._setState({
4505
+ schema
4506
+ });
4507
+ return formField;
4508
+ }
4509
+ revert(context) {
4510
+ const {
4511
+ formField,
4512
+ oldProperties
4513
+ } = context;
4514
+ let {
4515
+ schema
4516
+ } = this._formEditor._getState();
4517
+ for (let key in oldProperties) {
4518
+ const property = oldProperties[key];
4519
+ if (key === 'id') {
4520
+ if (property !== formField.id) {
4521
+ this._formFieldRegistry.updateId(formField, property);
4522
+ }
4523
+ } else {
4524
+ formField[key] = property;
4525
+ }
4526
+ }
4527
+
4528
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4529
+ this._formEditor._setState({
4530
+ schema
4531
+ });
4532
+ return formField;
4533
+ }
4534
+ }
4535
+ EditFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
4536
+
4537
+ class MoveFormFieldHandler {
4538
+ /**
4539
+ * @constructor
4540
+ * @param { import('../../../FormEditor').FormEditor } formEditor
4541
+ * @param { import('../../../core/FormFieldRegistry').FormFieldRegistry } formFieldRegistry
4542
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
4543
+ * @param { import('@bpmn-io/form-js-viewer').FormLayouter } formLayouter
4544
+ */
4545
+ constructor(formEditor, formFieldRegistry, pathRegistry, formLayouter) {
4546
+ this._formEditor = formEditor;
4547
+ this._formFieldRegistry = formFieldRegistry;
4548
+ this._pathRegistry = pathRegistry;
4549
+ this._formLayouter = formLayouter;
4550
+ }
4551
+ execute(context) {
4552
+ this.moveFormField(context);
4553
+ }
4554
+ revert(context) {
4555
+ let {
4556
+ sourceFormField,
4557
+ targetFormField,
4558
+ sourceIndex,
4559
+ targetIndex,
4560
+ sourceRow,
4561
+ targetRow
4562
+ } = context;
4563
+ this.moveFormField({
4564
+ sourceFormField: targetFormField,
4565
+ targetFormField: sourceFormField,
4566
+ sourceIndex: targetIndex,
4567
+ targetIndex: sourceIndex,
4568
+ sourceRow: targetRow,
4569
+ targetRow: sourceRow
4570
+ }, true);
4571
+ }
4572
+ moveFormField(context, revert) {
4573
+ let {
4574
+ sourceFormField,
4575
+ targetFormField,
4576
+ sourceIndex,
4577
+ targetIndex,
4578
+ targetRow
4579
+ } = context;
4580
+ let {
4581
+ schema
4582
+ } = this._formEditor._getState();
4583
+ const sourcePath = [...sourceFormField._path, 'components'];
4584
+ if (sourceFormField.id === targetFormField.id) {
4585
+ if (revert) {
4586
+ if (sourceIndex > targetIndex) {
4587
+ sourceIndex--;
4588
+ }
4589
+ } else {
4590
+ if (sourceIndex < targetIndex) {
4591
+ targetIndex--;
4592
+ }
4593
+ }
4594
+ const formField = get(schema, [...sourcePath, sourceIndex]);
4595
+
4596
+ // (1) Add to row or create new one
4597
+ updateRow(formField, targetRow ? targetRow.id : this._formLayouter.nextRowId());
4598
+
4599
+ // (2) Move form field
4600
+ mutate(get(schema, sourcePath), sourceIndex, targetIndex);
4601
+
4602
+ // (3) Update internal paths of new form field and its siblings (and their children)
4603
+ get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4604
+ } else {
4605
+ const formField = get(schema, [...sourcePath, sourceIndex]);
4606
+
4607
+ // (1) Deregister form field (and children) from path registry
4608
+ this._pathRegistry.executeRecursivelyOnFields(formField, ({
4609
+ field
4610
+ }) => {
4611
+ this._pathRegistry.unclaimPath(this._pathRegistry.getValuePath(field));
4612
+ });
4613
+ formField._parent = targetFormField.id;
4614
+
4615
+ // (2) Remove form field
4616
+ arrayRemove(get(schema, sourcePath), sourceIndex);
4617
+
4618
+ // (3) Update internal paths of siblings (and their children)
4619
+ get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4620
+ const targetPath = [...targetFormField._path, 'components'];
4621
+
4622
+ // (4) Add to row or create new one
4623
+ updateRow(formField, targetRow ? targetRow.id : this._formLayouter.nextRowId());
4563
4624
 
4564
- /**
4565
- * A stack containing all re/undoable actions on the diagram
4566
- *
4567
- * @type {CommandStackAction[]}
4568
- */
4569
- this._stack = [];
4625
+ // (5) Add form field
4626
+ arrayAdd$1(get(schema, targetPath), targetIndex, formField);
4570
4627
 
4571
- /**
4572
- * The current index on the stack
4573
- *
4574
- * @type {number}
4575
- */
4576
- this._stackIdx = -1;
4628
+ // (6) Update internal paths of siblings (and their children)
4629
+ get(schema, targetPath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4577
4630
 
4578
- /**
4579
- * Current active commandStack execution
4580
- *
4581
- * @type {CurrentExecution}
4582
- */
4583
- this._currentExecution = {
4584
- actions: [],
4585
- dirty: [],
4586
- trigger: null
4587
- };
4631
+ // (7) Reregister form field (and children) from path registry
4632
+ this._pathRegistry.executeRecursivelyOnFields(formField, ({
4633
+ field,
4634
+ isClosed,
4635
+ isRepeatable
4636
+ }) => {
4637
+ this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), {
4638
+ isClosed,
4639
+ isRepeatable,
4640
+ claimerId: field.id
4641
+ });
4642
+ });
4643
+ }
4588
4644
 
4589
- /**
4590
- * @type {Injector}
4591
- */
4592
- this._injector = injector;
4645
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4646
+ this._formEditor._setState({
4647
+ schema
4648
+ });
4649
+ }
4650
+ }
4651
+ MoveFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry', 'pathRegistry', 'formLayouter'];
4593
4652
 
4653
+ class RemoveFormFieldHandler {
4594
4654
  /**
4595
- * @type EventBus
4655
+ * @constructor
4656
+ * @param { import('../../../FormEditor').FormEditor } formEditor
4657
+ * @param { import('../../../core/FormFieldRegistry').FormFieldRegistry } formFieldRegistry
4596
4658
  */
4597
- this._eventBus = eventBus;
4659
+ constructor(formEditor, formFieldRegistry) {
4660
+ this._formEditor = formEditor;
4661
+ this._formFieldRegistry = formFieldRegistry;
4662
+ }
4663
+ execute(context) {
4664
+ const {
4665
+ sourceFormField,
4666
+ sourceIndex
4667
+ } = context;
4668
+ let {
4669
+ schema
4670
+ } = this._formEditor._getState();
4671
+ const sourcePath = [...sourceFormField._path, 'components'];
4672
+ const formField = context.formField = get(schema, [...sourcePath, sourceIndex]);
4598
4673
 
4599
- /**
4600
- * @type { number }
4601
- */
4602
- this._uid = 1;
4603
- eventBus.on(['diagram.destroy', 'diagram.clear'], function () {
4604
- this.clear(false);
4605
- }, this);
4606
- }
4607
- CommandStack.$inject = ['eventBus', 'injector'];
4674
+ // (1) Remove form field
4675
+ arrayRemove(get(schema, sourcePath), sourceIndex);
4608
4676
 
4609
- /**
4610
- * Execute a command.
4611
- *
4612
- * @param {string} command The command to execute.
4613
- * @param {CommandContext} context The context with which to execute the command.
4614
- */
4615
- CommandStack.prototype.execute = function (command, context) {
4616
- if (!command) {
4617
- throw new Error('command required');
4677
+ // (2) Update internal paths of its siblings (and their children)
4678
+ get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4679
+
4680
+ // (3) Remove form field and children from form field registry
4681
+ runRecursively(formField, formField => this._formFieldRegistry.remove(formField));
4682
+
4683
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4684
+ this._formEditor._setState({
4685
+ schema
4686
+ });
4618
4687
  }
4619
- this._currentExecution.trigger = 'execute';
4620
- const action = {
4621
- command: command,
4622
- context: context
4623
- };
4624
- this._pushAction(action);
4625
- this._internalExecute(action);
4626
- this._popAction();
4627
- };
4688
+ revert(context) {
4689
+ const {
4690
+ formField,
4691
+ sourceFormField,
4692
+ sourceIndex
4693
+ } = context;
4694
+ let {
4695
+ schema
4696
+ } = this._formEditor._getState();
4697
+ const sourcePath = [...sourceFormField._path, 'components'];
4628
4698
 
4629
- /**
4630
- * Check whether a command can be executed.
4631
- *
4632
- * Implementors may hook into the mechanism on two ways:
4633
- *
4634
- * * in event listeners:
4635
- *
4636
- * Users may prevent the execution via an event listener.
4637
- * It must prevent the default action for `commandStack.(<command>.)canExecute` events.
4638
- *
4639
- * * in command handlers:
4640
- *
4641
- * If the method {@link CommandHandler#canExecute} is implemented in a handler
4642
- * it will be called to figure out whether the execution is allowed.
4643
- *
4644
- * @param {string} command The command to execute.
4645
- * @param {CommandContext} context The context with which to execute the command.
4646
- *
4647
- * @return {boolean} Whether the command can be executed with the given context.
4648
- */
4649
- CommandStack.prototype.canExecute = function (command, context) {
4650
- const action = {
4651
- command: command,
4652
- context: context
4653
- };
4654
- const handler = this._getHandler(command);
4655
- let result = this._fire(command, 'canExecute', action);
4699
+ // (1) Add form field
4700
+ arrayAdd$1(get(schema, sourcePath), sourceIndex, formField);
4656
4701
 
4657
- // handler#canExecute will only be called if no listener
4658
- // decided on a result already
4659
- if (result === undefined) {
4660
- if (!handler) {
4661
- return false;
4702
+ // (2) Update internal paths of its siblings (and their children)
4703
+ get(schema, sourcePath).forEach((formField, index) => updatePath(this._formFieldRegistry, formField, index));
4704
+
4705
+ // (3) Add form field and children to form field registry
4706
+ runRecursively(formField, formField => this._formFieldRegistry.add(formField));
4707
+
4708
+ // TODO: Create updater/change support that automatically updates paths and schema on command execution
4709
+ this._formEditor._setState({
4710
+ schema
4711
+ });
4712
+ }
4713
+ }
4714
+ RemoveFormFieldHandler.$inject = ['formEditor', 'formFieldRegistry'];
4715
+
4716
+ class UpdateIdClaimHandler {
4717
+ /**
4718
+ * @constructor
4719
+ * @param { import('../../../core/FormFieldRegistry').FormFieldRegistry } formFieldRegistry
4720
+ */
4721
+ constructor(formFieldRegistry) {
4722
+ this._formFieldRegistry = formFieldRegistry;
4723
+ }
4724
+ execute(context) {
4725
+ const {
4726
+ claiming,
4727
+ formField,
4728
+ id
4729
+ } = context;
4730
+ if (claiming) {
4731
+ this._formFieldRegistry._ids.claim(id, formField);
4732
+ } else {
4733
+ this._formFieldRegistry._ids.unclaim(id);
4662
4734
  }
4663
- if (handler.canExecute) {
4664
- result = handler.canExecute(context);
4735
+ }
4736
+ revert(context) {
4737
+ const {
4738
+ claiming,
4739
+ formField,
4740
+ id
4741
+ } = context;
4742
+ if (claiming) {
4743
+ this._formFieldRegistry._ids.unclaim(id);
4744
+ } else {
4745
+ this._formFieldRegistry._ids.claim(id, formField);
4665
4746
  }
4666
4747
  }
4667
- return result;
4668
- };
4748
+ }
4749
+ UpdateIdClaimHandler.$inject = ['formFieldRegistry'];
4669
4750
 
4670
- /**
4671
- * Clear the command stack, erasing all undo / redo history.
4672
- *
4673
- * @param {boolean} [emit=true] Whether to fire an event. Defaults to `true`.
4674
- */
4675
- CommandStack.prototype.clear = function (emit) {
4676
- this._stack.length = 0;
4677
- this._stackIdx = -1;
4678
- if (emit !== false) {
4679
- this._fire('changed', {
4680
- trigger: 'clear'
4681
- });
4751
+ class UpdateKeyClaimHandler {
4752
+ /**
4753
+ * @constructor
4754
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
4755
+ */
4756
+ constructor(pathRegistry) {
4757
+ this._pathRegistry = pathRegistry;
4682
4758
  }
4683
- };
4684
-
4685
- /**
4686
- * Undo last command(s)
4687
- */
4688
- CommandStack.prototype.undo = function () {
4689
- let action = this._getUndoAction(),
4690
- next;
4691
- if (action) {
4692
- this._currentExecution.trigger = 'undo';
4693
- this._pushAction(action);
4694
- while (action) {
4695
- this._internalUndo(action);
4696
- next = this._getUndoAction();
4697
- if (!next || next.id !== action.id) {
4698
- break;
4759
+ execute(context) {
4760
+ const {
4761
+ claiming,
4762
+ formField,
4763
+ key
4764
+ } = context;
4765
+ const options = {
4766
+ replacements: {
4767
+ [formField.id]: key
4699
4768
  }
4700
- action = next;
4769
+ };
4770
+ const valuePath = this._pathRegistry.getValuePath(formField, options);
4771
+ if (claiming) {
4772
+ this._pathRegistry.claimPath(valuePath, {
4773
+ isClosed: true,
4774
+ claimerId: formField.id
4775
+ });
4776
+ } else {
4777
+ this._pathRegistry.unclaimPath(valuePath);
4778
+ }
4779
+
4780
+ // cache path for revert
4781
+ context.valuePath = valuePath;
4782
+ }
4783
+ revert(context) {
4784
+ const {
4785
+ claiming,
4786
+ formField,
4787
+ valuePath
4788
+ } = context;
4789
+ if (claiming) {
4790
+ this._pathRegistry.unclaimPath(valuePath);
4791
+ } else {
4792
+ this._pathRegistry.claimPath(valuePath, {
4793
+ isClosed: true,
4794
+ claimerId: formField.id
4795
+ });
4701
4796
  }
4702
- this._popAction();
4703
4797
  }
4704
- };
4798
+ }
4799
+ UpdateKeyClaimHandler.$inject = ['pathRegistry'];
4705
4800
 
4706
- /**
4707
- * Redo last command(s)
4708
- */
4709
- CommandStack.prototype.redo = function () {
4710
- let action = this._getRedoAction(),
4711
- next;
4712
- if (action) {
4713
- this._currentExecution.trigger = 'redo';
4714
- this._pushAction(action);
4715
- while (action) {
4716
- this._internalExecute(action, true);
4717
- next = this._getRedoAction();
4718
- if (!next || next.id !== action.id) {
4719
- break;
4801
+ class UpdatePathClaimHandler {
4802
+ /**
4803
+ * @constructor
4804
+ * @param { import('@bpmn-io/form-js-viewer').PathRegistry } pathRegistry
4805
+ */
4806
+ constructor(pathRegistry) {
4807
+ this._pathRegistry = pathRegistry;
4808
+ }
4809
+ execute(context) {
4810
+ const {
4811
+ claiming,
4812
+ formField,
4813
+ path
4814
+ } = context;
4815
+ const options = {
4816
+ replacements: {
4817
+ [formField.id]: path
4720
4818
  }
4721
- action = next;
4819
+ };
4820
+ const valuePaths = [];
4821
+ if (claiming) {
4822
+ this._pathRegistry.executeRecursivelyOnFields(formField, ({
4823
+ field,
4824
+ isClosed,
4825
+ isRepeatable
4826
+ }) => {
4827
+ const valuePath = this._pathRegistry.getValuePath(field, options);
4828
+ valuePaths.push({
4829
+ valuePath,
4830
+ isClosed,
4831
+ isRepeatable,
4832
+ claimerId: field.id
4833
+ });
4834
+ this._pathRegistry.claimPath(valuePath, {
4835
+ isClosed,
4836
+ isRepeatable,
4837
+ claimerId: field.id
4838
+ });
4839
+ });
4840
+ } else {
4841
+ this._pathRegistry.executeRecursivelyOnFields(formField, ({
4842
+ field,
4843
+ isClosed,
4844
+ isRepeatable
4845
+ }) => {
4846
+ const valuePath = this._pathRegistry.getValuePath(field, options);
4847
+ valuePaths.push({
4848
+ valuePath,
4849
+ isClosed,
4850
+ isRepeatable,
4851
+ claimerId: field.id
4852
+ });
4853
+ this._pathRegistry.unclaimPath(valuePath);
4854
+ });
4722
4855
  }
4723
- this._popAction();
4724
- }
4725
- };
4726
-
4727
- /**
4728
- * Register a handler instance with the command stack.
4729
- *
4730
- * @param {string} command Command to be executed.
4731
- * @param {CommandHandler} handler Handler to execute the command.
4732
- */
4733
- CommandStack.prototype.register = function (command, handler) {
4734
- this._setHandler(command, handler);
4735
- };
4736
4856
 
4737
- /**
4738
- * Register a handler type with the command stack by instantiating it and
4739
- * injecting its dependencies.
4740
- *
4741
- * @param {string} command Command to be executed.
4742
- * @param {CommandHandlerConstructor} handlerCls Constructor to instantiate a {@link CommandHandler}.
4743
- */
4744
- CommandStack.prototype.registerHandler = function (command, handlerCls) {
4745
- if (!command || !handlerCls) {
4746
- throw new Error('command and handlerCls must be defined');
4857
+ // cache path info for revert
4858
+ context.valuePaths = valuePaths;
4747
4859
  }
4748
- const handler = this._injector.instantiate(handlerCls);
4749
- this.register(command, handler);
4750
- };
4751
-
4752
- /**
4753
- * @return {boolean}
4754
- */
4755
- CommandStack.prototype.canUndo = function () {
4756
- return !!this._getUndoAction();
4757
- };
4758
-
4759
- /**
4760
- * @return {boolean}
4761
- */
4762
- CommandStack.prototype.canRedo = function () {
4763
- return !!this._getRedoAction();
4764
- };
4765
-
4766
- // stack access //////////////////////
4767
-
4768
- CommandStack.prototype._getRedoAction = function () {
4769
- return this._stack[this._stackIdx + 1];
4770
- };
4771
- CommandStack.prototype._getUndoAction = function () {
4772
- return this._stack[this._stackIdx];
4773
- };
4774
-
4775
- // internal functionality //////////////////////
4776
-
4777
- CommandStack.prototype._internalUndo = function (action) {
4778
- const command = action.command,
4779
- context = action.context;
4780
- const handler = this._getHandler(command);
4781
-
4782
- // guard against illegal nested command stack invocations
4783
- this._atomicDo(() => {
4784
- this._fire(command, 'revert', action);
4785
- if (handler.revert) {
4786
- this._markDirty(handler.revert(context));
4860
+ revert(context) {
4861
+ const {
4862
+ claiming,
4863
+ valuePaths
4864
+ } = context;
4865
+ if (claiming) {
4866
+ valuePaths.forEach(({
4867
+ valuePath
4868
+ }) => {
4869
+ this._pathRegistry.unclaimPath(valuePath);
4870
+ });
4871
+ } else {
4872
+ valuePaths.forEach(({
4873
+ valuePath,
4874
+ isClosed,
4875
+ isRepeatable,
4876
+ claimerId
4877
+ }) => {
4878
+ this._pathRegistry.claimPath(valuePath, {
4879
+ isClosed,
4880
+ isRepeatable,
4881
+ claimerId
4882
+ });
4883
+ });
4787
4884
  }
4788
- this._revertedAction(action);
4789
- this._fire(command, 'reverted', action);
4790
- });
4791
- };
4792
- CommandStack.prototype._fire = function (command, qualifier, event) {
4793
- if (arguments.length < 3) {
4794
- event = qualifier;
4795
- qualifier = null;
4796
4885
  }
4797
- const names = qualifier ? [command + '.' + qualifier, qualifier] : [command];
4798
- let result;
4799
- event = this._eventBus.createEvent(event);
4800
- for (const name of names) {
4801
- result = this._eventBus.fire('commandStack.' + name, event);
4802
- if (event.cancelBubble) {
4803
- break;
4804
- }
4886
+ }
4887
+ UpdatePathClaimHandler.$inject = ['pathRegistry'];
4888
+
4889
+ class Modeling {
4890
+ constructor(commandStack, eventBus, formEditor, formFieldRegistry, fieldFactory) {
4891
+ this._commandStack = commandStack;
4892
+ this._formEditor = formEditor;
4893
+ this._formFieldRegistry = formFieldRegistry;
4894
+ this._fieldFactory = fieldFactory;
4895
+ eventBus.on('form.init', () => {
4896
+ this.registerHandlers();
4897
+ });
4805
4898
  }
4806
- return result;
4807
- };
4808
- CommandStack.prototype._createId = function () {
4809
- return this._uid++;
4810
- };
4811
- CommandStack.prototype._atomicDo = function (fn) {
4812
- const execution = this._currentExecution;
4813
- execution.atomic = true;
4814
- try {
4815
- fn();
4816
- } finally {
4817
- execution.atomic = false;
4899
+ registerHandlers() {
4900
+ Object.entries(this.getHandlers()).forEach(([id, handler]) => {
4901
+ this._commandStack.registerHandler(id, handler);
4902
+ });
4818
4903
  }
4819
- };
4820
- CommandStack.prototype._internalExecute = function (action, redo) {
4821
- const command = action.command,
4822
- context = action.context;
4823
- const handler = this._getHandler(command);
4824
- if (!handler) {
4825
- throw new Error('no command handler registered for <' + command + '>');
4904
+ getHandlers() {
4905
+ return {
4906
+ 'formField.add': AddFormFieldHandler,
4907
+ 'formField.edit': EditFormFieldHandler,
4908
+ 'formField.move': MoveFormFieldHandler,
4909
+ 'formField.remove': RemoveFormFieldHandler,
4910
+ 'id.updateClaim': UpdateIdClaimHandler,
4911
+ 'key.updateClaim': UpdateKeyClaimHandler,
4912
+ 'path.updateClaim': UpdatePathClaimHandler
4913
+ };
4826
4914
  }
4827
- this._pushAction(action);
4828
- if (!redo) {
4829
- this._fire(command, 'preExecute', action);
4830
- if (handler.preExecute) {
4831
- handler.preExecute(context);
4915
+ addFormField(attrs, targetFormField, targetIndex) {
4916
+ const formField = this._fieldFactory.create(attrs);
4917
+ const context = {
4918
+ formField,
4919
+ targetFormField,
4920
+ targetIndex
4921
+ };
4922
+ this._commandStack.execute('formField.add', context);
4923
+ return formField;
4924
+ }
4925
+ editFormField(formField, properties, value) {
4926
+ if (!isObject(properties)) {
4927
+ properties = {
4928
+ [properties]: value
4929
+ };
4832
4930
  }
4833
- this._fire(command, 'preExecuted', action);
4931
+ const context = {
4932
+ formField,
4933
+ properties
4934
+ };
4935
+ this._commandStack.execute('formField.edit', context);
4834
4936
  }
4835
-
4836
- // guard against illegal nested command stack invocations
4837
- this._atomicDo(() => {
4838
- this._fire(command, 'execute', action);
4839
- if (handler.execute) {
4840
- // actual execute + mark return results as dirty
4841
- this._markDirty(handler.execute(context));
4842
- }
4843
-
4844
- // log to stack
4845
- this._executedAction(action, redo);
4846
- this._fire(command, 'executed', action);
4847
- });
4848
- if (!redo) {
4849
- this._fire(command, 'postExecute', action);
4850
- if (handler.postExecute) {
4851
- handler.postExecute(context);
4852
- }
4853
- this._fire(command, 'postExecuted', action);
4937
+ moveFormField(formField, sourceFormField, targetFormField, sourceIndex, targetIndex, sourceRow, targetRow) {
4938
+ const context = {
4939
+ formField,
4940
+ sourceFormField,
4941
+ targetFormField,
4942
+ sourceIndex,
4943
+ targetIndex,
4944
+ sourceRow,
4945
+ targetRow
4946
+ };
4947
+ this._commandStack.execute('formField.move', context);
4854
4948
  }
4855
- this._popAction();
4856
- };
4857
- CommandStack.prototype._pushAction = function (action) {
4858
- const execution = this._currentExecution,
4859
- actions = execution.actions;
4860
- const baseAction = actions[0];
4861
- if (execution.atomic) {
4862
- throw new Error('illegal invocation in <execute> or <revert> phase (action: ' + action.command + ')');
4949
+ removeFormField(formField, sourceFormField, sourceIndex) {
4950
+ const context = {
4951
+ formField,
4952
+ sourceFormField,
4953
+ sourceIndex
4954
+ };
4955
+ this._commandStack.execute('formField.remove', context);
4863
4956
  }
4864
- if (!action.id) {
4865
- action.id = baseAction && baseAction.id || this._createId();
4957
+ claimId(formField, id) {
4958
+ const context = {
4959
+ formField,
4960
+ id,
4961
+ claiming: true
4962
+ };
4963
+ this._commandStack.execute('id.updateClaim', context);
4866
4964
  }
4867
- actions.push(action);
4868
- };
4869
- CommandStack.prototype._popAction = function () {
4870
- const execution = this._currentExecution,
4871
- trigger = execution.trigger,
4872
- actions = execution.actions,
4873
- dirty = execution.dirty;
4874
- actions.pop();
4875
- if (!actions.length) {
4876
- this._eventBus.fire('elements.changed', {
4877
- elements: uniqueBy('id', dirty.reverse())
4878
- });
4879
- dirty.length = 0;
4880
- this._fire('changed', {
4881
- trigger: trigger
4882
- });
4883
- execution.trigger = null;
4965
+ unclaimId(formField, id) {
4966
+ const context = {
4967
+ formField,
4968
+ id,
4969
+ claiming: false
4970
+ };
4971
+ this._commandStack.execute('id.updateClaim', context);
4884
4972
  }
4885
- };
4886
- CommandStack.prototype._markDirty = function (elements) {
4887
- const execution = this._currentExecution;
4888
- if (!elements) {
4889
- return;
4973
+ claimKey(formField, key) {
4974
+ const context = {
4975
+ formField,
4976
+ key,
4977
+ claiming: true
4978
+ };
4979
+ this._commandStack.execute('key.updateClaim', context);
4890
4980
  }
4891
- elements = isArray(elements) ? elements : [elements];
4892
- execution.dirty = execution.dirty.concat(elements);
4893
- };
4894
- CommandStack.prototype._executedAction = function (action, redo) {
4895
- const stackIdx = ++this._stackIdx;
4896
- if (!redo) {
4897
- this._stack.splice(stackIdx, this._stack.length, action);
4981
+ unclaimKey(formField, key) {
4982
+ const context = {
4983
+ formField,
4984
+ key,
4985
+ claiming: false
4986
+ };
4987
+ this._commandStack.execute('key.updateClaim', context);
4898
4988
  }
4899
- };
4900
- CommandStack.prototype._revertedAction = function (action) {
4901
- this._stackIdx--;
4902
- };
4903
- CommandStack.prototype._getHandler = function (command) {
4904
- return this._handlerMap[command];
4905
- };
4906
- CommandStack.prototype._setHandler = function (command, handler) {
4907
- if (!command || !handler) {
4908
- throw new Error('command and handler required');
4989
+ claimPath(formField, path) {
4990
+ const context = {
4991
+ formField,
4992
+ path,
4993
+ claiming: true
4994
+ };
4995
+ this._commandStack.execute('path.updateClaim', context);
4909
4996
  }
4910
- if (this._handlerMap[command]) {
4911
- throw new Error('overriding handler for command <' + command + '>');
4997
+ unclaimPath(formField, path) {
4998
+ const context = {
4999
+ formField,
5000
+ path,
5001
+ claiming: false
5002
+ };
5003
+ this._commandStack.execute('path.updateClaim', context);
4912
5004
  }
4913
- this._handlerMap[command] = handler;
4914
- };
4915
-
4916
- /**
4917
- * @type { import('didi').ModuleDeclaration }
4918
- */
4919
- var commandModule = {
4920
- commandStack: ['type', CommandStack]
4921
- };
5005
+ }
5006
+ Modeling.$inject = ['commandStack', 'eventBus', 'formEditor', 'formFieldRegistry', 'fieldFactory'];
4922
5007
 
4923
- var ModelingModule = {
4924
- __depends__: [behaviorModule, commandModule],
5008
+ const ModelingModule = {
5009
+ __depends__: [BehaviorModule, commandModule],
4925
5010
  __init__: ['formLayoutUpdater', 'modeling'],
4926
5011
  formLayoutUpdater: ['type', FormLayoutUpdater],
4927
5012
  modeling: ['type', Modeling]
@@ -4992,7 +5077,7 @@ class SelectionBehavior {
4992
5077
  }
4993
5078
  SelectionBehavior.$inject = ['eventBus', 'selection'];
4994
5079
 
4995
- var SelectionModule = {
5080
+ const SelectionModule = {
4996
5081
  __init__: ['selection', 'selectionBehavior'],
4997
5082
  selection: ['type', Selection],
4998
5083
  selectionBehavior: ['type', SelectionBehavior]
@@ -5063,15 +5148,15 @@ class SectionModuleBase {
5063
5148
  }
5064
5149
  }
5065
5150
 
5066
- let PaletteModule$1 = class PaletteModule extends SectionModuleBase {
5151
+ class PaletteRenderer extends SectionModuleBase {
5067
5152
  constructor(eventBus) {
5068
5153
  super(eventBus, 'palette');
5069
5154
  }
5070
- };
5071
- PaletteModule$1.$inject = ['eventBus'];
5155
+ }
5156
+ PaletteRenderer.$inject = ['eventBus'];
5072
5157
 
5073
- var PaletteModule = {
5074
- palette: ['type', PaletteModule$1]
5158
+ const PaletteModule = {
5159
+ palette: ['type', PaletteRenderer]
5075
5160
  };
5076
5161
 
5077
5162
  var ArrowIcon = function ArrowIcon(props) {
@@ -5198,6 +5283,24 @@ HelpIcon.defaultProps = {
5198
5283
  xmlns: "http://www.w3.org/2000/svg",
5199
5284
  viewBox: "0 0 32 32"
5200
5285
  };
5286
+ var PopupIcon = function PopupIcon(props) {
5287
+ return jsxs("svg", {
5288
+ ...props,
5289
+ children: [jsx("path", {
5290
+ fill: "currentColor",
5291
+ d: "M28 4H10a2.006 2.006 0 0 0-2 2v14a2.006 2.006 0 0 0 2 2h18a2.006 2.006 0 0 0 2-2V6a2.006 2.006 0 0 0-2-2Zm0 16H10V6h18Z"
5292
+ }), jsx("path", {
5293
+ fill: "currentColor",
5294
+ d: "M18 26H4V16h2v-2H4a2.006 2.006 0 0 0-2 2v10a2.006 2.006 0 0 0 2 2h14a2.006 2.006 0 0 0 2-2v-2h-2Z"
5295
+ })]
5296
+ });
5297
+ };
5298
+ PopupIcon.defaultProps = {
5299
+ xmlns: "http://www.w3.org/2000/svg",
5300
+ width: "16",
5301
+ height: "16",
5302
+ viewBox: "0 0 32 32"
5303
+ };
5201
5304
  function Header(props) {
5202
5305
  const {
5203
5306
  element,
@@ -5268,6 +5371,7 @@ const ErrorsContext = createContext({
5268
5371
  *
5269
5372
  * @returns void
5270
5373
  */
5374
+
5271
5375
  const EventContext = createContext({
5272
5376
  eventBus: null
5273
5377
  });
@@ -5323,7 +5427,9 @@ function Tooltip(props) {
5323
5427
  const {
5324
5428
  forId,
5325
5429
  value,
5326
- parent
5430
+ parent,
5431
+ direction = 'right',
5432
+ position
5327
5433
  } = props;
5328
5434
  const [visible, setShow] = useState(false);
5329
5435
  const [focusedViaKeyboard, setFocusedViaKeyboard] = useState(false);
@@ -5392,11 +5498,11 @@ function Tooltip(props) {
5392
5498
  }, [wrapperRef.current, visible, focusedViaKeyboard]);
5393
5499
  const renderTooltip = () => {
5394
5500
  return jsxs("div", {
5395
- class: "bio-properties-panel-tooltip",
5501
+ class: `bio-properties-panel-tooltip ${direction}`,
5396
5502
  role: "tooltip",
5397
5503
  id: "bio-properties-panel-tooltip",
5398
5504
  "aria-labelledby": forId,
5399
- style: getTooltipPosition(wrapperRef.current),
5505
+ style: position || getTooltipPosition(wrapperRef.current),
5400
5506
  ref: tooltipRef,
5401
5507
  onClick: e => e.stopPropagation(),
5402
5508
  children: [jsx("div", {
@@ -6010,7 +6116,7 @@ const CodeEditor = forwardRef((props, ref) => {
6010
6116
  tooltipContainer: tooltipContainer,
6011
6117
  value: localValue,
6012
6118
  variables: variables,
6013
- extensions: [...(enableGutters ? [lineNumbers()] : [])]
6119
+ extensions: [...(enableGutters ? [lineNumbers()] : []), EditorView.lineWrapping]
6014
6120
  });
6015
6121
  setEditor(editor);
6016
6122
  return () => {
@@ -6053,7 +6159,7 @@ const CodeEditor = forwardRef((props, ref) => {
6053
6159
  title: "Open pop-up editor",
6054
6160
  class: "bio-properties-panel-open-feel-popup",
6055
6161
  onClick: () => onPopupOpen(),
6056
- children: jsx(ExternalLinkIcon, {})
6162
+ children: jsx(PopupIcon, {})
6057
6163
  })]
6058
6164
  });
6059
6165
  });
@@ -6264,6 +6370,7 @@ function PopupComponent(props, globalRef) {
6264
6370
  }
6265
6371
  return () => focusTrapRef.current && focusTrapRef.current.deactivate();
6266
6372
  }, [popupRef]);
6373
+ useEvent('propertiesPanel.detach', onClose);
6267
6374
  return createPortal(jsx("div", {
6268
6375
  "aria-label": title,
6269
6376
  tabIndex: -1,
@@ -6442,7 +6549,13 @@ function FEELPopupRoot(props) {
6442
6549
  setSourceElement(_sourceElement);
6443
6550
  emit('open');
6444
6551
  };
6445
- const handleClose = () => {
6552
+ const handleClose = (event = {}) => {
6553
+ const {
6554
+ id
6555
+ } = event;
6556
+ if (id && id !== source) {
6557
+ return;
6558
+ }
6446
6559
  setOpen(false);
6447
6560
  setSource(null);
6448
6561
  };
@@ -6612,7 +6725,7 @@ function FeelPopupComponent(props) {
6612
6725
  }), jsx(Popup.Footer, {
6613
6726
  children: jsx("button", {
6614
6727
  type: "button",
6615
- onClick: onClose,
6728
+ onClick: () => onClose(),
6616
6729
  title: "Close pop-up editor",
6617
6730
  class: "bio-properties-panel-feel-popup__close-btn",
6618
6731
  children: "Close"
@@ -6788,18 +6901,14 @@ function NumberField(props) {
6788
6901
  } = props;
6789
6902
  const [localValue, setLocalValue] = useState(value);
6790
6903
  const handleInputCallback = useMemo(() => {
6791
- return debounce(event => {
6792
- const {
6793
- validity,
6794
- value
6795
- } = event.target;
6796
- if (validity.valid) {
6797
- onInput(value ? parseFloat(value) : undefined);
6904
+ return debounce(target => {
6905
+ if (target.validity.valid) {
6906
+ onInput(target.value ? parseFloat(target.value) : undefined);
6798
6907
  }
6799
6908
  });
6800
6909
  }, [onInput, debounce]);
6801
6910
  const handleInput = e => {
6802
- handleInputCallback(e);
6911
+ handleInputCallback(e.target);
6803
6912
  setLocalValue(e.target.value);
6804
6913
  };
6805
6914
  useEffect(() => {
@@ -6876,7 +6985,7 @@ function NumberFieldEntry(props) {
6876
6985
  const newValidationError = validate(value) || null;
6877
6986
  setLocalError(newValidationError);
6878
6987
  }
6879
- }, [value]);
6988
+ }, [value, validate]);
6880
6989
  const onInput = newValue => {
6881
6990
  let newValidationError = null;
6882
6991
  if (isFunction(validate)) {
@@ -6921,7 +7030,7 @@ function prefixId$6(id) {
6921
7030
  return `bio-properties-panel-${id}`;
6922
7031
  }
6923
7032
  const noop$2 = () => {};
6924
- function FeelTextfield(props) {
7033
+ function FeelTextfieldComponent(props) {
6925
7034
  const {
6926
7035
  debounce,
6927
7036
  id,
@@ -6998,9 +7107,7 @@ function FeelTextfield(props) {
6998
7107
  onError(undefined);
6999
7108
  return;
7000
7109
  }
7001
- const error = lint[0];
7002
- const message = `${error.source}: ${error.message}`;
7003
- onError(message);
7110
+ onError('Unparsable FEEL expression.');
7004
7111
  });
7005
7112
  const handlePopupOpen = (type = 'feel') => {
7006
7113
  const popupOptions = {
@@ -7123,6 +7230,7 @@ function FeelTextfield(props) {
7123
7230
  })]
7124
7231
  });
7125
7232
  }
7233
+ const FeelTextfield = withAutoClosePopup(FeelTextfieldComponent);
7126
7234
  const OptionalFeelInput = forwardRef((props, ref) => {
7127
7235
  const {
7128
7236
  id,
@@ -7375,7 +7483,7 @@ function FeelEntry(props) {
7375
7483
  const newValidationError = validate(value) || null;
7376
7484
  setValidationError(newValidationError);
7377
7485
  }
7378
- }, [value]);
7486
+ }, [value, validate]);
7379
7487
  const onInput = useStaticCallback(newValue => {
7380
7488
  let newValidationError = null;
7381
7489
  if (isFunction(validate)) {
@@ -7547,6 +7655,27 @@ function getPopupTitle(element, label) {
7547
7655
  }
7548
7656
  return `${popupTitle}${label}`;
7549
7657
  }
7658
+ function withAutoClosePopup(Component) {
7659
+ return function (props) {
7660
+ const {
7661
+ id
7662
+ } = props;
7663
+ const {
7664
+ close
7665
+ } = useContext(FeelPopupContext);
7666
+ const closePopup = useStaticCallback(close);
7667
+ useEffect(() => {
7668
+ return () => {
7669
+ closePopup({
7670
+ id
7671
+ });
7672
+ };
7673
+ }, []);
7674
+ return jsx(Component, {
7675
+ ...props
7676
+ });
7677
+ };
7678
+ }
7550
7679
  const DEFAULT_LAYOUT = {};
7551
7680
  const DEFAULT_DESCRIPTION = {};
7552
7681
  const DEFAULT_TOOLTIP = {};
@@ -7631,7 +7760,7 @@ const DEFAULT_TOOLTIP = {};
7631
7760
  * @param {HTMLElement} [props.feelPopupContainer]
7632
7761
  * @param {Object} [props.eventBus]
7633
7762
  */
7634
- function PropertiesPanel(props) {
7763
+ function PropertiesPanel$1(props) {
7635
7764
  const {
7636
7765
  element,
7637
7766
  headerProvider,
@@ -8350,7 +8479,7 @@ function SelectEntry(props) {
8350
8479
  const newValidationError = validate(value) || null;
8351
8480
  setLocalError(newValidationError);
8352
8481
  }
8353
- }, [value]);
8482
+ }, [value, validate]);
8354
8483
  const onChange = newValue => {
8355
8484
  let newValidationError = null;
8356
8485
  if (isFunction(validate)) {
@@ -8418,12 +8547,10 @@ function TextArea(props) {
8418
8547
  const [localValue, setLocalValue] = useState(value);
8419
8548
  const ref = useShowEntryEvent(id);
8420
8549
  const handleInputCallback = useMemo(() => {
8421
- return debounce(({
8422
- target
8423
- }) => onInput(target.value.length ? target.value : undefined));
8550
+ return debounce(target => onInput(target.value.length ? target.value : undefined));
8424
8551
  }, [onInput, debounce]);
8425
8552
  const handleInput = e => {
8426
- handleInputCallback(e);
8553
+ handleInputCallback(e.target);
8427
8554
  autoResize && resizeToContents(e.target);
8428
8555
  setLocalValue(e.target.value);
8429
8556
  };
@@ -8506,7 +8633,7 @@ function TextAreaEntry(props) {
8506
8633
  const newValidationError = validate(value) || null;
8507
8634
  setLocalError(newValidationError);
8508
8635
  }
8509
- }, [value]);
8636
+ }, [value, validate]);
8510
8637
  const onInput = newValue => {
8511
8638
  let newValidationError = null;
8512
8639
  if (isFunction(validate)) {
@@ -8567,12 +8694,10 @@ function Textfield(props) {
8567
8694
  const [localValue, setLocalValue] = useState(value || '');
8568
8695
  const ref = useShowEntryEvent(id);
8569
8696
  const handleInputCallback = useMemo(() => {
8570
- return debounce(({
8571
- target
8572
- }) => onInput(target.value.length ? target.value : undefined));
8697
+ return debounce(target => onInput(target.value.length ? target.value : undefined));
8573
8698
  }, [onInput, debounce]);
8574
8699
  const handleInput = e => {
8575
- handleInputCallback(e);
8700
+ handleInputCallback(e.target);
8576
8701
  setLocalValue(e.target.value);
8577
8702
  };
8578
8703
  useEffect(() => {
@@ -8647,7 +8772,7 @@ function TextfieldEntry(props) {
8647
8772
  const newValidationError = validate(value) || null;
8648
8773
  setLocalError(newValidationError);
8649
8774
  }
8650
- }, [value]);
8775
+ }, [value, validate]);
8651
8776
  const onInput = newValue => {
8652
8777
  let newValidationError = null;
8653
8778
  if (isFunction(validate)) {
@@ -8737,10 +8862,9 @@ var index = {
8737
8862
  * @returns {any}
8738
8863
  */
8739
8864
  function getService(type, strict) {}
8740
- const PropertiesPanelContext = createContext({
8865
+ const FormPropertiesPanelContext = createContext({
8741
8866
  getService
8742
8867
  });
8743
- var FormPropertiesPanelContext = PropertiesPanelContext;
8744
8868
 
8745
8869
  function arrayAdd(array, index, item) {
8746
8870
  const copy = [...array];
@@ -8752,6 +8876,12 @@ function countDecimals(number) {
8752
8876
  if (num.toString() === num.toFixed(0)) return 0;
8753
8877
  return num.toFixed().split('.')[1].length || 0;
8754
8878
  }
8879
+
8880
+ /**
8881
+ *
8882
+ * @param {unknown} value
8883
+ * @returns {boolean}
8884
+ */
8755
8885
  function isValidNumber(value) {
8756
8886
  return (typeof value === 'number' || typeof value === 'string') && value !== '' && !isNaN(Number(value));
8757
8887
  }
@@ -8774,6 +8904,7 @@ function textToLabel(text) {
8774
8904
  function isValidDotPath(path) {
8775
8905
  return /^\w+(\.\w+)*$/.test(path);
8776
8906
  }
8907
+ const LABELED_NON_INPUTS = ['button', 'group', 'dynamiclist', 'iframe', 'table'];
8777
8908
  const INPUTS = ['checkbox', 'checklist', 'datetime', 'number', 'radio', 'select', 'taglist', 'textfield', 'textarea'];
8778
8909
  const OPTIONS_INPUTS = ['checklist', 'radio', 'select', 'taglist'];
8779
8910
  function hasEntryConfigured(formFieldDefinition, entryId) {
@@ -8802,7 +8933,7 @@ function hasIntegerPathSegment(path) {
8802
8933
  return path.split('.').some(segment => /^\d+$/.test(segment));
8803
8934
  }
8804
8935
 
8805
- function useService (type, strict) {
8936
+ function useService(type, strict) {
8806
8937
  const {
8807
8938
  getService
8808
8939
  } = useContext(FormPropertiesPanelContext);
@@ -8820,12 +8951,13 @@ function useVariables() {
8820
8951
  return getSchemaVariables(schema);
8821
8952
  }
8822
8953
 
8954
+ const headerlessTypes = ['spacer', 'separator', 'html'];
8823
8955
  const PropertiesPanelHeaderProvider = {
8824
8956
  getElementLabel: field => {
8825
8957
  const {
8826
8958
  type
8827
8959
  } = field;
8828
- if (type === 'spacer') {
8960
+ if (headerlessTypes.includes(type)) {
8829
8961
  return '';
8830
8962
  }
8831
8963
  if (type === 'text') {
@@ -8894,7 +9026,7 @@ const PropertiesPanelPlaceholderProvider = {
8894
9026
  }
8895
9027
  };
8896
9028
 
8897
- function FormPropertiesPanel(props) {
9029
+ function PropertiesPanel(props) {
8898
9030
  const {
8899
9031
  eventBus,
8900
9032
  getProviders,
@@ -8964,7 +9096,7 @@ function FormPropertiesPanel(props) {
8964
9096
  onBlurCapture: onBlur,
8965
9097
  children: jsx(FormPropertiesPanelContext.Provider, {
8966
9098
  value: propertiesPanelContext,
8967
- children: jsx(PropertiesPanel, {
9099
+ children: jsx(PropertiesPanel$1, {
8968
9100
  element: selectedFormField,
8969
9101
  eventBus: eventBus,
8970
9102
  groups: groups,
@@ -8980,7 +9112,7 @@ const DEFAULT_PRIORITY = 1000;
8980
9112
 
8981
9113
  /**
8982
9114
  * @typedef { { parent: Element } } PropertiesPanelConfig
8983
- * @typedef { import('../../core/EventBus').default } EventBus
9115
+ * @typedef { import('../../core/EventBus').EventBus } EventBus
8984
9116
  * @typedef { import('../../types').Injector } Injector
8985
9117
  * @typedef { { getGroups: ({ formField, editFormField }) => ({ groups}) => Array } } PropertiesProvider
8986
9118
  */
@@ -9040,7 +9172,7 @@ class PropertiesPanelRenderer {
9040
9172
  }
9041
9173
  }
9042
9174
  _render() {
9043
- render(jsx(FormPropertiesPanel, {
9175
+ render(jsx(PropertiesPanel, {
9044
9176
  getProviders: this._getProviders.bind(this),
9045
9177
  eventBus: this._eventBus,
9046
9178
  injector: this._injector
@@ -9200,9 +9332,9 @@ function Columns(props) {
9200
9332
  } = props;
9201
9333
  const debounce = useService('debounce');
9202
9334
  const formLayoutValidator = useService('formLayoutValidator');
9203
- const validate = value => {
9335
+ const validate = useCallback(value => {
9204
9336
  return formLayoutValidator.validateField(field, value ? parseInt(value) : null);
9205
- };
9337
+ }, [field, formLayoutValidator]);
9206
9338
  const setValue = (value, error) => {
9207
9339
  if (error) {
9208
9340
  return;
@@ -9295,7 +9427,7 @@ function Description(props) {
9295
9427
  }
9296
9428
 
9297
9429
  const EMPTY_OPTION = null;
9298
- function DefaultOptionEntry(props) {
9430
+ function DefaultValueEntry(props) {
9299
9431
  const {
9300
9432
  editField,
9301
9433
  field
@@ -9313,26 +9445,26 @@ function DefaultOptionEntry(props) {
9313
9445
  return matchers(field);
9314
9446
  };
9315
9447
  }
9316
- const defaultOptions = {
9448
+ const defaulValueBase = {
9317
9449
  editField,
9318
9450
  field,
9319
9451
  id: 'defaultValue',
9320
9452
  label: 'Default value'
9321
9453
  };
9322
9454
  entries.push({
9323
- ...defaultOptions,
9455
+ ...defaulValueBase,
9324
9456
  component: DefaultValueCheckbox,
9325
9457
  isEdited: isEdited$3,
9326
9458
  isDefaultVisible: isDefaultVisible(field => field.type === 'checkbox')
9327
9459
  });
9328
9460
  entries.push({
9329
- ...defaultOptions,
9461
+ ...defaulValueBase,
9330
9462
  component: DefaultValueNumber,
9331
9463
  isEdited: isEdited,
9332
9464
  isDefaultVisible: isDefaultVisible(field => field.type === 'number')
9333
9465
  });
9334
9466
  entries.push({
9335
- ...defaultOptions,
9467
+ ...defaulValueBase,
9336
9468
  component: DefaultValueSingleSelect,
9337
9469
  isEdited: isEdited$3,
9338
9470
  isDefaultVisible: isDefaultVisible(field => field.type === 'radio' || field.type === 'select')
@@ -9341,13 +9473,13 @@ function DefaultOptionEntry(props) {
9341
9473
  // todo(Skaiir): implement a multiselect equivalent (cf. https://github.com/bpmn-io/form-js/issues/265)
9342
9474
 
9343
9475
  entries.push({
9344
- ...defaultOptions,
9476
+ ...defaulValueBase,
9345
9477
  component: DefaultValueTextfield,
9346
9478
  isEdited: isEdited,
9347
9479
  isDefaultVisible: isDefaultVisible(field => field.type === 'textfield')
9348
9480
  });
9349
9481
  entries.push({
9350
- ...defaultOptions,
9482
+ ...defaulValueBase,
9351
9483
  component: DefaultValueTextarea,
9352
9484
  isEdited: isEdited$1,
9353
9485
  isDefaultVisible: isDefaultVisible(field => field.type === 'textarea')
@@ -9420,6 +9552,17 @@ function DefaultValueNumber(props) {
9420
9552
  return editField(field, path, newValue);
9421
9553
  };
9422
9554
  const decimalDigitsSet = decimalDigits || decimalDigits === 0;
9555
+ const validate = useCallback(value => {
9556
+ if (value === undefined || value === null) {
9557
+ return;
9558
+ }
9559
+ if (!isValidNumber(value)) {
9560
+ return 'Should be a valid number';
9561
+ }
9562
+ if (decimalDigitsSet && countDecimals(value) > decimalDigits) {
9563
+ return `Should not contain more than ${decimalDigits} decimal digits`;
9564
+ }
9565
+ }, [decimalDigitsSet, decimalDigits]);
9423
9566
  return TextfieldEntry({
9424
9567
  debounce,
9425
9568
  label,
@@ -9427,11 +9570,7 @@ function DefaultValueNumber(props) {
9427
9570
  getValue,
9428
9571
  id,
9429
9572
  setValue,
9430
- validate: value => {
9431
- if (value === undefined || value === null) return;
9432
- if (!isValidNumber(value)) return 'Should be a valid number';
9433
- if (decimalDigitsSet && countDecimals(value) > decimalDigits) return `Should not contain more than ${decimalDigits} decimal digits`;
9434
- }
9573
+ validate
9435
9574
  });
9436
9575
  }
9437
9576
  function DefaultValueSingleSelect(props) {
@@ -9605,8 +9744,8 @@ function Id(props) {
9605
9744
  }
9606
9745
  return editField(field, path, value);
9607
9746
  };
9608
- const validate = value => {
9609
- if (isUndefined(value) || !value.length) {
9747
+ const validate = useCallback(value => {
9748
+ if (typeof value !== 'string' || value.length === 0) {
9610
9749
  return 'Must not be empty.';
9611
9750
  }
9612
9751
  const assigned = formFieldRegistry._ids.assigned(value);
@@ -9614,7 +9753,7 @@ function Id(props) {
9614
9753
  return 'Must be unique.';
9615
9754
  }
9616
9755
  return validateId(value) || null;
9617
- };
9756
+ }, [formFieldRegistry, field]);
9618
9757
  return TextfieldEntry({
9619
9758
  debounce,
9620
9759
  element: field,
@@ -9691,7 +9830,7 @@ function Key$2(props) {
9691
9830
  }
9692
9831
  return editField(field, path, value);
9693
9832
  };
9694
- const validate = value => {
9833
+ const validate = useCallback(value => {
9695
9834
  if (value === field.key) {
9696
9835
  return null;
9697
9836
  }
@@ -9723,7 +9862,7 @@ function Key$2(props) {
9723
9862
  claimerId: field.id
9724
9863
  });
9725
9864
  return canClaim ? null : 'Must not conflict with other key/path assignments.';
9726
- };
9865
+ }, [field, pathRegistry]);
9727
9866
  return TextfieldEntry({
9728
9867
  debounce,
9729
9868
  description: 'Binds to a form variable',
@@ -9779,7 +9918,7 @@ function Path(props) {
9779
9918
  }
9780
9919
  return editField(field, path, value);
9781
9920
  };
9782
- const validate = value => {
9921
+ const validate = useCallback(value => {
9783
9922
  if (!value && isRepeating) {
9784
9923
  return 'Must not be empty';
9785
9924
  }
@@ -9825,7 +9964,7 @@ function Path(props) {
9825
9964
 
9826
9965
  // If all checks pass
9827
9966
  return null;
9828
- };
9967
+ }, [field, isRepeating, pathRegistry]);
9829
9968
  const tooltip = isRepeating ? 'Routes the children of this component into a form variable, may be left empty to route at the root level.' : 'Routes the children of this component into a form variable.';
9830
9969
  return TextfieldEntry({
9831
9970
  debounce,
@@ -9943,8 +10082,7 @@ function simpleRangeIntegerEntryFactory(options) {
9943
10082
  path,
9944
10083
  props,
9945
10084
  min,
9946
- max,
9947
- defaultValue
10085
+ max
9948
10086
  } = options;
9949
10087
  const {
9950
10088
  editField,
@@ -9958,9 +10096,8 @@ function simpleRangeIntegerEntryFactory(options) {
9958
10096
  editField,
9959
10097
  min,
9960
10098
  max,
9961
- defaultValue,
9962
10099
  component: SimpleRangeIntegerEntry,
9963
- isEdited: isEdited$7
10100
+ isEdited: isEdited
9964
10101
  };
9965
10102
  }
9966
10103
  const SimpleRangeIntegerEntry = props => {
@@ -9970,28 +10107,43 @@ const SimpleRangeIntegerEntry = props => {
9970
10107
  path,
9971
10108
  field,
9972
10109
  editField,
9973
- min,
9974
- max,
9975
- defaultValue
10110
+ min = Number.MIN_SAFE_INTEGER,
10111
+ max = Number.MAX_SAFE_INTEGER
9976
10112
  } = props;
9977
10113
  const debounce = useService('debounce');
9978
10114
  const getValue = () => {
9979
- const value = get(field, path, defaultValue);
9980
- return Number.isInteger(value) ? value : defaultValue;
10115
+ const value = get(field, path);
10116
+ const isValid = isValidNumber(value) && Number.isInteger(value);
10117
+ return isValid ? value : null;
9981
10118
  };
9982
- const setValue = value => {
9983
- editField(field, path, value);
10119
+ const setValue = (value, error) => {
10120
+ if (error) {
10121
+ return;
10122
+ }
10123
+ editField(field, path, Number(value));
9984
10124
  };
9985
- return NumberFieldEntry({
10125
+ const validate = useCallback(value => {
10126
+ if (value === undefined || value === null || value === '') {
10127
+ return;
10128
+ }
10129
+ if (!Number.isInteger(Number(value))) {
10130
+ return 'Should be an integer.';
10131
+ }
10132
+ if (Big(value).cmp(min) < 0) {
10133
+ return `Should be at least ${min}.`;
10134
+ }
10135
+ if (Big(value).cmp(max) > 0) {
10136
+ return `Should be at most ${max}.`;
10137
+ }
10138
+ }, [min, max]);
10139
+ return TextfieldEntry({
9986
10140
  debounce,
9987
10141
  label,
9988
10142
  element: field,
9989
- step: 1,
9990
- min,
9991
- max,
9992
10143
  getValue,
9993
10144
  id,
9994
- setValue
10145
+ setValue,
10146
+ validate
9995
10147
  });
9996
10148
  };
9997
10149
 
@@ -10040,13 +10192,16 @@ function LabelEntry(props) {
10040
10192
  return field.type === 'datetime' && (field.subtype === DATETIME_SUBTYPES.TIME || field.subtype === DATETIME_SUBTYPES.DATETIME);
10041
10193
  }
10042
10194
  });
10195
+ const isSimplyLabled = field => {
10196
+ return [...INPUTS.filter(input => input !== 'datetime'), ...LABELED_NON_INPUTS].includes(field.type);
10197
+ };
10043
10198
  entries.push({
10044
10199
  id: 'label',
10045
10200
  component: Label$2,
10046
10201
  editField,
10047
10202
  field,
10048
10203
  isEdited: isEdited$6,
10049
- isDefaultVisible: field => [...INPUTS, 'button', 'group', 'table', 'iframe', 'dynamiclist'].includes(field.type)
10204
+ isDefaultVisible: isSimplyLabled
10050
10205
  });
10051
10206
  return entries;
10052
10207
  }
@@ -10207,14 +10362,28 @@ function Height(props) {
10207
10362
  id,
10208
10363
  getValue,
10209
10364
  setValue,
10210
- validate: value => {
10211
- if (value === undefined || value === null) return;
10212
- if (value < 1) return 'Should be greater than zero.';
10213
- if (!Number.isInteger(value)) return 'Should be an integer.';
10214
- }
10365
+ validate: validate$7
10215
10366
  });
10216
10367
  }
10217
10368
 
10369
+ // helpers //////////
10370
+
10371
+ /**
10372
+ * @param {number|void} value
10373
+ * @returns {string|null}
10374
+ */
10375
+ const validate$7 = value => {
10376
+ if (typeof value !== 'number') {
10377
+ return null;
10378
+ }
10379
+ if (!Number.isInteger(value)) {
10380
+ return 'Should be an integer.';
10381
+ }
10382
+ if (value < 1) {
10383
+ return 'Should be greater than zero.';
10384
+ }
10385
+ };
10386
+
10218
10387
  function IFrameHeightEntry(props) {
10219
10388
  return [...HeightEntry({
10220
10389
  ...props,
@@ -10259,14 +10428,6 @@ function Url(props) {
10259
10428
  const setValue = value => {
10260
10429
  return editField(field, path, value);
10261
10430
  };
10262
- const validate = value => {
10263
- if (!value) {
10264
- return;
10265
- }
10266
- if (!HTTPS_PATTERN.test(value)) {
10267
- return 'For security reasons the URL must start with "https".';
10268
- }
10269
- };
10270
10431
  return FeelTemplatingEntry({
10271
10432
  debounce,
10272
10433
  element: field,
@@ -10276,15 +10437,15 @@ function Url(props) {
10276
10437
  label: 'URL',
10277
10438
  setValue,
10278
10439
  singleLine: true,
10279
- tooltip: getTooltip(),
10280
- validate,
10440
+ tooltip: getTooltip$1(),
10441
+ validate: validate$6,
10281
10442
  variables
10282
10443
  });
10283
10444
  }
10284
10445
 
10285
10446
  // helper //////////////////////
10286
10447
 
10287
- function getTooltip() {
10448
+ function getTooltip$1() {
10288
10449
  return jsxs(Fragment$1, {
10289
10450
  children: [jsx("p", {
10290
10451
  children: "Enter a HTTPS URL to a source or populate it dynamically via a template or an expression (e.g., to pass a value from the variable)."
@@ -10300,7 +10461,20 @@ function getTooltip() {
10300
10461
  });
10301
10462
  }
10302
10463
 
10303
- function SourceEntry(props) {
10464
+ /**
10465
+ * @param {string|void} value
10466
+ * @returns {string|null}
10467
+ */
10468
+ const validate$6 = value => {
10469
+ if (!value || value.startsWith('=')) {
10470
+ return;
10471
+ }
10472
+ if (!HTTPS_PATTERN.test(value)) {
10473
+ return 'For security reasons the URL must start with "https".';
10474
+ }
10475
+ };
10476
+
10477
+ function ImageSourceEntry(props) {
10304
10478
  const {
10305
10479
  editField,
10306
10480
  field
@@ -10380,16 +10554,9 @@ function Text(props) {
10380
10554
  const setValue = value => {
10381
10555
  return editField(field, path, value || '');
10382
10556
  };
10383
- const description = useMemo(() => jsxs(Fragment$1, {
10384
- children: ["Supports markdown and templating. ", jsx("a", {
10385
- href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-text/",
10386
- target: "_blank",
10387
- children: "Learn more"
10388
- })]
10389
- }), []);
10390
10557
  return FeelTemplatingEntry({
10391
10558
  debounce,
10392
- description,
10559
+ description: description$1,
10393
10560
  element: field,
10394
10561
  getValue,
10395
10562
  id,
@@ -10399,6 +10566,85 @@ function Text(props) {
10399
10566
  variables
10400
10567
  });
10401
10568
  }
10569
+ const description$1 = jsxs(Fragment$1, {
10570
+ children: ["Supports markdown and templating. ", jsx("a", {
10571
+ href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-text/",
10572
+ target: "_blank",
10573
+ children: "Learn more"
10574
+ })]
10575
+ });
10576
+
10577
+ function HtmlEntry(props) {
10578
+ const {
10579
+ editField,
10580
+ field
10581
+ } = props;
10582
+ const entries = [{
10583
+ id: 'content',
10584
+ component: Content,
10585
+ editField: editField,
10586
+ field: field,
10587
+ isEdited: isEdited$6,
10588
+ isDefaultVisible: field => field.type === 'html'
10589
+ }];
10590
+ return entries;
10591
+ }
10592
+ function Content(props) {
10593
+ const {
10594
+ editField,
10595
+ field,
10596
+ id
10597
+ } = props;
10598
+ const debounce = useService('debounce');
10599
+ const variables = useVariables().map(name => ({
10600
+ name
10601
+ }));
10602
+ const path = ['content'];
10603
+ const getValue = () => {
10604
+ return get(field, path, '');
10605
+ };
10606
+ const setValue = value => {
10607
+ return editField(field, path, value || '');
10608
+ };
10609
+ return FeelTemplatingEntry({
10610
+ debounce,
10611
+ description,
10612
+ element: field,
10613
+ getValue,
10614
+ id,
10615
+ label: 'Content',
10616
+ hostLanguage: 'html',
10617
+ validate: validate$5,
10618
+ setValue,
10619
+ variables
10620
+ });
10621
+ }
10622
+
10623
+ // helpers //////////
10624
+
10625
+ const description = jsxs(Fragment$1, {
10626
+ children: ["Supports HTML, styling, and templating. Styles are automatically scoped to the HTML component. ", jsx("a", {
10627
+ href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-html/",
10628
+ target: "_blank",
10629
+ children: "Learn more"
10630
+ })]
10631
+ });
10632
+
10633
+ /**
10634
+ * @param {string|void} value
10635
+ * @returns {string|null}
10636
+ */
10637
+ const validate$5 = value => {
10638
+ // allow empty state
10639
+ if (typeof value !== 'string' || value === '') {
10640
+ return null;
10641
+ }
10642
+
10643
+ // allow expressions
10644
+ if (value.startsWith('=')) {
10645
+ return null;
10646
+ }
10647
+ };
10402
10648
 
10403
10649
  function NumberEntries(props) {
10404
10650
  const {
@@ -10447,11 +10693,7 @@ function NumberDecimalDigits(props) {
10447
10693
  getValue,
10448
10694
  id,
10449
10695
  setValue,
10450
- validate: value => {
10451
- if (value === undefined || value === null) return;
10452
- if (value < 0) return 'Should be greater than or equal to zero.';
10453
- if (!Number.isInteger(value)) return 'Should be an integer.';
10454
- }
10696
+ validate: validateNumberEntries
10455
10697
  });
10456
10698
  }
10457
10699
  function NumberArrowStep(props) {
@@ -10481,6 +10723,26 @@ function NumberArrowStep(props) {
10481
10723
  editField(field, ['increment'], clearLeadingZeroes(value));
10482
10724
  };
10483
10725
  const decimalDigitsSet = decimalDigits || decimalDigits === 0;
10726
+ const validate = useCallback(value => {
10727
+ if (value === undefined || value === null) {
10728
+ return;
10729
+ }
10730
+ if (!isValidNumber(value)) {
10731
+ return 'Should be a valid number.';
10732
+ }
10733
+ if (Big(value).cmp(0) <= 0) {
10734
+ return 'Should be greater than zero.';
10735
+ }
10736
+ if (decimalDigitsSet) {
10737
+ const minimumValue = Big(`1e-${decimalDigits}`);
10738
+ if (Big(value).cmp(minimumValue) < 0) {
10739
+ return `Should be at least ${minimumValue.toString()}.`;
10740
+ }
10741
+ if (countDecimals(value) > decimalDigits) {
10742
+ return `Should not contain more than ${decimalDigits} decimal digits.`;
10743
+ }
10744
+ }
10745
+ }, [decimalDigitsSet, decimalDigits]);
10484
10746
  return TextfieldEntry({
10485
10747
  debounce,
10486
10748
  label: 'Increment',
@@ -10488,19 +10750,28 @@ function NumberArrowStep(props) {
10488
10750
  getValue,
10489
10751
  id,
10490
10752
  setValue,
10491
- validate: value => {
10492
- if (value === undefined || value === null) return;
10493
- if (!isValidNumber(value)) return 'Should be a valid number.';
10494
- if (Big(value).cmp(0) <= 0) return 'Should be greater than zero.';
10495
- if (decimalDigitsSet) {
10496
- const minimumValue = Big(`1e-${decimalDigits}`);
10497
- if (Big(value).cmp(minimumValue) < 0) return `Should be at least ${minimumValue.toString()}.`;
10498
- if (countDecimals(value) > decimalDigits) return `Should not contain more than ${decimalDigits} decimal digits.`;
10499
- }
10500
- }
10753
+ validate
10501
10754
  });
10502
10755
  }
10503
10756
 
10757
+ // helpers //////////
10758
+
10759
+ /**
10760
+ * @param {number|void} value
10761
+ * @returns {string|void}
10762
+ */
10763
+ const validateNumberEntries = value => {
10764
+ if (typeof value !== 'number') {
10765
+ return;
10766
+ }
10767
+ if (!Number.isInteger(value)) {
10768
+ return 'Should be an integer.';
10769
+ }
10770
+ if (value < 0) {
10771
+ return 'Should be greater than or equal to zero.';
10772
+ }
10773
+ };
10774
+
10504
10775
  function NumberSerializationEntry(props) {
10505
10776
  const {
10506
10777
  editField,
@@ -10825,6 +11096,7 @@ function Label$1(props) {
10825
11096
  const getValue = () => {
10826
11097
  return get(field, ['values', index, 'label']);
10827
11098
  };
11099
+ const validate = useMemo(() => validateFactory(get(field, ['values', index, 'label']), entry => entry.label), [field, index, validateFactory]);
10828
11100
  return TextfieldEntry({
10829
11101
  debounce,
10830
11102
  element: field,
@@ -10832,7 +11104,7 @@ function Label$1(props) {
10832
11104
  id,
10833
11105
  label: 'Label',
10834
11106
  setValue,
10835
- validate: validateFactory(getValue(), entry => entry.label)
11107
+ validate
10836
11108
  });
10837
11109
  }
10838
11110
  function Value$1(props) {
@@ -10854,6 +11126,7 @@ function Value$1(props) {
10854
11126
  const getValue = () => {
10855
11127
  return get(field, ['values', index, 'value']);
10856
11128
  };
11129
+ const validate = useMemo(() => validateFactory(get(field, ['values', index, 'value']), entry => entry.value), [field, index, validateFactory]);
10857
11130
  return TextfieldEntry({
10858
11131
  debounce,
10859
11132
  element: field,
@@ -10861,7 +11134,7 @@ function Value$1(props) {
10861
11134
  id,
10862
11135
  label: 'Value',
10863
11136
  setValue,
10864
- validate: validateFactory(getValue(), entry => entry.value)
11137
+ validate
10865
11138
  });
10866
11139
  }
10867
11140
 
@@ -10912,6 +11185,7 @@ function Key$1(props) {
10912
11185
  const getValue = () => {
10913
11186
  return Object.keys(get(field, ['properties']))[index];
10914
11187
  };
11188
+ const validate = useMemo(() => validateFactory(Object.keys(get(field, ['properties']))[index]), [validateFactory, field, index]);
10915
11189
  return TextfieldEntry({
10916
11190
  debounce,
10917
11191
  element: field,
@@ -10919,7 +11193,7 @@ function Key$1(props) {
10919
11193
  id,
10920
11194
  label: 'Key',
10921
11195
  setValue,
10922
- validate: validateFactory(getValue())
11196
+ validate
10923
11197
  });
10924
11198
  }
10925
11199
  function Value(props) {
@@ -11113,15 +11387,6 @@ function InputValuesKey(props) {
11113
11387
  }
11114
11388
  editField(field, path, value || '');
11115
11389
  };
11116
- const validate = value => {
11117
- if (isUndefined(value) || !value.length) {
11118
- return 'Must not be empty.';
11119
- }
11120
- if (/\s/.test(value)) {
11121
- return 'Must not contain spaces.';
11122
- }
11123
- return null;
11124
- };
11125
11390
  return TextfieldEntry({
11126
11391
  debounce,
11127
11392
  description: 'Define which input property to populate the values from',
@@ -11131,10 +11396,26 @@ function InputValuesKey(props) {
11131
11396
  id,
11132
11397
  label: 'Input values key',
11133
11398
  setValue,
11134
- validate
11399
+ validate: validate$4
11135
11400
  });
11136
11401
  }
11137
11402
 
11403
+ // helpers //////////
11404
+
11405
+ /**
11406
+ * @param {string|void} value
11407
+ * @returns {string|null}
11408
+ */
11409
+ const validate$4 = value => {
11410
+ if (typeof value !== 'string' || value.length === 0) {
11411
+ return 'Must not be empty.';
11412
+ }
11413
+ if (/\s/.test(value)) {
11414
+ return 'Must not contain spaces.';
11415
+ }
11416
+ return null;
11417
+ };
11418
+
11138
11419
  function StaticOptionsSourceEntry(props) {
11139
11420
  const {
11140
11421
  editField,
@@ -11158,7 +11439,7 @@ function StaticOptionsSourceEntry(props) {
11158
11439
  if (value === key) {
11159
11440
  return;
11160
11441
  }
11161
- if (isUndefined(value) || !value.length) {
11442
+ if (typeof value !== 'string' || value.length === 0) {
11162
11443
  return 'Must not be empty.';
11163
11444
  }
11164
11445
  const isValueAssigned = values.find(entry => getValue(entry) === value);
@@ -11383,7 +11664,7 @@ function RepeatableEntry(props) {
11383
11664
  id: 'defaultRepetitions',
11384
11665
  path: ['defaultRepetitions'],
11385
11666
  label: 'Default number of items',
11386
- min: 0,
11667
+ min: 1,
11387
11668
  max: 20,
11388
11669
  props
11389
11670
  }), simpleBoolEntryFactory({
@@ -11544,26 +11825,6 @@ function Source(props) {
11544
11825
  }
11545
11826
  editField(field, path, value);
11546
11827
  };
11547
-
11548
- /**
11549
- * @param {string|void} value
11550
- * @returns {string|null}
11551
- */
11552
- const validate = value => {
11553
- if (!isString(value) || value.length === 0) {
11554
- return 'Must not be empty.';
11555
- }
11556
- if (value.startsWith('=')) {
11557
- return null;
11558
- }
11559
- if (!isValidDotPath(value)) {
11560
- return 'Must be a variable or a dot separated path.';
11561
- }
11562
- if (hasIntegerPathSegment(value)) {
11563
- return 'Must not contain numerical path segments.';
11564
- }
11565
- return null;
11566
- };
11567
11828
  return FeelTemplatingEntry({
11568
11829
  debounce,
11569
11830
  description: 'Specify the source from which to populate the table',
@@ -11576,10 +11837,32 @@ function Source(props) {
11576
11837
  setValue,
11577
11838
  singleLine: true,
11578
11839
  variables,
11579
- validate
11840
+ validate: validate$3
11580
11841
  });
11581
11842
  }
11582
11843
 
11844
+ // helper ////////////////
11845
+
11846
+ /**
11847
+ * @param {string|void} value
11848
+ * @returns {string|null}
11849
+ */
11850
+ const validate$3 = value => {
11851
+ if (!isString(value) || value.length === 0) {
11852
+ return 'Must not be empty.';
11853
+ }
11854
+ if (value.startsWith('=')) {
11855
+ return null;
11856
+ }
11857
+ if (!isValidDotPath(value)) {
11858
+ return 'Must be a variable or a dot separated path.';
11859
+ }
11860
+ if (hasIntegerPathSegment(value)) {
11861
+ return 'Must not contain numerical path segments.';
11862
+ }
11863
+ return null;
11864
+ };
11865
+
11583
11866
  function PaginationEntry(props) {
11584
11867
  const {
11585
11868
  editField,
@@ -11661,26 +11944,6 @@ function RowCount(props) {
11661
11944
  }
11662
11945
  editField(field, path$2, value);
11663
11946
  };
11664
-
11665
- /**
11666
- * @param {string|void} value
11667
- * @returns {string|null}
11668
- */
11669
- const validate = value => {
11670
- if (isNil(value)) {
11671
- return null;
11672
- }
11673
- if (!isNumber(value)) {
11674
- return 'Must be number';
11675
- }
11676
- if (!Number.isInteger(value)) {
11677
- return 'Should be an integer.';
11678
- }
11679
- if (value < 1) {
11680
- return 'Should be greater than zero.';
11681
- }
11682
- return null;
11683
- };
11684
11947
  return NumberFieldEntry({
11685
11948
  debounce,
11686
11949
  label: 'Number of rows per page',
@@ -11688,10 +11951,32 @@ function RowCount(props) {
11688
11951
  id,
11689
11952
  getValue,
11690
11953
  setValue,
11691
- validate
11954
+ validate: validate$2
11692
11955
  });
11693
11956
  }
11694
11957
 
11958
+ // helpers //////////
11959
+
11960
+ /**
11961
+ * @param {string|void} value
11962
+ * @returns {string|null}
11963
+ */
11964
+ const validate$2 = value => {
11965
+ if (isNil(value)) {
11966
+ return null;
11967
+ }
11968
+ if (!isNumber(value)) {
11969
+ return 'Must be number';
11970
+ }
11971
+ if (!Number.isInteger(value)) {
11972
+ return 'Should be an integer.';
11973
+ }
11974
+ if (value < 1) {
11975
+ return 'Should be greater than zero.';
11976
+ }
11977
+ return null;
11978
+ };
11979
+
11695
11980
  const OPTIONS = {
11696
11981
  static: {
11697
11982
  label: 'List of items',
@@ -11830,17 +12115,6 @@ function ColumnsExpression(props) {
11830
12115
  }
11831
12116
  editField(field, PATH, value);
11832
12117
  };
11833
-
11834
- /**
11835
- * @param {string|void} value
11836
- * @returns {string|null}
11837
- */
11838
- const validate = value => {
11839
- if (!isString(value) || value.length === 0 || value === '=') {
11840
- return 'Must not be empty.';
11841
- }
11842
- return null;
11843
- };
11844
12118
  const schema = '[\n {\n "key": "column_1",\n "label": "Column 1"\n }\n]';
11845
12119
  const tooltip = jsxs("div", {
11846
12120
  children: ["The expression may result in an array of simple values or alternatively follow this schema:", jsx("pre", {
@@ -11861,10 +12135,23 @@ function ColumnsExpression(props) {
11861
12135
  setValue,
11862
12136
  singleLine: true,
11863
12137
  variables,
11864
- validate
12138
+ validate: validate$1
11865
12139
  });
11866
12140
  }
11867
12141
 
12142
+ // helpers //////////
12143
+
12144
+ /**
12145
+ * @param {string|void} value
12146
+ * @returns {string|null}
12147
+ */
12148
+ const validate$1 = value => {
12149
+ if (!isString(value) || value.length === 0 || value === '=') {
12150
+ return 'Must not be empty.';
12151
+ }
12152
+ return null;
12153
+ };
12154
+
11868
12155
  const path$1 = 'columns';
11869
12156
  const labelPath = 'label';
11870
12157
  const keyPath = 'key';
@@ -11873,8 +12160,7 @@ function ColumnEntry(props) {
11873
12160
  editField,
11874
12161
  field,
11875
12162
  idPrefix,
11876
- index,
11877
- validateFactory
12163
+ index
11878
12164
  } = props;
11879
12165
  const entries = [{
11880
12166
  component: Label,
@@ -11882,16 +12168,14 @@ function ColumnEntry(props) {
11882
12168
  field,
11883
12169
  id: idPrefix + '-label',
11884
12170
  idPrefix,
11885
- index,
11886
- validateFactory
12171
+ index
11887
12172
  }, {
11888
12173
  component: Key,
11889
12174
  editField,
11890
12175
  field,
11891
12176
  id: idPrefix + '-key',
11892
12177
  idPrefix,
11893
- index,
11894
- validateFactory
12178
+ index
11895
12179
  }];
11896
12180
  return entries;
11897
12181
  }
@@ -12041,7 +12325,7 @@ function GeneralGroup(field, editField, getService) {
12041
12325
  field,
12042
12326
  editField,
12043
12327
  getService
12044
- }), ...DefaultOptionEntry({
12328
+ }), ...DefaultValueEntry({
12045
12329
  field,
12046
12330
  editField
12047
12331
  }), ...ActionEntry({
@@ -12054,6 +12338,10 @@ function GeneralGroup(field, editField, getService) {
12054
12338
  field,
12055
12339
  editField,
12056
12340
  getService
12341
+ }), ...HtmlEntry({
12342
+ field,
12343
+ editField,
12344
+ getService
12057
12345
  }), ...IFrameUrlEntry({
12058
12346
  field,
12059
12347
  editField
@@ -12066,7 +12354,7 @@ function GeneralGroup(field, editField, getService) {
12066
12354
  }), ...NumberEntries({
12067
12355
  field,
12068
12356
  editField
12069
- }), ...SourceEntry({
12357
+ }), ...ImageSourceEntry({
12070
12358
  field,
12071
12359
  editField
12072
12360
  }), ...AltTextEntry({
@@ -12476,7 +12764,7 @@ function CustomPropertiesGroup(field, editField) {
12476
12764
  if (value === key) {
12477
12765
  return;
12478
12766
  }
12479
- if (isUndefined(value) || !value.length) {
12767
+ if (typeof value !== 'string' || value.length === 0) {
12480
12768
  return 'Must not be empty.';
12481
12769
  }
12482
12770
  if (has(properties, value)) {
@@ -12580,6 +12868,75 @@ function LayoutGroup(field, editField) {
12580
12868
  };
12581
12869
  }
12582
12870
 
12871
+ function SecurityAttributesGroup(field, editField) {
12872
+ const {
12873
+ type
12874
+ } = field;
12875
+ if (type !== 'iframe') {
12876
+ return null;
12877
+ }
12878
+ const entries = createEntries({
12879
+ field,
12880
+ editField
12881
+ });
12882
+ if (!entries.length) {
12883
+ return null;
12884
+ }
12885
+ return {
12886
+ id: 'securityAttributes',
12887
+ label: 'Security attributes',
12888
+ entries,
12889
+ tooltip: getTooltip()
12890
+ };
12891
+ }
12892
+ function createEntries(props) {
12893
+ const {
12894
+ editField,
12895
+ field
12896
+ } = props;
12897
+ const securityEntries = SECURITY_ATTRIBUTES_DEFINITIONS.map(definition => {
12898
+ const {
12899
+ label,
12900
+ property
12901
+ } = definition;
12902
+ return simpleBoolEntryFactory({
12903
+ id: property,
12904
+ label: label,
12905
+ isDefaultVisible: field => field.type === 'iframe',
12906
+ path: ['security', property],
12907
+ props,
12908
+ getValue: () => get(field, ['security', property]),
12909
+ setValue: value => {
12910
+ const security = get(field, ['security'], {});
12911
+ editField(field, ['security'], set$1(security, [property], value));
12912
+ }
12913
+ });
12914
+ });
12915
+ return [{
12916
+ component: Advisory
12917
+ }, ...securityEntries];
12918
+ }
12919
+ const Advisory = props => {
12920
+ return jsx("div", {
12921
+ class: "bio-properties-panel-description fjs-properties-panel-detached-description",
12922
+ children: "These options can incur security risks, especially if used in combination with dynamic links. Ensure that you are aware of them, that you trust the source url and only enable what your use case requires."
12923
+ });
12924
+ };
12925
+
12926
+ // helpers //////////
12927
+
12928
+ function getTooltip() {
12929
+ return jsx(Fragment$1, {
12930
+ children: jsxs("p", {
12931
+ children: ["Allow the iframe to access more functionality of your browser, details regarding the various options can be found in the ", jsx("a", {
12932
+ target: "_blank",
12933
+ href: "https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe",
12934
+ children: "MDN iFrame documentation."
12935
+ })]
12936
+ })
12937
+ });
12938
+ }
12939
+
12583
12940
  function ConditionGroup(field, editField) {
12584
12941
  const {
12585
12942
  type
@@ -12682,7 +13039,7 @@ class PropertiesProvider {
12682
13039
  return groups;
12683
13040
  }
12684
13041
  const getService = (type, strict = true) => this._injector.get(type, strict);
12685
- groups = [...groups, GeneralGroup(field, editField, getService), ...TableHeaderGroups(field, editField), ConditionGroup(field, editField), LayoutGroup(field, editField), AppearanceGroup(field, editField), SerializationGroup(field, editField), ...OptionsGroups(field, editField, getService), ConstraintsGroup(field, editField), ValidationGroup(field, editField), CustomPropertiesGroup(field, editField)].filter(group => group != null);
13042
+ groups = [...groups, GeneralGroup(field, editField, getService), ...TableHeaderGroups(field, editField), SecurityAttributesGroup(field, editField), ConditionGroup(field, editField), LayoutGroup(field, editField), AppearanceGroup(field, editField), SerializationGroup(field, editField), ...OptionsGroups(field, editField, getService), ConstraintsGroup(field, editField), ValidationGroup(field, editField), CustomPropertiesGroup(field, editField)].filter(group => group != null);
12686
13043
  this._filterVisibleEntries(groups, field, getService);
12687
13044
 
12688
13045
  // contract: if a group has no entries or items, it should not be displayed at all
@@ -12694,7 +13051,7 @@ class PropertiesProvider {
12694
13051
  }
12695
13052
  PropertiesProvider.$inject = ['propertiesPanel', 'injector'];
12696
13053
 
12697
- var PropertiesPanelModule = {
13054
+ const PropertiesPanelModule = {
12698
13055
  __depends__: [index],
12699
13056
  __init__: ['propertiesPanel', 'propertiesProvider'],
12700
13057
  propertiesPanel: ['type', PropertiesPanelRenderer],
@@ -12743,7 +13100,7 @@ class RenderInjector extends SectionModuleBase {
12743
13100
  }
12744
13101
  RenderInjector.$inject = ['eventBus'];
12745
13102
 
12746
- var RenderInjectionModule = {
13103
+ const RenderInjectionModule = {
12747
13104
  __init__: ['renderInjector'],
12748
13105
  renderInjector: ['type', RenderInjector]
12749
13106
  };
@@ -12763,7 +13120,7 @@ var SvgRepeat = function SvgRepeat(props) {
12763
13120
  };
12764
13121
  var RepeatSvg = SvgRepeat;
12765
13122
 
12766
- class RepeatRenderManager {
13123
+ class EditorRepeatRenderManager {
12767
13124
  constructor(formFields, formFieldRegistry) {
12768
13125
  this._formFields = formFields;
12769
13126
  this._formFieldRegistry = formFieldRegistry;
@@ -12793,30 +13150,11 @@ class RepeatRenderManager {
12793
13150
  });
12794
13151
  }
12795
13152
  }
12796
- RepeatRenderManager.$inject = ['formFields', 'formFieldRegistry'];
13153
+ EditorRepeatRenderManager.$inject = ['formFields', 'formFieldRegistry'];
12797
13154
 
12798
- var RepeatRenderManagerModule = {
13155
+ const RepeatRenderModule = {
12799
13156
  __init__: ['repeatRenderManager'],
12800
- repeatRenderManager: ['type', RepeatRenderManager]
12801
- };
12802
-
12803
- class EditorTemplating {
12804
- // same rules as viewer templating
12805
- isTemplate(value) {
12806
- return isString(value) && (value.startsWith('=') || /{{/.test(value));
12807
- }
12808
-
12809
- // return the template raw, as we usually just want to display that
12810
- evaluate(template) {
12811
- return template;
12812
- }
12813
- }
12814
- EditorTemplating.$inject = [];
12815
-
12816
- var ExpressionLanguageModule = {
12817
- __init__: ['expressionLanguage', 'templating'],
12818
- expressionLanguage: ['type', FeelExpressionLanguage],
12819
- templating: ['type', EditorTemplating]
13157
+ repeatRenderManager: ['type', EditorRepeatRenderManager]
12820
13158
  };
12821
13159
 
12822
13160
  const ids = new Ids([32, 36, 1]);
@@ -13040,7 +13378,7 @@ class FormEditor {
13040
13378
  config: ['value', enrichedConfig]
13041
13379
  }, {
13042
13380
  formEditor: ['value', this]
13043
- }, core, ...modules, ...additionalModules]);
13381
+ }, CoreModule, ...modules, ...additionalModules]);
13044
13382
  }
13045
13383
 
13046
13384
  /**
@@ -13072,7 +13410,7 @@ class FormEditor {
13072
13410
  * @internal
13073
13411
  */
13074
13412
  _getModules() {
13075
- return [ModelingModule, EditorActionsModule, DraggingModule, KeyboardModule, SelectionModule, PaletteModule, ExpressionLanguageModule, MarkdownModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderManagerModule];
13413
+ return [ModelingModule, EditorActionsModule, FormEditorKeyboardModule, DraggingModule, SelectionModule, PaletteModule, EditorExpressionLanguageModule, MarkdownRendererModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderModule];
13076
13414
  }
13077
13415
 
13078
13416
  /**