@bpmn-io/form-js-viewer 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import Ids from 'ids';
2
2
  import { isString, isArray, isFunction, isNumber, bind, assign, isNil, groupBy, flatten, get, isUndefined, set, findIndex, isObject } from 'min-dash';
3
- import { evaluate, parseExpressions, parseUnaryTests, unaryTest } from 'feelin';
4
- import { evaluate as evaluate$1 } from 'feelers';
3
+ import { parseExpressions, parseUnaryTests, evaluate, unaryTest } from 'feelin';
4
+ import { evaluate as evaluate$1, parser, buildSimpleTree } from 'feelers';
5
5
  import showdown from 'showdown';
6
6
  import Big from 'big.js';
7
7
  import classNames from 'classnames';
@@ -13,30 +13,179 @@ import flatpickr from 'flatpickr';
13
13
  import Markup from 'preact-markup';
14
14
  import { Injector } from 'didi';
15
15
 
16
+ const getFlavouredFeelVariableNames = (feelString, feelFlavour, options = {}) => {
17
+ const {
18
+ depth = 0,
19
+ specialDepthAccessors = {}
20
+ } = options;
21
+ if (!['expression', 'unaryTest'].includes(feelFlavour)) return [];
22
+ const tree = feelFlavour === 'expression' ? parseExpressions(feelString) : parseUnaryTests(feelString);
23
+ const simpleExpressionTree = _buildSimpleFeelStructureTree(tree, feelString);
24
+ return function _unfoldVariables(node) {
25
+ if (node.name === 'PathExpression') {
26
+ if (Object.keys(specialDepthAccessors).length === 0) {
27
+ return depth === 0 ? [_getVariableNameAtPathIndex(node, 0)] : [];
28
+ }
29
+
30
+ // if using special depth accessors, use a more complex extraction
31
+ return Array.from(_smartExtractVariableNames(node, depth, specialDepthAccessors));
32
+ }
33
+ if (depth === 0 && node.name === 'VariableName') return [node.variableName];
34
+
35
+ // for any other kind of node, traverse its children and flatten the result
36
+ if (node.children) {
37
+ return node.children.reduce((acc, child) => {
38
+ return acc.concat(_unfoldVariables(child));
39
+ }, []);
40
+ }
41
+ return [];
42
+ }(simpleExpressionTree);
43
+ };
44
+
45
+ /**
46
+ * Get the variable name at the specified index in a given path expression.
47
+ *
48
+ * @param {Object} root - The root node of the path expression tree.
49
+ * @param {number} index - The index of the variable name to retrieve.
50
+ * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
51
+ */
52
+ const _getVariableNameAtPathIndex = (root, index) => {
53
+ const accessors = _deconstructPathExpression(root);
54
+ return accessors[index] || null;
55
+ };
56
+
57
+ /**
58
+ * Extracts the variables which are required of the external context for a given path expression.
59
+ * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
60
+ *
61
+ * @param {Object} node - The root node of the path expression tree.
62
+ * @param {number} initialDepth - The depth at which the root node is located in the outer context.
63
+ * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
64
+ * @returns {Set} - A set containing the extracted variable names.
65
+ */
66
+ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {
67
+ // depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
68
+ // we track multiple of these to account for the fact that a path expression may be ambiguous due to special keywords
69
+ let accessorDepthInfos = [{
70
+ previous: null,
71
+ current: initialDepth - 1
72
+ }];
73
+ const extractedVariables = new Set();
74
+ const nodeAccessors = _deconstructPathExpression(node);
75
+ for (let i = 0; i < nodeAccessors.length; i++) {
76
+ const currentAccessor = nodeAccessors[i];
77
+ if (currentAccessor in specialDepthAccessors) {
78
+ const depthOffsets = specialDepthAccessors[currentAccessor];
79
+
80
+ // if the current accessor is a special keyword, we need to expand the current depth info set
81
+ // this is done to account for the ambiguity of keywords like parent, which may be used to access
82
+ // the parent of the current node, or a child variable of the same name
83
+ accessorDepthInfos = depthOffsets.reduce((accumulator, offset) => {
84
+ return [...accumulator, ...accessorDepthInfos.map(depthInfo => ({
85
+ previous: depthInfo.current,
86
+ current: depthInfo.current + offset
87
+ }))];
88
+ }, []).filter(depthInfo => depthInfo.current >= -1); // discard all depth infos which are out of bounds
89
+ } else {
90
+ // if the current accessor is not a special keyword, we know it's simply accessing a child
91
+ // hence we are now one level deeper in the tree and simply increment
92
+ accessorDepthInfos = accessorDepthInfos.map(depthInfo => ({
93
+ previous: depthInfo.current,
94
+ current: depthInfo.current + 1
95
+ }));
96
+ }
97
+
98
+ // finally, we check if for the current accessor, there is a scenario where:
99
+ // previous it was at depth -1 (i.e. the root context), and is now at depth 0 (i.e. a variable)
100
+ // these are the variables we need to request, so we add them to the set
101
+ if (accessorDepthInfos.some(depthInfo => depthInfo.previous === -1 && depthInfo.current === 0)) {
102
+ extractedVariables.add(currentAccessor);
103
+ }
104
+ }
105
+
106
+ // we return a set to avoid duplicates
107
+ return new Set(extractedVariables);
108
+ };
109
+
110
+ /**
111
+ * Deconstructs a path expression tree into an array of components.
112
+ *
113
+ * @param {Object} root - The root node of the path expression tree.
114
+ * @returns {Array<string>} An array of components in the path expression, in the correct order.
115
+ */
116
+ const _deconstructPathExpression = root => {
117
+ let node = root;
118
+ let parts = [];
119
+
120
+ // Traverse the tree and collect path components
121
+ while (node.name === 'PathExpression') {
122
+ parts.push(node.children[1].variableName);
123
+ node = node.children[0];
124
+ }
125
+
126
+ // Add the last component to the array
127
+ parts.push(node.variableName);
128
+
129
+ // Reverse and return the array to get the correct order
130
+ return parts.reverse();
131
+ };
132
+
133
+ /**
134
+ * Builds a simplified feel structure tree from the given parse tree and feel string.
135
+ * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
136
+ *
137
+ * @param {Object} parseTree - The parse tree generated by a parser.
138
+ * @param {string} feelString - The feel string used for parsing.
139
+ * @returns {Object} The simplified feel structure tree.
140
+ */
141
+ const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
142
+ const stack = [{
143
+ children: []
144
+ }];
145
+ parseTree.iterate({
146
+ enter: node => {
147
+ const nodeRepresentation = {
148
+ name: node.type.name,
149
+ children: []
150
+ };
151
+ if (node.type.name === 'VariableName') {
152
+ nodeRepresentation.variableName = feelString.slice(node.from, node.to);
153
+ }
154
+ stack.push(nodeRepresentation);
155
+ },
156
+ leave: () => {
157
+ const result = stack.pop();
158
+ const parent = stack[stack.length - 1];
159
+ parent.children.push(result);
160
+ }
161
+ });
162
+ return stack[0].children[0];
163
+ };
164
+
16
165
  class FeelExpressionLanguage {
17
166
  constructor(eventBus) {
18
167
  this._eventBus = eventBus;
19
168
  }
20
169
 
21
- /**
22
- * Determines if the given string is a FEEL expression.
23
- *
24
- * @param {string} value
25
- * @returns {boolean}
26
- *
170
+ /**
171
+ * Determines if the given value is a FEEL expression.
172
+ *
173
+ * @param {any} value
174
+ * @returns {boolean}
175
+ *
27
176
  */
28
177
  isExpression(value) {
29
178
  return isString(value) && value.startsWith('=');
30
179
  }
31
180
 
32
- /**
33
- * Retrieve variable names from a given FEEL expression.
34
- *
35
- * @param {string} expression
36
- * @param {object} [options]
37
- * @param {string} [options.type]
38
- *
39
- * @returns {string[]}
181
+ /**
182
+ * Retrieve variable names from a given FEEL expression.
183
+ *
184
+ * @param {string} expression
185
+ * @param {object} [options]
186
+ * @param {string} [options.type]
187
+ *
188
+ * @returns {string[]}
40
189
  */
41
190
  getVariableNames(expression, options = {}) {
42
191
  const {
@@ -45,21 +194,19 @@ class FeelExpressionLanguage {
45
194
  if (!this.isExpression(expression)) {
46
195
  return [];
47
196
  }
48
- if (type === 'unaryTest') {
49
- return this._getUnaryVariableNames(expression);
50
- } else if (type === 'expression') {
51
- return this._getExpressionVariableNames(expression);
197
+ if (!['unaryTest', 'expression'].includes(type)) {
198
+ throw new Error('Unknown expression type: ' + type);
52
199
  }
53
- throw new Error('Unknown expression type: ' + options.type);
200
+ return getFlavouredFeelVariableNames(expression, type);
54
201
  }
55
202
 
56
- /**
57
- * Evaluate an expression.
58
- *
59
- * @param {string} expression
60
- * @param {import('../../types').Data} [data]
61
- *
62
- * @returns {any}
203
+ /**
204
+ * Evaluate an expression.
205
+ *
206
+ * @param {string} expression
207
+ * @param {import('../../types').Data} [data]
208
+ *
209
+ * @returns {any}
63
210
  */
64
211
  evaluate(expression, data = {}) {
65
212
  if (!expression) {
@@ -78,50 +225,65 @@ class FeelExpressionLanguage {
78
225
  return null;
79
226
  }
80
227
  }
81
- _getExpressionVariableNames(expression) {
82
- const tree = parseExpressions(expression);
83
- const cursor = tree.cursor();
84
- const variables = new Set();
85
- do {
86
- const node = cursor.node;
87
- if (node.type.name === 'VariableName') {
88
- variables.add(expression.slice(node.from, node.to));
89
- }
90
- } while (cursor.next());
91
- return Array.from(variables);
92
- }
93
- _getUnaryVariableNames(unaryTest) {
94
- const tree = parseUnaryTests(unaryTest);
95
- const cursor = tree.cursor();
96
- const variables = new Set();
97
- do {
98
- const node = cursor.node;
99
- if (node.type.name === 'VariableName') {
100
- variables.add(unaryTest.slice(node.from, node.to));
101
- }
102
- } while (cursor.next());
103
- return Array.from(variables);
104
- }
105
228
  }
106
229
  FeelExpressionLanguage.$inject = ['eventBus'];
107
230
 
108
231
  class FeelersTemplating {
109
232
  constructor() {}
233
+
234
+ /**
235
+ * Determines if the given value is a feelers template.
236
+ *
237
+ * @param {any} value
238
+ * @returns {boolean}
239
+ *
240
+ */
110
241
  isTemplate(value) {
111
- return isString(value) && (value.startsWith('=') || /{{/.test(value));
242
+ return isString(value) && (value.startsWith('=') || /{{.*?}}/.test(value));
112
243
  }
113
244
 
114
- /**
115
- * Evaluate a template.
116
- *
117
- * @param {string} template
118
- * @param {Object<string, any>} context
119
- * @param {Object} options
120
- * @param {boolean} [options.debug = false]
121
- * @param {boolean} [options.strict = false]
122
- * @param {Function} [options.buildDebugString]
123
- *
124
- * @returns
245
+ /**
246
+ * Retrieve variable names from a given feelers template.
247
+ *
248
+ * @param {string} template
249
+ *
250
+ * @returns {string[]}
251
+ */
252
+ getVariableNames(template) {
253
+ if (!this.isTemplate(template)) {
254
+ return [];
255
+ }
256
+ const expressions = this._extractExpressionsWithDepth(template);
257
+
258
+ // defines special accessors, and the change(s) in depth they could imply (e.g. parent can be used to access the parent context (depth - 1) or a child variable named parent (depth + 1)
259
+ const specialDepthAccessors = {
260
+ parent: [-1, 1],
261
+ _parent_: [-1],
262
+ this: [0, 1],
263
+ _this_: [0]
264
+ };
265
+ return expressions.reduce((variables, {
266
+ expression,
267
+ depth
268
+ }) => {
269
+ return variables.concat(getFlavouredFeelVariableNames(expression, 'expression', {
270
+ depth,
271
+ specialDepthAccessors
272
+ }));
273
+ }, []);
274
+ }
275
+
276
+ /**
277
+ * Evaluate a template.
278
+ *
279
+ * @param {string} template
280
+ * @param {Object<string, any>} context
281
+ * @param {Object} options
282
+ * @param {boolean} [options.debug = false]
283
+ * @param {boolean} [options.strict = false]
284
+ * @param {Function} [options.buildDebugString]
285
+ *
286
+ * @returns
125
287
  */
126
288
  evaluate(template, context = {}, options = {}) {
127
289
  const {
@@ -135,12 +297,56 @@ class FeelersTemplating {
135
297
  buildDebugString
136
298
  });
137
299
  }
300
+
301
+ /**
302
+ * @typedef {Object} ExpressionWithDepth
303
+ * @property {number} depth - The depth of the expression in the syntax tree.
304
+ * @property {string} expression - The extracted expression
305
+ */
306
+
307
+ /**
308
+ * Extracts all feel expressions in the template along with their depth in the syntax tree.
309
+ * The depth is incremented for child expressions of loops to account for context drilling.
310
+ * @name extractExpressionsWithDepth
311
+ * @param {string} template - A feelers template string.
312
+ * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
313
+ *
314
+ * @example
315
+ * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
316
+ * const extractedExpressions = _extractExpressionsWithDepth(template);
317
+ */
318
+ _extractExpressionsWithDepth(template) {
319
+ // build simplified feelers syntax tree
320
+ const parseTree = parser.parse(template);
321
+ const tree = buildSimpleTree(parseTree, template);
322
+ return function _traverse(n, depth = 0) {
323
+ if (['Feel', 'FeelBlock'].includes(n.name)) {
324
+ return [{
325
+ depth,
326
+ expression: n.content
327
+ }];
328
+ }
329
+ if (n.name === 'LoopSpanner') {
330
+ const loopExpression = n.children[0].content;
331
+ const childResults = n.children.slice(1).reduce((acc, child) => {
332
+ return acc.concat(_traverse(child, depth + 1));
333
+ }, []);
334
+ return [{
335
+ depth,
336
+ expression: loopExpression
337
+ }, ...childResults];
338
+ }
339
+ return n.children.reduce((acc, child) => {
340
+ return acc.concat(_traverse(child, depth));
341
+ }, []);
342
+ }(tree);
343
+ }
138
344
  }
139
345
  FeelersTemplating.$inject = [];
140
346
 
141
- /**
142
- * @typedef {object} Condition
143
- * @property {string} [hide]
347
+ /**
348
+ * @typedef {object} Condition
349
+ * @property {string} [hide]
144
350
  */
145
351
 
146
352
  class ConditionChecker {
@@ -149,11 +355,11 @@ class ConditionChecker {
149
355
  this._eventBus = eventBus;
150
356
  }
151
357
 
152
- /**
153
- * For given data, remove properties based on condition.
154
- *
155
- * @param {Object<string, any>} properties
156
- * @param {Object<string, any>} data
358
+ /**
359
+ * For given data, remove properties based on condition.
360
+ *
361
+ * @param {Object<string, any>} properties
362
+ * @param {Object<string, any>} data
157
363
  */
158
364
  applyConditions(properties, data = {}) {
159
365
  const conditions = this._getConditions();
@@ -172,13 +378,13 @@ class ConditionChecker {
172
378
  return newProperties;
173
379
  }
174
380
 
175
- /**
176
- * Check if given condition is met. Returns null for invalid/missing conditions.
177
- *
178
- * @param {string} condition
179
- * @param {import('../../types').Data} [data]
180
- *
181
- * @returns {boolean|null}
381
+ /**
382
+ * Check if given condition is met. Returns null for invalid/missing conditions.
383
+ *
384
+ * @param {string} condition
385
+ * @param {import('../../types').Data} [data]
386
+ *
387
+ * @returns {boolean|null}
182
388
  */
183
389
  check(condition, data = {}) {
184
390
  if (!condition) {
@@ -199,12 +405,12 @@ class ConditionChecker {
199
405
  }
200
406
  }
201
407
 
202
- /**
203
- * Check if hide condition is met.
204
- *
205
- * @param {Condition} condition
206
- * @param {Object<string, any>} data
207
- * @returns {boolean}
408
+ /**
409
+ * Check if hide condition is met.
410
+ *
411
+ * @param {Condition} condition
412
+ * @param {Object<string, any>} data
413
+ * @returns {boolean}
208
414
  */
209
415
  _checkHideCondition(condition, data) {
210
416
  if (!condition.hide) {
@@ -246,12 +452,12 @@ class MarkdownRenderer {
246
452
  this._converter = new showdown.Converter();
247
453
  }
248
454
 
249
- /**
250
- * Render markdown to HTML.
251
- *
252
- * @param {string} markdown - The markdown to render
253
- *
254
- * @returns {string} HTML
455
+ /**
456
+ * Render markdown to HTML.
457
+ *
458
+ * @param {string} markdown - The markdown to render
459
+ *
460
+ * @returns {string} HTML
255
461
  */
256
462
  render(markdown) {
257
463
  return this._converter.makeHtml(markdown);
@@ -840,23 +1046,23 @@ class FormFieldRegistry {
840
1046
  }
841
1047
  FormFieldRegistry.$inject = ['eventBus'];
842
1048
 
843
- /**
844
- * @typedef { { id: String, components: Array<String> } } FormRow
845
- * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
1049
+ /**
1050
+ * @typedef { { id: String, components: Array<String> } } FormRow
1051
+ * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
846
1052
  */
847
1053
 
848
- /**
849
- * Maintains the Form layout in a given structure, for example
850
- *
851
- * [
852
- * {
853
- * formFieldId: 'FormField_1',
854
- * rows: [
855
- * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
856
- * ]
857
- * }
858
- * ]
859
- *
1054
+ /**
1055
+ * Maintains the Form layout in a given structure, for example
1056
+ *
1057
+ * [
1058
+ * {
1059
+ * formFieldId: 'FormField_1',
1060
+ * rows: [
1061
+ * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
1062
+ * ]
1063
+ * }
1064
+ * ]
1065
+ *
860
1066
  */
861
1067
  class FormLayouter {
862
1068
  constructor(eventBus) {
@@ -866,8 +1072,8 @@ class FormLayouter {
866
1072
  this._eventBus = eventBus;
867
1073
  }
868
1074
 
869
- /**
870
- * @param {FormRow} row
1075
+ /**
1076
+ * @param {FormRow} row
871
1077
  */
872
1078
  addRow(formFieldId, row) {
873
1079
  let rowsPerComponent = this._rows.find(r => r.formFieldId === formFieldId);
@@ -881,18 +1087,18 @@ class FormLayouter {
881
1087
  rowsPerComponent.rows.push(row);
882
1088
  }
883
1089
 
884
- /**
885
- * @param {String} id
886
- * @returns {FormRow}
1090
+ /**
1091
+ * @param {String} id
1092
+ * @returns {FormRow}
887
1093
  */
888
1094
  getRow(id) {
889
1095
  const rows = allRows(this._rows);
890
1096
  return rows.find(r => r.id === id);
891
1097
  }
892
1098
 
893
- /**
894
- * @param {any} formField
895
- * @returns {FormRow}
1099
+ /**
1100
+ * @param {any} formField
1101
+ * @returns {FormRow}
896
1102
  */
897
1103
  getRowForField(formField) {
898
1104
  return allRows(this._rows).find(r => {
@@ -903,9 +1109,9 @@ class FormLayouter {
903
1109
  });
904
1110
  }
905
1111
 
906
- /**
907
- * @param {String} formFieldId
908
- * @returns { Array<FormRow> }
1112
+ /**
1113
+ * @param {String} formFieldId
1114
+ * @returns { Array<FormRow> }
909
1115
  */
910
1116
  getRows(formFieldId) {
911
1117
  const rowsForField = this._rows.find(r => formFieldId === r.formFieldId);
@@ -915,15 +1121,15 @@ class FormLayouter {
915
1121
  return rowsForField.rows;
916
1122
  }
917
1123
 
918
- /**
919
- * @returns {string}
1124
+ /**
1125
+ * @returns {string}
920
1126
  */
921
1127
  nextRowId() {
922
1128
  return this._ids.nextPrefixed('Row_');
923
1129
  }
924
1130
 
925
- /**
926
- * @param {any} formField
1131
+ /**
1132
+ * @param {any} formField
927
1133
  */
928
1134
  calculateLayout(formField) {
929
1135
  const {
@@ -977,9 +1183,9 @@ function groupByRow(components, ids) {
977
1183
  });
978
1184
  }
979
1185
 
980
- /**
981
- * @param {Array<FormRows>} formRows
982
- * @returns {Array<FormRow>}
1186
+ /**
1187
+ * @param {Array<FormRows>} formRows
1188
+ * @returns {Array<FormRow>}
983
1189
  */
984
1190
  function allRows(formRows) {
985
1191
  return flatten(formRows.map(c => c.rows));
@@ -1056,10 +1262,10 @@ function createInjector(bootstrapModules) {
1056
1262
  return injector;
1057
1263
  }
1058
1264
 
1059
- /**
1060
- * @param {string?} prefix
1061
- *
1062
- * @returns Element
1265
+ /**
1266
+ * @param {string?} prefix
1267
+ *
1268
+ * @returns Element
1063
1269
  */
1064
1270
  function createFormContainer(prefix = 'fjs') {
1065
1271
  const container = document.createElement('div');
@@ -1068,6 +1274,7 @@ function createFormContainer(prefix = 'fjs') {
1068
1274
  }
1069
1275
 
1070
1276
  const EXPRESSION_PROPERTIES = ['alt', 'source', 'text'];
1277
+ const TEMPLATE_PROPERTIES = ['text'];
1071
1278
  function findErrors(errors, path) {
1072
1279
  return errors[pathStringify(path)];
1073
1280
  }
@@ -1104,24 +1311,24 @@ function generateIdForType(type) {
1104
1311
  return `${type}${generateIndexForType(type)}`;
1105
1312
  }
1106
1313
 
1107
- /**
1108
- * @template T
1109
- * @param {T} data
1110
- * @param {(this: any, key: string, value: any) => any} [replacer]
1111
- * @return {T}
1314
+ /**
1315
+ * @template T
1316
+ * @param {T} data
1317
+ * @param {(this: any, key: string, value: any) => any} [replacer]
1318
+ * @return {T}
1112
1319
  */
1113
1320
  function clone(data, replacer) {
1114
1321
  return JSON.parse(JSON.stringify(data, replacer));
1115
1322
  }
1116
1323
 
1117
- /**
1118
- * Parse the schema for input variables a form might make use of
1119
- *
1120
- * @param {any} schema
1121
- *
1122
- * @return {string[]}
1324
+ /**
1325
+ * Parse the schema for input variables a form might make use of
1326
+ *
1327
+ * @param {any} schema
1328
+ *
1329
+ * @return {string[]}
1123
1330
  */
1124
- function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLanguage(null)) {
1331
+ function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLanguage(null), templating = new FeelersTemplating()) {
1125
1332
  if (!schema.components) {
1126
1333
  return [];
1127
1334
  }
@@ -1156,6 +1363,13 @@ function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLangu
1156
1363
  variables = [...variables, ...expressionVariables];
1157
1364
  }
1158
1365
  });
1366
+ TEMPLATE_PROPERTIES.forEach(prop => {
1367
+ const property = component[prop];
1368
+ if (property && !expressionLanguage.isExpression(property) && templating.isTemplate(property)) {
1369
+ const templateVariables = templating.getVariableNames(property);
1370
+ variables = [...variables, ...templateVariables];
1371
+ }
1372
+ });
1159
1373
  return variables;
1160
1374
  }, []);
1161
1375
 
@@ -1164,11 +1378,11 @@ function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLangu
1164
1378
  }
1165
1379
 
1166
1380
  class Importer {
1167
- /**
1168
- * @constructor
1169
- * @param { import('../core').FormFieldRegistry } formFieldRegistry
1170
- * @param { import('../render/FormFields').default } formFields
1171
- * @param { import('../core').FormLayouter } formLayouter
1381
+ /**
1382
+ * @constructor
1383
+ * @param { import('../core').FormFieldRegistry } formFieldRegistry
1384
+ * @param { import('../render/FormFields').default } formFields
1385
+ * @param { import('../core').FormLayouter } formLayouter
1172
1386
  */
1173
1387
  constructor(formFieldRegistry, formFields, formLayouter) {
1174
1388
  this._formFieldRegistry = formFieldRegistry;
@@ -1176,15 +1390,15 @@ class Importer {
1176
1390
  this._formLayouter = formLayouter;
1177
1391
  }
1178
1392
 
1179
- /**
1180
- * Import schema adding `id`, `_parent` and `_path`
1181
- * information to each field and adding it to the
1182
- * form field registry.
1183
- *
1184
- * @param {any} schema
1185
- * @param {any} [data]
1186
- *
1187
- * @return { { warnings: Array<any>, schema: any, data: any } }
1393
+ /**
1394
+ * Import schema adding `id`, `_parent` and `_path`
1395
+ * information to each field and adding it to the
1396
+ * form field registry.
1397
+ *
1398
+ * @param {any} schema
1399
+ * @param {any} [data]
1400
+ *
1401
+ * @return { { warnings: Array<any>, schema: any, data: any } }
1188
1402
  */
1189
1403
  importSchema(schema, data = {}) {
1190
1404
  // TODO: Add warnings - https://github.com/bpmn-io/form-js/issues/289
@@ -1205,11 +1419,11 @@ class Importer {
1205
1419
  }
1206
1420
  }
1207
1421
 
1208
- /**
1209
- * @param {any} formField
1210
- * @param {string} [parentId]
1211
- *
1212
- * @return {any} importedField
1422
+ /**
1423
+ * @param {any} formField
1424
+ * @param {string} [parentId]
1425
+ *
1426
+ * @return {any} importedField
1213
1427
  */
1214
1428
  importFormField(formField, parentId) {
1215
1429
  const {
@@ -1260,10 +1474,10 @@ class Importer {
1260
1474
  });
1261
1475
  }
1262
1476
 
1263
- /**
1264
- * @param {Object} data
1265
- *
1266
- * @return {Object} initializedData
1477
+ /**
1478
+ * @param {Object} data
1479
+ *
1480
+ * @return {Object} initializedData
1267
1481
  */
1268
1482
  initializeFieldValues(data) {
1269
1483
  return this._formFieldRegistry.getAll().reduce((initializedData, formField) => {
@@ -1395,11 +1609,11 @@ const FormRenderContext = createContext({
1395
1609
  });
1396
1610
  var FormRenderContext$1 = FormRenderContext;
1397
1611
 
1398
- /**
1399
- * @param {string} type
1400
- * @param {boolean} [strict]
1401
- *
1402
- * @returns {any}
1612
+ /**
1613
+ * @param {string} type
1614
+ * @param {boolean} [strict]
1615
+ *
1616
+ * @returns {any}
1403
1617
  */
1404
1618
  function getService(type, strict) {}
1405
1619
  const FormContext = createContext({
@@ -1578,8 +1792,8 @@ function useService(type, strict) {
1578
1792
  return getService(type, strict);
1579
1793
  }
1580
1794
 
1581
- /**
1582
- * @enum { String }
1795
+ /**
1796
+ * @enum { String }
1583
1797
  */
1584
1798
  const LOAD_STATES = {
1585
1799
  LOADING: 'loading',
@@ -1587,17 +1801,17 @@ const LOAD_STATES = {
1587
1801
  ERROR: 'error'
1588
1802
  };
1589
1803
 
1590
- /**
1591
- * @typedef {Object} ValuesGetter
1592
- * @property {Object[]} values - The values data
1593
- * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
1804
+ /**
1805
+ * @typedef {Object} ValuesGetter
1806
+ * @property {Object[]} values - The values data
1807
+ * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
1594
1808
  */
1595
1809
 
1596
- /**
1597
- * A hook to load values for single and multiselect components.
1598
- *
1599
- * @param {Object} field - The form field to handle values for
1600
- * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1810
+ /**
1811
+ * A hook to load values for single and multiselect components.
1812
+ *
1813
+ * @param {Object} field - The form field to handle values for
1814
+ * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1601
1815
  */
1602
1816
  function useValuesAsync (field) {
1603
1817
  const {
@@ -1943,10 +2157,10 @@ Checklist.emptyValue = [];
1943
2157
  Checklist.sanitizeValue = sanitizeMultiSelectValue;
1944
2158
  Checklist.group = 'selection';
1945
2159
 
1946
- /**
1947
- * Returns the conditionally filtered data of a form reactively.
1948
- * Memoised to minimize re-renders
1949
- *
2160
+ /**
2161
+ * Returns the conditionally filtered data of a form reactively.
2162
+ * Memoised to minimize re-renders
2163
+ *
1950
2164
  */
1951
2165
  function useFilteredFormData() {
1952
2166
  const {
@@ -1963,12 +2177,12 @@ function useFilteredFormData() {
1963
2177
  }, [conditionChecker, data, initialData]);
1964
2178
  }
1965
2179
 
1966
- /**
1967
- * Evaluate if condition is met reactively based on the conditionChecker and form data.
1968
- *
1969
- * @param {string | undefined} condition
1970
- *
1971
- * @returns {boolean} true if condition is met or no condition or condition checker exists
2180
+ /**
2181
+ * Evaluate if condition is met reactively based on the conditionChecker and form data.
2182
+ *
2183
+ * @param {string | undefined} condition
2184
+ *
2185
+ * @returns {boolean} true if condition is met or no condition or condition checker exists
1972
2186
  */
1973
2187
  function useCondition(condition) {
1974
2188
  const conditionChecker = useService('conditionChecker', false);
@@ -1978,13 +2192,13 @@ function useCondition(condition) {
1978
2192
  }, [conditionChecker, condition, filteredData]);
1979
2193
  }
1980
2194
 
1981
- /**
1982
- * Evaluate a string reactively based on the expressionLanguage and form data.
1983
- * If the string is not an expression, it is returned as is.
1984
- * Memoised to minimize re-renders.
1985
- *
1986
- * @param {string} value
1987
- *
2195
+ /**
2196
+ * Evaluate a string reactively based on the expressionLanguage and form data.
2197
+ * If the string is not an expression, it is returned as is.
2198
+ * Memoised to minimize re-renders.
2199
+ *
2200
+ * @param {string} value
2201
+ *
1988
2202
  */
1989
2203
  function useExpressionEvaluation(value) {
1990
2204
  const formData = useFilteredFormData();
@@ -2013,16 +2227,16 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
2013
2227
  });
2014
2228
  }
2015
2229
 
2016
- /**
2017
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2018
- * Memoised to minimize re-renders
2019
- *
2020
- * @param {string} value
2021
- * @param {Object} options
2022
- * @param {boolean} [options.debug = false]
2023
- * @param {boolean} [options.strict = false]
2024
- * @param {Function} [options.buildDebugString]
2025
- *
2230
+ /**
2231
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2232
+ * Memoised to minimize re-renders
2233
+ *
2234
+ * @param {string} value
2235
+ * @param {Object} options
2236
+ * @param {boolean} [options.debug = false]
2237
+ * @param {boolean} [options.strict = false]
2238
+ * @param {Function} [options.buildDebugString]
2239
+ *
2026
2240
  */
2027
2241
  function useTemplateEvaluation(value, options) {
2028
2242
  const filteredData = useFilteredFormData();
@@ -2773,10 +2987,10 @@ Datetime.sanitizeValue = sanitizeDateTimePickerValue;
2773
2987
  Datetime.label = 'Date time';
2774
2988
  Datetime.group = 'basic-input';
2775
2989
 
2776
- /**
2777
- * This file must not be changed or exchanged.
2778
- *
2779
- * @see http://bpmn.io/license for more information.
2990
+ /**
2991
+ * This file must not be changed or exchanged.
2992
+ *
2993
+ * @see http://bpmn.io/license for more information.
2780
2994
  */
2781
2995
  function Logo() {
2782
2996
  return jsxs("svg", {
@@ -2903,11 +3117,11 @@ const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u
2903
3117
 
2904
3118
  const FORM_ELEMENT = document.createElement('form');
2905
3119
 
2906
- /**
2907
- * Sanitize a HTML string and return the cleaned, safe version.
2908
- *
2909
- * @param {string} html
2910
- * @return {string}
3120
+ /**
3121
+ * Sanitize a HTML string and return the cleaned, safe version.
3122
+ *
3123
+ * @param {string} html
3124
+ * @return {string}
2911
3125
  */
2912
3126
 
2913
3127
  // see https://github.com/developit/snarkdown/issues/70
@@ -2925,29 +3139,29 @@ function sanitizeHTML(html) {
2925
3139
  }
2926
3140
  }
2927
3141
 
2928
- /**
2929
- * Sanitizes an image source to ensure we only allow for data URI and links
2930
- * that start with http(s).
2931
- *
2932
- * Note: Most browsers anyway do not support script execution in <img> elements.
2933
- *
2934
- * @param {string} src
2935
- * @returns {string}
3142
+ /**
3143
+ * Sanitizes an image source to ensure we only allow for data URI and links
3144
+ * that start with http(s).
3145
+ *
3146
+ * Note: Most browsers anyway do not support script execution in <img> elements.
3147
+ *
3148
+ * @param {string} src
3149
+ * @returns {string}
2936
3150
  */
2937
3151
  function sanitizeImageSource(src) {
2938
3152
  const valid = ALLOWED_IMAGE_SRC_PATTERN.test(src);
2939
3153
  return valid ? src : '';
2940
3154
  }
2941
3155
 
2942
- /**
2943
- * Recursively sanitize a HTML node, potentially
2944
- * removing it, its children or attributes.
2945
- *
2946
- * Inspired by https://github.com/developit/snarkdown/issues/70
2947
- * and https://github.com/cure53/DOMPurify. Simplified
2948
- * for our use-case.
2949
- *
2950
- * @param {Element} node
3156
+ /**
3157
+ * Recursively sanitize a HTML node, potentially
3158
+ * removing it, its children or attributes.
3159
+ *
3160
+ * Inspired by https://github.com/developit/snarkdown/issues/70
3161
+ * and https://github.com/cure53/DOMPurify. Simplified
3162
+ * for our use-case.
3163
+ *
3164
+ * @param {Element} node
2951
3165
  */
2952
3166
  function sanitizeNode(node) {
2953
3167
  // allow text nodes
@@ -2991,13 +3205,13 @@ function sanitizeNode(node) {
2991
3205
  }
2992
3206
  }
2993
3207
 
2994
- /**
2995
- * Validates attributes for validity.
2996
- *
2997
- * @param {string} lcTag
2998
- * @param {string} lcName
2999
- * @param {string} value
3000
- * @return {boolean}
3208
+ /**
3209
+ * Validates attributes for validity.
3210
+ *
3211
+ * @param {string} lcTag
3212
+ * @param {string} lcName
3213
+ * @param {string} value
3214
+ * @return {boolean}
3001
3215
  */
3002
3216
  function isValidAttribute(lcTag, lcName, value) {
3003
3217
  // disallow most attributes based on whitelist
@@ -3718,6 +3932,12 @@ function Select(props) {
3718
3932
  errors,
3719
3933
  disabled
3720
3934
  }),
3935
+ onKeyDown: event => {
3936
+ if (event.key === 'Enter') {
3937
+ event.preventDefault();
3938
+ event.stopPropagation();
3939
+ }
3940
+ },
3721
3941
  children: [jsx(Label, {
3722
3942
  id: prefixId(id, formId),
3723
3943
  label: label,
@@ -3868,6 +4088,12 @@ function Taglist(props) {
3868
4088
  errors,
3869
4089
  disabled
3870
4090
  }),
4091
+ onKeyDown: event => {
4092
+ if (event.key === 'Enter') {
4093
+ event.stopPropagation();
4094
+ event.preventDefault();
4095
+ }
4096
+ },
3871
4097
  children: [jsx(Label, {
3872
4098
  label: label,
3873
4099
  required: required,