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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -721,6 +721,20 @@ function generateIdForType(type) {
721
721
  function clone(data, replacer) {
722
722
  return JSON.parse(JSON.stringify(data, replacer));
723
723
  }
724
+ function runRecursively(formField, fn) {
725
+ const components = formField.components || [];
726
+ components.forEach((component, _) => {
727
+ runRecursively(component, fn);
728
+ });
729
+ fn(formField);
730
+ }
731
+ function wrapObjectKeysWithUnderscores(obj) {
732
+ const newObj = {};
733
+ for (const [key, value] of Object.entries(obj)) {
734
+ newObj[`_${key}_`] = value;
735
+ }
736
+ return newObj;
737
+ }
724
738
 
725
739
  /**
726
740
  * Transform a LocalExpressionContext object into a usable FEEL context.
@@ -737,25 +751,24 @@ function buildExpressionContext(context) {
737
751
  return {
738
752
  ...specialContextKeys,
739
753
  ...data,
740
- ..._wrapObjectKeysWithUnderscores(specialContextKeys)
754
+ ...wrapObjectKeysWithUnderscores(specialContextKeys)
741
755
  };
742
756
  }
743
- function runRecursively(formField, fn) {
744
- const components = formField.components || [];
745
- components.forEach((component, index) => {
746
- runRecursively(component, fn);
747
- });
748
- fn(formField);
749
- }
750
-
751
- // helpers //////////////////////
752
757
 
753
- function _wrapObjectKeysWithUnderscores(obj) {
754
- const newObj = {};
755
- for (const [key, value] of Object.entries(obj)) {
756
- newObj[`_${key}_`] = value;
758
+ /**
759
+ * Evaluate a string based on the expressionLanguage and context information.
760
+ * If the string is not an expression, it is returned as is.
761
+ *
762
+ * @param {any} expressionLanguage - The expression language to use.
763
+ * @param {string} value - The string to evaluate.
764
+ * @param {Object} expressionContextInfo - The context information to use.
765
+ * @returns {any} - Evaluated value or the original value if not an expression.
766
+ */
767
+ function runExpressionEvaluation(expressionLanguage, value, expressionContextInfo) {
768
+ if (expressionLanguage && expressionLanguage.isExpression(value)) {
769
+ return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo));
757
770
  }
758
- return newObj;
771
+ return value;
759
772
  }
760
773
 
761
774
  /**
@@ -888,12 +901,7 @@ function _isAllowedValue(value) {
888
901
  function useExpressionEvaluation(value) {
889
902
  const expressionLanguage = useService('expressionLanguage');
890
903
  const expressionContextInfo = useContext(LocalExpressionContext);
891
- return useMemo(() => {
892
- if (expressionLanguage && expressionLanguage.isExpression(value)) {
893
- return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo));
894
- }
895
- return value;
896
- }, [expressionLanguage, expressionContextInfo, value]);
904
+ return useMemo(() => runExpressionEvaluation(expressionLanguage, value, expressionContextInfo), [expressionLanguage, expressionContextInfo, value]);
897
905
  }
898
906
 
899
907
  /**
@@ -1770,7 +1778,6 @@ function Checkbox(props) {
1770
1778
  target
1771
1779
  }) => {
1772
1780
  props.onChange({
1773
- field,
1774
1781
  value: target.checked
1775
1782
  });
1776
1783
  };
@@ -1849,7 +1856,6 @@ function Checklist(props) {
1849
1856
  const toggleCheckbox = toggledValue => {
1850
1857
  const newValues = hasEqualValue(toggledValue, values) ? values.filter(value => !isEqual(value, toggledValue)) : [...values, toggledValue];
1851
1858
  props.onChange({
1852
- field,
1853
1859
  value: newValues
1854
1860
  });
1855
1861
  };
@@ -1937,7 +1943,7 @@ function FormField(props) {
1937
1943
  const {
1938
1944
  field,
1939
1945
  indexes,
1940
- onChange
1946
+ onChange: _onChange
1941
1947
  } = props;
1942
1948
  const formFields = useService('formFields'),
1943
1949
  viewerCommands = useService('viewerCommands', false),
@@ -1979,21 +1985,22 @@ function FormField(props) {
1979
1985
  // add precedence: global readonly > form field disabled
1980
1986
  const disabled = !properties.readOnly && (properties.disabled || field.disabled || false);
1981
1987
  const hidden = useCondition(field.conditional && field.conditional.hide || null);
1988
+ const fieldInstance = useMemo(() => ({
1989
+ id: field.id,
1990
+ expressionContextInfo: localExpressionContext,
1991
+ valuePath,
1992
+ indexes
1993
+ }), [field.id, valuePath, localExpressionContext, indexes]);
1982
1994
 
1983
1995
  // register form field instance
1984
1996
  useEffect(() => {
1985
1997
  if (formFieldInstanceRegistry && !hidden) {
1986
- const instanceId = formFieldInstanceRegistry.add({
1987
- id: field.id,
1988
- expressionContextInfo: localExpressionContext,
1989
- valuePath,
1990
- indexes
1991
- });
1998
+ const instanceId = formFieldInstanceRegistry.add(fieldInstance);
1992
1999
  return () => {
1993
2000
  formFieldInstanceRegistry.remove(instanceId);
1994
2001
  };
1995
2002
  }
1996
- }, [formFieldInstanceRegistry, field.id, localExpressionContext, valuePath, indexes, hidden]);
2003
+ }, [fieldInstance, formFieldInstanceRegistry, hidden]);
1997
2004
 
1998
2005
  // ensures the initial validation behavior can be re-triggered upon form reset
1999
2006
  useEffect(() => {
@@ -2014,34 +2021,33 @@ function FormField(props) {
2014
2021
  const hasInitialValue = initialValue && !isEqual(initialValue, []);
2015
2022
  if (initialValidationTrigger && hasInitialValue) {
2016
2023
  setInitialValidationTrigger(false);
2017
- viewerCommands.updateFieldValidation(field, initialValue, indexes);
2024
+ viewerCommands.updateFieldInstanceValidation(fieldInstance, initialValue);
2018
2025
  }
2019
- }, [viewerCommands, field, initialValue, initialValidationTrigger, indexes]);
2026
+ }, [fieldInstance, initialValidationTrigger, initialValue, viewerCommands]);
2020
2027
  const onBlur = useCallback(() => {
2021
2028
  const value = get(data, valuePath);
2022
2029
  if (initialValidationTrigger) {
2023
2030
  setInitialValidationTrigger(false);
2024
- viewerCommands.updateFieldValidation(field, value, indexes);
2031
+ viewerCommands.updateFieldInstanceValidation(fieldInstance, value);
2025
2032
  }
2026
2033
  eventBus.fire('formField.blur', {
2027
2034
  formField: field
2028
2035
  });
2029
- }, [eventBus, field, indexes, viewerCommands, initialValidationTrigger, data, valuePath]);
2036
+ }, [data, eventBus, field, fieldInstance, initialValidationTrigger, valuePath, viewerCommands]);
2030
2037
  const onFocus = useCallback(() => {
2031
2038
  eventBus.fire('formField.focus', {
2032
2039
  formField: field
2033
2040
  });
2034
2041
  }, [eventBus, field]);
2035
- const onChangeIndexed = useCallback(update => {
2036
- // any data change will trigger validation
2042
+ const onChange = useCallback(update => {
2037
2043
  setInitialValidationTrigger(false);
2038
-
2039
- // add indexes of the keyed field to the update, if any
2040
- onChange(fieldConfig.keyed ? {
2041
- ...update,
2042
- indexes
2043
- } : update);
2044
- }, [onChange, fieldConfig.keyed, indexes]);
2044
+ _onChange({
2045
+ field,
2046
+ indexes,
2047
+ fieldInstance,
2048
+ ...update
2049
+ });
2050
+ }, [_onChange, field, fieldInstance, indexes]);
2045
2051
  if (hidden) {
2046
2052
  return jsx(Hidden, {
2047
2053
  field: field
@@ -2054,11 +2060,12 @@ function FormField(props) {
2054
2060
  disabled: disabled,
2055
2061
  errors: fieldErrors,
2056
2062
  domId: domId,
2057
- onChange: disabled || readonly ? noop$1 : onChangeIndexed,
2063
+ onChange: disabled || readonly ? noop$1 : onChange,
2058
2064
  onBlur: disabled || readonly ? noop$1 : onBlur,
2059
2065
  onFocus: disabled || readonly ? noop$1 : onFocus,
2060
2066
  readonly: readonly,
2061
- value: value
2067
+ value: value,
2068
+ fieldInstance: fieldInstance
2062
2069
  });
2063
2070
  if (fieldConfig.escapeGridRender) {
2064
2071
  return formFieldElement;
@@ -3894,7 +3901,6 @@ function Radio(props) {
3894
3901
  } = validate;
3895
3902
  const onChange = v => {
3896
3903
  props.onChange({
3897
- field,
3898
3904
  value: v
3899
3905
  });
3900
3906
  };
@@ -4042,10 +4048,9 @@ function SearchableSelect(props) {
4042
4048
  const setValue = useCallback(option => {
4043
4049
  setFilter(option && option.label || '');
4044
4050
  props.onChange({
4045
- value: option && option.value || null,
4046
- field
4051
+ value: option && option.value || null
4047
4052
  });
4048
- }, [field, props]);
4053
+ }, [props]);
4049
4054
  const displayState = useMemo(() => {
4050
4055
  const ds = {};
4051
4056
  ds.componentReady = !disabled && !readonly && loadState === LOAD_STATES.LOADED;
@@ -4191,10 +4196,9 @@ function SimpleSelect(props) {
4191
4196
  const valueLabel = useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
4192
4197
  const setValue = useCallback(option => {
4193
4198
  props.onChange({
4194
- value: option && option.value || null,
4195
- field
4199
+ value: option && option.value || null
4196
4200
  });
4197
- }, [field, props]);
4201
+ }, [props]);
4198
4202
  const displayState = useMemo(() => {
4199
4203
  const ds = {};
4200
4204
  ds.componentReady = !disabled && !readonly && loadState === LOAD_STATES.LOADED;
@@ -4532,15 +4536,13 @@ function Taglist(props) {
4532
4536
  return;
4533
4537
  }
4534
4538
  props.onChange({
4535
- value: [...values, value],
4536
- field
4539
+ value: [...values, value]
4537
4540
  });
4538
4541
  };
4539
4542
  const deselectValue = value => {
4540
4543
  const newValues = values.filter(v => !isEqual(v, value));
4541
4544
  props.onChange({
4542
- value: newValues,
4543
- field
4545
+ value: newValues
4544
4546
  });
4545
4547
  };
4546
4548
  const onInputChange = ({
@@ -5029,7 +5031,6 @@ function Textfield(props) {
5029
5031
  target
5030
5032
  }) => {
5031
5033
  props.onChange({
5032
- field,
5033
5034
  value: target.value
5034
5035
  });
5035
5036
  });
@@ -5129,7 +5130,6 @@ function Textarea(props) {
5129
5130
  target
5130
5131
  }) => {
5131
5132
  props.onChange({
5132
- field,
5133
5133
  value: target.value
5134
5134
  });
5135
5135
  });
@@ -6598,6 +6598,9 @@ var commandModule = {
6598
6598
  commandStack: ['type', CommandStack]
6599
6599
  };
6600
6600
 
6601
+ /**
6602
+ * @deprecated
6603
+ */
6601
6604
  class UpdateFieldValidationHandler {
6602
6605
  constructor(form, validator) {
6603
6606
  this._form = form;
@@ -6627,6 +6630,38 @@ class UpdateFieldValidationHandler {
6627
6630
  }
6628
6631
  UpdateFieldValidationHandler.$inject = ['form', 'validator'];
6629
6632
 
6633
+ class UpdateFieldInstanceValidationHandler {
6634
+ constructor(form, validator) {
6635
+ this._form = form;
6636
+ this._validator = validator;
6637
+ }
6638
+ execute(context) {
6639
+ const {
6640
+ fieldInstance,
6641
+ value
6642
+ } = context;
6643
+ const {
6644
+ id,
6645
+ indexes
6646
+ } = fieldInstance;
6647
+ const {
6648
+ errors
6649
+ } = this._form._getState();
6650
+ context.oldErrors = clone(errors);
6651
+ const fieldErrors = this._validator.validateFieldInstance(fieldInstance, value);
6652
+ const updatedErrors = set(errors, [id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
6653
+ this._form._setState({
6654
+ errors: updatedErrors
6655
+ });
6656
+ }
6657
+ revert(context) {
6658
+ this._form._setState({
6659
+ errors: context.oldErrors
6660
+ });
6661
+ }
6662
+ }
6663
+ UpdateFieldInstanceValidationHandler.$inject = ['form', 'validator'];
6664
+
6630
6665
  class ViewerCommands {
6631
6666
  constructor(commandStack, eventBus) {
6632
6667
  this._commandStack = commandStack;
@@ -6641,9 +6676,14 @@ class ViewerCommands {
6641
6676
  }
6642
6677
  getHandlers() {
6643
6678
  return {
6644
- 'formField.validation.update': UpdateFieldValidationHandler
6679
+ 'formField.validation.update': UpdateFieldValidationHandler,
6680
+ 'formFieldInstance.validation.update': UpdateFieldInstanceValidationHandler
6645
6681
  };
6646
6682
  }
6683
+
6684
+ /**
6685
+ * @deprecated
6686
+ */
6647
6687
  updateFieldValidation(field, value, indexes) {
6648
6688
  const context = {
6649
6689
  field,
@@ -6652,6 +6692,13 @@ class ViewerCommands {
6652
6692
  };
6653
6693
  this._commandStack.execute('formField.validation.update', context);
6654
6694
  }
6695
+ updateFieldInstanceValidation(fieldInstance, value) {
6696
+ const context = {
6697
+ fieldInstance,
6698
+ value
6699
+ };
6700
+ this._commandStack.execute('formFieldInstance.validation.update', context);
6701
+ }
6655
6702
  }
6656
6703
  ViewerCommands.$inject = ['commandStack', 'eventBus'];
6657
6704
 
@@ -6836,9 +6883,7 @@ class RepeatRenderManager {
6836
6883
  updatedValues.push(newItem);
6837
6884
  shouldScroll.current = true;
6838
6885
  props.onChange({
6839
- field: repeaterField,
6840
- value: updatedValues,
6841
- indexes
6886
+ value: updatedValues
6842
6887
  });
6843
6888
  setSharedRepeatState(state => ({
6844
6889
  ...state,
@@ -7497,11 +7542,18 @@ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-
7497
7542
  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}$/;
7498
7543
  const VALIDATE_FEEL_PROPERTIES = ['min', 'max', 'minLength', 'maxLength'];
7499
7544
  class Validator {
7500
- constructor(expressionLanguage, conditionChecker, form) {
7545
+ constructor(expressionLanguage, conditionChecker, form, formFieldRegistry) {
7501
7546
  this._expressionLanguage = expressionLanguage;
7502
7547
  this._conditionChecker = conditionChecker;
7503
7548
  this._form = form;
7549
+ this._formFieldRegistry = formFieldRegistry;
7504
7550
  }
7551
+
7552
+ /**
7553
+ * Validate against a field definition, does not support proper expression evaluation.
7554
+ *
7555
+ * @deprecated use validateFieldInstance instead
7556
+ */
7505
7557
  validateField(field, value) {
7506
7558
  const {
7507
7559
  type,
@@ -7509,72 +7561,121 @@ class Validator {
7509
7561
  } = field;
7510
7562
  let errors = [];
7511
7563
  if (type === 'number') {
7512
- const {
7513
- decimalDigits,
7514
- increment
7515
- } = field;
7516
- if (value === 'NaN') {
7517
- errors = [...errors, 'Value is not a number.'];
7518
- } else if (value) {
7519
- if (decimalDigits >= 0 && countDecimals(value) > decimalDigits) {
7520
- errors = [...errors, 'Value is expected to ' + (decimalDigits === 0 ? 'be an integer' : `have at most ${decimalDigits} decimal digit${decimalDigits > 1 ? 's' : ''}`) + '.'];
7521
- }
7522
- if (increment) {
7523
- const bigValue = Big(value);
7524
- const bigIncrement = Big(increment);
7525
- const offset = bigValue.mod(bigIncrement);
7526
- if (offset.cmp(0) !== 0) {
7527
- const previousValue = bigValue.minus(offset);
7528
- const nextValue = previousValue.plus(bigIncrement);
7529
- errors = [...errors, `Please select a valid value, the two nearest valid values are ${previousValue} and ${nextValue}.`];
7530
- }
7531
- }
7532
- }
7564
+ errors = [...errors, ...runNumberValidation(field, value)];
7533
7565
  }
7534
7566
  if (!validate) {
7535
7567
  return errors;
7536
7568
  }
7537
- const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
7538
- if (evaluatedValidation.pattern && value && !new RegExp(evaluatedValidation.pattern).test(value)) {
7539
- errors = [...errors, `Field must match pattern ${evaluatedValidation.pattern}.`];
7540
- }
7541
- if (evaluatedValidation.required) {
7542
- const isUncheckedCheckbox = type === 'checkbox' && value === false;
7543
- const isUnsetValue = isNil(value) || value === '';
7544
- const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
7545
- if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
7546
- errors = [...errors, 'Field is required.'];
7547
- }
7548
- }
7549
- if ('min' in evaluatedValidation && (value || value === 0) && value < evaluatedValidation.min) {
7550
- errors = [...errors, `Field must have minimum value of ${evaluatedValidation.min}.`];
7551
- }
7552
- if ('max' in evaluatedValidation && (value || value === 0) && value > evaluatedValidation.max) {
7553
- errors = [...errors, `Field must have maximum value of ${evaluatedValidation.max}.`];
7554
- }
7555
- if ('minLength' in evaluatedValidation && value && value.trim().length < evaluatedValidation.minLength) {
7556
- errors = [...errors, `Field must have minimum length of ${evaluatedValidation.minLength}.`];
7557
- }
7558
- if ('maxLength' in evaluatedValidation && value && value.trim().length > evaluatedValidation.maxLength) {
7559
- errors = [...errors, `Field must have maximum length of ${evaluatedValidation.maxLength}.`];
7560
- }
7561
- if ('validationType' in evaluatedValidation && value && evaluatedValidation.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
7562
- errors = [...errors, 'Field must be a valid international phone number. (e.g. +4930664040900)'];
7569
+ const evaluatedValidation = oldEvaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
7570
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
7571
+ return errors;
7572
+ }
7573
+
7574
+ /**
7575
+ * Validate a field instance.
7576
+ *
7577
+ * @param {Object} fieldInstance
7578
+ * @param {string} value
7579
+ *
7580
+ * @returns {Array<string>}
7581
+ */
7582
+ validateFieldInstance(fieldInstance, value) {
7583
+ const {
7584
+ id,
7585
+ expressionContextInfo
7586
+ } = fieldInstance;
7587
+ const field = this._formFieldRegistry.get(id);
7588
+ const {
7589
+ type,
7590
+ validate
7591
+ } = field;
7592
+ let errors = [];
7593
+ if (type === 'number') {
7594
+ errors = [...errors, ...runNumberValidation(field, value)];
7563
7595
  }
7564
- if ('validationType' in evaluatedValidation && value && evaluatedValidation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
7565
- errors = [...errors, 'Field must be a valid email.'];
7596
+ if (!validate) {
7597
+ return errors;
7566
7598
  }
7599
+ const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, expressionContextInfo);
7600
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
7567
7601
  return errors;
7568
7602
  }
7569
7603
  }
7570
- Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form'];
7604
+ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry'];
7571
7605
 
7572
7606
  // helpers //////////
7573
7607
 
7574
- /**
7575
- * Helper function to evaluate optional FEEL validation values.
7576
- */
7577
- function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
7608
+ function runNumberValidation(field, value) {
7609
+ const {
7610
+ decimalDigits,
7611
+ increment
7612
+ } = field;
7613
+ const errors = [];
7614
+ if (value === 'NaN') {
7615
+ errors.push('Value is not a number.');
7616
+ } else if (value) {
7617
+ if (decimalDigits >= 0 && countDecimals(value) > decimalDigits) {
7618
+ errors.push('Value is expected to ' + (decimalDigits === 0 ? 'be an integer' : `have at most ${decimalDigits} decimal digit${decimalDigits > 1 ? 's' : ''}`) + '.');
7619
+ }
7620
+ if (increment) {
7621
+ const bigValue = Big(value);
7622
+ const bigIncrement = Big(increment);
7623
+ const offset = bigValue.mod(bigIncrement);
7624
+ if (offset.cmp(0) !== 0) {
7625
+ const previousValue = bigValue.minus(offset);
7626
+ const nextValue = previousValue.plus(bigIncrement);
7627
+ errors.push(`Please select a valid value, the two nearest valid values are ${previousValue} and ${nextValue}.`);
7628
+ }
7629
+ }
7630
+ }
7631
+ return errors;
7632
+ }
7633
+ function runPresetValidation(field, validation, value) {
7634
+ const errors = [];
7635
+ if (validation.pattern && value && !new RegExp(validation.pattern).test(value)) {
7636
+ errors.push(`Field must match pattern ${validation.pattern}.`);
7637
+ }
7638
+ if (validation.required) {
7639
+ const isUncheckedCheckbox = field.type === 'checkbox' && value === false;
7640
+ const isUnsetValue = isNil(value) || value === '';
7641
+ const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
7642
+ if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
7643
+ errors.push('Field is required.');
7644
+ }
7645
+ }
7646
+ if ('min' in validation && (value || value === 0) && value < validation.min) {
7647
+ errors.push(`Field must have minimum value of ${validation.min}.`);
7648
+ }
7649
+ if ('max' in validation && (value || value === 0) && value > validation.max) {
7650
+ errors.push(`Field must have maximum value of ${validation.max}.`);
7651
+ }
7652
+ if ('minLength' in validation && value && value.trim().length < validation.minLength) {
7653
+ errors.push(`Field must have minimum length of ${validation.minLength}.`);
7654
+ }
7655
+ if ('maxLength' in validation && value && value.trim().length > validation.maxLength) {
7656
+ errors.push(`Field must have maximum length of ${validation.maxLength}.`);
7657
+ }
7658
+ if ('validationType' in validation && value && validation.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
7659
+ errors.push('Field must be a valid international phone number. (e.g. +4930664040900)');
7660
+ }
7661
+ if ('validationType' in validation && value && validation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
7662
+ errors.push('Field must be a valid email.');
7663
+ }
7664
+ return errors;
7665
+ }
7666
+ function evaluateFEELValues(validate, expressionLanguage, expressionContextInfo) {
7667
+ const evaluatedValidate = {
7668
+ ...validate
7669
+ };
7670
+ VALIDATE_FEEL_PROPERTIES.forEach(property => {
7671
+ const path = property.split('.');
7672
+ const value = get(evaluatedValidate, path);
7673
+ const evaluatedValue = runExpressionEvaluation(expressionLanguage, value, expressionContextInfo);
7674
+ set(evaluatedValidate, path, evaluatedValue === null ? undefined : evaluatedValue);
7675
+ });
7676
+ return evaluatedValidate;
7677
+ }
7678
+ function oldEvaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
7578
7679
  const evaluatedValidate = {
7579
7680
  ...validate
7580
7681
  };
@@ -8319,9 +8420,13 @@ class FormFieldInstanceRegistry {
8319
8420
  return this.getAll().filter(({
8320
8421
  id
8321
8422
  }) => {
8423
+ const formFieldDefinition = this._formFieldRegistry.get(id);
8424
+ if (!formFieldDefinition) {
8425
+ return false;
8426
+ }
8322
8427
  const {
8323
8428
  type
8324
- } = this._formFieldRegistry.get(id);
8429
+ } = formFieldDefinition;
8325
8430
  const {
8326
8431
  config
8327
8432
  } = this._formFields.get(type);
@@ -8581,11 +8686,12 @@ class Form {
8581
8686
  } = this._getState();
8582
8687
  const errors = {};
8583
8688
  const getErrorPath = (id, indexes) => [id, ...Object.values(indexes || {})];
8584
- formFieldInstanceRegistry.getAllKeyed().forEach(({
8585
- id,
8586
- valuePath,
8587
- indexes
8588
- }) => {
8689
+ formFieldInstanceRegistry.getAllKeyed().forEach(fieldInstance => {
8690
+ const {
8691
+ id,
8692
+ valuePath,
8693
+ indexes
8694
+ } = fieldInstance;
8589
8695
  const field = formFieldRegistry.get(id);
8590
8696
 
8591
8697
  // (1) Skip disabled fields
@@ -8593,9 +8699,9 @@ class Form {
8593
8699
  return;
8594
8700
  }
8595
8701
 
8596
- // (2) Validate the field
8702
+ // (2) Validate the field instance
8597
8703
  const value = get(data, valuePath);
8598
- const fieldErrors = validator.validateField(field, value);
8704
+ const fieldErrors = validator.validateFieldInstance(fieldInstance, value);
8599
8705
  if (fieldErrors.length) {
8600
8706
  set(errors, getErrorPath(field.id, indexes), fieldErrors);
8601
8707
  }
@@ -8700,26 +8806,26 @@ class Form {
8700
8806
  /**
8701
8807
  * @internal
8702
8808
  *
8703
- * @param { { field: any, indexes: object, value: any } } update
8809
+ * @param { { fieldInstance: any, value: any } } update
8704
8810
  */
8705
8811
  _update(update) {
8706
8812
  const {
8707
- field,
8708
- indexes,
8813
+ fieldInstance,
8709
8814
  value
8710
8815
  } = update;
8816
+ const {
8817
+ id,
8818
+ valuePath,
8819
+ indexes
8820
+ } = fieldInstance;
8711
8821
  const {
8712
8822
  data,
8713
8823
  errors
8714
8824
  } = this._getState();
8715
- const validator = this.get('validator'),
8716
- pathRegistry = this.get('pathRegistry');
8717
- const fieldErrors = validator.validateField(field, value);
8718
- const valuePath = pathRegistry.getValuePath(field, {
8719
- indexes
8720
- });
8825
+ const validator = this.get('validator');
8826
+ const fieldErrors = validator.validateFieldInstance(fieldInstance, value);
8721
8827
  set(data, valuePath, value);
8722
- set(errors, [field.id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
8828
+ set(errors, [id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
8723
8829
  this._emit('field.updated', update);
8724
8830
  this._setState({
8725
8831
  data: clone(data),
@@ -8899,5 +9005,5 @@ function createForm(options) {
8899
9005
  });
8900
9006
  }
8901
9007
 
8902
- export { ALLOW_ATTRIBUTE, Button, Checkbox, Checklist, ConditionChecker, DATETIME_SUBTYPES, DATETIME_SUBTYPES_LABELS, DATETIME_SUBTYPE_PATH, DATE_DISALLOW_PAST_PATH, DATE_LABEL_PATH, Datetime, Default, Description, DynamicList, Errors, ExpressionField, ExpressionFieldModule, ExpressionLanguageModule, ExpressionLoopPreventer, FeelExpressionLanguage, FeelersTemplating, FieldFactory, Form, FormComponent, FormContext, FormField, FormFieldRegistry, FormFields, FormLayouter, FormRenderContext, Group, Html, IFrame, Image, Importer, Label, LocalExpressionContext, MINUTES_IN_DAY, MarkdownRenderer, MarkdownRendererModule, Numberfield, OPTIONS_SOURCES, OPTIONS_SOURCES_DEFAULTS, OPTIONS_SOURCES_LABELS, OPTIONS_SOURCES_PATHS, OPTIONS_SOURCE_DEFAULT, PathRegistry, Radio, RenderModule, RepeatRenderManager, RepeatRenderModule, SANDBOX_ATTRIBUTE, SECURITY_ATTRIBUTES_DEFINITIONS, Select, Separator, Spacer, TIME_INTERVAL_PATH, TIME_LABEL_PATH, TIME_SERIALISINGFORMAT_LABELS, TIME_SERIALISING_FORMATS, TIME_SERIALISING_FORMAT_PATH, TIME_USE24H_PATH, Table, Taglist, Text, Textarea, Textfield, ViewerCommands, ViewerCommandsModule, buildExpressionContext, clone, createForm, createFormContainer, createInjector, escapeHTML, formFields, generateIdForType, generateIndexForType, getAncestryList, getOptionsSource, getSchemaVariables, getScrollContainer, hasEqualValue, iconsByType, isRequired, pathParse, pathsEqual, runRecursively, sanitizeDateTimePickerValue, sanitizeHTML, sanitizeIFrameSource, sanitizeImageSource, sanitizeMultiSelectValue, sanitizeSingleSelectValue, schemaVersion, useExpressionEvaluation, useSingleLineTemplateEvaluation, useTemplateEvaluation, wrapCSSStyles };
9008
+ export { ALLOW_ATTRIBUTE, Button, Checkbox, Checklist, ConditionChecker, DATETIME_SUBTYPES, DATETIME_SUBTYPES_LABELS, DATETIME_SUBTYPE_PATH, DATE_DISALLOW_PAST_PATH, DATE_LABEL_PATH, Datetime, Default, Description, DynamicList, Errors, ExpressionField, ExpressionFieldModule, ExpressionLanguageModule, ExpressionLoopPreventer, FeelExpressionLanguage, FeelersTemplating, FieldFactory, Form, FormComponent, FormContext, FormField, FormFieldRegistry, FormFields, FormLayouter, FormRenderContext, Group, Html, IFrame, Image, Importer, Label, LocalExpressionContext, MINUTES_IN_DAY, MarkdownRenderer, MarkdownRendererModule, Numberfield, OPTIONS_SOURCES, OPTIONS_SOURCES_DEFAULTS, OPTIONS_SOURCES_LABELS, OPTIONS_SOURCES_PATHS, OPTIONS_SOURCE_DEFAULT, PathRegistry, Radio, RenderModule, RepeatRenderManager, RepeatRenderModule, SANDBOX_ATTRIBUTE, SECURITY_ATTRIBUTES_DEFINITIONS, Select, Separator, Spacer, TIME_INTERVAL_PATH, TIME_LABEL_PATH, TIME_SERIALISINGFORMAT_LABELS, TIME_SERIALISING_FORMATS, TIME_SERIALISING_FORMAT_PATH, TIME_USE24H_PATH, Table, Taglist, Text, Textarea, Textfield, ViewerCommands, ViewerCommandsModule, buildExpressionContext, clone, createForm, createFormContainer, createInjector, escapeHTML, formFields, generateIdForType, generateIndexForType, getAncestryList, getOptionsSource, getSchemaVariables, getScrollContainer, hasEqualValue, iconsByType, isRequired, pathParse, pathsEqual, runExpressionEvaluation, runRecursively, sanitizeDateTimePickerValue, sanitizeHTML, sanitizeIFrameSource, sanitizeImageSource, sanitizeMultiSelectValue, sanitizeSingleSelectValue, schemaVersion, useExpressionEvaluation, useSingleLineTemplateEvaluation, useTemplateEvaluation, wrapCSSStyles, wrapObjectKeysWithUnderscores };
8903
9009
  //# sourceMappingURL=index.es.js.map