@bpmn-io/form-js-viewer 1.8.4 → 1.8.6

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
@@ -741,6 +741,20 @@ function generateIdForType(type) {
741
741
  function clone(data, replacer) {
742
742
  return JSON.parse(JSON.stringify(data, replacer));
743
743
  }
744
+ function runRecursively(formField, fn) {
745
+ const components = formField.components || [];
746
+ components.forEach((component, _) => {
747
+ runRecursively(component, fn);
748
+ });
749
+ fn(formField);
750
+ }
751
+ function wrapObjectKeysWithUnderscores(obj) {
752
+ const newObj = {};
753
+ for (const [key, value] of Object.entries(obj)) {
754
+ newObj[`_${key}_`] = value;
755
+ }
756
+ return newObj;
757
+ }
744
758
 
745
759
  /**
746
760
  * Transform a LocalExpressionContext object into a usable FEEL context.
@@ -757,25 +771,24 @@ function buildExpressionContext(context) {
757
771
  return {
758
772
  ...specialContextKeys,
759
773
  ...data,
760
- ..._wrapObjectKeysWithUnderscores(specialContextKeys)
774
+ ...wrapObjectKeysWithUnderscores(specialContextKeys)
761
775
  };
762
776
  }
763
- function runRecursively(formField, fn) {
764
- const components = formField.components || [];
765
- components.forEach((component, index) => {
766
- runRecursively(component, fn);
767
- });
768
- fn(formField);
769
- }
770
-
771
- // helpers //////////////////////
772
777
 
773
- function _wrapObjectKeysWithUnderscores(obj) {
774
- const newObj = {};
775
- for (const [key, value] of Object.entries(obj)) {
776
- newObj[`_${key}_`] = value;
778
+ /**
779
+ * Evaluate a string based on the expressionLanguage and context information.
780
+ * If the string is not an expression, it is returned as is.
781
+ *
782
+ * @param {any} expressionLanguage - The expression language to use.
783
+ * @param {string} value - The string to evaluate.
784
+ * @param {Object} expressionContextInfo - The context information to use.
785
+ * @returns {any} - Evaluated value or the original value if not an expression.
786
+ */
787
+ function runExpressionEvaluation(expressionLanguage, value, expressionContextInfo) {
788
+ if (expressionLanguage && expressionLanguage.isExpression(value)) {
789
+ return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo));
777
790
  }
778
- return newObj;
791
+ return value;
779
792
  }
780
793
 
781
794
  /**
@@ -908,12 +921,7 @@ function _isAllowedValue(value) {
908
921
  function useExpressionEvaluation(value) {
909
922
  const expressionLanguage = useService('expressionLanguage');
910
923
  const expressionContextInfo = hooks.useContext(LocalExpressionContext);
911
- return hooks.useMemo(() => {
912
- if (expressionLanguage && expressionLanguage.isExpression(value)) {
913
- return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo));
914
- }
915
- return value;
916
- }, [expressionLanguage, expressionContextInfo, value]);
924
+ return hooks.useMemo(() => runExpressionEvaluation(expressionLanguage, value, expressionContextInfo), [expressionLanguage, expressionContextInfo, value]);
917
925
  }
918
926
 
919
927
  /**
@@ -1790,7 +1798,6 @@ function Checkbox(props) {
1790
1798
  target
1791
1799
  }) => {
1792
1800
  props.onChange({
1793
- field,
1794
1801
  value: target.checked
1795
1802
  });
1796
1803
  };
@@ -1869,7 +1876,6 @@ function Checklist(props) {
1869
1876
  const toggleCheckbox = toggledValue => {
1870
1877
  const newValues = hasEqualValue(toggledValue, values) ? values.filter(value => !isEqual(value, toggledValue)) : [...values, toggledValue];
1871
1878
  props.onChange({
1872
- field,
1873
1879
  value: newValues
1874
1880
  });
1875
1881
  };
@@ -1957,7 +1963,7 @@ function FormField(props) {
1957
1963
  const {
1958
1964
  field,
1959
1965
  indexes,
1960
- onChange
1966
+ onChange: _onChange
1961
1967
  } = props;
1962
1968
  const formFields = useService('formFields'),
1963
1969
  viewerCommands = useService('viewerCommands', false),
@@ -1999,21 +2005,22 @@ function FormField(props) {
1999
2005
  // add precedence: global readonly > form field disabled
2000
2006
  const disabled = !properties.readOnly && (properties.disabled || field.disabled || false);
2001
2007
  const hidden = useCondition(field.conditional && field.conditional.hide || null);
2008
+ const fieldInstance = hooks.useMemo(() => ({
2009
+ id: field.id,
2010
+ expressionContextInfo: localExpressionContext,
2011
+ valuePath,
2012
+ indexes
2013
+ }), [field.id, valuePath, localExpressionContext, indexes]);
2002
2014
 
2003
2015
  // register form field instance
2004
2016
  hooks.useEffect(() => {
2005
2017
  if (formFieldInstanceRegistry && !hidden) {
2006
- const instanceId = formFieldInstanceRegistry.add({
2007
- id: field.id,
2008
- expressionContextInfo: localExpressionContext,
2009
- valuePath,
2010
- indexes
2011
- });
2018
+ const instanceId = formFieldInstanceRegistry.add(fieldInstance);
2012
2019
  return () => {
2013
2020
  formFieldInstanceRegistry.remove(instanceId);
2014
2021
  };
2015
2022
  }
2016
- }, [formFieldInstanceRegistry, field.id, localExpressionContext, valuePath, indexes, hidden]);
2023
+ }, [fieldInstance, formFieldInstanceRegistry, hidden]);
2017
2024
 
2018
2025
  // ensures the initial validation behavior can be re-triggered upon form reset
2019
2026
  hooks.useEffect(() => {
@@ -2034,34 +2041,33 @@ function FormField(props) {
2034
2041
  const hasInitialValue = initialValue && !isEqual(initialValue, []);
2035
2042
  if (initialValidationTrigger && hasInitialValue) {
2036
2043
  setInitialValidationTrigger(false);
2037
- viewerCommands.updateFieldValidation(field, initialValue, indexes);
2044
+ viewerCommands.updateFieldInstanceValidation(fieldInstance, initialValue);
2038
2045
  }
2039
- }, [viewerCommands, field, initialValue, initialValidationTrigger, indexes]);
2046
+ }, [fieldInstance, initialValidationTrigger, initialValue, viewerCommands]);
2040
2047
  const onBlur = hooks.useCallback(() => {
2041
2048
  const value = minDash.get(data, valuePath);
2042
2049
  if (initialValidationTrigger) {
2043
2050
  setInitialValidationTrigger(false);
2044
- viewerCommands.updateFieldValidation(field, value, indexes);
2051
+ viewerCommands.updateFieldInstanceValidation(fieldInstance, value);
2045
2052
  }
2046
2053
  eventBus.fire('formField.blur', {
2047
2054
  formField: field
2048
2055
  });
2049
- }, [eventBus, field, indexes, viewerCommands, initialValidationTrigger, data, valuePath]);
2056
+ }, [data, eventBus, field, fieldInstance, initialValidationTrigger, valuePath, viewerCommands]);
2050
2057
  const onFocus = hooks.useCallback(() => {
2051
2058
  eventBus.fire('formField.focus', {
2052
2059
  formField: field
2053
2060
  });
2054
2061
  }, [eventBus, field]);
2055
- const onChangeIndexed = hooks.useCallback(update => {
2056
- // any data change will trigger validation
2062
+ const onChange = hooks.useCallback(update => {
2057
2063
  setInitialValidationTrigger(false);
2058
-
2059
- // add indexes of the keyed field to the update, if any
2060
- onChange(fieldConfig.keyed ? {
2061
- ...update,
2062
- indexes
2063
- } : update);
2064
- }, [onChange, fieldConfig.keyed, indexes]);
2064
+ _onChange({
2065
+ field,
2066
+ indexes,
2067
+ fieldInstance,
2068
+ ...update
2069
+ });
2070
+ }, [_onChange, field, fieldInstance, indexes]);
2065
2071
  if (hidden) {
2066
2072
  return jsxRuntime.jsx(Hidden, {
2067
2073
  field: field
@@ -2074,11 +2080,12 @@ function FormField(props) {
2074
2080
  disabled: disabled,
2075
2081
  errors: fieldErrors,
2076
2082
  domId: domId,
2077
- onChange: disabled || readonly ? noop$1 : onChangeIndexed,
2083
+ onChange: disabled || readonly ? noop$1 : onChange,
2078
2084
  onBlur: disabled || readonly ? noop$1 : onBlur,
2079
2085
  onFocus: disabled || readonly ? noop$1 : onFocus,
2080
2086
  readonly: readonly,
2081
- value: value
2087
+ value: value,
2088
+ fieldInstance: fieldInstance
2082
2089
  });
2083
2090
  if (fieldConfig.escapeGridRender) {
2084
2091
  return formFieldElement;
@@ -3914,7 +3921,6 @@ function Radio(props) {
3914
3921
  } = validate;
3915
3922
  const onChange = v => {
3916
3923
  props.onChange({
3917
- field,
3918
3924
  value: v
3919
3925
  });
3920
3926
  };
@@ -4062,10 +4068,9 @@ function SearchableSelect(props) {
4062
4068
  const setValue = hooks.useCallback(option => {
4063
4069
  setFilter(option && option.label || '');
4064
4070
  props.onChange({
4065
- value: option && option.value || null,
4066
- field
4071
+ value: option && option.value || null
4067
4072
  });
4068
- }, [field, props]);
4073
+ }, [props]);
4069
4074
  const displayState = hooks.useMemo(() => {
4070
4075
  const ds = {};
4071
4076
  ds.componentReady = !disabled && !readonly && loadState === LOAD_STATES.LOADED;
@@ -4211,10 +4216,9 @@ function SimpleSelect(props) {
4211
4216
  const valueLabel = hooks.useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
4212
4217
  const setValue = hooks.useCallback(option => {
4213
4218
  props.onChange({
4214
- value: option && option.value || null,
4215
- field
4219
+ value: option && option.value || null
4216
4220
  });
4217
- }, [field, props]);
4221
+ }, [props]);
4218
4222
  const displayState = hooks.useMemo(() => {
4219
4223
  const ds = {};
4220
4224
  ds.componentReady = !disabled && !readonly && loadState === LOAD_STATES.LOADED;
@@ -4552,15 +4556,13 @@ function Taglist(props) {
4552
4556
  return;
4553
4557
  }
4554
4558
  props.onChange({
4555
- value: [...values, value],
4556
- field
4559
+ value: [...values, value]
4557
4560
  });
4558
4561
  };
4559
4562
  const deselectValue = value => {
4560
4563
  const newValues = values.filter(v => !isEqual(v, value));
4561
4564
  props.onChange({
4562
- value: newValues,
4563
- field
4565
+ value: newValues
4564
4566
  });
4565
4567
  };
4566
4568
  const onInputChange = ({
@@ -5049,7 +5051,6 @@ function Textfield(props) {
5049
5051
  target
5050
5052
  }) => {
5051
5053
  props.onChange({
5052
- field,
5053
5054
  value: target.value
5054
5055
  });
5055
5056
  });
@@ -5149,7 +5150,6 @@ function Textarea(props) {
5149
5150
  target
5150
5151
  }) => {
5151
5152
  props.onChange({
5152
- field,
5153
5153
  value: target.value
5154
5154
  });
5155
5155
  });
@@ -6618,6 +6618,9 @@ var commandModule = {
6618
6618
  commandStack: ['type', CommandStack]
6619
6619
  };
6620
6620
 
6621
+ /**
6622
+ * @deprecated
6623
+ */
6621
6624
  class UpdateFieldValidationHandler {
6622
6625
  constructor(form, validator) {
6623
6626
  this._form = form;
@@ -6647,6 +6650,38 @@ class UpdateFieldValidationHandler {
6647
6650
  }
6648
6651
  UpdateFieldValidationHandler.$inject = ['form', 'validator'];
6649
6652
 
6653
+ class UpdateFieldInstanceValidationHandler {
6654
+ constructor(form, validator) {
6655
+ this._form = form;
6656
+ this._validator = validator;
6657
+ }
6658
+ execute(context) {
6659
+ const {
6660
+ fieldInstance,
6661
+ value
6662
+ } = context;
6663
+ const {
6664
+ id,
6665
+ indexes
6666
+ } = fieldInstance;
6667
+ const {
6668
+ errors
6669
+ } = this._form._getState();
6670
+ context.oldErrors = clone(errors);
6671
+ const fieldErrors = this._validator.validateFieldInstance(fieldInstance, value);
6672
+ const updatedErrors = minDash.set(errors, [id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
6673
+ this._form._setState({
6674
+ errors: updatedErrors
6675
+ });
6676
+ }
6677
+ revert(context) {
6678
+ this._form._setState({
6679
+ errors: context.oldErrors
6680
+ });
6681
+ }
6682
+ }
6683
+ UpdateFieldInstanceValidationHandler.$inject = ['form', 'validator'];
6684
+
6650
6685
  class ViewerCommands {
6651
6686
  constructor(commandStack, eventBus) {
6652
6687
  this._commandStack = commandStack;
@@ -6661,9 +6696,14 @@ class ViewerCommands {
6661
6696
  }
6662
6697
  getHandlers() {
6663
6698
  return {
6664
- 'formField.validation.update': UpdateFieldValidationHandler
6699
+ 'formField.validation.update': UpdateFieldValidationHandler,
6700
+ 'formFieldInstance.validation.update': UpdateFieldInstanceValidationHandler
6665
6701
  };
6666
6702
  }
6703
+
6704
+ /**
6705
+ * @deprecated
6706
+ */
6667
6707
  updateFieldValidation(field, value, indexes) {
6668
6708
  const context = {
6669
6709
  field,
@@ -6672,6 +6712,13 @@ class ViewerCommands {
6672
6712
  };
6673
6713
  this._commandStack.execute('formField.validation.update', context);
6674
6714
  }
6715
+ updateFieldInstanceValidation(fieldInstance, value) {
6716
+ const context = {
6717
+ fieldInstance,
6718
+ value
6719
+ };
6720
+ this._commandStack.execute('formFieldInstance.validation.update', context);
6721
+ }
6675
6722
  }
6676
6723
  ViewerCommands.$inject = ['commandStack', 'eventBus'];
6677
6724
 
@@ -6856,9 +6903,7 @@ class RepeatRenderManager {
6856
6903
  updatedValues.push(newItem);
6857
6904
  shouldScroll.current = true;
6858
6905
  props.onChange({
6859
- field: repeaterField,
6860
- value: updatedValues,
6861
- indexes
6906
+ value: updatedValues
6862
6907
  });
6863
6908
  setSharedRepeatState(state => ({
6864
6909
  ...state,
@@ -7517,11 +7562,18 @@ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-
7517
7562
  const PHONE_PATTERN = /(\+|00)(297|93|244|1264|358|355|376|971|54|374|1684|1268|61|43|994|257|32|229|226|880|359|973|1242|387|590|375|501|1441|591|55|1246|673|975|267|236|1|61|41|56|86|225|237|243|242|682|57|269|238|506|53|5999|61|1345|357|420|49|253|1767|45|1809|1829|1849|213|593|20|291|212|34|372|251|358|679|500|33|298|691|241|44|995|44|233|350|224|590|220|245|240|30|1473|299|502|594|1671|592|852|504|385|509|36|62|44|91|246|353|98|964|354|972|39|1876|44|962|81|76|77|254|996|855|686|1869|82|383|965|856|961|231|218|1758|423|94|266|370|352|371|853|590|212|377|373|261|960|52|692|389|223|356|95|382|976|1670|258|222|1664|596|230|265|60|262|264|687|227|672|234|505|683|31|47|977|674|64|968|92|507|64|51|63|680|675|48|1787|1939|850|351|595|970|689|974|262|40|7|250|966|249|221|65|500|4779|677|232|503|378|252|508|381|211|239|597|421|386|46|268|1721|248|963|1649|235|228|66|992|690|993|670|676|1868|216|90|688|886|255|256|380|598|1|998|3906698|379|1784|58|1284|1340|84|678|681|685|967|27|260|263)(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{4,20}$/;
7518
7563
  const VALIDATE_FEEL_PROPERTIES = ['min', 'max', 'minLength', 'maxLength'];
7519
7564
  class Validator {
7520
- constructor(expressionLanguage, conditionChecker, form) {
7565
+ constructor(expressionLanguage, conditionChecker, form, formFieldRegistry) {
7521
7566
  this._expressionLanguage = expressionLanguage;
7522
7567
  this._conditionChecker = conditionChecker;
7523
7568
  this._form = form;
7569
+ this._formFieldRegistry = formFieldRegistry;
7524
7570
  }
7571
+
7572
+ /**
7573
+ * Validate against a field definition, does not support proper expression evaluation.
7574
+ *
7575
+ * @deprecated use validateFieldInstance instead
7576
+ */
7525
7577
  validateField(field, value) {
7526
7578
  const {
7527
7579
  type,
@@ -7529,72 +7581,124 @@ class Validator {
7529
7581
  } = field;
7530
7582
  let errors = [];
7531
7583
  if (type === 'number') {
7532
- const {
7533
- decimalDigits,
7534
- increment
7535
- } = field;
7536
- if (value === 'NaN') {
7537
- errors = [...errors, 'Value is not a number.'];
7538
- } else if (value) {
7539
- if (decimalDigits >= 0 && countDecimals(value) > decimalDigits) {
7540
- errors = [...errors, 'Value is expected to ' + (decimalDigits === 0 ? 'be an integer' : `have at most ${decimalDigits} decimal digit${decimalDigits > 1 ? 's' : ''}`) + '.'];
7541
- }
7542
- if (increment) {
7543
- const bigValue = Big(value);
7544
- const bigIncrement = Big(increment);
7545
- const offset = bigValue.mod(bigIncrement);
7546
- if (offset.cmp(0) !== 0) {
7547
- const previousValue = bigValue.minus(offset);
7548
- const nextValue = previousValue.plus(bigIncrement);
7549
- errors = [...errors, `Please select a valid value, the two nearest valid values are ${previousValue} and ${nextValue}.`];
7550
- }
7551
- }
7552
- }
7584
+ errors = [...errors, ...runNumberValidation(field, value)];
7553
7585
  }
7554
7586
  if (!validate) {
7555
7587
  return errors;
7556
7588
  }
7557
- const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
7558
- if (evaluatedValidation.pattern && value && !new RegExp(evaluatedValidation.pattern).test(value)) {
7559
- errors = [...errors, `Field must match pattern ${evaluatedValidation.pattern}.`];
7560
- }
7561
- if (evaluatedValidation.required) {
7562
- const isUncheckedCheckbox = type === 'checkbox' && value === false;
7563
- const isUnsetValue = minDash.isNil(value) || value === '';
7564
- const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
7565
- if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
7566
- errors = [...errors, 'Field is required.'];
7567
- }
7568
- }
7569
- if ('min' in evaluatedValidation && (value || value === 0) && value < evaluatedValidation.min) {
7570
- errors = [...errors, `Field must have minimum value of ${evaluatedValidation.min}.`];
7571
- }
7572
- if ('max' in evaluatedValidation && (value || value === 0) && value > evaluatedValidation.max) {
7573
- errors = [...errors, `Field must have maximum value of ${evaluatedValidation.max}.`];
7574
- }
7575
- if ('minLength' in evaluatedValidation && value && value.trim().length < evaluatedValidation.minLength) {
7576
- errors = [...errors, `Field must have minimum length of ${evaluatedValidation.minLength}.`];
7577
- }
7578
- if ('maxLength' in evaluatedValidation && value && value.trim().length > evaluatedValidation.maxLength) {
7579
- errors = [...errors, `Field must have maximum length of ${evaluatedValidation.maxLength}.`];
7589
+ const evaluatedValidation = oldEvaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
7590
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
7591
+ return errors;
7592
+ }
7593
+
7594
+ /**
7595
+ * Validate a field instance.
7596
+ *
7597
+ * @param {Object} fieldInstance
7598
+ * @param {string} value
7599
+ *
7600
+ * @returns {Array<string>}
7601
+ */
7602
+ validateFieldInstance(fieldInstance, value) {
7603
+ const {
7604
+ id,
7605
+ expressionContextInfo
7606
+ } = fieldInstance;
7607
+ const field = this._formFieldRegistry.get(id);
7608
+ if (!field) {
7609
+ return [];
7580
7610
  }
7581
- if ('validationType' in evaluatedValidation && value && evaluatedValidation.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
7582
- errors = [...errors, 'Field must be a valid international phone number. (e.g. +4930664040900)'];
7611
+ const {
7612
+ type,
7613
+ validate
7614
+ } = field;
7615
+ let errors = [];
7616
+ if (type === 'number') {
7617
+ errors = [...errors, ...runNumberValidation(field, value)];
7583
7618
  }
7584
- if ('validationType' in evaluatedValidation && value && evaluatedValidation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
7585
- errors = [...errors, 'Field must be a valid email.'];
7619
+ if (!validate) {
7620
+ return errors;
7586
7621
  }
7622
+ const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, expressionContextInfo);
7623
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
7587
7624
  return errors;
7588
7625
  }
7589
7626
  }
7590
- Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form'];
7627
+ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry'];
7591
7628
 
7592
7629
  // helpers //////////
7593
7630
 
7594
- /**
7595
- * Helper function to evaluate optional FEEL validation values.
7596
- */
7597
- function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
7631
+ function runNumberValidation(field, value) {
7632
+ const {
7633
+ decimalDigits,
7634
+ increment
7635
+ } = field;
7636
+ const errors = [];
7637
+ if (value === 'NaN') {
7638
+ errors.push('Value is not a number.');
7639
+ } else if (value) {
7640
+ if (decimalDigits >= 0 && countDecimals(value) > decimalDigits) {
7641
+ errors.push('Value is expected to ' + (decimalDigits === 0 ? 'be an integer' : `have at most ${decimalDigits} decimal digit${decimalDigits > 1 ? 's' : ''}`) + '.');
7642
+ }
7643
+ if (increment) {
7644
+ const bigValue = Big(value);
7645
+ const bigIncrement = Big(increment);
7646
+ const offset = bigValue.mod(bigIncrement);
7647
+ if (offset.cmp(0) !== 0) {
7648
+ const previousValue = bigValue.minus(offset);
7649
+ const nextValue = previousValue.plus(bigIncrement);
7650
+ errors.push(`Please select a valid value, the two nearest valid values are ${previousValue} and ${nextValue}.`);
7651
+ }
7652
+ }
7653
+ }
7654
+ return errors;
7655
+ }
7656
+ function runPresetValidation(field, validation, value) {
7657
+ const errors = [];
7658
+ if (validation.pattern && value && !new RegExp(validation.pattern).test(value)) {
7659
+ errors.push(`Field must match pattern ${validation.pattern}.`);
7660
+ }
7661
+ if (validation.required) {
7662
+ const isUncheckedCheckbox = field.type === 'checkbox' && value === false;
7663
+ const isUnsetValue = minDash.isNil(value) || value === '';
7664
+ const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
7665
+ if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
7666
+ errors.push('Field is required.');
7667
+ }
7668
+ }
7669
+ if ('min' in validation && (value || value === 0) && value < validation.min) {
7670
+ errors.push(`Field must have minimum value of ${validation.min}.`);
7671
+ }
7672
+ if ('max' in validation && (value || value === 0) && value > validation.max) {
7673
+ errors.push(`Field must have maximum value of ${validation.max}.`);
7674
+ }
7675
+ if ('minLength' in validation && value && value.trim().length < validation.minLength) {
7676
+ errors.push(`Field must have minimum length of ${validation.minLength}.`);
7677
+ }
7678
+ if ('maxLength' in validation && value && value.trim().length > validation.maxLength) {
7679
+ errors.push(`Field must have maximum length of ${validation.maxLength}.`);
7680
+ }
7681
+ if ('validationType' in validation && value && validation.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
7682
+ errors.push('Field must be a valid international phone number. (e.g. +4930664040900)');
7683
+ }
7684
+ if ('validationType' in validation && value && validation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
7685
+ errors.push('Field must be a valid email.');
7686
+ }
7687
+ return errors;
7688
+ }
7689
+ function evaluateFEELValues(validate, expressionLanguage, expressionContextInfo) {
7690
+ const evaluatedValidate = {
7691
+ ...validate
7692
+ };
7693
+ VALIDATE_FEEL_PROPERTIES.forEach(property => {
7694
+ const path = property.split('.');
7695
+ const value = minDash.get(evaluatedValidate, path);
7696
+ const evaluatedValue = runExpressionEvaluation(expressionLanguage, value, expressionContextInfo);
7697
+ minDash.set(evaluatedValidate, path, evaluatedValue === null ? undefined : evaluatedValue);
7698
+ });
7699
+ return evaluatedValidate;
7700
+ }
7701
+ function oldEvaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
7598
7702
  const evaluatedValidate = {
7599
7703
  ...validate
7600
7704
  };
@@ -8324,6 +8428,10 @@ class FormFieldInstanceRegistry {
8324
8428
  valuePath,
8325
8429
  indexes
8326
8430
  };
8431
+ this._eventBus.fire('formFieldInstanceRegistry.changed', {
8432
+ instanceId,
8433
+ action: 'added'
8434
+ });
8327
8435
  return instanceId;
8328
8436
  }
8329
8437
  remove(instanceId) {
@@ -8331,6 +8439,10 @@ class FormFieldInstanceRegistry {
8331
8439
  return;
8332
8440
  }
8333
8441
  delete this._formFieldInstances[instanceId];
8442
+ this._eventBus.fire('formFieldInstanceRegistry.changed', {
8443
+ instanceId,
8444
+ action: 'removed'
8445
+ });
8334
8446
  }
8335
8447
  getAll() {
8336
8448
  return Object.values(this._formFieldInstances);
@@ -8339,9 +8451,13 @@ class FormFieldInstanceRegistry {
8339
8451
  return this.getAll().filter(({
8340
8452
  id
8341
8453
  }) => {
8454
+ const formFieldDefinition = this._formFieldRegistry.get(id);
8455
+ if (!formFieldDefinition) {
8456
+ return false;
8457
+ }
8342
8458
  const {
8343
8459
  type
8344
- } = this._formFieldRegistry.get(id);
8460
+ } = formFieldDefinition;
8345
8461
  const {
8346
8462
  config
8347
8463
  } = this._formFields.get(type);
@@ -8601,11 +8717,12 @@ class Form {
8601
8717
  } = this._getState();
8602
8718
  const errors = {};
8603
8719
  const getErrorPath = (id, indexes) => [id, ...Object.values(indexes || {})];
8604
- formFieldInstanceRegistry.getAllKeyed().forEach(({
8605
- id,
8606
- valuePath,
8607
- indexes
8608
- }) => {
8720
+ formFieldInstanceRegistry.getAllKeyed().forEach(fieldInstance => {
8721
+ const {
8722
+ id,
8723
+ valuePath,
8724
+ indexes
8725
+ } = fieldInstance;
8609
8726
  const field = formFieldRegistry.get(id);
8610
8727
 
8611
8728
  // (1) Skip disabled fields
@@ -8613,9 +8730,9 @@ class Form {
8613
8730
  return;
8614
8731
  }
8615
8732
 
8616
- // (2) Validate the field
8733
+ // (2) Validate the field instance
8617
8734
  const value = minDash.get(data, valuePath);
8618
- const fieldErrors = validator.validateField(field, value);
8735
+ const fieldErrors = validator.validateFieldInstance(fieldInstance, value);
8619
8736
  if (fieldErrors.length) {
8620
8737
  minDash.set(errors, getErrorPath(field.id, indexes), fieldErrors);
8621
8738
  }
@@ -8720,26 +8837,26 @@ class Form {
8720
8837
  /**
8721
8838
  * @internal
8722
8839
  *
8723
- * @param { { field: any, indexes: object, value: any } } update
8840
+ * @param { { fieldInstance: any, value: any } } update
8724
8841
  */
8725
8842
  _update(update) {
8726
8843
  const {
8727
- field,
8728
- indexes,
8844
+ fieldInstance,
8729
8845
  value
8730
8846
  } = update;
8847
+ const {
8848
+ id,
8849
+ valuePath,
8850
+ indexes
8851
+ } = fieldInstance;
8731
8852
  const {
8732
8853
  data,
8733
8854
  errors
8734
8855
  } = this._getState();
8735
- const validator = this.get('validator'),
8736
- pathRegistry = this.get('pathRegistry');
8737
- const fieldErrors = validator.validateField(field, value);
8738
- const valuePath = pathRegistry.getValuePath(field, {
8739
- indexes
8740
- });
8856
+ const validator = this.get('validator');
8857
+ const fieldErrors = validator.validateFieldInstance(fieldInstance, value);
8741
8858
  minDash.set(data, valuePath, value);
8742
- minDash.set(errors, [field.id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
8859
+ minDash.set(errors, [id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
8743
8860
  this._emit('field.updated', update);
8744
8861
  this._setState({
8745
8862
  data: clone(data),
@@ -9006,6 +9123,7 @@ exports.iconsByType = iconsByType;
9006
9123
  exports.isRequired = isRequired;
9007
9124
  exports.pathParse = pathParse;
9008
9125
  exports.pathsEqual = pathsEqual;
9126
+ exports.runExpressionEvaluation = runExpressionEvaluation;
9009
9127
  exports.runRecursively = runRecursively;
9010
9128
  exports.sanitizeDateTimePickerValue = sanitizeDateTimePickerValue;
9011
9129
  exports.sanitizeHTML = sanitizeHTML;
@@ -9018,4 +9136,5 @@ exports.useExpressionEvaluation = useExpressionEvaluation;
9018
9136
  exports.useSingleLineTemplateEvaluation = useSingleLineTemplateEvaluation;
9019
9137
  exports.useTemplateEvaluation = useTemplateEvaluation;
9020
9138
  exports.wrapCSSStyles = wrapCSSStyles;
9139
+ exports.wrapObjectKeysWithUnderscores = wrapObjectKeysWithUnderscores;
9021
9140
  //# sourceMappingURL=index.cjs.map