@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.cjs CHANGED
@@ -7,6 +7,7 @@ var classNames = require('classnames');
7
7
  var jsxRuntime = require('preact/jsx-runtime');
8
8
  var hooks = require('preact/hooks');
9
9
  var preact = require('preact');
10
+ var isEqual = require('lodash/isEqual');
10
11
  var flatpickr = require('flatpickr');
11
12
  var React = require('preact/compat');
12
13
  var Markup = require('preact-markup');
@@ -73,26 +74,26 @@ const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'expression', o
73
74
  return [...new Set(variables)];
74
75
  };
75
76
 
76
- /**
77
- * Get the variable name at the specified index in a given path expression.
78
- *
79
- * @param {Object} root - The root node of the path expression tree.
80
- * @param {number} index - The index of the variable name to retrieve.
81
- * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
77
+ /**
78
+ * Get the variable name at the specified index in a given path expression.
79
+ *
80
+ * @param {Object} root - The root node of the path expression tree.
81
+ * @param {number} index - The index of the variable name to retrieve.
82
+ * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
82
83
  */
83
84
  const _getVariableNameAtPathIndex = (root, index) => {
84
85
  const nodes = _linearizePathExpression(root);
85
86
  return nodes[index].variableName || null;
86
87
  };
87
88
 
88
- /**
89
- * Extracts the variables which are required of the external context for a given path expression.
90
- * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
91
- *
92
- * @param {Object} node - The root node of the path expression tree.
93
- * @param {number} initialDepth - The depth at which the root node is located in the outer context.
94
- * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
95
- * @returns {Set} - A set containing the extracted variable names.
89
+ /**
90
+ * Extracts the variables which are required of the external context for a given path expression.
91
+ * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
92
+ *
93
+ * @param {Object} node - The root node of the path expression tree.
94
+ * @param {number} initialDepth - The depth at which the root node is located in the outer context.
95
+ * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
96
+ * @returns {Set} - A set containing the extracted variable names.
96
97
  */
97
98
  const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {
98
99
  // depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
@@ -138,11 +139,11 @@ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) =
138
139
  return new Set(extractedVariables);
139
140
  };
140
141
 
141
- /**
142
- * Deconstructs a path expression tree into an array of components.
143
- *
144
- * @param {Object} root - The root node of the path expression tree.
145
- * @returns {Array<object>} An array of components in the path expression, in the correct order.
142
+ /**
143
+ * Deconstructs a path expression tree into an array of components.
144
+ *
145
+ * @param {Object} root - The root node of the path expression tree.
146
+ * @returns {Array<object>} An array of components in the path expression, in the correct order.
146
147
  */
147
148
  const _linearizePathExpression = root => {
148
149
  let node = root;
@@ -161,13 +162,13 @@ const _linearizePathExpression = root => {
161
162
  return parts.reverse();
162
163
  };
163
164
 
164
- /**
165
- * Builds a simplified feel structure tree from the given parse tree and feel string.
166
- * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
167
- *
168
- * @param {Object} parseTree - The parse tree generated by a parser.
169
- * @param {string} feelString - The feel string used for parsing.
170
- * @returns {Object} The simplified feel structure tree.
165
+ /**
166
+ * Builds a simplified feel structure tree from the given parse tree and feel string.
167
+ * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
168
+ *
169
+ * @param {Object} parseTree - The parse tree generated by a parser.
170
+ * @param {string} feelString - The feel string used for parsing.
171
+ * @returns {Object} The simplified feel structure tree.
171
172
  */
172
173
  const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
173
174
  const stack = [{
@@ -193,9 +194,9 @@ const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
193
194
  return _extractFilterExpressions(stack[0].children[0]);
194
195
  };
195
196
 
196
- /**
197
- * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
198
- * This is done to simplify the extraction of variables and match the context hierarchy.
197
+ /**
198
+ * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
199
+ * This is done to simplify the extraction of variables and match the context hierarchy.
199
200
  */
200
201
  const _extractFilterExpressions = tree => {
201
202
  const flattenedExpressionTree = {
@@ -236,25 +237,25 @@ class FeelExpressionLanguage {
236
237
  this._eventBus = eventBus;
237
238
  }
238
239
 
239
- /**
240
- * Determines if the given value is a FEEL expression.
241
- *
242
- * @param {any} value
243
- * @returns {boolean}
244
- *
240
+ /**
241
+ * Determines if the given value is a FEEL expression.
242
+ *
243
+ * @param {any} value
244
+ * @returns {boolean}
245
+ *
245
246
  */
246
247
  isExpression(value) {
247
248
  return minDash.isString(value) && value.startsWith('=');
248
249
  }
249
250
 
250
- /**
251
- * Retrieve variable names from a given FEEL expression.
252
- *
253
- * @param {string} expression
254
- * @param {object} [options]
255
- * @param {string} [options.type]
256
- *
257
- * @returns {string[]}
251
+ /**
252
+ * Retrieve variable names from a given FEEL expression.
253
+ *
254
+ * @param {string} expression
255
+ * @param {object} [options]
256
+ * @param {string} [options.type]
257
+ *
258
+ * @returns {string[]}
258
259
  */
259
260
  getVariableNames(expression, options = {}) {
260
261
  const {
@@ -269,13 +270,13 @@ class FeelExpressionLanguage {
269
270
  return getFlavouredFeelVariableNames(expression, type);
270
271
  }
271
272
 
272
- /**
273
- * Evaluate an expression.
274
- *
275
- * @param {string} expression
276
- * @param {import('../../types').Data} [data]
277
- *
278
- * @returns {any}
273
+ /**
274
+ * Evaluate an expression.
275
+ *
276
+ * @param {string} expression
277
+ * @param {import('../../types').Data} [data]
278
+ *
279
+ * @returns {any}
279
280
  */
280
281
  evaluate(expression, data = {}) {
281
282
  if (!expression) {
@@ -300,23 +301,23 @@ FeelExpressionLanguage.$inject = ['eventBus'];
300
301
  class FeelersTemplating {
301
302
  constructor() {}
302
303
 
303
- /**
304
- * Determines if the given value is a feelers template.
305
- *
306
- * @param {any} value
307
- * @returns {boolean}
308
- *
304
+ /**
305
+ * Determines if the given value is a feelers template.
306
+ *
307
+ * @param {any} value
308
+ * @returns {boolean}
309
+ *
309
310
  */
310
311
  isTemplate(value) {
311
312
  return minDash.isString(value) && (value.startsWith('=') || /{{.*?}}/.test(value));
312
313
  }
313
314
 
314
- /**
315
- * Retrieve variable names from a given feelers template.
316
- *
317
- * @param {string} template
318
- *
319
- * @returns {string[]}
315
+ /**
316
+ * Retrieve variable names from a given feelers template.
317
+ *
318
+ * @param {string} template
319
+ *
320
+ * @returns {string[]}
320
321
  */
321
322
  getVariableNames(template) {
322
323
  if (!this.isTemplate(template)) {
@@ -342,17 +343,17 @@ class FeelersTemplating {
342
343
  }, []);
343
344
  }
344
345
 
345
- /**
346
- * Evaluate a template.
347
- *
348
- * @param {string} template
349
- * @param {Object<string, any>} context
350
- * @param {Object} options
351
- * @param {boolean} [options.debug = false]
352
- * @param {boolean} [options.strict = false]
353
- * @param {Function} [options.buildDebugString]
354
- *
355
- * @returns
346
+ /**
347
+ * Evaluate a template.
348
+ *
349
+ * @param {string} template
350
+ * @param {Object<string, any>} context
351
+ * @param {Object} options
352
+ * @param {boolean} [options.debug = false]
353
+ * @param {boolean} [options.strict = false]
354
+ * @param {Function} [options.buildDebugString]
355
+ *
356
+ * @returns
356
357
  */
357
358
  evaluate(template, context = {}, options = {}) {
358
359
  const {
@@ -367,22 +368,22 @@ class FeelersTemplating {
367
368
  });
368
369
  }
369
370
 
370
- /**
371
- * @typedef {Object} ExpressionWithDepth
372
- * @property {number} depth - The depth of the expression in the syntax tree.
373
- * @property {string} expression - The extracted expression
371
+ /**
372
+ * @typedef {Object} ExpressionWithDepth
373
+ * @property {number} depth - The depth of the expression in the syntax tree.
374
+ * @property {string} expression - The extracted expression
374
375
  */
375
376
 
376
- /**
377
- * Extracts all feel expressions in the template along with their depth in the syntax tree.
378
- * The depth is incremented for child expressions of loops to account for context drilling.
379
- * @name extractExpressionsWithDepth
380
- * @param {string} template - A feelers template string.
381
- * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
382
- *
383
- * @example
384
- * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
385
- * const extractedExpressions = _extractExpressionsWithDepth(template);
377
+ /**
378
+ * Extracts all feel expressions in the template along with their depth in the syntax tree.
379
+ * The depth is incremented for child expressions of loops to account for context drilling.
380
+ * @name extractExpressionsWithDepth
381
+ * @param {string} template - A feelers template string.
382
+ * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
383
+ *
384
+ * @example
385
+ * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
386
+ * const extractedExpressions = _extractExpressionsWithDepth(template);
386
387
  */
387
388
  _extractExpressionsWithDepth(template) {
388
389
  // build simplified feelers syntax tree
@@ -488,10 +489,10 @@ function createInjector(bootstrapModules) {
488
489
  return injector;
489
490
  }
490
491
 
491
- /**
492
- * @param {string?} prefix
493
- *
494
- * @returns Element
492
+ /**
493
+ * @param {string?} prefix
494
+ *
495
+ * @returns Element
495
496
  */
496
497
  function createFormContainer(prefix = 'fjs') {
497
498
  const container = document.createElement('div');
@@ -622,11 +623,11 @@ const LocalExpressionContext = preact.createContext({
622
623
  });
623
624
  var LocalExpressionContext$1 = LocalExpressionContext;
624
625
 
625
- /**
626
- * @param {string} type
627
- * @param {boolean} [strict]
628
- *
629
- * @returns {any}
626
+ /**
627
+ * @param {string} type
628
+ * @param {boolean} [strict]
629
+ *
630
+ * @returns {any}
630
631
  */
631
632
  function getService(type, strict) {}
632
633
  const FormContext = preact.createContext({
@@ -669,21 +670,21 @@ function generateIdForType(type) {
669
670
  return `${type}${generateIndexForType(type)}`;
670
671
  }
671
672
 
672
- /**
673
- * @template T
674
- * @param {T} data
675
- * @param {(this: any, key: string, value: any) => any} [replacer]
676
- * @return {T}
673
+ /**
674
+ * @template T
675
+ * @param {T} data
676
+ * @param {(this: any, key: string, value: any) => any} [replacer]
677
+ * @return {T}
677
678
  */
678
679
  function clone(data, replacer) {
679
680
  return JSON.parse(JSON.stringify(data, replacer));
680
681
  }
681
682
 
682
- /**
683
- * Transform a LocalExpressionContext object into a usable FEEL context.
684
- *
685
- * @param {Object} context - The LocalExpressionContext object.
686
- * @returns {Object} The usable FEEL context.
683
+ /**
684
+ * Transform a LocalExpressionContext object into a usable FEEL context.
685
+ *
686
+ * @param {Object} context - The LocalExpressionContext object.
687
+ * @returns {Object} The usable FEEL context.
687
688
  */
688
689
 
689
690
  function buildExpressionContext(context) {
@@ -715,12 +716,12 @@ function _wrapObjectKeysWithUnderscores(obj) {
715
716
  return newObj;
716
717
  }
717
718
 
718
- /**
719
- * Evaluate if condition is met reactively based on the conditionChecker and form data.
720
- *
721
- * @param {string | undefined} condition
722
- *
723
- * @returns {boolean} true if condition is met or no condition or condition checker exists
719
+ /**
720
+ * Evaluate if condition is met reactively based on the conditionChecker and form data.
721
+ *
722
+ * @param {string | undefined} condition
723
+ *
724
+ * @returns {boolean} true if condition is met or no condition or condition checker exists
724
725
  */
725
726
  function useCondition(condition) {
726
727
  const conditionChecker = useService('conditionChecker', false);
@@ -730,17 +731,17 @@ function useCondition(condition) {
730
731
  }, [conditionChecker, condition, expressionContextInfo]);
731
732
  }
732
733
 
733
- /**
734
- * Custom hook to scroll an element into view only when it is not visible within the viewport.
735
- *
736
- * @param {Object} targetRef - A ref pointing to the DOM element to scroll into view.
737
- * @param {Array} deps - An array of dependencies that trigger the effect.
738
- * @param {Array} flagRefs - An array of refs that are used as flags to control when to scroll.
739
- * @param {Object} [scrollOptions={}] - Options defining the behavior of the scrolling.
740
- * @param {String} [scrollOptions.align='center'] - The alignment of the element within the viewport.
741
- * @param {String} [scrollOptions.behavior='auto'] - The scrolling behavior.
742
- * @param {Number} [scrollOptions.offset=0] - An offset that is added to the scroll position.
743
- * @param {Boolean} [scrollOptions.scrollIfVisible=false] - Whether to scroll even if the element is visible.
734
+ /**
735
+ * Custom hook to scroll an element into view only when it is not visible within the viewport.
736
+ *
737
+ * @param {Object} targetRef - A ref pointing to the DOM element to scroll into view.
738
+ * @param {Array} deps - An array of dependencies that trigger the effect.
739
+ * @param {Array} flagRefs - An array of refs that are used as flags to control when to scroll.
740
+ * @param {Object} [scrollOptions={}] - Options defining the behavior of the scrolling.
741
+ * @param {String} [scrollOptions.align='center'] - The alignment of the element within the viewport.
742
+ * @param {String} [scrollOptions.behavior='auto'] - The scrolling behavior.
743
+ * @param {Number} [scrollOptions.offset=0] - An offset that is added to the scroll position.
744
+ * @param {Boolean} [scrollOptions.scrollIfVisible=false] - Whether to scroll even if the element is visible.
744
745
  */
745
746
  function useScrollIntoView(targetRef, deps, scrollOptions = null, flagRefs = []) {
746
747
  hooks.useEffect(() => {
@@ -805,13 +806,13 @@ function _getTopOffset(item, scrollContainer, options) {
805
806
  return 0;
806
807
  }
807
808
 
808
- /**
809
- * Evaluate a string reactively based on the expressionLanguage and form data.
810
- * If the string is not an expression, it is returned as is.
811
- * The function is memoized to minimize re-renders.
812
- *
813
- * @param {string} value - The string to evaluate.
814
- * @returns {any} - Evaluated value or the original value if not an expression.
809
+ /**
810
+ * Evaluate a string reactively based on the expressionLanguage and form data.
811
+ * If the string is not an expression, it is returned as is.
812
+ * The function is memoized to minimize re-renders.
813
+ *
814
+ * @param {string} value - The string to evaluate.
815
+ * @returns {any} - Evaluated value or the original value if not an expression.
815
816
  */
816
817
  function useExpressionEvaluation(value) {
817
818
  const expressionLanguage = useService('expressionLanguage');
@@ -824,11 +825,11 @@ function useExpressionEvaluation(value) {
824
825
  }, [expressionLanguage, expressionContextInfo, value]);
825
826
  }
826
827
 
827
- /**
828
- * Returns the conditionally filtered data of a form reactively.
829
- * Memoised to minimize re-renders
830
- *
831
- * Warning: costly operation, use with care
828
+ /**
829
+ * Returns the conditionally filtered data of a form reactively.
830
+ * Memoised to minimize re-renders
831
+ *
832
+ * Warning: costly operation, use with care
832
833
  */
833
834
  function useFilteredFormData() {
834
835
  const {
@@ -861,16 +862,16 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
861
862
  });
862
863
  }
863
864
 
864
- /**
865
- * Retrieve readonly value of a form field, given it can be an
866
- * expression optionally or configured globally.
867
- *
868
- * @typedef { import('../../types').FormProperties } FormProperties
869
- *
870
- * @param {any} formField
871
- * @param {FormProperties} properties
872
- *
873
- * @returns {boolean}
865
+ /**
866
+ * Retrieve readonly value of a form field, given it can be an
867
+ * expression optionally or configured globally.
868
+ *
869
+ * @typedef { import('../../types').FormProperties } FormProperties
870
+ *
871
+ * @param {any} formField
872
+ * @param {FormProperties} properties
873
+ *
874
+ * @returns {boolean}
874
875
  */
875
876
  function useReadonly(formField, properties = {}) {
876
877
  const expressionLanguage = useService('expressionLanguage');
@@ -894,12 +895,12 @@ function usePrevious(value, defaultValue, dependencies) {
894
895
  return ref.current;
895
896
  }
896
897
 
897
- /**
898
- * A custom hook to manage state changes with deep comparison.
899
- *
900
- * @param {any} value - The current value to manage.
901
- * @param {any} defaultValue - The initial default value for the state.
902
- * @returns {any} - Returns the current state.
898
+ /**
899
+ * A custom hook to manage state changes with deep comparison.
900
+ *
901
+ * @param {any} value - The current value to manage.
902
+ * @param {any} defaultValue - The initial default value for the state.
903
+ * @returns {any} - Returns the current state.
903
904
  */
904
905
  function useDeepCompareState(value, defaultValue) {
905
906
  const [state, setState] = hooks.useState(defaultValue);
@@ -919,16 +920,16 @@ function compare(a, b) {
919
920
  return JSON.stringify(a) === JSON.stringify(b);
920
921
  }
921
922
 
922
- /**
923
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
924
- * Memoised to minimize re-renders
925
- *
926
- * @param {string} value
927
- * @param {Object} options
928
- * @param {boolean} [options.debug = false]
929
- * @param {boolean} [options.strict = false]
930
- * @param {Function} [options.buildDebugString]
931
- *
923
+ /**
924
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
925
+ * Memoised to minimize re-renders
926
+ *
927
+ * @param {string} value
928
+ * @param {Object} options
929
+ * @param {boolean} [options.debug = false]
930
+ * @param {boolean} [options.strict = false]
931
+ * @param {Function} [options.buildDebugString]
932
+ *
932
933
  */
933
934
  function useTemplateEvaluation(value, options = {}) {
934
935
  const templating = useService('templating');
@@ -941,17 +942,17 @@ function useTemplateEvaluation(value, options = {}) {
941
942
  }, [templating, value, expressionContextInfo, options]);
942
943
  }
943
944
 
944
- /**
945
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
946
- * If the string contains multiple lines, only the first line is returned.
947
- * Memoised to minimize re-renders
948
- *
949
- * @param {string} value
950
- * @param {Object} [options]
951
- * @param {boolean} [options.debug = false]
952
- * @param {boolean} [options.strict = false]
953
- * @param {Function} [options.buildDebugString]
954
- *
945
+ /**
946
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
947
+ * If the string contains multiple lines, only the first line is returned.
948
+ * Memoised to minimize re-renders
949
+ *
950
+ * @param {string} value
951
+ * @param {Object} [options]
952
+ * @param {boolean} [options.debug = false]
953
+ * @param {boolean} [options.strict = false]
954
+ * @param {Function} [options.buildDebugString]
955
+ *
955
956
  */
956
957
  function useSingleLineTemplateEvaluation(value, options = {}) {
957
958
  const evaluatedTemplate = useTemplateEvaluation(value, options);
@@ -1104,39 +1105,58 @@ function getOptionsData(formField, formData) {
1104
1105
 
1105
1106
  // transforms the provided options into a normalized format, trimming invalid options
1106
1107
  function normalizeOptionsData(optionsData) {
1107
- return optionsData.filter(_isOptionSomething).map(v => _normalizeOptionsData(v)).filter(v => v);
1108
+ return optionsData.filter(_isAllowedValue).map(_normalizeOption).filter(o => !minDash.isNil(o));
1108
1109
  }
1109
- function _normalizeOptionsData(optionData) {
1110
- if (_isAllowedOption(optionData)) {
1111
- // if a primitive is provided, use it as label and value
1110
+
1111
+ /**
1112
+ * Converts the provided option to a normalized format.
1113
+ * If the option is not valid, null is returned.
1114
+ *
1115
+ * @param {object} option
1116
+ * @param {string} option.label
1117
+ * @param {*} option.value
1118
+ *
1119
+ * @returns
1120
+ */
1121
+ function _normalizeOption(option) {
1122
+ // (1) simple primitive case, use it as both label and value
1123
+ if (_isAllowedPrimitive(option)) {
1112
1124
  return {
1113
- value: optionData,
1114
- label: `${optionData}`
1125
+ value: option,
1126
+ label: `${option}`
1115
1127
  };
1116
1128
  }
1117
- if (typeof optionData === 'object') {
1118
- if (!optionData.label && _isAllowedOption(optionData.value)) {
1119
- // if no label is provided, use the value as label
1129
+ if (minDash.isObject(option)) {
1130
+ const isValidLabel = _isValidLabel(option.label);
1131
+
1132
+ // (2) no label provided, but value is a simple primitive, use it as label and value
1133
+ if (!isValidLabel && _isAllowedPrimitive(option.value)) {
1120
1134
  return {
1121
- value: optionData.value,
1122
- label: `${optionData.value}`
1135
+ value: option.value,
1136
+ label: `${option.value}`
1123
1137
  };
1124
1138
  }
1125
- if (_isOptionSomething(optionData.value) && _isAllowedOption(optionData.label)) {
1126
- // if both value and label are provided, use them as is, in this scenario, the value may also be an object
1127
- return optionData;
1139
+
1140
+ // (3) both label and value are provided, use them as is
1141
+ if (isValidLabel && _isAllowedValue(option.value)) {
1142
+ return option;
1128
1143
  }
1129
1144
  }
1130
1145
  return null;
1131
1146
  }
1132
- function _isAllowedOption(option) {
1133
- return _isReadableType(option) && _isOptionSomething(option);
1147
+ function _isAllowedPrimitive(value) {
1148
+ const isAllowedPrimitiveType = ['number', 'string', 'boolean'].includes(typeof value);
1149
+ const isValid = value || value === 0 || value === false;
1150
+ return isAllowedPrimitiveType && isValid;
1134
1151
  }
1135
- function _isReadableType(option) {
1136
- return ['number', 'string', 'boolean'].includes(typeof option);
1152
+ function _isValidLabel(label) {
1153
+ return label && minDash.isString(label);
1137
1154
  }
1138
- function _isOptionSomething(option) {
1139
- return option || option === 0 || option === false;
1155
+ function _isAllowedValue(value) {
1156
+ if (minDash.isObject(value)) {
1157
+ return Object.keys(value).length > 0;
1158
+ }
1159
+ return _isAllowedPrimitive(value);
1140
1160
  }
1141
1161
  function createEmptyOptions(options = {}) {
1142
1162
  const defaults = {};
@@ -1154,8 +1174,8 @@ function createEmptyOptions(options = {}) {
1154
1174
  };
1155
1175
  }
1156
1176
 
1157
- /**
1158
- * @enum { String }
1177
+ /**
1178
+ * @enum { String }
1159
1179
  */
1160
1180
  const LOAD_STATES = {
1161
1181
  LOADING: 'loading',
@@ -1163,17 +1183,17 @@ const LOAD_STATES = {
1163
1183
  ERROR: 'error'
1164
1184
  };
1165
1185
 
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
1186
+ /**
1187
+ * @typedef {Object} OptionsGetter
1188
+ * @property {Object[]} options - The options data
1189
+ * @property {(LOAD_STATES)} loadState - The options data's loading state, to use for conditional rendering
1170
1190
  */
1171
1191
 
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
1192
+ /**
1193
+ * A hook to load options for single and multiselect components.
1194
+ *
1195
+ * @param {Object} field - The form field to handle options for
1196
+ * @return {OptionsGetter} optionsGetter - A options getter object providing loading state and options
1177
1197
  */
1178
1198
  function useOptionsAsync (field) {
1179
1199
  const {
@@ -1230,30 +1250,6 @@ const buildLoadedState = options => ({
1230
1250
  loadState: LOAD_STATES.LOADED
1231
1251
  });
1232
1252
 
1233
- function useCleanupMultiSelectValues (props) {
1234
- const {
1235
- field,
1236
- options,
1237
- loadState,
1238
- onChange,
1239
- values
1240
- } = props;
1241
-
1242
- // Ensures that the values are always a subset of the possible options
1243
- hooks.useEffect(() => {
1244
- if (loadState !== LOAD_STATES.LOADED) {
1245
- return;
1246
- }
1247
- const hasValuesNotInOptions = values.some(v => !options.map(o => o.value).includes(v));
1248
- if (hasValuesNotInOptions) {
1249
- onChange({
1250
- field,
1251
- value: values.filter(v => options.map(o => o.value).includes(v))
1252
- });
1253
- }
1254
- }, [field, options, onChange, JSON.stringify(values), loadState]);
1255
- }
1256
-
1257
1253
  const ENTER_KEYDOWN_EVENT = new KeyboardEvent('keydown', {
1258
1254
  code: 'Enter',
1259
1255
  key: 'Enter',
@@ -1429,6 +1425,12 @@ function sanitizeDateTimePickerValue(options) {
1429
1425
  if (subtype === DATETIME_SUBTYPES.DATETIME && (isInvalidDateString(value) || !isDateTimeInputInformationSufficient(value))) return null;
1430
1426
  return value;
1431
1427
  }
1428
+ function hasEqualValue(value, array) {
1429
+ if (!Array.isArray(array)) {
1430
+ return false;
1431
+ }
1432
+ return array.some(element => isEqual(value, element));
1433
+ }
1432
1434
  function sanitizeSingleSelectValue(options) {
1433
1435
  const {
1434
1436
  formField,
@@ -1437,7 +1439,7 @@ function sanitizeSingleSelectValue(options) {
1437
1439
  } = options;
1438
1440
  try {
1439
1441
  const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1440
- return validValues.includes(value) ? value : null;
1442
+ return hasEqualValue(value, validValues) ? value : null;
1441
1443
  } catch (error) {
1442
1444
  // use default value in case of formatting error
1443
1445
  // TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
@@ -1452,7 +1454,7 @@ function sanitizeMultiSelectValue(options) {
1452
1454
  } = options;
1453
1455
  try {
1454
1456
  const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1455
- return value.filter(v => validValues.includes(v));
1457
+ return value.filter(v => hasEqualValue(v, validValues));
1456
1458
  } catch (error) {
1457
1459
  // use default value in case of formatting error
1458
1460
  // TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
@@ -1460,6 +1462,31 @@ function sanitizeMultiSelectValue(options) {
1460
1462
  }
1461
1463
  }
1462
1464
 
1465
+ function useCleanupMultiSelectValues (props) {
1466
+ const {
1467
+ field,
1468
+ options,
1469
+ loadState,
1470
+ onChange,
1471
+ values
1472
+ } = props;
1473
+
1474
+ // Ensures that the values are always a subset of the possible options
1475
+ hooks.useEffect(() => {
1476
+ if (loadState !== LOAD_STATES.LOADED) {
1477
+ return;
1478
+ }
1479
+ const optionValues = options.map(o => o.value);
1480
+ const hasValuesNotInOptions = values.some(v => !hasEqualValue(v, optionValues));
1481
+ if (hasValuesNotInOptions) {
1482
+ onChange({
1483
+ field,
1484
+ value: values.filter(v => hasEqualValue(v, optionValues))
1485
+ });
1486
+ }
1487
+ }, [field, options, onChange, JSON.stringify(values), loadState]);
1488
+ }
1489
+
1463
1490
  const type$d = 'checklist';
1464
1491
  function Checklist(props) {
1465
1492
  const {
@@ -1482,16 +1509,11 @@ function Checklist(props) {
1482
1509
  const {
1483
1510
  required
1484
1511
  } = validate;
1485
- const toggleCheckbox = v => {
1486
- let newValue = [...values];
1487
- if (!newValue.includes(v)) {
1488
- newValue.push(v);
1489
- } else {
1490
- newValue = newValue.filter(x => x != v);
1491
- }
1512
+ const toggleCheckbox = toggledValue => {
1513
+ const newValues = hasEqualValue(toggledValue, values) ? values.filter(value => !isEqual(value, toggledValue)) : [...values, toggledValue];
1492
1514
  props.onChange({
1493
1515
  field,
1494
- value: newValue
1516
+ value: newValues
1495
1517
  });
1496
1518
  };
1497
1519
  const onCheckboxBlur = e => {
@@ -1529,15 +1551,16 @@ function Checklist(props) {
1529
1551
  required: required
1530
1552
  }), loadState == LOAD_STATES.LOADED && options.map((o, index) => {
1531
1553
  const itemDomId = `${domId}-${index}`;
1554
+ const isChecked = hasEqualValue(o.value, values);
1532
1555
  return jsxRuntime.jsx(Label, {
1533
1556
  id: itemDomId,
1534
1557
  label: o.label,
1535
1558
  class: classNames({
1536
- 'fjs-checked': values.includes(o.value)
1559
+ 'fjs-checked': isChecked
1537
1560
  }),
1538
1561
  required: false,
1539
1562
  children: jsxRuntime.jsx("input", {
1540
- checked: values.includes(o.value),
1563
+ checked: isChecked,
1541
1564
  class: "fjs-input",
1542
1565
  disabled: disabled,
1543
1566
  readOnly: readonly,
@@ -1817,12 +1840,12 @@ FormComponent$1.config = {
1817
1840
  })
1818
1841
  };
1819
1842
 
1820
- /**
1821
- * Returns date format for the provided locale.
1822
- * If the locale is not provided, uses the browser's locale.
1823
- *
1824
- * @param {string} [locale] - The locale to get date format for.
1825
- * @returns {string} The date format for the locale.
1843
+ /**
1844
+ * Returns date format for the provided locale.
1845
+ * If the locale is not provided, uses the browser's locale.
1846
+ *
1847
+ * @param {string} [locale] - The locale to get date format for.
1848
+ * @returns {string} The date format for the locale.
1826
1849
  */
1827
1850
  function getLocaleDateFormat(locale = 'default') {
1828
1851
  const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(Date.UTC(2020, 5, 5)));
@@ -1841,12 +1864,12 @@ function getLocaleDateFormat(locale = 'default') {
1841
1864
  }).join('');
1842
1865
  }
1843
1866
 
1844
- /**
1845
- * Returns readable date format for the provided locale.
1846
- * If the locale is not provided, uses the browser's locale.
1847
- *
1848
- * @param {string} [locale] - The locale to get readable date format for.
1849
- * @returns {string} The readable date format for the locale.
1867
+ /**
1868
+ * Returns readable date format for the provided locale.
1869
+ * If the locale is not provided, uses the browser's locale.
1870
+ *
1871
+ * @param {string} [locale] - The locale to get readable date format for.
1872
+ * @returns {string} The readable date format for the locale.
1850
1873
  */
1851
1874
  function getLocaleReadableDateFormat(locale) {
1852
1875
  let format = getLocaleDateFormat(locale).toLowerCase();
@@ -1863,12 +1886,12 @@ function getLocaleReadableDateFormat(locale) {
1863
1886
  return format;
1864
1887
  }
1865
1888
 
1866
- /**
1867
- * Returns flatpickr config for the provided locale.
1868
- * If the locale is not provided, uses the browser's locale.
1869
- *
1870
- * @param {string} [locale] - The locale to get flatpickr config for.
1871
- * @returns {object} The flatpickr config for the locale.
1889
+ /**
1890
+ * Returns flatpickr config for the provided locale.
1891
+ * If the locale is not provided, uses the browser's locale.
1892
+ *
1893
+ * @param {string} [locale] - The locale to get flatpickr config for.
1894
+ * @returns {object} The flatpickr config for the locale.
1872
1895
  */
1873
1896
  function getLocaleDateFlatpickrConfig(locale) {
1874
1897
  return flatpickerizeDateFormat(getLocaleDateFormat(locale));
@@ -2580,10 +2603,10 @@ Datetime.config = {
2580
2603
  }
2581
2604
  };
2582
2605
 
2583
- /**
2584
- * This file must not be changed or exchanged.
2585
- *
2586
- * @see http://bpmn.io/license for more information.
2606
+ /**
2607
+ * This file must not be changed or exchanged.
2608
+ *
2609
+ * @see http://bpmn.io/license for more information.
2587
2610
  */
2588
2611
  function Logo() {
2589
2612
  return jsxRuntime.jsxs("svg", {
@@ -2769,11 +2792,11 @@ const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u
2769
2792
 
2770
2793
  const FORM_ELEMENT = document.createElement('form');
2771
2794
 
2772
- /**
2773
- * Sanitize a HTML string and return the cleaned, safe version.
2774
- *
2775
- * @param {string} html
2776
- * @return {string}
2795
+ /**
2796
+ * Sanitize a HTML string and return the cleaned, safe version.
2797
+ *
2798
+ * @param {string} html
2799
+ * @return {string}
2777
2800
  */
2778
2801
 
2779
2802
  // see https://github.com/developit/snarkdown/issues/70
@@ -2791,41 +2814,41 @@ function sanitizeHTML(html) {
2791
2814
  }
2792
2815
  }
2793
2816
 
2794
- /**
2795
- * Sanitizes an image source to ensure we only allow for data URI and links
2796
- * that start with http(s).
2797
- *
2798
- * Note: Most browsers anyway do not support script execution in <img> elements.
2799
- *
2800
- * @param {string} src
2801
- * @returns {string}
2817
+ /**
2818
+ * Sanitizes an image source to ensure we only allow for data URI and links
2819
+ * that start with http(s).
2820
+ *
2821
+ * Note: Most browsers anyway do not support script execution in <img> elements.
2822
+ *
2823
+ * @param {string} src
2824
+ * @returns {string}
2802
2825
  */
2803
2826
  function sanitizeImageSource(src) {
2804
2827
  const valid = ALLOWED_IMAGE_SRC_PATTERN.test(src);
2805
2828
  return valid ? src : '';
2806
2829
  }
2807
2830
 
2808
- /**
2809
- * Sanitizes an iframe source to ensure we only allow for links
2810
- * that start with http(s).
2811
- *
2812
- * @param {string} src
2813
- * @returns {string}
2831
+ /**
2832
+ * Sanitizes an iframe source to ensure we only allow for links
2833
+ * that start with http(s).
2834
+ *
2835
+ * @param {string} src
2836
+ * @returns {string}
2814
2837
  */
2815
2838
  function sanitizeIFrameSource(src) {
2816
2839
  const valid = ALLOWED_IFRAME_SRC_PATTERN.test(src);
2817
2840
  return valid ? src : '';
2818
2841
  }
2819
2842
 
2820
- /**
2821
- * Recursively sanitize a HTML node, potentially
2822
- * removing it, its children or attributes.
2823
- *
2824
- * Inspired by https://github.com/developit/snarkdown/issues/70
2825
- * and https://github.com/cure53/DOMPurify. Simplified
2826
- * for our use-case.
2827
- *
2828
- * @param {Element} node
2843
+ /**
2844
+ * Recursively sanitize a HTML node, potentially
2845
+ * removing it, its children or attributes.
2846
+ *
2847
+ * Inspired by https://github.com/developit/snarkdown/issues/70
2848
+ * and https://github.com/cure53/DOMPurify. Simplified
2849
+ * for our use-case.
2850
+ *
2851
+ * @param {Element} node
2829
2852
  */
2830
2853
  function sanitizeNode(node) {
2831
2854
  // allow text nodes
@@ -2869,13 +2892,13 @@ function sanitizeNode(node) {
2869
2892
  }
2870
2893
  }
2871
2894
 
2872
- /**
2873
- * Validates attributes for validity.
2874
- *
2875
- * @param {string} lcTag
2876
- * @param {string} lcName
2877
- * @param {string} value
2878
- * @return {boolean}
2895
+ /**
2896
+ * Validates attributes for validity.
2897
+ *
2898
+ * @param {string} lcTag
2899
+ * @param {string} lcName
2900
+ * @param {string} value
2901
+ * @return {boolean}
2879
2902
  */
2880
2903
  function isValidAttribute(lcTag, lcName, value) {
2881
2904
  // disallow most attributes based on whitelist
@@ -2938,7 +2961,7 @@ function IFrame(props) {
2938
2961
  height: height,
2939
2962
  class: "fjs-iframe",
2940
2963
  id: prefixId(id, formId),
2941
- sandbox: ""
2964
+ sandbox: "allow-scripts"
2942
2965
  }), evaluatedUrl && !safeUrl && jsxRuntime.jsx(IFramePlaceholder, {
2943
2966
  text: "External content couldn't be loaded."
2944
2967
  })]
@@ -3055,6 +3078,40 @@ Image.config = {
3055
3078
  })
3056
3079
  };
3057
3080
 
3081
+ function useFlushDebounce(func, additionalDeps = []) {
3082
+ const timeoutRef = hooks.useRef(null);
3083
+ const lastArgsRef = hooks.useRef(null);
3084
+ const config = useService('config', false);
3085
+ const debounce = config && config.debounce;
3086
+ const shouldDebounce = debounce !== false && debounce !== 0;
3087
+ const delay = typeof debounce === 'number' ? debounce : 300;
3088
+ const debounceFunc = hooks.useCallback((...args) => {
3089
+ if (!shouldDebounce) {
3090
+ func(...args);
3091
+ return;
3092
+ }
3093
+ lastArgsRef.current = args;
3094
+ if (timeoutRef.current) {
3095
+ clearTimeout(timeoutRef.current);
3096
+ }
3097
+ timeoutRef.current = setTimeout(() => {
3098
+ func(...lastArgsRef.current);
3099
+ lastArgsRef.current = null;
3100
+ }, delay);
3101
+ }, [func, delay, shouldDebounce, ...additionalDeps]);
3102
+ const flushFunc = hooks.useCallback(() => {
3103
+ if (timeoutRef.current) {
3104
+ clearTimeout(timeoutRef.current);
3105
+ if (lastArgsRef.current !== null) {
3106
+ func(...lastArgsRef.current);
3107
+ lastArgsRef.current = null;
3108
+ }
3109
+ timeoutRef.current = null;
3110
+ }
3111
+ }, [func, ...additionalDeps]);
3112
+ return [debounceFunc, flushFunc];
3113
+ }
3114
+
3058
3115
  function TemplatedInputAdorner(props) {
3059
3116
  const {
3060
3117
  pre,
@@ -3145,8 +3202,7 @@ function Numberfield(props) {
3145
3202
  onFocus,
3146
3203
  field,
3147
3204
  value,
3148
- readonly,
3149
- onChange
3205
+ readonly
3150
3206
  } = props;
3151
3207
  const {
3152
3208
  description,
@@ -3166,6 +3222,16 @@ function Numberfield(props) {
3166
3222
  } = validate;
3167
3223
  const inputRef = hooks.useRef();
3168
3224
  const [stringValueCache, setStringValueCache] = hooks.useState('');
3225
+ const [onChangeDebounced, flushOnChange] = useFlushDebounce(params => {
3226
+ props.onChange(params);
3227
+ }, [props.onChange]);
3228
+ const onInputBlur = () => {
3229
+ flushOnChange && flushOnChange();
3230
+ onBlur && onBlur();
3231
+ };
3232
+ const onInputFocus = () => {
3233
+ onFocus && onFocus();
3234
+ };
3169
3235
 
3170
3236
  // checks whether the value currently in the form data is practically different from the one in the input field cache
3171
3237
  // this allows us to guarantee the field always displays valid form data, but without auto-simplifying values like 1.000 to 1
@@ -3189,7 +3255,7 @@ function Numberfield(props) {
3189
3255
  const setValue = hooks.useCallback(stringValue => {
3190
3256
  if (isNullEquivalentValue(stringValue)) {
3191
3257
  setStringValueCache('');
3192
- onChange({
3258
+ onChangeDebounced({
3193
3259
  field,
3194
3260
  value: null
3195
3261
  });
@@ -3204,18 +3270,18 @@ function Numberfield(props) {
3204
3270
  }
3205
3271
  if (isNaN(Number(stringValue))) {
3206
3272
  setStringValueCache('NaN');
3207
- onChange({
3273
+ onChangeDebounced({
3208
3274
  field,
3209
3275
  value: 'NaN'
3210
3276
  });
3211
3277
  return;
3212
3278
  }
3213
3279
  setStringValueCache(stringValue);
3214
- onChange({
3280
+ onChangeDebounced({
3215
3281
  field,
3216
3282
  value: serializeToString ? stringValue : Number(stringValue)
3217
3283
  });
3218
- }, [field, onChange, serializeToString]);
3284
+ }, [field, onChangeDebounced, serializeToString]);
3219
3285
  const increment = () => {
3220
3286
  if (readonly) {
3221
3287
  return;
@@ -3299,8 +3365,8 @@ function Numberfield(props) {
3299
3365
  id: domId,
3300
3366
  onKeyDown: onKeyDown,
3301
3367
  onKeyPress: onKeyPress,
3302
- onBlur: () => onBlur && onBlur(),
3303
- onFocus: () => onFocus && onFocus()
3368
+ onBlur: onInputBlur,
3369
+ onFocus: onInputFocus
3304
3370
 
3305
3371
  // @ts-ignore
3306
3372
  ,
@@ -3377,7 +3443,8 @@ function useCleanupSingleSelectValue (props) {
3377
3443
  if (loadState !== LOAD_STATES.LOADED) {
3378
3444
  return;
3379
3445
  }
3380
- const hasValueNotInOptions = value && !options.map(o => o.value).includes(value);
3446
+ const optionValues = options.map(o => o.value);
3447
+ const hasValueNotInOptions = value && !hasEqualValue(value, optionValues);
3381
3448
  if (hasValueNotInOptions) {
3382
3449
  onChange({
3383
3450
  field,
@@ -3450,15 +3517,16 @@ function Radio(props) {
3450
3517
  required: required
3451
3518
  }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
3452
3519
  const itemDomId = `${domId}-${index}`;
3520
+ const isChecked = isEqual(option.value, value);
3453
3521
  return jsxRuntime.jsx(Label, {
3454
3522
  id: itemDomId,
3455
3523
  label: option.label,
3456
3524
  class: classNames({
3457
- 'fjs-checked': option.value === value
3525
+ 'fjs-checked': isChecked
3458
3526
  }),
3459
3527
  required: false,
3460
3528
  children: jsxRuntime.jsx("input", {
3461
- checked: option.value === value,
3529
+ checked: isChecked,
3462
3530
  class: "fjs-input",
3463
3531
  disabled: disabled,
3464
3532
  readOnly: readonly,
@@ -3488,6 +3556,21 @@ Radio.config = {
3488
3556
  create: createEmptyOptions
3489
3557
  };
3490
3558
 
3559
+ /**
3560
+ * This hook allows us to retrieve the label from a value in linear time by caching it in a map
3561
+ * @param {Array} options
3562
+ */
3563
+ function useGetLabelCorrelation(options) {
3564
+ // This allows us to retrieve the label from a value in linear time
3565
+ const labelMap = hooks.useMemo(() => Object.assign({}, ...options.map(o => ({
3566
+ [_getValueHash(o.value)]: o.label
3567
+ }))), [options]);
3568
+ return hooks.useCallback(value => labelMap[_getValueHash(value)], [labelMap]);
3569
+ }
3570
+ const _getValueHash = value => {
3571
+ return minDash.isObject(value) ? JSON.stringify(value) : value;
3572
+ };
3573
+
3491
3574
  var _path$q;
3492
3575
  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); }
3493
3576
  var SvgXMark = function SvgXMark(props) {
@@ -3519,7 +3602,7 @@ function SearchableSelect(props) {
3519
3602
  } = props;
3520
3603
  const [filter, setFilter] = hooks.useState('');
3521
3604
  const [isDropdownExpanded, setIsDropdownExpanded] = hooks.useState(false);
3522
- const [shouldApplyFilter, setShouldApplyFilter] = hooks.useState(true);
3605
+ const [isFilterActive, setIsFilterActive] = hooks.useState(true);
3523
3606
  const [isEscapeClosed, setIsEscapeClose] = hooks.useState(false);
3524
3607
  const searchbarRef = hooks.useRef();
3525
3608
  const eventBus = useService('eventBus');
@@ -3534,23 +3617,22 @@ function SearchableSelect(props) {
3534
3617
  value,
3535
3618
  onChange: props.onChange
3536
3619
  });
3537
-
3538
- // 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
3539
- const valueToOptionMap = hooks.useMemo(() => Object.assign({}, ...options.map((o, x) => ({
3540
- [o.value]: options[x]
3541
- }))), [options]);
3542
- const valueLabel = hooks.useMemo(() => value && valueToOptionMap[value] && valueToOptionMap[value].label || '', [value, valueToOptionMap]);
3620
+ const getLabelCorrelation = useGetLabelCorrelation(options);
3621
+ const label = hooks.useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
3543
3622
 
3544
3623
  // whenever we change the underlying value, set the label to it
3545
3624
  hooks.useEffect(() => {
3546
- setFilter(valueLabel);
3547
- }, [valueLabel]);
3625
+ setFilter(label);
3626
+ }, [label]);
3548
3627
  const filteredOptions = hooks.useMemo(() => {
3549
- if (loadState === LOAD_STATES.LOADED) {
3550
- return shouldApplyFilter ? options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase())) : options;
3628
+ if (loadState !== LOAD_STATES.LOADED) {
3629
+ return [];
3551
3630
  }
3552
- return [];
3553
- }, [filter, loadState, options, shouldApplyFilter]);
3631
+ if (!filter || !isFilterActive) {
3632
+ return options;
3633
+ }
3634
+ return options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()));
3635
+ }, [filter, loadState, options, isFilterActive]);
3554
3636
  const setValue = hooks.useCallback(option => {
3555
3637
  setFilter(option && option.label || '');
3556
3638
  props.onChange({
@@ -3577,7 +3659,7 @@ function SearchableSelect(props) {
3577
3659
  }) => {
3578
3660
  setIsEscapeClose(false);
3579
3661
  setIsDropdownExpanded(true);
3580
- setShouldApplyFilter(true);
3662
+ setIsFilterActive(true);
3581
3663
  setFilter(target.value || '');
3582
3664
  eventBus.fire('formField.search', {
3583
3665
  formField: field,
@@ -3593,7 +3675,7 @@ function SearchableSelect(props) {
3593
3675
  {
3594
3676
  if (!isDropdownExpanded) {
3595
3677
  setIsDropdownExpanded(true);
3596
- setShouldApplyFilter(false);
3678
+ setIsFilterActive(false);
3597
3679
  }
3598
3680
  keyDownEvent.preventDefault();
3599
3681
  break;
@@ -3611,7 +3693,7 @@ function SearchableSelect(props) {
3611
3693
  const onInputMouseDown = hooks.useCallback(() => {
3612
3694
  setIsEscapeClose(false);
3613
3695
  setIsDropdownExpanded(true);
3614
- setShouldApplyFilter(false);
3696
+ setIsFilterActive(false);
3615
3697
  }, []);
3616
3698
  const onInputFocus = hooks.useCallback(() => {
3617
3699
  setIsEscapeClose(false);
@@ -3620,9 +3702,9 @@ function SearchableSelect(props) {
3620
3702
  }, [onFocus]);
3621
3703
  const onInputBlur = hooks.useCallback(() => {
3622
3704
  setIsDropdownExpanded(false);
3623
- setFilter(valueLabel);
3705
+ setFilter(label);
3624
3706
  onBlur && onBlur();
3625
- }, [onBlur, valueLabel]);
3707
+ }, [onBlur, label]);
3626
3708
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
3627
3709
  children: [jsxRuntime.jsxs("div", {
3628
3710
  class: classNames('fjs-input-group', {
@@ -3699,12 +3781,8 @@ function SimpleSelect(props) {
3699
3781
  value,
3700
3782
  onChange: props.onChange
3701
3783
  });
3702
-
3703
- // 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
3704
- const valueToOptionMap = hooks.useMemo(() => Object.assign({}, ...options.map((o, x) => ({
3705
- [o.value]: options[x]
3706
- }))), [options]);
3707
- const valueLabel = hooks.useMemo(() => value && valueToOptionMap[value] && valueToOptionMap[value].label || '', [value, valueToOptionMap]);
3784
+ const getLabelCorrelation = useGetLabelCorrelation(options);
3785
+ const valueLabel = hooks.useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
3708
3786
  const setValue = hooks.useCallback(option => {
3709
3787
  props.onChange({
3710
3788
  value: option && option.value || null,
@@ -4019,11 +4097,7 @@ function Taglist(props) {
4019
4097
  values,
4020
4098
  onChange: props.onChange
4021
4099
  });
4022
-
4023
- // 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
4024
- const valueToOptionMap = hooks.useMemo(() => Object.assign({}, ...options.map((o, x) => ({
4025
- [o.value]: options[x]
4026
- }))), [options]);
4100
+ const getLabelCorrelation = useGetLabelCorrelation(options);
4027
4101
  const hasOptionsLeft = hooks.useMemo(() => options.length > values.length, [options.length, values.length]);
4028
4102
 
4029
4103
  // Usage of stringify is necessary here because we want this effect to only trigger when there is a value change to the array
@@ -4031,12 +4105,14 @@ function Taglist(props) {
4031
4105
  if (loadState !== LOAD_STATES.LOADED) {
4032
4106
  return [];
4033
4107
  }
4034
- return options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value));
4108
+ const isValidFilteredOption = option => {
4109
+ const filterMatches = option.label.toLowerCase().includes(filter.toLowerCase());
4110
+ return filterMatches && !hasEqualValue(option.value, values);
4111
+ };
4112
+ return options.filter(isValidFilteredOption);
4035
4113
  }, [filter, options, JSON.stringify(values), loadState]);
4036
4114
  const selectValue = value => {
4037
- if (filter) {
4038
- setFilter('');
4039
- }
4115
+ setFilter('');
4040
4116
 
4041
4117
  // Ensure values cannot be double selected due to latency
4042
4118
  if (values.at(-1) === value) {
@@ -4048,8 +4124,9 @@ function Taglist(props) {
4048
4124
  });
4049
4125
  };
4050
4126
  const deselectValue = value => {
4127
+ const newValues = values.filter(v => !isEqual(v, value));
4051
4128
  props.onChange({
4052
- value: values.filter(v => v != value),
4129
+ value: newValues,
4053
4130
  field
4054
4131
  });
4055
4132
  };
@@ -4159,7 +4236,7 @@ function Taglist(props) {
4159
4236
  onMouseDown: e => e.preventDefault(),
4160
4237
  children: [jsxRuntime.jsx("span", {
4161
4238
  class: "fjs-taglist-tag-label",
4162
- children: valueToOptionMap[v] ? valueToOptionMap[v].label : undefined
4239
+ children: getLabelCorrelation(v)
4163
4240
  }), !disabled && !readonly && jsxRuntime.jsx("button", {
4164
4241
  type: "button",
4165
4242
  title: "Remove tag",
@@ -4325,13 +4402,20 @@ function Textfield(props) {
4325
4402
  const {
4326
4403
  required
4327
4404
  } = validate;
4328
- const onChange = ({
4405
+ const [onInputChange, flushOnChange] = useFlushDebounce(({
4329
4406
  target
4330
4407
  }) => {
4331
4408
  props.onChange({
4332
4409
  field,
4333
4410
  value: target.value
4334
4411
  });
4412
+ }, [props.onChange]);
4413
+ const onInputBlur = () => {
4414
+ flushOnChange && flushOnChange();
4415
+ onBlur && onBlur();
4416
+ };
4417
+ const onInputFocus = () => {
4418
+ onFocus && onFocus();
4335
4419
  };
4336
4420
  return jsxRuntime.jsxs("div", {
4337
4421
  class: formFieldClasses(type$2, {
@@ -4353,9 +4437,9 @@ function Textfield(props) {
4353
4437
  disabled: disabled,
4354
4438
  readOnly: readonly,
4355
4439
  id: domId,
4356
- onInput: onChange,
4357
- onBlur: () => onBlur && onBlur(),
4358
- onFocus: () => onFocus && onFocus(),
4440
+ onInput: onInputChange,
4441
+ onBlur: onInputBlur,
4442
+ onFocus: onInputFocus,
4359
4443
  type: "text",
4360
4444
  value: value,
4361
4445
  "aria-describedby": errorMessageId
@@ -4414,13 +4498,20 @@ function Textarea(props) {
4414
4498
  required
4415
4499
  } = validate;
4416
4500
  const textareaRef = hooks.useRef();
4417
- const onInput = ({
4501
+ const [onInputChange, flushOnChange] = useFlushDebounce(({
4418
4502
  target
4419
4503
  }) => {
4420
4504
  props.onChange({
4421
4505
  field,
4422
4506
  value: target.value
4423
4507
  });
4508
+ }, [props.onChange]);
4509
+ const onInputBlur = () => {
4510
+ flushOnChange && flushOnChange();
4511
+ onBlur && onBlur();
4512
+ };
4513
+ const onInputFocus = () => {
4514
+ onFocus && onFocus();
4424
4515
  };
4425
4516
  hooks.useLayoutEffect(() => {
4426
4517
  autoSizeTextarea(textareaRef.current);
@@ -4443,9 +4534,9 @@ function Textarea(props) {
4443
4534
  disabled: disabled,
4444
4535
  readonly: readonly,
4445
4536
  id: domId,
4446
- onInput: onInput,
4447
- onBlur: () => onBlur && onBlur(),
4448
- onFocus: () => onFocus && onFocus(),
4537
+ onInput: onInputChange,
4538
+ onBlur: onInputBlur,
4539
+ onFocus: onInputFocus,
4449
4540
  value: value,
4450
4541
  ref: textareaRef,
4451
4542
  "aria-describedby": errorMessageId
@@ -4545,28 +4636,28 @@ var CaretRightIcon = SvgCaretRight;
4545
4636
 
4546
4637
  const type = 'table';
4547
4638
 
4548
- /**
4549
- * @typedef {('asc'|'desc')} Direction
4550
- *
4551
- * @typedef Sorting
4552
- * @property {string} key
4553
- * @property {Direction} direction
4554
- *
4555
- * @typedef Column
4556
- * @property {string} label
4557
- * @property {string} key
4558
- *
4559
- * @typedef Props
4560
- * @property {Object} field
4561
- * @property {string} field.id
4562
- * @property {Array<Column>} [field.columns]
4563
- * @property {string} [field.columnsExpression]
4564
- * @property {string} [field.label]
4565
- * @property {number} [field.rowCount]
4566
- * @property {string} [field.dataSource]
4567
- *
4568
- * @param {Props} props
4569
- * @returns {import("preact").JSX.Element}
4639
+ /**
4640
+ * @typedef {('asc'|'desc')} Direction
4641
+ *
4642
+ * @typedef Sorting
4643
+ * @property {string} key
4644
+ * @property {Direction} direction
4645
+ *
4646
+ * @typedef Column
4647
+ * @property {string} label
4648
+ * @property {string} key
4649
+ *
4650
+ * @typedef Props
4651
+ * @property {Object} field
4652
+ * @property {string} field.id
4653
+ * @property {Array<Column>} [field.columns]
4654
+ * @property {string} [field.columnsExpression]
4655
+ * @property {string} [field.label]
4656
+ * @property {number} [field.rowCount]
4657
+ * @property {string} [field.dataSource]
4658
+ *
4659
+ * @param {Props} props
4660
+ * @returns {import("preact").JSX.Element}
4570
4661
  */
4571
4662
  function Table(props) {
4572
4663
  const {
@@ -4776,10 +4867,10 @@ Table.config = {
4776
4867
 
4777
4868
  // helpers /////////////////////////////
4778
4869
 
4779
- /**
4780
- * @param {string|void} columnsExpression
4781
- * @param {Column[]} fallbackColumns
4782
- * @returns {Column[]}
4870
+ /**
4871
+ * @param {string|void} columnsExpression
4872
+ * @param {Column[]} fallbackColumns
4873
+ * @returns {Column[]}
4783
4874
  */
4784
4875
  function useEvaluatedColumns(columnsExpression, fallbackColumns) {
4785
4876
  /** @type {Column[]|null} */
@@ -4787,18 +4878,18 @@ function useEvaluatedColumns(columnsExpression, fallbackColumns) {
4787
4878
  return Array.isArray(evaluation) && evaluation.every(isColumn) ? evaluation : fallbackColumns;
4788
4879
  }
4789
4880
 
4790
- /**
4791
- * @param {any} column
4792
- * @returns {column is Column}
4881
+ /**
4882
+ * @param {any} column
4883
+ * @returns {column is Column}
4793
4884
  */
4794
4885
  function isColumn(column) {
4795
4886
  return minDash.isObject(column) && minDash.isString(column['label']) && minDash.isString(column['key']);
4796
4887
  }
4797
4888
 
4798
- /**
4799
- * @param {Array} array
4800
- * @param {number} size
4801
- * @returns {Array}
4889
+ /**
4890
+ * @param {Array} array
4891
+ * @param {number} size
4892
+ * @returns {Array}
4802
4893
  */
4803
4894
  function chunk(array, size) {
4804
4895
  return array.reduce((chunks, item, index) => {
@@ -4811,11 +4902,11 @@ function chunk(array, size) {
4811
4902
  }, []);
4812
4903
  }
4813
4904
 
4814
- /**
4815
- * @param {unknown[]} array
4816
- * @param {string} key
4817
- * @param {Direction} direction
4818
- * @returns {unknown[]}
4905
+ /**
4906
+ * @param {unknown[]} array
4907
+ * @param {string} key
4908
+ * @param {Direction} direction
4909
+ * @returns {unknown[]}
4819
4910
  */
4820
4911
  function sortByColumn(array, key, direction) {
4821
4912
  return [...array].sort((a, b) => {
@@ -4829,10 +4920,10 @@ function sortByColumn(array, key, direction) {
4829
4920
  });
4830
4921
  }
4831
4922
 
4832
- /**
4833
- * @param {null|Sorting} sortBy
4834
- * @param {string} key
4835
- * @param {string} label
4923
+ /**
4924
+ * @param {null|Sorting} sortBy
4925
+ * @param {string} key
4926
+ * @param {string} label
4836
4927
  */
4837
4928
  function getHeaderAriaLabel(sortBy, key, label) {
4838
4929
  if (sortBy === null || sortBy.key !== key) {
@@ -5207,37 +5298,37 @@ class FormFields {
5207
5298
  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'];
5208
5299
  const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text', 'url'];
5209
5300
 
5210
- /**
5211
- * @typedef { import('../types').Schema } Schema
5301
+ /**
5302
+ * @typedef { import('../types').Schema } Schema
5212
5303
  */
5213
5304
 
5214
- /**
5215
- * Parse the schema for variables a form might make use of.
5216
- *
5217
- * @example
5218
- *
5219
- * // retrieve variables from schema
5220
- * const variables = getSchemaVariables(schema);
5221
- *
5222
- * @example
5223
- *
5224
- * // retrieve input variables from schema
5225
- * const inputVariables = getSchemaVariables(schema, { outputs: false });
5226
- *
5227
- * @example
5228
- *
5229
- * // retrieve output variables from schema
5230
- * const outputVariables = getSchemaVariables(schema, { inputs: false });
5231
- *
5232
- * @param {Schema} schema
5233
- * @param {object} [options]
5234
- * @param {any} [options.expressionLanguage]
5235
- * @param {any} [options.templating]
5236
- * @param {any} [options.formFields]
5237
- * @param {boolean} [options.inputs=true]
5238
- * @param {boolean} [options.outputs=true]
5239
- *
5240
- * @return {string[]}
5305
+ /**
5306
+ * Parse the schema for variables a form might make use of.
5307
+ *
5308
+ * @example
5309
+ *
5310
+ * // retrieve variables from schema
5311
+ * const variables = getSchemaVariables(schema);
5312
+ *
5313
+ * @example
5314
+ *
5315
+ * // retrieve input variables from schema
5316
+ * const inputVariables = getSchemaVariables(schema, { outputs: false });
5317
+ *
5318
+ * @example
5319
+ *
5320
+ * // retrieve output variables from schema
5321
+ * const outputVariables = getSchemaVariables(schema, { inputs: false });
5322
+ *
5323
+ * @param {Schema} schema
5324
+ * @param {object} [options]
5325
+ * @param {any} [options.expressionLanguage]
5326
+ * @param {any} [options.templating]
5327
+ * @param {any} [options.formFields]
5328
+ * @param {boolean} [options.inputs=true]
5329
+ * @param {boolean} [options.outputs=true]
5330
+ *
5331
+ * @return {string[]}
5241
5332
  */
5242
5333
  function getSchemaVariables(schema, options = {}) {
5243
5334
  const {
@@ -5313,13 +5404,13 @@ function getSchemaVariables(schema, options = {}) {
5313
5404
  return Array.from(new Set(variables));
5314
5405
  }
5315
5406
 
5316
- /**
5317
- * Get the ancestry list of a form field.
5318
- *
5319
- * @param {string} formFieldId
5320
- * @param {import('../core/FormFieldRegistry').default} formFieldRegistry
5321
- *
5322
- * @return {Array<string>} ancestry list
5407
+ /**
5408
+ * Get the ancestry list of a form field.
5409
+ *
5410
+ * @param {string} formFieldId
5411
+ * @param {import('../core/FormFieldRegistry').default} formFieldRegistry
5412
+ *
5413
+ * @return {Array<string>} ancestry list
5323
5414
  */
5324
5415
  const getAncestryList = (formFieldId, formFieldRegistry) => {
5325
5416
  const ids = [];
@@ -5331,9 +5422,9 @@ const getAncestryList = (formFieldId, formFieldRegistry) => {
5331
5422
  return ids;
5332
5423
  };
5333
5424
 
5334
- /**
5335
- * @typedef {object} Condition
5336
- * @property {string} [hide]
5425
+ /**
5426
+ * @typedef {object} Condition
5427
+ * @property {string} [hide]
5337
5428
  */
5338
5429
 
5339
5430
  class ConditionChecker {
@@ -5343,14 +5434,14 @@ class ConditionChecker {
5343
5434
  this._eventBus = eventBus;
5344
5435
  }
5345
5436
 
5346
- /**
5347
- * For given data, remove properties based on condition.
5348
- *
5349
- * @param {Object<string, any>} data
5350
- * @param {Object<string, any>} contextData
5351
- * @param {Object} [options]
5352
- * @param {Function} [options.getFilterPath]
5353
- * @param {boolean} [options.leafNodeDeletionOnly]
5437
+ /**
5438
+ * For given data, remove properties based on condition.
5439
+ *
5440
+ * @param {Object<string, any>} data
5441
+ * @param {Object<string, any>} contextData
5442
+ * @param {Object} [options]
5443
+ * @param {Function} [options.getFilterPath]
5444
+ * @param {boolean} [options.leafNodeDeletionOnly]
5354
5445
  */
5355
5446
  applyConditions(data, contextData = {}, options = {}) {
5356
5447
  const workingData = clone(data);
@@ -5444,13 +5535,13 @@ class ConditionChecker {
5444
5535
  return workingData;
5445
5536
  }
5446
5537
 
5447
- /**
5448
- * Check if given condition is met. Returns null for invalid/missing conditions.
5449
- *
5450
- * @param {string} condition
5451
- * @param {import('../../types').Data} [data]
5452
- *
5453
- * @returns {boolean|null}
5538
+ /**
5539
+ * Check if given condition is met. Returns null for invalid/missing conditions.
5540
+ *
5541
+ * @param {string} condition
5542
+ * @param {import('../../types').Data} [data]
5543
+ *
5544
+ * @returns {boolean|null}
5454
5545
  */
5455
5546
  check(condition, data = {}) {
5456
5547
  if (!condition) {
@@ -5471,12 +5562,12 @@ class ConditionChecker {
5471
5562
  }
5472
5563
  }
5473
5564
 
5474
- /**
5475
- * Check if hide condition is met.
5476
- *
5477
- * @param {Condition} condition
5478
- * @param {Object<string, any>} data
5479
- * @returns {boolean}
5565
+ /**
5566
+ * Check if hide condition is met.
5567
+ *
5568
+ * @param {Condition} condition
5569
+ * @param {Object<string, any>} data
5570
+ * @returns {boolean}
5480
5571
  */
5481
5572
  _checkHideCondition(condition, data) {
5482
5573
  if (!condition.hide) {
@@ -5518,12 +5609,12 @@ class MarkdownRenderer {
5518
5609
  this._converter = new showdown.Converter();
5519
5610
  }
5520
5611
 
5521
- /**
5522
- * Render markdown to HTML.
5523
- *
5524
- * @param {string} markdown - The markdown to render
5525
- *
5526
- * @returns {string} HTML
5612
+ /**
5613
+ * Render markdown to HTML.
5614
+ *
5615
+ * @param {string} markdown - The markdown to render
5616
+ *
5617
+ * @returns {string} HTML
5527
5618
  */
5528
5619
  render(markdown) {
5529
5620
  return this._converter.makeHtml(markdown);
@@ -6144,11 +6235,11 @@ class RepeatRenderManager {
6144
6235
  this.RepeatFooter = this.RepeatFooter.bind(this);
6145
6236
  }
6146
6237
 
6147
- /**
6148
- * Checks whether a field is currently repeating its children.
6149
- *
6150
- * @param {string} id - The id of the field to check
6151
- * @returns {boolean} - True if repeatable, false otherwise
6238
+ /**
6239
+ * Checks whether a field is currently repeating its children.
6240
+ *
6241
+ * @param {string} id - The id of the field to check
6242
+ * @returns {boolean} - True if repeatable, false otherwise
6152
6243
  */
6153
6244
  isFieldRepeating(id) {
6154
6245
  if (!id) {
@@ -6908,8 +6999,8 @@ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form'];
6908
6999
 
6909
7000
  // helpers //////////
6910
7001
 
6911
- /**
6912
- * Helper function to evaluate optional FEEL validation values.
7002
+ /**
7003
+ * Helper function to evaluate optional FEEL validation values.
6913
7004
  */
6914
7005
  function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
6915
7006
  const evaluatedValidate = {
@@ -6943,12 +7034,12 @@ function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form
6943
7034
  }
6944
7035
 
6945
7036
  class Importer {
6946
- /**
6947
- * @constructor
6948
- * @param { import('./FormFieldRegistry').default } formFieldRegistry
6949
- * @param { import('./PathRegistry').default } pathRegistry
6950
- * @param { import('./FieldFactory').default } fieldFactory
6951
- * @param { import('./FormLayouter').default } formLayouter
7037
+ /**
7038
+ * @constructor
7039
+ * @param { import('./FormFieldRegistry').default } formFieldRegistry
7040
+ * @param { import('./PathRegistry').default } pathRegistry
7041
+ * @param { import('./FieldFactory').default } fieldFactory
7042
+ * @param { import('./FormLayouter').default } formLayouter
6952
7043
  */
6953
7044
  constructor(formFieldRegistry, pathRegistry, fieldFactory, formLayouter) {
6954
7045
  this._formFieldRegistry = formFieldRegistry;
@@ -6957,21 +7048,21 @@ class Importer {
6957
7048
  this._formLayouter = formLayouter;
6958
7049
  }
6959
7050
 
6960
- /**
6961
- * Import schema creating rows, fields, attaching additional
6962
- * information to each field and adding fields to the
6963
- * field registry.
6964
- *
6965
- * Additional information attached:
6966
- *
6967
- * * `id` (unless present)
6968
- * * `_parent`
6969
- * * `_path`
6970
- *
6971
- * @param {any} schema
6972
- *
6973
- * @typedef {{ warnings: Error[], schema: any }} ImportResult
6974
- * @returns {ImportResult}
7051
+ /**
7052
+ * Import schema creating rows, fields, attaching additional
7053
+ * information to each field and adding fields to the
7054
+ * field registry.
7055
+ *
7056
+ * Additional information attached:
7057
+ *
7058
+ * * `id` (unless present)
7059
+ * * `_parent`
7060
+ * * `_path`
7061
+ *
7062
+ * @param {any} schema
7063
+ *
7064
+ * @typedef {{ warnings: Error[], schema: any }} ImportResult
7065
+ * @returns {ImportResult}
6975
7066
  */
6976
7067
  importSchema(schema) {
6977
7068
  // TODO: Add warnings
@@ -6996,12 +7087,12 @@ class Importer {
6996
7087
  this._pathRegistry.clear();
6997
7088
  }
6998
7089
 
6999
- /**
7000
- * @param {{[x: string]: any}} fieldAttrs
7001
- * @param {String} [parentId]
7002
- * @param {number} [index]
7003
- *
7004
- * @return {any} field
7090
+ /**
7091
+ * @param {{[x: string]: any}} fieldAttrs
7092
+ * @param {String} [parentId]
7093
+ * @param {number} [index]
7094
+ *
7095
+ * @return {any} field
7005
7096
  */
7006
7097
  importFormField(fieldAttrs, parentId, index) {
7007
7098
  const {
@@ -7026,11 +7117,11 @@ class Importer {
7026
7117
  return field;
7027
7118
  }
7028
7119
 
7029
- /**
7030
- * @param {Array<any>} components
7031
- * @param {string} parentId
7032
- *
7033
- * @return {Array<any>} imported components
7120
+ /**
7121
+ * @param {Array<any>} components
7122
+ * @param {string} parentId
7123
+ *
7124
+ * @return {Array<any>} imported components
7034
7125
  */
7035
7126
  importFormFields(components, parentId) {
7036
7127
  return components.map((component, index) => {
@@ -7041,11 +7132,11 @@ class Importer {
7041
7132
  Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
7042
7133
 
7043
7134
  class FieldFactory {
7044
- /**
7045
- * @constructor
7046
- *
7047
- * @param formFieldRegistry
7048
- * @param formFields
7135
+ /**
7136
+ * @constructor
7137
+ *
7138
+ * @param formFieldRegistry
7139
+ * @param formFields
7049
7140
  */
7050
7141
  constructor(formFieldRegistry, pathRegistry, formFields) {
7051
7142
  this._formFieldRegistry = formFieldRegistry;
@@ -7155,36 +7246,36 @@ class FieldFactory {
7155
7246
  }
7156
7247
  FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
7157
7248
 
7158
- /**
7159
- * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
7160
- * It enables claiming, unclaiming, and validating paths within this structure.
7161
- *
7162
- * Example Tree Structure:
7163
- *
7164
- * [
7165
- * {
7166
- * segment: 'root',
7167
- * claimCount: 1,
7168
- * children: [
7169
- * {
7170
- * segment: 'child1',
7171
- * claimCount: 2,
7172
- * children: null // A leaf node (closed path)
7173
- * },
7174
- * {
7175
- * segment: 'child2',
7176
- * claimCount: 1,
7177
- * children: [
7178
- * {
7179
- * segment: 'subChild1',
7180
- * claimCount: 1,
7181
- * children: [] // An open node (open path)
7182
- * }
7183
- * ]
7184
- * }
7185
- * ]
7186
- * }
7187
- * ]
7249
+ /**
7250
+ * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
7251
+ * It enables claiming, unclaiming, and validating paths within this structure.
7252
+ *
7253
+ * Example Tree Structure:
7254
+ *
7255
+ * [
7256
+ * {
7257
+ * segment: 'root',
7258
+ * claimCount: 1,
7259
+ * children: [
7260
+ * {
7261
+ * segment: 'child1',
7262
+ * claimCount: 2,
7263
+ * children: null // A leaf node (closed path)
7264
+ * },
7265
+ * {
7266
+ * segment: 'child2',
7267
+ * claimCount: 1,
7268
+ * children: [
7269
+ * {
7270
+ * segment: 'subChild1',
7271
+ * claimCount: 1,
7272
+ * children: [] // An open node (open path)
7273
+ * }
7274
+ * ]
7275
+ * }
7276
+ * ]
7277
+ * }
7278
+ * ]
7188
7279
  */
7189
7280
  class PathRegistry {
7190
7281
  constructor(formFieldRegistry, formFields, injector) {
@@ -7300,16 +7391,16 @@ class PathRegistry {
7300
7391
  }
7301
7392
  }
7302
7393
 
7303
- /**
7304
- * Applies a function (fn) recursively on a given field and its children.
7305
- *
7306
- * - `field`: Starting field object.
7307
- * - `fn`: Function to apply.
7308
- * - `context`: Optional object for passing data between calls.
7309
- *
7310
- * Stops early if `fn` returns `false`. Useful for traversing the form field tree.
7311
- *
7312
- * @returns {boolean} Success status based on function execution.
7394
+ /**
7395
+ * Applies a function (fn) recursively on a given field and its children.
7396
+ *
7397
+ * - `field`: Starting field object.
7398
+ * - `fn`: Function to apply.
7399
+ * - `context`: Optional object for passing data between calls.
7400
+ *
7401
+ * Stops early if `fn` returns `false`. Useful for traversing the form field tree.
7402
+ *
7403
+ * @returns {boolean} Success status based on function execution.
7313
7404
  */
7314
7405
  executeRecursivelyOnFields(field, fn, context = {}) {
7315
7406
  let result = true;
@@ -7350,16 +7441,16 @@ class PathRegistry {
7350
7441
  return result;
7351
7442
  }
7352
7443
 
7353
- /**
7354
- * Generates an array representing the binding path to an underlying data object for a form field.
7355
- *
7356
- * @param {Object} field - The field object with properties: `key`, `path`, `id`, and optionally `_parent`.
7357
- * @param {Object} [options={}] - Configuration options.
7358
- * @param {Object} [options.replacements={}] - A map of field IDs to alternative path arrays.
7359
- * @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.
7360
- * @param {Object} [options.cutoffNode] - The ID of the parent field at which to stop generating the path.
7361
- *
7362
- * @returns {(Array<string>|undefined)} An array of strings representing the binding path, or undefined if not determinable.
7444
+ /**
7445
+ * Generates an array representing the binding path to an underlying data object for a form field.
7446
+ *
7447
+ * @param {Object} field - The field object with properties: `key`, `path`, `id`, and optionally `_parent`.
7448
+ * @param {Object} [options={}] - Configuration options.
7449
+ * @param {Object} [options.replacements={}] - A map of field IDs to alternative path arrays.
7450
+ * @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.
7451
+ * @param {Object} [options.cutoffNode] - The ID of the parent field at which to stop generating the path.
7452
+ *
7453
+ * @returns {(Array<string>|undefined)} An array of strings representing the binding path, or undefined if not determinable.
7363
7454
  */
7364
7455
  getValuePath(field, options = {}) {
7365
7456
  const {
@@ -7420,23 +7511,23 @@ const _getNextSegment = (node, segment) => {
7420
7511
  };
7421
7512
  PathRegistry.$inject = ['formFieldRegistry', 'formFields', 'injector'];
7422
7513
 
7423
- /**
7424
- * @typedef { { id: String, components: Array<String> } } FormRow
7425
- * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
7514
+ /**
7515
+ * @typedef { { id: String, components: Array<String> } } FormRow
7516
+ * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
7426
7517
  */
7427
7518
 
7428
- /**
7429
- * Maintains the Form layout in a given structure, for example
7430
- *
7431
- * [
7432
- * {
7433
- * formFieldId: 'FormField_1',
7434
- * rows: [
7435
- * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
7436
- * ]
7437
- * }
7438
- * ]
7439
- *
7519
+ /**
7520
+ * Maintains the Form layout in a given structure, for example
7521
+ *
7522
+ * [
7523
+ * {
7524
+ * formFieldId: 'FormField_1',
7525
+ * rows: [
7526
+ * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
7527
+ * ]
7528
+ * }
7529
+ * ]
7530
+ *
7440
7531
  */
7441
7532
  class FormLayouter {
7442
7533
  constructor(eventBus) {
@@ -7446,8 +7537,8 @@ class FormLayouter {
7446
7537
  this._eventBus = eventBus;
7447
7538
  }
7448
7539
 
7449
- /**
7450
- * @param {FormRow} row
7540
+ /**
7541
+ * @param {FormRow} row
7451
7542
  */
7452
7543
  addRow(formFieldId, row) {
7453
7544
  let rowsPerComponent = this._rows.find(r => r.formFieldId === formFieldId);
@@ -7461,18 +7552,18 @@ class FormLayouter {
7461
7552
  rowsPerComponent.rows.push(row);
7462
7553
  }
7463
7554
 
7464
- /**
7465
- * @param {String} id
7466
- * @returns {FormRow}
7555
+ /**
7556
+ * @param {String} id
7557
+ * @returns {FormRow}
7467
7558
  */
7468
7559
  getRow(id) {
7469
7560
  const rows = allRows(this._rows);
7470
7561
  return rows.find(r => r.id === id);
7471
7562
  }
7472
7563
 
7473
- /**
7474
- * @param {any} formField
7475
- * @returns {FormRow}
7564
+ /**
7565
+ * @param {any} formField
7566
+ * @returns {FormRow}
7476
7567
  */
7477
7568
  getRowForField(formField) {
7478
7569
  return allRows(this._rows).find(r => {
@@ -7483,9 +7574,9 @@ class FormLayouter {
7483
7574
  });
7484
7575
  }
7485
7576
 
7486
- /**
7487
- * @param {String} formFieldId
7488
- * @returns { Array<FormRow> }
7577
+ /**
7578
+ * @param {String} formFieldId
7579
+ * @returns { Array<FormRow> }
7489
7580
  */
7490
7581
  getRows(formFieldId) {
7491
7582
  const rowsForField = this._rows.find(r => formFieldId === r.formFieldId);
@@ -7495,15 +7586,15 @@ class FormLayouter {
7495
7586
  return rowsForField.rows;
7496
7587
  }
7497
7588
 
7498
- /**
7499
- * @returns {string}
7589
+ /**
7590
+ * @returns {string}
7500
7591
  */
7501
7592
  nextRowId() {
7502
7593
  return this._ids.nextPrefixed('Row_');
7503
7594
  }
7504
7595
 
7505
- /**
7506
- * @param {any} formField
7596
+ /**
7597
+ * @param {any} formField
7507
7598
  */
7508
7599
  calculateLayout(formField) {
7509
7600
  const {
@@ -7557,9 +7648,9 @@ function groupByRow(components, ids) {
7557
7648
  });
7558
7649
  }
7559
7650
 
7560
- /**
7561
- * @param {Array<FormRows>} formRows
7562
- * @returns {Array<FormRow>}
7651
+ /**
7652
+ * @param {Array<FormRows>} formRows
7653
+ * @returns {Array<FormRow>}
7563
7654
  */
7564
7655
  function allRows(formRows) {
7565
7656
  return minDash.flatten(formRows.map(c => c.rows));
@@ -7684,55 +7775,55 @@ var core = {
7684
7775
  validator: ['type', Validator]
7685
7776
  };
7686
7777
 
7687
- /**
7688
- * @typedef { import('./types').Injector } Injector
7689
- * @typedef { import('./types').Data } Data
7690
- * @typedef { import('./types').Errors } Errors
7691
- * @typedef { import('./types').Schema } Schema
7692
- * @typedef { import('./types').FormProperties } FormProperties
7693
- * @typedef { import('./types').FormProperty } FormProperty
7694
- * @typedef { import('./types').FormEvent } FormEvent
7695
- * @typedef { import('./types').FormOptions } FormOptions
7696
- *
7697
- * @typedef { {
7698
- * data: Data,
7699
- * initialData: Data,
7700
- * errors: Errors,
7701
- * properties: FormProperties,
7702
- * schema: Schema
7703
- * } } State
7704
- *
7705
- * @typedef { (type:FormEvent, priority:number, handler:Function) => void } OnEventWithPriority
7706
- * @typedef { (type:FormEvent, handler:Function) => void } OnEventWithOutPriority
7707
- * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
7778
+ /**
7779
+ * @typedef { import('./types').Injector } Injector
7780
+ * @typedef { import('./types').Data } Data
7781
+ * @typedef { import('./types').Errors } Errors
7782
+ * @typedef { import('./types').Schema } Schema
7783
+ * @typedef { import('./types').FormProperties } FormProperties
7784
+ * @typedef { import('./types').FormProperty } FormProperty
7785
+ * @typedef { import('./types').FormEvent } FormEvent
7786
+ * @typedef { import('./types').FormOptions } FormOptions
7787
+ *
7788
+ * @typedef { {
7789
+ * data: Data,
7790
+ * initialData: Data,
7791
+ * errors: Errors,
7792
+ * properties: FormProperties,
7793
+ * schema: Schema
7794
+ * } } State
7795
+ *
7796
+ * @typedef { (type:FormEvent, priority:number, handler:Function) => void } OnEventWithPriority
7797
+ * @typedef { (type:FormEvent, handler:Function) => void } OnEventWithOutPriority
7798
+ * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
7708
7799
  */
7709
7800
 
7710
7801
  const ids = new Ids([32, 36, 1]);
7711
7802
 
7712
- /**
7713
- * The form.
7803
+ /**
7804
+ * The form.
7714
7805
  */
7715
7806
  class Form {
7716
- /**
7717
- * @constructor
7718
- * @param {FormOptions} options
7807
+ /**
7808
+ * @constructor
7809
+ * @param {FormOptions} options
7719
7810
  */
7720
7811
  constructor(options = {}) {
7721
- /**
7722
- * @public
7723
- * @type {OnEventType}
7812
+ /**
7813
+ * @public
7814
+ * @type {OnEventType}
7724
7815
  */
7725
7816
  this.on = this._onEvent;
7726
7817
 
7727
- /**
7728
- * @public
7729
- * @type {String}
7818
+ /**
7819
+ * @public
7820
+ * @type {String}
7730
7821
  */
7731
7822
  this._id = ids.next();
7732
7823
 
7733
- /**
7734
- * @private
7735
- * @type {Element}
7824
+ /**
7825
+ * @private
7826
+ * @type {Element}
7736
7827
  */
7737
7828
  this._container = createFormContainer();
7738
7829
  const {
@@ -7741,9 +7832,9 @@ class Form {
7741
7832
  properties = {}
7742
7833
  } = options;
7743
7834
 
7744
- /**
7745
- * @private
7746
- * @type {State}
7835
+ /**
7836
+ * @private
7837
+ * @type {State}
7747
7838
  */
7748
7839
  this._state = {
7749
7840
  initialData: null,
@@ -7767,9 +7858,9 @@ class Form {
7767
7858
  this._emit('form.clear');
7768
7859
  }
7769
7860
 
7770
- /**
7771
- * Destroy the form, removing it from DOM,
7772
- * if attached.
7861
+ /**
7862
+ * Destroy the form, removing it from DOM,
7863
+ * if attached.
7773
7864
  */
7774
7865
  destroy() {
7775
7866
  // destroy form services
@@ -7780,13 +7871,13 @@ class Form {
7780
7871
  this._detach(false);
7781
7872
  }
7782
7873
 
7783
- /**
7784
- * Open a form schema with the given initial data.
7785
- *
7786
- * @param {Schema} schema
7787
- * @param {Data} [data]
7788
- *
7789
- * @return Promise<{ warnings: Array<any> }>
7874
+ /**
7875
+ * Open a form schema with the given initial data.
7876
+ *
7877
+ * @param {Schema} schema
7878
+ * @param {Data} [data]
7879
+ *
7880
+ * @return Promise<{ warnings: Array<any> }>
7790
7881
  */
7791
7882
  importSchema(schema, data = {}) {
7792
7883
  return new Promise((resolve, reject) => {
@@ -7819,10 +7910,10 @@ class Form {
7819
7910
  });
7820
7911
  }
7821
7912
 
7822
- /**
7823
- * Submit the form, triggering all field validations.
7824
- *
7825
- * @returns { { data: Data, errors: Errors } }
7913
+ /**
7914
+ * Submit the form, triggering all field validations.
7915
+ *
7916
+ * @returns { { data: Data, errors: Errors } }
7826
7917
  */
7827
7918
  submit() {
7828
7919
  const {
@@ -7848,8 +7939,8 @@ class Form {
7848
7939
  });
7849
7940
  }
7850
7941
 
7851
- /**
7852
- * @returns {Errors}
7942
+ /**
7943
+ * @returns {Errors}
7853
7944
  */
7854
7945
  validate() {
7855
7946
  const formFields = this.get('formFields'),
@@ -7921,8 +8012,8 @@ class Form {
7921
8012
  return filteredErrors;
7922
8013
  }
7923
8014
 
7924
- /**
7925
- * @param {Element|string} parentNode
8015
+ /**
8016
+ * @param {Element|string} parentNode
7926
8017
  */
7927
8018
  attachTo(parentNode) {
7928
8019
  if (!parentNode) {
@@ -7940,10 +8031,10 @@ class Form {
7940
8031
  this._detach();
7941
8032
  }
7942
8033
 
7943
- /**
7944
- * @private
7945
- *
7946
- * @param {boolean} [emit]
8034
+ /**
8035
+ * @private
8036
+ *
8037
+ * @param {boolean} [emit]
7947
8038
  */
7948
8039
  _detach(emit = true) {
7949
8040
  const container = this._container,
@@ -7957,9 +8048,9 @@ class Form {
7957
8048
  parentNode.removeChild(container);
7958
8049
  }
7959
8050
 
7960
- /**
7961
- * @param {FormProperty} property
7962
- * @param {any} value
8051
+ /**
8052
+ * @param {FormProperty} property
8053
+ * @param {any} value
7963
8054
  */
7964
8055
  setProperty(property, value) {
7965
8056
  const properties = minDash.set(this._getState().properties, [property], value);
@@ -7968,50 +8059,52 @@ class Form {
7968
8059
  });
7969
8060
  }
7970
8061
 
7971
- /**
7972
- * @param {FormEvent} type
7973
- * @param {Function} handler
8062
+ /**
8063
+ * @param {FormEvent} type
8064
+ * @param {Function} handler
7974
8065
  */
7975
8066
  off(type, handler) {
7976
8067
  this.get('eventBus').off(type, handler);
7977
8068
  }
7978
8069
 
7979
- /**
7980
- * @private
7981
- *
7982
- * @param {FormOptions} options
7983
- * @param {Element} container
7984
- *
7985
- * @returns {Injector}
8070
+ /**
8071
+ * @private
8072
+ *
8073
+ * @param {FormOptions} options
8074
+ * @param {Element} container
8075
+ *
8076
+ * @returns {Injector}
7986
8077
  */
7987
8078
  _createInjector(options, container) {
7988
8079
  const {
8080
+ modules = this._getModules(),
7989
8081
  additionalModules = [],
7990
- modules = this._getModules()
8082
+ ...config
7991
8083
  } = options;
7992
- const config = {
8084
+ const enrichedConfig = {
8085
+ ...config,
7993
8086
  renderer: {
7994
8087
  container
7995
8088
  }
7996
8089
  };
7997
8090
  return createInjector([{
7998
- config: ['value', config]
8091
+ config: ['value', enrichedConfig]
7999
8092
  }, {
8000
8093
  form: ['value', this]
8001
8094
  }, core, ...modules, ...additionalModules]);
8002
8095
  }
8003
8096
 
8004
- /**
8005
- * @private
8097
+ /**
8098
+ * @private
8006
8099
  */
8007
8100
  _emit(type, data) {
8008
8101
  this.get('eventBus').fire(type, data);
8009
8102
  }
8010
8103
 
8011
- /**
8012
- * @internal
8013
- *
8014
- * @param { { add?: boolean, field: any, indexes: object, remove?: number, value?: any } } update
8104
+ /**
8105
+ * @internal
8106
+ *
8107
+ * @param { { add?: boolean, field: any, indexes: object, remove?: number, value?: any } } update
8015
8108
  */
8016
8109
  _update(update) {
8017
8110
  const {
@@ -8037,15 +8130,15 @@ class Form {
8037
8130
  });
8038
8131
  }
8039
8132
 
8040
- /**
8041
- * @internal
8133
+ /**
8134
+ * @internal
8042
8135
  */
8043
8136
  _getState() {
8044
8137
  return this._state;
8045
8138
  }
8046
8139
 
8047
- /**
8048
- * @internal
8140
+ /**
8141
+ * @internal
8049
8142
  */
8050
8143
  _setState(state) {
8051
8144
  this._state = {
@@ -8055,22 +8148,22 @@ class Form {
8055
8148
  this._emit('changed', this._getState());
8056
8149
  }
8057
8150
 
8058
- /**
8059
- * @internal
8151
+ /**
8152
+ * @internal
8060
8153
  */
8061
8154
  _getModules() {
8062
8155
  return [ExpressionLanguageModule, MarkdownModule, ViewerCommandsModule, RepeatRenderModule];
8063
8156
  }
8064
8157
 
8065
- /**
8066
- * @internal
8158
+ /**
8159
+ * @internal
8067
8160
  */
8068
8161
  _onEvent(type, priority, handler) {
8069
8162
  this.get('eventBus').on(type, priority, handler);
8070
8163
  }
8071
8164
 
8072
- /**
8073
- * @internal
8165
+ /**
8166
+ * @internal
8074
8167
  */
8075
8168
  _getSubmitData() {
8076
8169
  const formFieldRegistry = this.get('formFieldRegistry');
@@ -8127,16 +8220,16 @@ class Form {
8127
8220
  return this._applyConditions(workingSubmitData, formData);
8128
8221
  }
8129
8222
 
8130
- /**
8131
- * @internal
8223
+ /**
8224
+ * @internal
8132
8225
  */
8133
8226
  _applyConditions(toFilter, data, options = {}) {
8134
8227
  const conditionChecker = this.get('conditionChecker');
8135
8228
  return conditionChecker.applyConditions(toFilter, data, options);
8136
8229
  }
8137
8230
 
8138
- /**
8139
- * @internal
8231
+ /**
8232
+ * @internal
8140
8233
  */
8141
8234
  _getInitializedFieldData(data, options = {}) {
8142
8235
  const formFieldRegistry = this.get('formFieldRegistry');
@@ -8242,9 +8335,9 @@ function createForm(options) {
8242
8335
  const {
8243
8336
  data,
8244
8337
  schema,
8245
- ...rest
8338
+ ...formOptions
8246
8339
  } = options;
8247
- const form = new Form(rest);
8340
+ const form = new Form(formOptions);
8248
8341
  return form.importSchema(schema, data).then(function () {
8249
8342
  return form;
8250
8343
  });