@bpmn-io/form-js-viewer 1.6.0 → 1.6.2

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,10 +1,11 @@
1
1
  import Ids from 'ids';
2
- import { isString, get, some, isNumber, set, findIndex, isArray, isObject, isNil, isDefined, values, uniqueBy, isFunction, bind, assign, groupBy, flatten, isUndefined } from 'min-dash';
2
+ import { isString, get, some, isNil, isObject, isNumber, set, findIndex, isArray, isDefined, values, uniqueBy, isFunction, bind, assign, groupBy, flatten, isUndefined } from 'min-dash';
3
3
  import Big from 'big.js';
4
4
  import classNames from 'classnames';
5
5
  import { jsx, jsxs, Fragment } from 'preact/jsx-runtime';
6
6
  import { useContext, useMemo, useEffect, useRef, useState, useCallback, useLayoutEffect } from 'preact/hooks';
7
7
  import { createContext, createElement, Fragment as Fragment$1, render } from 'preact';
8
+ import isEqual from 'lodash/isEqual';
8
9
  import flatpickr from 'flatpickr';
9
10
  import * as React from 'preact/compat';
10
11
  import { createPortal } from 'preact/compat';
@@ -53,26 +54,26 @@ const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'expression', o
53
54
  return [...new Set(variables)];
54
55
  };
55
56
 
56
- /**
57
- * Get the variable name at the specified index in a given path expression.
58
- *
59
- * @param {Object} root - The root node of the path expression tree.
60
- * @param {number} index - The index of the variable name to retrieve.
61
- * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
57
+ /**
58
+ * Get the variable name at the specified index in a given path expression.
59
+ *
60
+ * @param {Object} root - The root node of the path expression tree.
61
+ * @param {number} index - The index of the variable name to retrieve.
62
+ * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
62
63
  */
63
64
  const _getVariableNameAtPathIndex = (root, index) => {
64
65
  const nodes = _linearizePathExpression(root);
65
66
  return nodes[index].variableName || null;
66
67
  };
67
68
 
68
- /**
69
- * Extracts the variables which are required of the external context for a given path expression.
70
- * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
71
- *
72
- * @param {Object} node - The root node of the path expression tree.
73
- * @param {number} initialDepth - The depth at which the root node is located in the outer context.
74
- * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
75
- * @returns {Set} - A set containing the extracted variable names.
69
+ /**
70
+ * Extracts the variables which are required of the external context for a given path expression.
71
+ * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
72
+ *
73
+ * @param {Object} node - The root node of the path expression tree.
74
+ * @param {number} initialDepth - The depth at which the root node is located in the outer context.
75
+ * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
76
+ * @returns {Set} - A set containing the extracted variable names.
76
77
  */
77
78
  const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {
78
79
  // depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
@@ -118,11 +119,11 @@ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) =
118
119
  return new Set(extractedVariables);
119
120
  };
120
121
 
121
- /**
122
- * Deconstructs a path expression tree into an array of components.
123
- *
124
- * @param {Object} root - The root node of the path expression tree.
125
- * @returns {Array<object>} An array of components in the path expression, in the correct order.
122
+ /**
123
+ * Deconstructs a path expression tree into an array of components.
124
+ *
125
+ * @param {Object} root - The root node of the path expression tree.
126
+ * @returns {Array<object>} An array of components in the path expression, in the correct order.
126
127
  */
127
128
  const _linearizePathExpression = root => {
128
129
  let node = root;
@@ -141,13 +142,13 @@ const _linearizePathExpression = root => {
141
142
  return parts.reverse();
142
143
  };
143
144
 
144
- /**
145
- * Builds a simplified feel structure tree from the given parse tree and feel string.
146
- * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
147
- *
148
- * @param {Object} parseTree - The parse tree generated by a parser.
149
- * @param {string} feelString - The feel string used for parsing.
150
- * @returns {Object} The simplified feel structure tree.
145
+ /**
146
+ * Builds a simplified feel structure tree from the given parse tree and feel string.
147
+ * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
148
+ *
149
+ * @param {Object} parseTree - The parse tree generated by a parser.
150
+ * @param {string} feelString - The feel string used for parsing.
151
+ * @returns {Object} The simplified feel structure tree.
151
152
  */
152
153
  const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
153
154
  const stack = [{
@@ -173,9 +174,9 @@ const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
173
174
  return _extractFilterExpressions(stack[0].children[0]);
174
175
  };
175
176
 
176
- /**
177
- * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
178
- * This is done to simplify the extraction of variables and match the context hierarchy.
177
+ /**
178
+ * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
179
+ * This is done to simplify the extraction of variables and match the context hierarchy.
179
180
  */
180
181
  const _extractFilterExpressions = tree => {
181
182
  const flattenedExpressionTree = {
@@ -216,25 +217,25 @@ class FeelExpressionLanguage {
216
217
  this._eventBus = eventBus;
217
218
  }
218
219
 
219
- /**
220
- * Determines if the given value is a FEEL expression.
221
- *
222
- * @param {any} value
223
- * @returns {boolean}
224
- *
220
+ /**
221
+ * Determines if the given value is a FEEL expression.
222
+ *
223
+ * @param {any} value
224
+ * @returns {boolean}
225
+ *
225
226
  */
226
227
  isExpression(value) {
227
228
  return isString(value) && value.startsWith('=');
228
229
  }
229
230
 
230
- /**
231
- * Retrieve variable names from a given FEEL expression.
232
- *
233
- * @param {string} expression
234
- * @param {object} [options]
235
- * @param {string} [options.type]
236
- *
237
- * @returns {string[]}
231
+ /**
232
+ * Retrieve variable names from a given FEEL expression.
233
+ *
234
+ * @param {string} expression
235
+ * @param {object} [options]
236
+ * @param {string} [options.type]
237
+ *
238
+ * @returns {string[]}
238
239
  */
239
240
  getVariableNames(expression, options = {}) {
240
241
  const {
@@ -249,13 +250,13 @@ class FeelExpressionLanguage {
249
250
  return getFlavouredFeelVariableNames(expression, type);
250
251
  }
251
252
 
252
- /**
253
- * Evaluate an expression.
254
- *
255
- * @param {string} expression
256
- * @param {import('../../types').Data} [data]
257
- *
258
- * @returns {any}
253
+ /**
254
+ * Evaluate an expression.
255
+ *
256
+ * @param {string} expression
257
+ * @param {import('../../types').Data} [data]
258
+ *
259
+ * @returns {any}
259
260
  */
260
261
  evaluate(expression, data = {}) {
261
262
  if (!expression) {
@@ -280,23 +281,23 @@ FeelExpressionLanguage.$inject = ['eventBus'];
280
281
  class FeelersTemplating {
281
282
  constructor() {}
282
283
 
283
- /**
284
- * Determines if the given value is a feelers template.
285
- *
286
- * @param {any} value
287
- * @returns {boolean}
288
- *
284
+ /**
285
+ * Determines if the given value is a feelers template.
286
+ *
287
+ * @param {any} value
288
+ * @returns {boolean}
289
+ *
289
290
  */
290
291
  isTemplate(value) {
291
292
  return isString(value) && (value.startsWith('=') || /{{.*?}}/.test(value));
292
293
  }
293
294
 
294
- /**
295
- * Retrieve variable names from a given feelers template.
296
- *
297
- * @param {string} template
298
- *
299
- * @returns {string[]}
295
+ /**
296
+ * Retrieve variable names from a given feelers template.
297
+ *
298
+ * @param {string} template
299
+ *
300
+ * @returns {string[]}
300
301
  */
301
302
  getVariableNames(template) {
302
303
  if (!this.isTemplate(template)) {
@@ -322,17 +323,17 @@ class FeelersTemplating {
322
323
  }, []);
323
324
  }
324
325
 
325
- /**
326
- * Evaluate a template.
327
- *
328
- * @param {string} template
329
- * @param {Object<string, any>} context
330
- * @param {Object} options
331
- * @param {boolean} [options.debug = false]
332
- * @param {boolean} [options.strict = false]
333
- * @param {Function} [options.buildDebugString]
334
- *
335
- * @returns
326
+ /**
327
+ * Evaluate a template.
328
+ *
329
+ * @param {string} template
330
+ * @param {Object<string, any>} context
331
+ * @param {Object} options
332
+ * @param {boolean} [options.debug = false]
333
+ * @param {boolean} [options.strict = false]
334
+ * @param {Function} [options.buildDebugString]
335
+ *
336
+ * @returns
336
337
  */
337
338
  evaluate(template, context = {}, options = {}) {
338
339
  const {
@@ -347,22 +348,22 @@ class FeelersTemplating {
347
348
  });
348
349
  }
349
350
 
350
- /**
351
- * @typedef {Object} ExpressionWithDepth
352
- * @property {number} depth - The depth of the expression in the syntax tree.
353
- * @property {string} expression - The extracted expression
351
+ /**
352
+ * @typedef {Object} ExpressionWithDepth
353
+ * @property {number} depth - The depth of the expression in the syntax tree.
354
+ * @property {string} expression - The extracted expression
354
355
  */
355
356
 
356
- /**
357
- * Extracts all feel expressions in the template along with their depth in the syntax tree.
358
- * The depth is incremented for child expressions of loops to account for context drilling.
359
- * @name extractExpressionsWithDepth
360
- * @param {string} template - A feelers template string.
361
- * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
362
- *
363
- * @example
364
- * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
365
- * const extractedExpressions = _extractExpressionsWithDepth(template);
357
+ /**
358
+ * Extracts all feel expressions in the template along with their depth in the syntax tree.
359
+ * The depth is incremented for child expressions of loops to account for context drilling.
360
+ * @name extractExpressionsWithDepth
361
+ * @param {string} template - A feelers template string.
362
+ * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
363
+ *
364
+ * @example
365
+ * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
366
+ * const extractedExpressions = _extractExpressionsWithDepth(template);
366
367
  */
367
368
  _extractExpressionsWithDepth(template) {
368
369
  // build simplified feelers syntax tree
@@ -468,10 +469,10 @@ function createInjector(bootstrapModules) {
468
469
  return injector;
469
470
  }
470
471
 
471
- /**
472
- * @param {string?} prefix
473
- *
474
- * @returns Element
472
+ /**
473
+ * @param {string?} prefix
474
+ *
475
+ * @returns Element
475
476
  */
476
477
  function createFormContainer(prefix = 'fjs') {
477
478
  const container = document.createElement('div');
@@ -602,11 +603,11 @@ const LocalExpressionContext = createContext({
602
603
  });
603
604
  var LocalExpressionContext$1 = LocalExpressionContext;
604
605
 
605
- /**
606
- * @param {string} type
607
- * @param {boolean} [strict]
608
- *
609
- * @returns {any}
606
+ /**
607
+ * @param {string} type
608
+ * @param {boolean} [strict]
609
+ *
610
+ * @returns {any}
610
611
  */
611
612
  function getService(type, strict) {}
612
613
  const FormContext = createContext({
@@ -649,21 +650,21 @@ function generateIdForType(type) {
649
650
  return `${type}${generateIndexForType(type)}`;
650
651
  }
651
652
 
652
- /**
653
- * @template T
654
- * @param {T} data
655
- * @param {(this: any, key: string, value: any) => any} [replacer]
656
- * @return {T}
653
+ /**
654
+ * @template T
655
+ * @param {T} data
656
+ * @param {(this: any, key: string, value: any) => any} [replacer]
657
+ * @return {T}
657
658
  */
658
659
  function clone(data, replacer) {
659
660
  return JSON.parse(JSON.stringify(data, replacer));
660
661
  }
661
662
 
662
- /**
663
- * Transform a LocalExpressionContext object into a usable FEEL context.
664
- *
665
- * @param {Object} context - The LocalExpressionContext object.
666
- * @returns {Object} The usable FEEL context.
663
+ /**
664
+ * Transform a LocalExpressionContext object into a usable FEEL context.
665
+ *
666
+ * @param {Object} context - The LocalExpressionContext object.
667
+ * @returns {Object} The usable FEEL context.
667
668
  */
668
669
 
669
670
  function buildExpressionContext(context) {
@@ -695,12 +696,12 @@ function _wrapObjectKeysWithUnderscores(obj) {
695
696
  return newObj;
696
697
  }
697
698
 
698
- /**
699
- * Evaluate if condition is met reactively based on the conditionChecker and form data.
700
- *
701
- * @param {string | undefined} condition
702
- *
703
- * @returns {boolean} true if condition is met or no condition or condition checker exists
699
+ /**
700
+ * Evaluate if condition is met reactively based on the conditionChecker and form data.
701
+ *
702
+ * @param {string | undefined} condition
703
+ *
704
+ * @returns {boolean} true if condition is met or no condition or condition checker exists
704
705
  */
705
706
  function useCondition(condition) {
706
707
  const conditionChecker = useService('conditionChecker', false);
@@ -710,17 +711,17 @@ function useCondition(condition) {
710
711
  }, [conditionChecker, condition, expressionContextInfo]);
711
712
  }
712
713
 
713
- /**
714
- * Custom hook to scroll an element into view only when it is not visible within the viewport.
715
- *
716
- * @param {Object} targetRef - A ref pointing to the DOM element to scroll into view.
717
- * @param {Array} deps - An array of dependencies that trigger the effect.
718
- * @param {Array} flagRefs - An array of refs that are used as flags to control when to scroll.
719
- * @param {Object} [scrollOptions={}] - Options defining the behavior of the scrolling.
720
- * @param {String} [scrollOptions.align='center'] - The alignment of the element within the viewport.
721
- * @param {String} [scrollOptions.behavior='auto'] - The scrolling behavior.
722
- * @param {Number} [scrollOptions.offset=0] - An offset that is added to the scroll position.
723
- * @param {Boolean} [scrollOptions.scrollIfVisible=false] - Whether to scroll even if the element is visible.
714
+ /**
715
+ * Custom hook to scroll an element into view only when it is not visible within the viewport.
716
+ *
717
+ * @param {Object} targetRef - A ref pointing to the DOM element to scroll into view.
718
+ * @param {Array} deps - An array of dependencies that trigger the effect.
719
+ * @param {Array} flagRefs - An array of refs that are used as flags to control when to scroll.
720
+ * @param {Object} [scrollOptions={}] - Options defining the behavior of the scrolling.
721
+ * @param {String} [scrollOptions.align='center'] - The alignment of the element within the viewport.
722
+ * @param {String} [scrollOptions.behavior='auto'] - The scrolling behavior.
723
+ * @param {Number} [scrollOptions.offset=0] - An offset that is added to the scroll position.
724
+ * @param {Boolean} [scrollOptions.scrollIfVisible=false] - Whether to scroll even if the element is visible.
724
725
  */
725
726
  function useScrollIntoView(targetRef, deps, scrollOptions = null, flagRefs = []) {
726
727
  useEffect(() => {
@@ -785,13 +786,13 @@ function _getTopOffset(item, scrollContainer, options) {
785
786
  return 0;
786
787
  }
787
788
 
788
- /**
789
- * Evaluate a string reactively based on the expressionLanguage and form data.
790
- * If the string is not an expression, it is returned as is.
791
- * The function is memoized to minimize re-renders.
792
- *
793
- * @param {string} value - The string to evaluate.
794
- * @returns {any} - Evaluated value or the original value if not an expression.
789
+ /**
790
+ * Evaluate a string reactively based on the expressionLanguage and form data.
791
+ * If the string is not an expression, it is returned as is.
792
+ * The function is memoized to minimize re-renders.
793
+ *
794
+ * @param {string} value - The string to evaluate.
795
+ * @returns {any} - Evaluated value or the original value if not an expression.
795
796
  */
796
797
  function useExpressionEvaluation(value) {
797
798
  const expressionLanguage = useService('expressionLanguage');
@@ -804,11 +805,11 @@ function useExpressionEvaluation(value) {
804
805
  }, [expressionLanguage, expressionContextInfo, value]);
805
806
  }
806
807
 
807
- /**
808
- * Returns the conditionally filtered data of a form reactively.
809
- * Memoised to minimize re-renders
810
- *
811
- * Warning: costly operation, use with care
808
+ /**
809
+ * Returns the conditionally filtered data of a form reactively.
810
+ * Memoised to minimize re-renders
811
+ *
812
+ * Warning: costly operation, use with care
812
813
  */
813
814
  function useFilteredFormData() {
814
815
  const {
@@ -841,16 +842,16 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
841
842
  });
842
843
  }
843
844
 
844
- /**
845
- * Retrieve readonly value of a form field, given it can be an
846
- * expression optionally or configured globally.
847
- *
848
- * @typedef { import('../../types').FormProperties } FormProperties
849
- *
850
- * @param {any} formField
851
- * @param {FormProperties} properties
852
- *
853
- * @returns {boolean}
845
+ /**
846
+ * Retrieve readonly value of a form field, given it can be an
847
+ * expression optionally or configured globally.
848
+ *
849
+ * @typedef { import('../../types').FormProperties } FormProperties
850
+ *
851
+ * @param {any} formField
852
+ * @param {FormProperties} properties
853
+ *
854
+ * @returns {boolean}
854
855
  */
855
856
  function useReadonly(formField, properties = {}) {
856
857
  const expressionLanguage = useService('expressionLanguage');
@@ -874,12 +875,12 @@ function usePrevious(value, defaultValue, dependencies) {
874
875
  return ref.current;
875
876
  }
876
877
 
877
- /**
878
- * A custom hook to manage state changes with deep comparison.
879
- *
880
- * @param {any} value - The current value to manage.
881
- * @param {any} defaultValue - The initial default value for the state.
882
- * @returns {any} - Returns the current state.
878
+ /**
879
+ * A custom hook to manage state changes with deep comparison.
880
+ *
881
+ * @param {any} value - The current value to manage.
882
+ * @param {any} defaultValue - The initial default value for the state.
883
+ * @returns {any} - Returns the current state.
883
884
  */
884
885
  function useDeepCompareState(value, defaultValue) {
885
886
  const [state, setState] = useState(defaultValue);
@@ -899,16 +900,16 @@ function compare(a, b) {
899
900
  return JSON.stringify(a) === JSON.stringify(b);
900
901
  }
901
902
 
902
- /**
903
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
904
- * Memoised to minimize re-renders
905
- *
906
- * @param {string} value
907
- * @param {Object} options
908
- * @param {boolean} [options.debug = false]
909
- * @param {boolean} [options.strict = false]
910
- * @param {Function} [options.buildDebugString]
911
- *
903
+ /**
904
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
905
+ * Memoised to minimize re-renders
906
+ *
907
+ * @param {string} value
908
+ * @param {Object} options
909
+ * @param {boolean} [options.debug = false]
910
+ * @param {boolean} [options.strict = false]
911
+ * @param {Function} [options.buildDebugString]
912
+ *
912
913
  */
913
914
  function useTemplateEvaluation(value, options = {}) {
914
915
  const templating = useService('templating');
@@ -921,17 +922,17 @@ function useTemplateEvaluation(value, options = {}) {
921
922
  }, [templating, value, expressionContextInfo, options]);
922
923
  }
923
924
 
924
- /**
925
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
926
- * If the string contains multiple lines, only the first line is returned.
927
- * Memoised to minimize re-renders
928
- *
929
- * @param {string} value
930
- * @param {Object} [options]
931
- * @param {boolean} [options.debug = false]
932
- * @param {boolean} [options.strict = false]
933
- * @param {Function} [options.buildDebugString]
934
- *
925
+ /**
926
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
927
+ * If the string contains multiple lines, only the first line is returned.
928
+ * Memoised to minimize re-renders
929
+ *
930
+ * @param {string} value
931
+ * @param {Object} [options]
932
+ * @param {boolean} [options.debug = false]
933
+ * @param {boolean} [options.strict = false]
934
+ * @param {Function} [options.buildDebugString]
935
+ *
935
936
  */
936
937
  function useSingleLineTemplateEvaluation(value, options = {}) {
937
938
  const evaluatedTemplate = useTemplateEvaluation(value, options);
@@ -1084,39 +1085,58 @@ function getOptionsData(formField, formData) {
1084
1085
 
1085
1086
  // transforms the provided options into a normalized format, trimming invalid options
1086
1087
  function normalizeOptionsData(optionsData) {
1087
- return optionsData.filter(_isOptionSomething).map(v => _normalizeOptionsData(v)).filter(v => v);
1088
+ return optionsData.filter(_isAllowedValue).map(_normalizeOption).filter(o => !isNil(o));
1088
1089
  }
1089
- function _normalizeOptionsData(optionData) {
1090
- if (_isAllowedOption(optionData)) {
1091
- // if a primitive is provided, use it as label and value
1090
+
1091
+ /**
1092
+ * Converts the provided option to a normalized format.
1093
+ * If the option is not valid, null is returned.
1094
+ *
1095
+ * @param {object} option
1096
+ * @param {string} option.label
1097
+ * @param {*} option.value
1098
+ *
1099
+ * @returns
1100
+ */
1101
+ function _normalizeOption(option) {
1102
+ // (1) simple primitive case, use it as both label and value
1103
+ if (_isAllowedPrimitive(option)) {
1092
1104
  return {
1093
- value: optionData,
1094
- label: `${optionData}`
1105
+ value: option,
1106
+ label: `${option}`
1095
1107
  };
1096
1108
  }
1097
- if (typeof optionData === 'object') {
1098
- if (!optionData.label && _isAllowedOption(optionData.value)) {
1099
- // if no label is provided, use the value as label
1109
+ if (isObject(option)) {
1110
+ const isValidLabel = _isValidLabel(option.label);
1111
+
1112
+ // (2) no label provided, but value is a simple primitive, use it as label and value
1113
+ if (!isValidLabel && _isAllowedPrimitive(option.value)) {
1100
1114
  return {
1101
- value: optionData.value,
1102
- label: `${optionData.value}`
1115
+ value: option.value,
1116
+ label: `${option.value}`
1103
1117
  };
1104
1118
  }
1105
- if (_isOptionSomething(optionData.value) && _isAllowedOption(optionData.label)) {
1106
- // if both value and label are provided, use them as is, in this scenario, the value may also be an object
1107
- return optionData;
1119
+
1120
+ // (3) both label and value are provided, use them as is
1121
+ if (isValidLabel && _isAllowedValue(option.value)) {
1122
+ return option;
1108
1123
  }
1109
1124
  }
1110
1125
  return null;
1111
1126
  }
1112
- function _isAllowedOption(option) {
1113
- return _isReadableType(option) && _isOptionSomething(option);
1127
+ function _isAllowedPrimitive(value) {
1128
+ const isAllowedPrimitiveType = ['number', 'string', 'boolean'].includes(typeof value);
1129
+ const isValid = value || value === 0 || value === false;
1130
+ return isAllowedPrimitiveType && isValid;
1114
1131
  }
1115
- function _isReadableType(option) {
1116
- return ['number', 'string', 'boolean'].includes(typeof option);
1132
+ function _isValidLabel(label) {
1133
+ return label && isString(label);
1117
1134
  }
1118
- function _isOptionSomething(option) {
1119
- return option || option === 0 || option === false;
1135
+ function _isAllowedValue(value) {
1136
+ if (isObject(value)) {
1137
+ return Object.keys(value).length > 0;
1138
+ }
1139
+ return _isAllowedPrimitive(value);
1120
1140
  }
1121
1141
  function createEmptyOptions(options = {}) {
1122
1142
  const defaults = {};
@@ -1134,8 +1154,8 @@ function createEmptyOptions(options = {}) {
1134
1154
  };
1135
1155
  }
1136
1156
 
1137
- /**
1138
- * @enum { String }
1157
+ /**
1158
+ * @enum { String }
1139
1159
  */
1140
1160
  const LOAD_STATES = {
1141
1161
  LOADING: 'loading',
@@ -1143,17 +1163,17 @@ const LOAD_STATES = {
1143
1163
  ERROR: 'error'
1144
1164
  };
1145
1165
 
1146
- /**
1147
- * @typedef {Object} OptionsGetter
1148
- * @property {Object[]} options - The options data
1149
- * @property {(LOAD_STATES)} loadState - The options data's loading state, to use for conditional rendering
1166
+ /**
1167
+ * @typedef {Object} OptionsGetter
1168
+ * @property {Object[]} options - The options data
1169
+ * @property {(LOAD_STATES)} loadState - The options data's loading state, to use for conditional rendering
1150
1170
  */
1151
1171
 
1152
- /**
1153
- * A hook to load options for single and multiselect components.
1154
- *
1155
- * @param {Object} field - The form field to handle options for
1156
- * @return {OptionsGetter} optionsGetter - A options getter object providing loading state and options
1172
+ /**
1173
+ * A hook to load options for single and multiselect components.
1174
+ *
1175
+ * @param {Object} field - The form field to handle options for
1176
+ * @return {OptionsGetter} optionsGetter - A options getter object providing loading state and options
1157
1177
  */
1158
1178
  function useOptionsAsync (field) {
1159
1179
  const {
@@ -1210,30 +1230,6 @@ const buildLoadedState = options => ({
1210
1230
  loadState: LOAD_STATES.LOADED
1211
1231
  });
1212
1232
 
1213
- function useCleanupMultiSelectValues (props) {
1214
- const {
1215
- field,
1216
- options,
1217
- loadState,
1218
- onChange,
1219
- values
1220
- } = props;
1221
-
1222
- // Ensures that the values are always a subset of the possible options
1223
- useEffect(() => {
1224
- if (loadState !== LOAD_STATES.LOADED) {
1225
- return;
1226
- }
1227
- const hasValuesNotInOptions = values.some(v => !options.map(o => o.value).includes(v));
1228
- if (hasValuesNotInOptions) {
1229
- onChange({
1230
- field,
1231
- value: values.filter(v => options.map(o => o.value).includes(v))
1232
- });
1233
- }
1234
- }, [field, options, onChange, JSON.stringify(values), loadState]);
1235
- }
1236
-
1237
1233
  const ENTER_KEYDOWN_EVENT = new KeyboardEvent('keydown', {
1238
1234
  code: 'Enter',
1239
1235
  key: 'Enter',
@@ -1409,6 +1405,12 @@ function sanitizeDateTimePickerValue(options) {
1409
1405
  if (subtype === DATETIME_SUBTYPES.DATETIME && (isInvalidDateString(value) || !isDateTimeInputInformationSufficient(value))) return null;
1410
1406
  return value;
1411
1407
  }
1408
+ function hasEqualValue(value, array) {
1409
+ if (!Array.isArray(array)) {
1410
+ return false;
1411
+ }
1412
+ return array.some(element => isEqual(value, element));
1413
+ }
1412
1414
  function sanitizeSingleSelectValue(options) {
1413
1415
  const {
1414
1416
  formField,
@@ -1417,7 +1419,7 @@ function sanitizeSingleSelectValue(options) {
1417
1419
  } = options;
1418
1420
  try {
1419
1421
  const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1420
- return validValues.includes(value) ? value : null;
1422
+ return hasEqualValue(value, validValues) ? value : null;
1421
1423
  } catch (error) {
1422
1424
  // use default value in case of formatting error
1423
1425
  // TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
@@ -1432,7 +1434,7 @@ function sanitizeMultiSelectValue(options) {
1432
1434
  } = options;
1433
1435
  try {
1434
1436
  const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1435
- return value.filter(v => validValues.includes(v));
1437
+ return value.filter(v => hasEqualValue(v, validValues));
1436
1438
  } catch (error) {
1437
1439
  // use default value in case of formatting error
1438
1440
  // TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
@@ -1440,6 +1442,31 @@ function sanitizeMultiSelectValue(options) {
1440
1442
  }
1441
1443
  }
1442
1444
 
1445
+ function useCleanupMultiSelectValues (props) {
1446
+ const {
1447
+ field,
1448
+ options,
1449
+ loadState,
1450
+ onChange,
1451
+ values
1452
+ } = props;
1453
+
1454
+ // Ensures that the values are always a subset of the possible options
1455
+ useEffect(() => {
1456
+ if (loadState !== LOAD_STATES.LOADED) {
1457
+ return;
1458
+ }
1459
+ const optionValues = options.map(o => o.value);
1460
+ const hasValuesNotInOptions = values.some(v => !hasEqualValue(v, optionValues));
1461
+ if (hasValuesNotInOptions) {
1462
+ onChange({
1463
+ field,
1464
+ value: values.filter(v => hasEqualValue(v, optionValues))
1465
+ });
1466
+ }
1467
+ }, [field, options, onChange, JSON.stringify(values), loadState]);
1468
+ }
1469
+
1443
1470
  const type$d = 'checklist';
1444
1471
  function Checklist(props) {
1445
1472
  const {
@@ -1462,16 +1489,11 @@ function Checklist(props) {
1462
1489
  const {
1463
1490
  required
1464
1491
  } = validate;
1465
- const toggleCheckbox = v => {
1466
- let newValue = [...values];
1467
- if (!newValue.includes(v)) {
1468
- newValue.push(v);
1469
- } else {
1470
- newValue = newValue.filter(x => x != v);
1471
- }
1492
+ const toggleCheckbox = toggledValue => {
1493
+ const newValues = hasEqualValue(toggledValue, values) ? values.filter(value => !isEqual(value, toggledValue)) : [...values, toggledValue];
1472
1494
  props.onChange({
1473
1495
  field,
1474
- value: newValue
1496
+ value: newValues
1475
1497
  });
1476
1498
  };
1477
1499
  const onCheckboxBlur = e => {
@@ -1509,15 +1531,16 @@ function Checklist(props) {
1509
1531
  required: required
1510
1532
  }), loadState == LOAD_STATES.LOADED && options.map((o, index) => {
1511
1533
  const itemDomId = `${domId}-${index}`;
1534
+ const isChecked = hasEqualValue(o.value, values);
1512
1535
  return jsx(Label, {
1513
1536
  id: itemDomId,
1514
1537
  label: o.label,
1515
1538
  class: classNames({
1516
- 'fjs-checked': values.includes(o.value)
1539
+ 'fjs-checked': isChecked
1517
1540
  }),
1518
1541
  required: false,
1519
1542
  children: jsx("input", {
1520
- checked: values.includes(o.value),
1543
+ checked: isChecked,
1521
1544
  class: "fjs-input",
1522
1545
  disabled: disabled,
1523
1546
  readOnly: readonly,
@@ -1797,12 +1820,12 @@ FormComponent$1.config = {
1797
1820
  })
1798
1821
  };
1799
1822
 
1800
- /**
1801
- * Returns date format for the provided locale.
1802
- * If the locale is not provided, uses the browser's locale.
1803
- *
1804
- * @param {string} [locale] - The locale to get date format for.
1805
- * @returns {string} The date format for the locale.
1823
+ /**
1824
+ * Returns date format for the provided locale.
1825
+ * If the locale is not provided, uses the browser's locale.
1826
+ *
1827
+ * @param {string} [locale] - The locale to get date format for.
1828
+ * @returns {string} The date format for the locale.
1806
1829
  */
1807
1830
  function getLocaleDateFormat(locale = 'default') {
1808
1831
  const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(Date.UTC(2020, 5, 5)));
@@ -1821,12 +1844,12 @@ function getLocaleDateFormat(locale = 'default') {
1821
1844
  }).join('');
1822
1845
  }
1823
1846
 
1824
- /**
1825
- * Returns readable date format for the provided locale.
1826
- * If the locale is not provided, uses the browser's locale.
1827
- *
1828
- * @param {string} [locale] - The locale to get readable date format for.
1829
- * @returns {string} The readable date format for the locale.
1847
+ /**
1848
+ * Returns readable date format for the provided locale.
1849
+ * If the locale is not provided, uses the browser's locale.
1850
+ *
1851
+ * @param {string} [locale] - The locale to get readable date format for.
1852
+ * @returns {string} The readable date format for the locale.
1830
1853
  */
1831
1854
  function getLocaleReadableDateFormat(locale) {
1832
1855
  let format = getLocaleDateFormat(locale).toLowerCase();
@@ -1843,12 +1866,12 @@ function getLocaleReadableDateFormat(locale) {
1843
1866
  return format;
1844
1867
  }
1845
1868
 
1846
- /**
1847
- * Returns flatpickr config for the provided locale.
1848
- * If the locale is not provided, uses the browser's locale.
1849
- *
1850
- * @param {string} [locale] - The locale to get flatpickr config for.
1851
- * @returns {object} The flatpickr config for the locale.
1869
+ /**
1870
+ * Returns flatpickr config for the provided locale.
1871
+ * If the locale is not provided, uses the browser's locale.
1872
+ *
1873
+ * @param {string} [locale] - The locale to get flatpickr config for.
1874
+ * @returns {object} The flatpickr config for the locale.
1852
1875
  */
1853
1876
  function getLocaleDateFlatpickrConfig(locale) {
1854
1877
  return flatpickerizeDateFormat(getLocaleDateFormat(locale));
@@ -2560,10 +2583,10 @@ Datetime.config = {
2560
2583
  }
2561
2584
  };
2562
2585
 
2563
- /**
2564
- * This file must not be changed or exchanged.
2565
- *
2566
- * @see http://bpmn.io/license for more information.
2586
+ /**
2587
+ * This file must not be changed or exchanged.
2588
+ *
2589
+ * @see http://bpmn.io/license for more information.
2567
2590
  */
2568
2591
  function Logo() {
2569
2592
  return jsxs("svg", {
@@ -2749,11 +2772,11 @@ const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u
2749
2772
 
2750
2773
  const FORM_ELEMENT = document.createElement('form');
2751
2774
 
2752
- /**
2753
- * Sanitize a HTML string and return the cleaned, safe version.
2754
- *
2755
- * @param {string} html
2756
- * @return {string}
2775
+ /**
2776
+ * Sanitize a HTML string and return the cleaned, safe version.
2777
+ *
2778
+ * @param {string} html
2779
+ * @return {string}
2757
2780
  */
2758
2781
 
2759
2782
  // see https://github.com/developit/snarkdown/issues/70
@@ -2771,41 +2794,41 @@ function sanitizeHTML(html) {
2771
2794
  }
2772
2795
  }
2773
2796
 
2774
- /**
2775
- * Sanitizes an image source to ensure we only allow for data URI and links
2776
- * that start with http(s).
2777
- *
2778
- * Note: Most browsers anyway do not support script execution in <img> elements.
2779
- *
2780
- * @param {string} src
2781
- * @returns {string}
2797
+ /**
2798
+ * Sanitizes an image source to ensure we only allow for data URI and links
2799
+ * that start with http(s).
2800
+ *
2801
+ * Note: Most browsers anyway do not support script execution in <img> elements.
2802
+ *
2803
+ * @param {string} src
2804
+ * @returns {string}
2782
2805
  */
2783
2806
  function sanitizeImageSource(src) {
2784
2807
  const valid = ALLOWED_IMAGE_SRC_PATTERN.test(src);
2785
2808
  return valid ? src : '';
2786
2809
  }
2787
2810
 
2788
- /**
2789
- * Sanitizes an iframe source to ensure we only allow for links
2790
- * that start with http(s).
2791
- *
2792
- * @param {string} src
2793
- * @returns {string}
2811
+ /**
2812
+ * Sanitizes an iframe source to ensure we only allow for links
2813
+ * that start with http(s).
2814
+ *
2815
+ * @param {string} src
2816
+ * @returns {string}
2794
2817
  */
2795
2818
  function sanitizeIFrameSource(src) {
2796
2819
  const valid = ALLOWED_IFRAME_SRC_PATTERN.test(src);
2797
2820
  return valid ? src : '';
2798
2821
  }
2799
2822
 
2800
- /**
2801
- * Recursively sanitize a HTML node, potentially
2802
- * removing it, its children or attributes.
2803
- *
2804
- * Inspired by https://github.com/developit/snarkdown/issues/70
2805
- * and https://github.com/cure53/DOMPurify. Simplified
2806
- * for our use-case.
2807
- *
2808
- * @param {Element} node
2823
+ /**
2824
+ * Recursively sanitize a HTML node, potentially
2825
+ * removing it, its children or attributes.
2826
+ *
2827
+ * Inspired by https://github.com/developit/snarkdown/issues/70
2828
+ * and https://github.com/cure53/DOMPurify. Simplified
2829
+ * for our use-case.
2830
+ *
2831
+ * @param {Element} node
2809
2832
  */
2810
2833
  function sanitizeNode(node) {
2811
2834
  // allow text nodes
@@ -2849,13 +2872,13 @@ function sanitizeNode(node) {
2849
2872
  }
2850
2873
  }
2851
2874
 
2852
- /**
2853
- * Validates attributes for validity.
2854
- *
2855
- * @param {string} lcTag
2856
- * @param {string} lcName
2857
- * @param {string} value
2858
- * @return {boolean}
2875
+ /**
2876
+ * Validates attributes for validity.
2877
+ *
2878
+ * @param {string} lcTag
2879
+ * @param {string} lcName
2880
+ * @param {string} value
2881
+ * @return {boolean}
2859
2882
  */
2860
2883
  function isValidAttribute(lcTag, lcName, value) {
2861
2884
  // disallow most attributes based on whitelist
@@ -2918,7 +2941,7 @@ function IFrame(props) {
2918
2941
  height: height,
2919
2942
  class: "fjs-iframe",
2920
2943
  id: prefixId(id, formId),
2921
- sandbox: ""
2944
+ sandbox: "allow-scripts"
2922
2945
  }), evaluatedUrl && !safeUrl && jsx(IFramePlaceholder, {
2923
2946
  text: "External content couldn't be loaded."
2924
2947
  })]
@@ -3035,6 +3058,40 @@ Image.config = {
3035
3058
  })
3036
3059
  };
3037
3060
 
3061
+ function useFlushDebounce(func, additionalDeps = []) {
3062
+ const timeoutRef = useRef(null);
3063
+ const lastArgsRef = useRef(null);
3064
+ const config = useService('config', false);
3065
+ const debounce = config && config.debounce;
3066
+ const shouldDebounce = debounce !== false && debounce !== 0;
3067
+ const delay = typeof debounce === 'number' ? debounce : 300;
3068
+ const debounceFunc = useCallback((...args) => {
3069
+ if (!shouldDebounce) {
3070
+ func(...args);
3071
+ return;
3072
+ }
3073
+ lastArgsRef.current = args;
3074
+ if (timeoutRef.current) {
3075
+ clearTimeout(timeoutRef.current);
3076
+ }
3077
+ timeoutRef.current = setTimeout(() => {
3078
+ func(...lastArgsRef.current);
3079
+ lastArgsRef.current = null;
3080
+ }, delay);
3081
+ }, [func, delay, shouldDebounce, ...additionalDeps]);
3082
+ const flushFunc = useCallback(() => {
3083
+ if (timeoutRef.current) {
3084
+ clearTimeout(timeoutRef.current);
3085
+ if (lastArgsRef.current !== null) {
3086
+ func(...lastArgsRef.current);
3087
+ lastArgsRef.current = null;
3088
+ }
3089
+ timeoutRef.current = null;
3090
+ }
3091
+ }, [func, ...additionalDeps]);
3092
+ return [debounceFunc, flushFunc];
3093
+ }
3094
+
3038
3095
  function TemplatedInputAdorner(props) {
3039
3096
  const {
3040
3097
  pre,
@@ -3125,8 +3182,7 @@ function Numberfield(props) {
3125
3182
  onFocus,
3126
3183
  field,
3127
3184
  value,
3128
- readonly,
3129
- onChange
3185
+ readonly
3130
3186
  } = props;
3131
3187
  const {
3132
3188
  description,
@@ -3146,6 +3202,16 @@ function Numberfield(props) {
3146
3202
  } = validate;
3147
3203
  const inputRef = useRef();
3148
3204
  const [stringValueCache, setStringValueCache] = useState('');
3205
+ const [onChangeDebounced, flushOnChange] = useFlushDebounce(params => {
3206
+ props.onChange(params);
3207
+ }, [props.onChange]);
3208
+ const onInputBlur = () => {
3209
+ flushOnChange && flushOnChange();
3210
+ onBlur && onBlur();
3211
+ };
3212
+ const onInputFocus = () => {
3213
+ onFocus && onFocus();
3214
+ };
3149
3215
 
3150
3216
  // checks whether the value currently in the form data is practically different from the one in the input field cache
3151
3217
  // this allows us to guarantee the field always displays valid form data, but without auto-simplifying values like 1.000 to 1
@@ -3169,7 +3235,7 @@ function Numberfield(props) {
3169
3235
  const setValue = useCallback(stringValue => {
3170
3236
  if (isNullEquivalentValue(stringValue)) {
3171
3237
  setStringValueCache('');
3172
- onChange({
3238
+ onChangeDebounced({
3173
3239
  field,
3174
3240
  value: null
3175
3241
  });
@@ -3184,18 +3250,18 @@ function Numberfield(props) {
3184
3250
  }
3185
3251
  if (isNaN(Number(stringValue))) {
3186
3252
  setStringValueCache('NaN');
3187
- onChange({
3253
+ onChangeDebounced({
3188
3254
  field,
3189
3255
  value: 'NaN'
3190
3256
  });
3191
3257
  return;
3192
3258
  }
3193
3259
  setStringValueCache(stringValue);
3194
- onChange({
3260
+ onChangeDebounced({
3195
3261
  field,
3196
3262
  value: serializeToString ? stringValue : Number(stringValue)
3197
3263
  });
3198
- }, [field, onChange, serializeToString]);
3264
+ }, [field, onChangeDebounced, serializeToString]);
3199
3265
  const increment = () => {
3200
3266
  if (readonly) {
3201
3267
  return;
@@ -3279,8 +3345,8 @@ function Numberfield(props) {
3279
3345
  id: domId,
3280
3346
  onKeyDown: onKeyDown,
3281
3347
  onKeyPress: onKeyPress,
3282
- onBlur: () => onBlur && onBlur(),
3283
- onFocus: () => onFocus && onFocus()
3348
+ onBlur: onInputBlur,
3349
+ onFocus: onInputFocus
3284
3350
 
3285
3351
  // @ts-ignore
3286
3352
  ,
@@ -3357,7 +3423,8 @@ function useCleanupSingleSelectValue (props) {
3357
3423
  if (loadState !== LOAD_STATES.LOADED) {
3358
3424
  return;
3359
3425
  }
3360
- const hasValueNotInOptions = value && !options.map(o => o.value).includes(value);
3426
+ const optionValues = options.map(o => o.value);
3427
+ const hasValueNotInOptions = value && !hasEqualValue(value, optionValues);
3361
3428
  if (hasValueNotInOptions) {
3362
3429
  onChange({
3363
3430
  field,
@@ -3430,15 +3497,16 @@ function Radio(props) {
3430
3497
  required: required
3431
3498
  }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
3432
3499
  const itemDomId = `${domId}-${index}`;
3500
+ const isChecked = isEqual(option.value, value);
3433
3501
  return jsx(Label, {
3434
3502
  id: itemDomId,
3435
3503
  label: option.label,
3436
3504
  class: classNames({
3437
- 'fjs-checked': option.value === value
3505
+ 'fjs-checked': isChecked
3438
3506
  }),
3439
3507
  required: false,
3440
3508
  children: jsx("input", {
3441
- checked: option.value === value,
3509
+ checked: isChecked,
3442
3510
  class: "fjs-input",
3443
3511
  disabled: disabled,
3444
3512
  readOnly: readonly,
@@ -3468,6 +3536,21 @@ Radio.config = {
3468
3536
  create: createEmptyOptions
3469
3537
  };
3470
3538
 
3539
+ /**
3540
+ * This hook allows us to retrieve the label from a value in linear time by caching it in a map
3541
+ * @param {Array} options
3542
+ */
3543
+ function useGetLabelCorrelation(options) {
3544
+ // This allows us to retrieve the label from a value in linear time
3545
+ const labelMap = useMemo(() => Object.assign({}, ...options.map(o => ({
3546
+ [_getValueHash(o.value)]: o.label
3547
+ }))), [options]);
3548
+ return useCallback(value => labelMap[_getValueHash(value)], [labelMap]);
3549
+ }
3550
+ const _getValueHash = value => {
3551
+ return isObject(value) ? JSON.stringify(value) : value;
3552
+ };
3553
+
3471
3554
  var _path$q;
3472
3555
  function _extends$r() { _extends$r = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$r.apply(this, arguments); }
3473
3556
  var SvgXMark = function SvgXMark(props) {
@@ -3499,7 +3582,7 @@ function SearchableSelect(props) {
3499
3582
  } = props;
3500
3583
  const [filter, setFilter] = useState('');
3501
3584
  const [isDropdownExpanded, setIsDropdownExpanded] = useState(false);
3502
- const [shouldApplyFilter, setShouldApplyFilter] = useState(true);
3585
+ const [isFilterActive, setIsFilterActive] = useState(true);
3503
3586
  const [isEscapeClosed, setIsEscapeClose] = useState(false);
3504
3587
  const searchbarRef = useRef();
3505
3588
  const eventBus = useService('eventBus');
@@ -3514,23 +3597,22 @@ function SearchableSelect(props) {
3514
3597
  value,
3515
3598
  onChange: props.onChange
3516
3599
  });
3517
-
3518
- // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
3519
- const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
3520
- [o.value]: options[x]
3521
- }))), [options]);
3522
- const valueLabel = useMemo(() => value && valueToOptionMap[value] && valueToOptionMap[value].label || '', [value, valueToOptionMap]);
3600
+ const getLabelCorrelation = useGetLabelCorrelation(options);
3601
+ const label = useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
3523
3602
 
3524
3603
  // whenever we change the underlying value, set the label to it
3525
3604
  useEffect(() => {
3526
- setFilter(valueLabel);
3527
- }, [valueLabel]);
3605
+ setFilter(label);
3606
+ }, [label]);
3528
3607
  const filteredOptions = useMemo(() => {
3529
- if (loadState === LOAD_STATES.LOADED) {
3530
- return shouldApplyFilter ? options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase())) : options;
3608
+ if (loadState !== LOAD_STATES.LOADED) {
3609
+ return [];
3531
3610
  }
3532
- return [];
3533
- }, [filter, loadState, options, shouldApplyFilter]);
3611
+ if (!filter || !isFilterActive) {
3612
+ return options;
3613
+ }
3614
+ return options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()));
3615
+ }, [filter, loadState, options, isFilterActive]);
3534
3616
  const setValue = useCallback(option => {
3535
3617
  setFilter(option && option.label || '');
3536
3618
  props.onChange({
@@ -3557,7 +3639,7 @@ function SearchableSelect(props) {
3557
3639
  }) => {
3558
3640
  setIsEscapeClose(false);
3559
3641
  setIsDropdownExpanded(true);
3560
- setShouldApplyFilter(true);
3642
+ setIsFilterActive(true);
3561
3643
  setFilter(target.value || '');
3562
3644
  eventBus.fire('formField.search', {
3563
3645
  formField: field,
@@ -3573,7 +3655,7 @@ function SearchableSelect(props) {
3573
3655
  {
3574
3656
  if (!isDropdownExpanded) {
3575
3657
  setIsDropdownExpanded(true);
3576
- setShouldApplyFilter(false);
3658
+ setIsFilterActive(false);
3577
3659
  }
3578
3660
  keyDownEvent.preventDefault();
3579
3661
  break;
@@ -3591,7 +3673,7 @@ function SearchableSelect(props) {
3591
3673
  const onInputMouseDown = useCallback(() => {
3592
3674
  setIsEscapeClose(false);
3593
3675
  setIsDropdownExpanded(true);
3594
- setShouldApplyFilter(false);
3676
+ setIsFilterActive(false);
3595
3677
  }, []);
3596
3678
  const onInputFocus = useCallback(() => {
3597
3679
  setIsEscapeClose(false);
@@ -3600,9 +3682,9 @@ function SearchableSelect(props) {
3600
3682
  }, [onFocus]);
3601
3683
  const onInputBlur = useCallback(() => {
3602
3684
  setIsDropdownExpanded(false);
3603
- setFilter(valueLabel);
3685
+ setFilter(label);
3604
3686
  onBlur && onBlur();
3605
- }, [onBlur, valueLabel]);
3687
+ }, [onBlur, label]);
3606
3688
  return jsxs(Fragment, {
3607
3689
  children: [jsxs("div", {
3608
3690
  class: classNames('fjs-input-group', {
@@ -3679,12 +3761,8 @@ function SimpleSelect(props) {
3679
3761
  value,
3680
3762
  onChange: props.onChange
3681
3763
  });
3682
-
3683
- // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
3684
- const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
3685
- [o.value]: options[x]
3686
- }))), [options]);
3687
- const valueLabel = useMemo(() => value && valueToOptionMap[value] && valueToOptionMap[value].label || '', [value, valueToOptionMap]);
3764
+ const getLabelCorrelation = useGetLabelCorrelation(options);
3765
+ const valueLabel = useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
3688
3766
  const setValue = useCallback(option => {
3689
3767
  props.onChange({
3690
3768
  value: option && option.value || null,
@@ -3999,11 +4077,7 @@ function Taglist(props) {
3999
4077
  values,
4000
4078
  onChange: props.onChange
4001
4079
  });
4002
-
4003
- // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
4004
- const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
4005
- [o.value]: options[x]
4006
- }))), [options]);
4080
+ const getLabelCorrelation = useGetLabelCorrelation(options);
4007
4081
  const hasOptionsLeft = useMemo(() => options.length > values.length, [options.length, values.length]);
4008
4082
 
4009
4083
  // Usage of stringify is necessary here because we want this effect to only trigger when there is a value change to the array
@@ -4011,12 +4085,14 @@ function Taglist(props) {
4011
4085
  if (loadState !== LOAD_STATES.LOADED) {
4012
4086
  return [];
4013
4087
  }
4014
- return options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value));
4088
+ const isValidFilteredOption = option => {
4089
+ const filterMatches = option.label.toLowerCase().includes(filter.toLowerCase());
4090
+ return filterMatches && !hasEqualValue(option.value, values);
4091
+ };
4092
+ return options.filter(isValidFilteredOption);
4015
4093
  }, [filter, options, JSON.stringify(values), loadState]);
4016
4094
  const selectValue = value => {
4017
- if (filter) {
4018
- setFilter('');
4019
- }
4095
+ setFilter('');
4020
4096
 
4021
4097
  // Ensure values cannot be double selected due to latency
4022
4098
  if (values.at(-1) === value) {
@@ -4028,8 +4104,9 @@ function Taglist(props) {
4028
4104
  });
4029
4105
  };
4030
4106
  const deselectValue = value => {
4107
+ const newValues = values.filter(v => !isEqual(v, value));
4031
4108
  props.onChange({
4032
- value: values.filter(v => v != value),
4109
+ value: newValues,
4033
4110
  field
4034
4111
  });
4035
4112
  };
@@ -4139,7 +4216,7 @@ function Taglist(props) {
4139
4216
  onMouseDown: e => e.preventDefault(),
4140
4217
  children: [jsx("span", {
4141
4218
  class: "fjs-taglist-tag-label",
4142
- children: valueToOptionMap[v] ? valueToOptionMap[v].label : undefined
4219
+ children: getLabelCorrelation(v)
4143
4220
  }), !disabled && !readonly && jsx("button", {
4144
4221
  type: "button",
4145
4222
  title: "Remove tag",
@@ -4305,13 +4382,20 @@ function Textfield(props) {
4305
4382
  const {
4306
4383
  required
4307
4384
  } = validate;
4308
- const onChange = ({
4385
+ const [onInputChange, flushOnChange] = useFlushDebounce(({
4309
4386
  target
4310
4387
  }) => {
4311
4388
  props.onChange({
4312
4389
  field,
4313
4390
  value: target.value
4314
4391
  });
4392
+ }, [props.onChange]);
4393
+ const onInputBlur = () => {
4394
+ flushOnChange && flushOnChange();
4395
+ onBlur && onBlur();
4396
+ };
4397
+ const onInputFocus = () => {
4398
+ onFocus && onFocus();
4315
4399
  };
4316
4400
  return jsxs("div", {
4317
4401
  class: formFieldClasses(type$2, {
@@ -4333,9 +4417,9 @@ function Textfield(props) {
4333
4417
  disabled: disabled,
4334
4418
  readOnly: readonly,
4335
4419
  id: domId,
4336
- onInput: onChange,
4337
- onBlur: () => onBlur && onBlur(),
4338
- onFocus: () => onFocus && onFocus(),
4420
+ onInput: onInputChange,
4421
+ onBlur: onInputBlur,
4422
+ onFocus: onInputFocus,
4339
4423
  type: "text",
4340
4424
  value: value,
4341
4425
  "aria-describedby": errorMessageId
@@ -4394,13 +4478,20 @@ function Textarea(props) {
4394
4478
  required
4395
4479
  } = validate;
4396
4480
  const textareaRef = useRef();
4397
- const onInput = ({
4481
+ const [onInputChange, flushOnChange] = useFlushDebounce(({
4398
4482
  target
4399
4483
  }) => {
4400
4484
  props.onChange({
4401
4485
  field,
4402
4486
  value: target.value
4403
4487
  });
4488
+ }, [props.onChange]);
4489
+ const onInputBlur = () => {
4490
+ flushOnChange && flushOnChange();
4491
+ onBlur && onBlur();
4492
+ };
4493
+ const onInputFocus = () => {
4494
+ onFocus && onFocus();
4404
4495
  };
4405
4496
  useLayoutEffect(() => {
4406
4497
  autoSizeTextarea(textareaRef.current);
@@ -4423,9 +4514,9 @@ function Textarea(props) {
4423
4514
  disabled: disabled,
4424
4515
  readonly: readonly,
4425
4516
  id: domId,
4426
- onInput: onInput,
4427
- onBlur: () => onBlur && onBlur(),
4428
- onFocus: () => onFocus && onFocus(),
4517
+ onInput: onInputChange,
4518
+ onBlur: onInputBlur,
4519
+ onFocus: onInputFocus,
4429
4520
  value: value,
4430
4521
  ref: textareaRef,
4431
4522
  "aria-describedby": errorMessageId
@@ -4525,28 +4616,28 @@ var CaretRightIcon = SvgCaretRight;
4525
4616
 
4526
4617
  const type = 'table';
4527
4618
 
4528
- /**
4529
- * @typedef {('asc'|'desc')} Direction
4530
- *
4531
- * @typedef Sorting
4532
- * @property {string} key
4533
- * @property {Direction} direction
4534
- *
4535
- * @typedef Column
4536
- * @property {string} label
4537
- * @property {string} key
4538
- *
4539
- * @typedef Props
4540
- * @property {Object} field
4541
- * @property {string} field.id
4542
- * @property {Array<Column>} [field.columns]
4543
- * @property {string} [field.columnsExpression]
4544
- * @property {string} [field.label]
4545
- * @property {number} [field.rowCount]
4546
- * @property {string} [field.dataSource]
4547
- *
4548
- * @param {Props} props
4549
- * @returns {import("preact").JSX.Element}
4619
+ /**
4620
+ * @typedef {('asc'|'desc')} Direction
4621
+ *
4622
+ * @typedef Sorting
4623
+ * @property {string} key
4624
+ * @property {Direction} direction
4625
+ *
4626
+ * @typedef Column
4627
+ * @property {string} label
4628
+ * @property {string} key
4629
+ *
4630
+ * @typedef Props
4631
+ * @property {Object} field
4632
+ * @property {string} field.id
4633
+ * @property {Array<Column>} [field.columns]
4634
+ * @property {string} [field.columnsExpression]
4635
+ * @property {string} [field.label]
4636
+ * @property {number} [field.rowCount]
4637
+ * @property {string} [field.dataSource]
4638
+ *
4639
+ * @param {Props} props
4640
+ * @returns {import("preact").JSX.Element}
4550
4641
  */
4551
4642
  function Table(props) {
4552
4643
  const {
@@ -4756,10 +4847,10 @@ Table.config = {
4756
4847
 
4757
4848
  // helpers /////////////////////////////
4758
4849
 
4759
- /**
4760
- * @param {string|void} columnsExpression
4761
- * @param {Column[]} fallbackColumns
4762
- * @returns {Column[]}
4850
+ /**
4851
+ * @param {string|void} columnsExpression
4852
+ * @param {Column[]} fallbackColumns
4853
+ * @returns {Column[]}
4763
4854
  */
4764
4855
  function useEvaluatedColumns(columnsExpression, fallbackColumns) {
4765
4856
  /** @type {Column[]|null} */
@@ -4767,18 +4858,18 @@ function useEvaluatedColumns(columnsExpression, fallbackColumns) {
4767
4858
  return Array.isArray(evaluation) && evaluation.every(isColumn) ? evaluation : fallbackColumns;
4768
4859
  }
4769
4860
 
4770
- /**
4771
- * @param {any} column
4772
- * @returns {column is Column}
4861
+ /**
4862
+ * @param {any} column
4863
+ * @returns {column is Column}
4773
4864
  */
4774
4865
  function isColumn(column) {
4775
4866
  return isObject(column) && isString(column['label']) && isString(column['key']);
4776
4867
  }
4777
4868
 
4778
- /**
4779
- * @param {Array} array
4780
- * @param {number} size
4781
- * @returns {Array}
4869
+ /**
4870
+ * @param {Array} array
4871
+ * @param {number} size
4872
+ * @returns {Array}
4782
4873
  */
4783
4874
  function chunk(array, size) {
4784
4875
  return array.reduce((chunks, item, index) => {
@@ -4791,11 +4882,11 @@ function chunk(array, size) {
4791
4882
  }, []);
4792
4883
  }
4793
4884
 
4794
- /**
4795
- * @param {unknown[]} array
4796
- * @param {string} key
4797
- * @param {Direction} direction
4798
- * @returns {unknown[]}
4885
+ /**
4886
+ * @param {unknown[]} array
4887
+ * @param {string} key
4888
+ * @param {Direction} direction
4889
+ * @returns {unknown[]}
4799
4890
  */
4800
4891
  function sortByColumn(array, key, direction) {
4801
4892
  return [...array].sort((a, b) => {
@@ -4809,10 +4900,10 @@ function sortByColumn(array, key, direction) {
4809
4900
  });
4810
4901
  }
4811
4902
 
4812
- /**
4813
- * @param {null|Sorting} sortBy
4814
- * @param {string} key
4815
- * @param {string} label
4903
+ /**
4904
+ * @param {null|Sorting} sortBy
4905
+ * @param {string} key
4906
+ * @param {string} label
4816
4907
  */
4817
4908
  function getHeaderAriaLabel(sortBy, key, label) {
4818
4909
  if (sortBy === null || sortBy.key !== key) {
@@ -5187,37 +5278,37 @@ class FormFields {
5187
5278
  const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression', 'url', 'dataSource', 'columnsExpression'];
5188
5279
  const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text', 'url'];
5189
5280
 
5190
- /**
5191
- * @typedef { import('../types').Schema } Schema
5281
+ /**
5282
+ * @typedef { import('../types').Schema } Schema
5192
5283
  */
5193
5284
 
5194
- /**
5195
- * Parse the schema for variables a form might make use of.
5196
- *
5197
- * @example
5198
- *
5199
- * // retrieve variables from schema
5200
- * const variables = getSchemaVariables(schema);
5201
- *
5202
- * @example
5203
- *
5204
- * // retrieve input variables from schema
5205
- * const inputVariables = getSchemaVariables(schema, { outputs: false });
5206
- *
5207
- * @example
5208
- *
5209
- * // retrieve output variables from schema
5210
- * const outputVariables = getSchemaVariables(schema, { inputs: false });
5211
- *
5212
- * @param {Schema} schema
5213
- * @param {object} [options]
5214
- * @param {any} [options.expressionLanguage]
5215
- * @param {any} [options.templating]
5216
- * @param {any} [options.formFields]
5217
- * @param {boolean} [options.inputs=true]
5218
- * @param {boolean} [options.outputs=true]
5219
- *
5220
- * @return {string[]}
5285
+ /**
5286
+ * Parse the schema for variables a form might make use of.
5287
+ *
5288
+ * @example
5289
+ *
5290
+ * // retrieve variables from schema
5291
+ * const variables = getSchemaVariables(schema);
5292
+ *
5293
+ * @example
5294
+ *
5295
+ * // retrieve input variables from schema
5296
+ * const inputVariables = getSchemaVariables(schema, { outputs: false });
5297
+ *
5298
+ * @example
5299
+ *
5300
+ * // retrieve output variables from schema
5301
+ * const outputVariables = getSchemaVariables(schema, { inputs: false });
5302
+ *
5303
+ * @param {Schema} schema
5304
+ * @param {object} [options]
5305
+ * @param {any} [options.expressionLanguage]
5306
+ * @param {any} [options.templating]
5307
+ * @param {any} [options.formFields]
5308
+ * @param {boolean} [options.inputs=true]
5309
+ * @param {boolean} [options.outputs=true]
5310
+ *
5311
+ * @return {string[]}
5221
5312
  */
5222
5313
  function getSchemaVariables(schema, options = {}) {
5223
5314
  const {
@@ -5293,13 +5384,13 @@ function getSchemaVariables(schema, options = {}) {
5293
5384
  return Array.from(new Set(variables));
5294
5385
  }
5295
5386
 
5296
- /**
5297
- * Get the ancestry list of a form field.
5298
- *
5299
- * @param {string} formFieldId
5300
- * @param {import('../core/FormFieldRegistry').default} formFieldRegistry
5301
- *
5302
- * @return {Array<string>} ancestry list
5387
+ /**
5388
+ * Get the ancestry list of a form field.
5389
+ *
5390
+ * @param {string} formFieldId
5391
+ * @param {import('../core/FormFieldRegistry').default} formFieldRegistry
5392
+ *
5393
+ * @return {Array<string>} ancestry list
5303
5394
  */
5304
5395
  const getAncestryList = (formFieldId, formFieldRegistry) => {
5305
5396
  const ids = [];
@@ -5311,9 +5402,9 @@ const getAncestryList = (formFieldId, formFieldRegistry) => {
5311
5402
  return ids;
5312
5403
  };
5313
5404
 
5314
- /**
5315
- * @typedef {object} Condition
5316
- * @property {string} [hide]
5405
+ /**
5406
+ * @typedef {object} Condition
5407
+ * @property {string} [hide]
5317
5408
  */
5318
5409
 
5319
5410
  class ConditionChecker {
@@ -5323,14 +5414,14 @@ class ConditionChecker {
5323
5414
  this._eventBus = eventBus;
5324
5415
  }
5325
5416
 
5326
- /**
5327
- * For given data, remove properties based on condition.
5328
- *
5329
- * @param {Object<string, any>} data
5330
- * @param {Object<string, any>} contextData
5331
- * @param {Object} [options]
5332
- * @param {Function} [options.getFilterPath]
5333
- * @param {boolean} [options.leafNodeDeletionOnly]
5417
+ /**
5418
+ * For given data, remove properties based on condition.
5419
+ *
5420
+ * @param {Object<string, any>} data
5421
+ * @param {Object<string, any>} contextData
5422
+ * @param {Object} [options]
5423
+ * @param {Function} [options.getFilterPath]
5424
+ * @param {boolean} [options.leafNodeDeletionOnly]
5334
5425
  */
5335
5426
  applyConditions(data, contextData = {}, options = {}) {
5336
5427
  const workingData = clone(data);
@@ -5424,13 +5515,13 @@ class ConditionChecker {
5424
5515
  return workingData;
5425
5516
  }
5426
5517
 
5427
- /**
5428
- * Check if given condition is met. Returns null for invalid/missing conditions.
5429
- *
5430
- * @param {string} condition
5431
- * @param {import('../../types').Data} [data]
5432
- *
5433
- * @returns {boolean|null}
5518
+ /**
5519
+ * Check if given condition is met. Returns null for invalid/missing conditions.
5520
+ *
5521
+ * @param {string} condition
5522
+ * @param {import('../../types').Data} [data]
5523
+ *
5524
+ * @returns {boolean|null}
5434
5525
  */
5435
5526
  check(condition, data = {}) {
5436
5527
  if (!condition) {
@@ -5451,12 +5542,12 @@ class ConditionChecker {
5451
5542
  }
5452
5543
  }
5453
5544
 
5454
- /**
5455
- * Check if hide condition is met.
5456
- *
5457
- * @param {Condition} condition
5458
- * @param {Object<string, any>} data
5459
- * @returns {boolean}
5545
+ /**
5546
+ * Check if hide condition is met.
5547
+ *
5548
+ * @param {Condition} condition
5549
+ * @param {Object<string, any>} data
5550
+ * @returns {boolean}
5460
5551
  */
5461
5552
  _checkHideCondition(condition, data) {
5462
5553
  if (!condition.hide) {
@@ -5498,12 +5589,12 @@ class MarkdownRenderer {
5498
5589
  this._converter = new showdown.Converter();
5499
5590
  }
5500
5591
 
5501
- /**
5502
- * Render markdown to HTML.
5503
- *
5504
- * @param {string} markdown - The markdown to render
5505
- *
5506
- * @returns {string} HTML
5592
+ /**
5593
+ * Render markdown to HTML.
5594
+ *
5595
+ * @param {string} markdown - The markdown to render
5596
+ *
5597
+ * @returns {string} HTML
5507
5598
  */
5508
5599
  render(markdown) {
5509
5600
  return this._converter.makeHtml(markdown);
@@ -6124,11 +6215,11 @@ class RepeatRenderManager {
6124
6215
  this.RepeatFooter = this.RepeatFooter.bind(this);
6125
6216
  }
6126
6217
 
6127
- /**
6128
- * Checks whether a field is currently repeating its children.
6129
- *
6130
- * @param {string} id - The id of the field to check
6131
- * @returns {boolean} - True if repeatable, false otherwise
6218
+ /**
6219
+ * Checks whether a field is currently repeating its children.
6220
+ *
6221
+ * @param {string} id - The id of the field to check
6222
+ * @returns {boolean} - True if repeatable, false otherwise
6132
6223
  */
6133
6224
  isFieldRepeating(id) {
6134
6225
  if (!id) {
@@ -6888,8 +6979,8 @@ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form'];
6888
6979
 
6889
6980
  // helpers //////////
6890
6981
 
6891
- /**
6892
- * Helper function to evaluate optional FEEL validation values.
6982
+ /**
6983
+ * Helper function to evaluate optional FEEL validation values.
6893
6984
  */
6894
6985
  function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
6895
6986
  const evaluatedValidate = {
@@ -6923,12 +7014,12 @@ function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form
6923
7014
  }
6924
7015
 
6925
7016
  class Importer {
6926
- /**
6927
- * @constructor
6928
- * @param { import('./FormFieldRegistry').default } formFieldRegistry
6929
- * @param { import('./PathRegistry').default } pathRegistry
6930
- * @param { import('./FieldFactory').default } fieldFactory
6931
- * @param { import('./FormLayouter').default } formLayouter
7017
+ /**
7018
+ * @constructor
7019
+ * @param { import('./FormFieldRegistry').default } formFieldRegistry
7020
+ * @param { import('./PathRegistry').default } pathRegistry
7021
+ * @param { import('./FieldFactory').default } fieldFactory
7022
+ * @param { import('./FormLayouter').default } formLayouter
6932
7023
  */
6933
7024
  constructor(formFieldRegistry, pathRegistry, fieldFactory, formLayouter) {
6934
7025
  this._formFieldRegistry = formFieldRegistry;
@@ -6937,21 +7028,21 @@ class Importer {
6937
7028
  this._formLayouter = formLayouter;
6938
7029
  }
6939
7030
 
6940
- /**
6941
- * Import schema creating rows, fields, attaching additional
6942
- * information to each field and adding fields to the
6943
- * field registry.
6944
- *
6945
- * Additional information attached:
6946
- *
6947
- * * `id` (unless present)
6948
- * * `_parent`
6949
- * * `_path`
6950
- *
6951
- * @param {any} schema
6952
- *
6953
- * @typedef {{ warnings: Error[], schema: any }} ImportResult
6954
- * @returns {ImportResult}
7031
+ /**
7032
+ * Import schema creating rows, fields, attaching additional
7033
+ * information to each field and adding fields to the
7034
+ * field registry.
7035
+ *
7036
+ * Additional information attached:
7037
+ *
7038
+ * * `id` (unless present)
7039
+ * * `_parent`
7040
+ * * `_path`
7041
+ *
7042
+ * @param {any} schema
7043
+ *
7044
+ * @typedef {{ warnings: Error[], schema: any }} ImportResult
7045
+ * @returns {ImportResult}
6955
7046
  */
6956
7047
  importSchema(schema) {
6957
7048
  // TODO: Add warnings
@@ -6976,12 +7067,12 @@ class Importer {
6976
7067
  this._pathRegistry.clear();
6977
7068
  }
6978
7069
 
6979
- /**
6980
- * @param {{[x: string]: any}} fieldAttrs
6981
- * @param {String} [parentId]
6982
- * @param {number} [index]
6983
- *
6984
- * @return {any} field
7070
+ /**
7071
+ * @param {{[x: string]: any}} fieldAttrs
7072
+ * @param {String} [parentId]
7073
+ * @param {number} [index]
7074
+ *
7075
+ * @return {any} field
6985
7076
  */
6986
7077
  importFormField(fieldAttrs, parentId, index) {
6987
7078
  const {
@@ -7006,11 +7097,11 @@ class Importer {
7006
7097
  return field;
7007
7098
  }
7008
7099
 
7009
- /**
7010
- * @param {Array<any>} components
7011
- * @param {string} parentId
7012
- *
7013
- * @return {Array<any>} imported components
7100
+ /**
7101
+ * @param {Array<any>} components
7102
+ * @param {string} parentId
7103
+ *
7104
+ * @return {Array<any>} imported components
7014
7105
  */
7015
7106
  importFormFields(components, parentId) {
7016
7107
  return components.map((component, index) => {
@@ -7021,11 +7112,11 @@ class Importer {
7021
7112
  Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
7022
7113
 
7023
7114
  class FieldFactory {
7024
- /**
7025
- * @constructor
7026
- *
7027
- * @param formFieldRegistry
7028
- * @param formFields
7115
+ /**
7116
+ * @constructor
7117
+ *
7118
+ * @param formFieldRegistry
7119
+ * @param formFields
7029
7120
  */
7030
7121
  constructor(formFieldRegistry, pathRegistry, formFields) {
7031
7122
  this._formFieldRegistry = formFieldRegistry;
@@ -7135,36 +7226,36 @@ class FieldFactory {
7135
7226
  }
7136
7227
  FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
7137
7228
 
7138
- /**
7139
- * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
7140
- * It enables claiming, unclaiming, and validating paths within this structure.
7141
- *
7142
- * Example Tree Structure:
7143
- *
7144
- * [
7145
- * {
7146
- * segment: 'root',
7147
- * claimCount: 1,
7148
- * children: [
7149
- * {
7150
- * segment: 'child1',
7151
- * claimCount: 2,
7152
- * children: null // A leaf node (closed path)
7153
- * },
7154
- * {
7155
- * segment: 'child2',
7156
- * claimCount: 1,
7157
- * children: [
7158
- * {
7159
- * segment: 'subChild1',
7160
- * claimCount: 1,
7161
- * children: [] // An open node (open path)
7162
- * }
7163
- * ]
7164
- * }
7165
- * ]
7166
- * }
7167
- * ]
7229
+ /**
7230
+ * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
7231
+ * It enables claiming, unclaiming, and validating paths within this structure.
7232
+ *
7233
+ * Example Tree Structure:
7234
+ *
7235
+ * [
7236
+ * {
7237
+ * segment: 'root',
7238
+ * claimCount: 1,
7239
+ * children: [
7240
+ * {
7241
+ * segment: 'child1',
7242
+ * claimCount: 2,
7243
+ * children: null // A leaf node (closed path)
7244
+ * },
7245
+ * {
7246
+ * segment: 'child2',
7247
+ * claimCount: 1,
7248
+ * children: [
7249
+ * {
7250
+ * segment: 'subChild1',
7251
+ * claimCount: 1,
7252
+ * children: [] // An open node (open path)
7253
+ * }
7254
+ * ]
7255
+ * }
7256
+ * ]
7257
+ * }
7258
+ * ]
7168
7259
  */
7169
7260
  class PathRegistry {
7170
7261
  constructor(formFieldRegistry, formFields, injector) {
@@ -7280,16 +7371,16 @@ class PathRegistry {
7280
7371
  }
7281
7372
  }
7282
7373
 
7283
- /**
7284
- * Applies a function (fn) recursively on a given field and its children.
7285
- *
7286
- * - `field`: Starting field object.
7287
- * - `fn`: Function to apply.
7288
- * - `context`: Optional object for passing data between calls.
7289
- *
7290
- * Stops early if `fn` returns `false`. Useful for traversing the form field tree.
7291
- *
7292
- * @returns {boolean} Success status based on function execution.
7374
+ /**
7375
+ * Applies a function (fn) recursively on a given field and its children.
7376
+ *
7377
+ * - `field`: Starting field object.
7378
+ * - `fn`: Function to apply.
7379
+ * - `context`: Optional object for passing data between calls.
7380
+ *
7381
+ * Stops early if `fn` returns `false`. Useful for traversing the form field tree.
7382
+ *
7383
+ * @returns {boolean} Success status based on function execution.
7293
7384
  */
7294
7385
  executeRecursivelyOnFields(field, fn, context = {}) {
7295
7386
  let result = true;
@@ -7330,16 +7421,16 @@ class PathRegistry {
7330
7421
  return result;
7331
7422
  }
7332
7423
 
7333
- /**
7334
- * Generates an array representing the binding path to an underlying data object for a form field.
7335
- *
7336
- * @param {Object} field - The field object with properties: `key`, `path`, `id`, and optionally `_parent`.
7337
- * @param {Object} [options={}] - Configuration options.
7338
- * @param {Object} [options.replacements={}] - A map of field IDs to alternative path arrays.
7339
- * @param {Object} [options.indexes=null] - A map of parent IDs to the index of the field within said parent, leave null to get an unindexed path.
7340
- * @param {Object} [options.cutoffNode] - The ID of the parent field at which to stop generating the path.
7341
- *
7342
- * @returns {(Array<string>|undefined)} An array of strings representing the binding path, or undefined if not determinable.
7424
+ /**
7425
+ * Generates an array representing the binding path to an underlying data object for a form field.
7426
+ *
7427
+ * @param {Object} field - The field object with properties: `key`, `path`, `id`, and optionally `_parent`.
7428
+ * @param {Object} [options={}] - Configuration options.
7429
+ * @param {Object} [options.replacements={}] - A map of field IDs to alternative path arrays.
7430
+ * @param {Object} [options.indexes=null] - A map of parent IDs to the index of the field within said parent, leave null to get an unindexed path.
7431
+ * @param {Object} [options.cutoffNode] - The ID of the parent field at which to stop generating the path.
7432
+ *
7433
+ * @returns {(Array<string>|undefined)} An array of strings representing the binding path, or undefined if not determinable.
7343
7434
  */
7344
7435
  getValuePath(field, options = {}) {
7345
7436
  const {
@@ -7400,23 +7491,23 @@ const _getNextSegment = (node, segment) => {
7400
7491
  };
7401
7492
  PathRegistry.$inject = ['formFieldRegistry', 'formFields', 'injector'];
7402
7493
 
7403
- /**
7404
- * @typedef { { id: String, components: Array<String> } } FormRow
7405
- * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
7494
+ /**
7495
+ * @typedef { { id: String, components: Array<String> } } FormRow
7496
+ * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
7406
7497
  */
7407
7498
 
7408
- /**
7409
- * Maintains the Form layout in a given structure, for example
7410
- *
7411
- * [
7412
- * {
7413
- * formFieldId: 'FormField_1',
7414
- * rows: [
7415
- * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
7416
- * ]
7417
- * }
7418
- * ]
7419
- *
7499
+ /**
7500
+ * Maintains the Form layout in a given structure, for example
7501
+ *
7502
+ * [
7503
+ * {
7504
+ * formFieldId: 'FormField_1',
7505
+ * rows: [
7506
+ * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
7507
+ * ]
7508
+ * }
7509
+ * ]
7510
+ *
7420
7511
  */
7421
7512
  class FormLayouter {
7422
7513
  constructor(eventBus) {
@@ -7426,8 +7517,8 @@ class FormLayouter {
7426
7517
  this._eventBus = eventBus;
7427
7518
  }
7428
7519
 
7429
- /**
7430
- * @param {FormRow} row
7520
+ /**
7521
+ * @param {FormRow} row
7431
7522
  */
7432
7523
  addRow(formFieldId, row) {
7433
7524
  let rowsPerComponent = this._rows.find(r => r.formFieldId === formFieldId);
@@ -7441,18 +7532,18 @@ class FormLayouter {
7441
7532
  rowsPerComponent.rows.push(row);
7442
7533
  }
7443
7534
 
7444
- /**
7445
- * @param {String} id
7446
- * @returns {FormRow}
7535
+ /**
7536
+ * @param {String} id
7537
+ * @returns {FormRow}
7447
7538
  */
7448
7539
  getRow(id) {
7449
7540
  const rows = allRows(this._rows);
7450
7541
  return rows.find(r => r.id === id);
7451
7542
  }
7452
7543
 
7453
- /**
7454
- * @param {any} formField
7455
- * @returns {FormRow}
7544
+ /**
7545
+ * @param {any} formField
7546
+ * @returns {FormRow}
7456
7547
  */
7457
7548
  getRowForField(formField) {
7458
7549
  return allRows(this._rows).find(r => {
@@ -7463,9 +7554,9 @@ class FormLayouter {
7463
7554
  });
7464
7555
  }
7465
7556
 
7466
- /**
7467
- * @param {String} formFieldId
7468
- * @returns { Array<FormRow> }
7557
+ /**
7558
+ * @param {String} formFieldId
7559
+ * @returns { Array<FormRow> }
7469
7560
  */
7470
7561
  getRows(formFieldId) {
7471
7562
  const rowsForField = this._rows.find(r => formFieldId === r.formFieldId);
@@ -7475,15 +7566,15 @@ class FormLayouter {
7475
7566
  return rowsForField.rows;
7476
7567
  }
7477
7568
 
7478
- /**
7479
- * @returns {string}
7569
+ /**
7570
+ * @returns {string}
7480
7571
  */
7481
7572
  nextRowId() {
7482
7573
  return this._ids.nextPrefixed('Row_');
7483
7574
  }
7484
7575
 
7485
- /**
7486
- * @param {any} formField
7576
+ /**
7577
+ * @param {any} formField
7487
7578
  */
7488
7579
  calculateLayout(formField) {
7489
7580
  const {
@@ -7537,9 +7628,9 @@ function groupByRow(components, ids) {
7537
7628
  });
7538
7629
  }
7539
7630
 
7540
- /**
7541
- * @param {Array<FormRows>} formRows
7542
- * @returns {Array<FormRow>}
7631
+ /**
7632
+ * @param {Array<FormRows>} formRows
7633
+ * @returns {Array<FormRow>}
7543
7634
  */
7544
7635
  function allRows(formRows) {
7545
7636
  return flatten(formRows.map(c => c.rows));
@@ -7664,55 +7755,55 @@ var core = {
7664
7755
  validator: ['type', Validator]
7665
7756
  };
7666
7757
 
7667
- /**
7668
- * @typedef { import('./types').Injector } Injector
7669
- * @typedef { import('./types').Data } Data
7670
- * @typedef { import('./types').Errors } Errors
7671
- * @typedef { import('./types').Schema } Schema
7672
- * @typedef { import('./types').FormProperties } FormProperties
7673
- * @typedef { import('./types').FormProperty } FormProperty
7674
- * @typedef { import('./types').FormEvent } FormEvent
7675
- * @typedef { import('./types').FormOptions } FormOptions
7676
- *
7677
- * @typedef { {
7678
- * data: Data,
7679
- * initialData: Data,
7680
- * errors: Errors,
7681
- * properties: FormProperties,
7682
- * schema: Schema
7683
- * } } State
7684
- *
7685
- * @typedef { (type:FormEvent, priority:number, handler:Function) => void } OnEventWithPriority
7686
- * @typedef { (type:FormEvent, handler:Function) => void } OnEventWithOutPriority
7687
- * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
7758
+ /**
7759
+ * @typedef { import('./types').Injector } Injector
7760
+ * @typedef { import('./types').Data } Data
7761
+ * @typedef { import('./types').Errors } Errors
7762
+ * @typedef { import('./types').Schema } Schema
7763
+ * @typedef { import('./types').FormProperties } FormProperties
7764
+ * @typedef { import('./types').FormProperty } FormProperty
7765
+ * @typedef { import('./types').FormEvent } FormEvent
7766
+ * @typedef { import('./types').FormOptions } FormOptions
7767
+ *
7768
+ * @typedef { {
7769
+ * data: Data,
7770
+ * initialData: Data,
7771
+ * errors: Errors,
7772
+ * properties: FormProperties,
7773
+ * schema: Schema
7774
+ * } } State
7775
+ *
7776
+ * @typedef { (type:FormEvent, priority:number, handler:Function) => void } OnEventWithPriority
7777
+ * @typedef { (type:FormEvent, handler:Function) => void } OnEventWithOutPriority
7778
+ * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
7688
7779
  */
7689
7780
 
7690
7781
  const ids = new Ids([32, 36, 1]);
7691
7782
 
7692
- /**
7693
- * The form.
7783
+ /**
7784
+ * The form.
7694
7785
  */
7695
7786
  class Form {
7696
- /**
7697
- * @constructor
7698
- * @param {FormOptions} options
7787
+ /**
7788
+ * @constructor
7789
+ * @param {FormOptions} options
7699
7790
  */
7700
7791
  constructor(options = {}) {
7701
- /**
7702
- * @public
7703
- * @type {OnEventType}
7792
+ /**
7793
+ * @public
7794
+ * @type {OnEventType}
7704
7795
  */
7705
7796
  this.on = this._onEvent;
7706
7797
 
7707
- /**
7708
- * @public
7709
- * @type {String}
7798
+ /**
7799
+ * @public
7800
+ * @type {String}
7710
7801
  */
7711
7802
  this._id = ids.next();
7712
7803
 
7713
- /**
7714
- * @private
7715
- * @type {Element}
7804
+ /**
7805
+ * @private
7806
+ * @type {Element}
7716
7807
  */
7717
7808
  this._container = createFormContainer();
7718
7809
  const {
@@ -7721,9 +7812,9 @@ class Form {
7721
7812
  properties = {}
7722
7813
  } = options;
7723
7814
 
7724
- /**
7725
- * @private
7726
- * @type {State}
7815
+ /**
7816
+ * @private
7817
+ * @type {State}
7727
7818
  */
7728
7819
  this._state = {
7729
7820
  initialData: null,
@@ -7747,9 +7838,9 @@ class Form {
7747
7838
  this._emit('form.clear');
7748
7839
  }
7749
7840
 
7750
- /**
7751
- * Destroy the form, removing it from DOM,
7752
- * if attached.
7841
+ /**
7842
+ * Destroy the form, removing it from DOM,
7843
+ * if attached.
7753
7844
  */
7754
7845
  destroy() {
7755
7846
  // destroy form services
@@ -7760,13 +7851,13 @@ class Form {
7760
7851
  this._detach(false);
7761
7852
  }
7762
7853
 
7763
- /**
7764
- * Open a form schema with the given initial data.
7765
- *
7766
- * @param {Schema} schema
7767
- * @param {Data} [data]
7768
- *
7769
- * @return Promise<{ warnings: Array<any> }>
7854
+ /**
7855
+ * Open a form schema with the given initial data.
7856
+ *
7857
+ * @param {Schema} schema
7858
+ * @param {Data} [data]
7859
+ *
7860
+ * @return Promise<{ warnings: Array<any> }>
7770
7861
  */
7771
7862
  importSchema(schema, data = {}) {
7772
7863
  return new Promise((resolve, reject) => {
@@ -7799,10 +7890,10 @@ class Form {
7799
7890
  });
7800
7891
  }
7801
7892
 
7802
- /**
7803
- * Submit the form, triggering all field validations.
7804
- *
7805
- * @returns { { data: Data, errors: Errors } }
7893
+ /**
7894
+ * Submit the form, triggering all field validations.
7895
+ *
7896
+ * @returns { { data: Data, errors: Errors } }
7806
7897
  */
7807
7898
  submit() {
7808
7899
  const {
@@ -7828,8 +7919,8 @@ class Form {
7828
7919
  });
7829
7920
  }
7830
7921
 
7831
- /**
7832
- * @returns {Errors}
7922
+ /**
7923
+ * @returns {Errors}
7833
7924
  */
7834
7925
  validate() {
7835
7926
  const formFields = this.get('formFields'),
@@ -7901,8 +7992,8 @@ class Form {
7901
7992
  return filteredErrors;
7902
7993
  }
7903
7994
 
7904
- /**
7905
- * @param {Element|string} parentNode
7995
+ /**
7996
+ * @param {Element|string} parentNode
7906
7997
  */
7907
7998
  attachTo(parentNode) {
7908
7999
  if (!parentNode) {
@@ -7920,10 +8011,10 @@ class Form {
7920
8011
  this._detach();
7921
8012
  }
7922
8013
 
7923
- /**
7924
- * @private
7925
- *
7926
- * @param {boolean} [emit]
8014
+ /**
8015
+ * @private
8016
+ *
8017
+ * @param {boolean} [emit]
7927
8018
  */
7928
8019
  _detach(emit = true) {
7929
8020
  const container = this._container,
@@ -7937,9 +8028,9 @@ class Form {
7937
8028
  parentNode.removeChild(container);
7938
8029
  }
7939
8030
 
7940
- /**
7941
- * @param {FormProperty} property
7942
- * @param {any} value
8031
+ /**
8032
+ * @param {FormProperty} property
8033
+ * @param {any} value
7943
8034
  */
7944
8035
  setProperty(property, value) {
7945
8036
  const properties = set(this._getState().properties, [property], value);
@@ -7948,50 +8039,52 @@ class Form {
7948
8039
  });
7949
8040
  }
7950
8041
 
7951
- /**
7952
- * @param {FormEvent} type
7953
- * @param {Function} handler
8042
+ /**
8043
+ * @param {FormEvent} type
8044
+ * @param {Function} handler
7954
8045
  */
7955
8046
  off(type, handler) {
7956
8047
  this.get('eventBus').off(type, handler);
7957
8048
  }
7958
8049
 
7959
- /**
7960
- * @private
7961
- *
7962
- * @param {FormOptions} options
7963
- * @param {Element} container
7964
- *
7965
- * @returns {Injector}
8050
+ /**
8051
+ * @private
8052
+ *
8053
+ * @param {FormOptions} options
8054
+ * @param {Element} container
8055
+ *
8056
+ * @returns {Injector}
7966
8057
  */
7967
8058
  _createInjector(options, container) {
7968
8059
  const {
8060
+ modules = this._getModules(),
7969
8061
  additionalModules = [],
7970
- modules = this._getModules()
8062
+ ...config
7971
8063
  } = options;
7972
- const config = {
8064
+ const enrichedConfig = {
8065
+ ...config,
7973
8066
  renderer: {
7974
8067
  container
7975
8068
  }
7976
8069
  };
7977
8070
  return createInjector([{
7978
- config: ['value', config]
8071
+ config: ['value', enrichedConfig]
7979
8072
  }, {
7980
8073
  form: ['value', this]
7981
8074
  }, core, ...modules, ...additionalModules]);
7982
8075
  }
7983
8076
 
7984
- /**
7985
- * @private
8077
+ /**
8078
+ * @private
7986
8079
  */
7987
8080
  _emit(type, data) {
7988
8081
  this.get('eventBus').fire(type, data);
7989
8082
  }
7990
8083
 
7991
- /**
7992
- * @internal
7993
- *
7994
- * @param { { add?: boolean, field: any, indexes: object, remove?: number, value?: any } } update
8084
+ /**
8085
+ * @internal
8086
+ *
8087
+ * @param { { add?: boolean, field: any, indexes: object, remove?: number, value?: any } } update
7995
8088
  */
7996
8089
  _update(update) {
7997
8090
  const {
@@ -8017,15 +8110,15 @@ class Form {
8017
8110
  });
8018
8111
  }
8019
8112
 
8020
- /**
8021
- * @internal
8113
+ /**
8114
+ * @internal
8022
8115
  */
8023
8116
  _getState() {
8024
8117
  return this._state;
8025
8118
  }
8026
8119
 
8027
- /**
8028
- * @internal
8120
+ /**
8121
+ * @internal
8029
8122
  */
8030
8123
  _setState(state) {
8031
8124
  this._state = {
@@ -8035,22 +8128,22 @@ class Form {
8035
8128
  this._emit('changed', this._getState());
8036
8129
  }
8037
8130
 
8038
- /**
8039
- * @internal
8131
+ /**
8132
+ * @internal
8040
8133
  */
8041
8134
  _getModules() {
8042
8135
  return [ExpressionLanguageModule, MarkdownModule, ViewerCommandsModule, RepeatRenderModule];
8043
8136
  }
8044
8137
 
8045
- /**
8046
- * @internal
8138
+ /**
8139
+ * @internal
8047
8140
  */
8048
8141
  _onEvent(type, priority, handler) {
8049
8142
  this.get('eventBus').on(type, priority, handler);
8050
8143
  }
8051
8144
 
8052
- /**
8053
- * @internal
8145
+ /**
8146
+ * @internal
8054
8147
  */
8055
8148
  _getSubmitData() {
8056
8149
  const formFieldRegistry = this.get('formFieldRegistry');
@@ -8107,16 +8200,16 @@ class Form {
8107
8200
  return this._applyConditions(workingSubmitData, formData);
8108
8201
  }
8109
8202
 
8110
- /**
8111
- * @internal
8203
+ /**
8204
+ * @internal
8112
8205
  */
8113
8206
  _applyConditions(toFilter, data, options = {}) {
8114
8207
  const conditionChecker = this.get('conditionChecker');
8115
8208
  return conditionChecker.applyConditions(toFilter, data, options);
8116
8209
  }
8117
8210
 
8118
- /**
8119
- * @internal
8211
+ /**
8212
+ * @internal
8120
8213
  */
8121
8214
  _getInitializedFieldData(data, options = {}) {
8122
8215
  const formFieldRegistry = this.get('formFieldRegistry');
@@ -8222,9 +8315,9 @@ function createForm(options) {
8222
8315
  const {
8223
8316
  data,
8224
8317
  schema,
8225
- ...rest
8318
+ ...formOptions
8226
8319
  } = options;
8227
- const form = new Form(rest);
8320
+ const form = new Form(formOptions);
8228
8321
  return form.importSchema(schema, data).then(function () {
8229
8322
  return form;
8230
8323
  });