@bpmn-io/form-js-viewer 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -3,7 +3,7 @@ import { isString, get, isNil, isObject, some, isNumber, set, findIndex, isArray
3
3
  import Big from 'big.js';
4
4
  import classNames from 'classnames';
5
5
  import { jsx, jsxs, Fragment } from 'preact/jsx-runtime';
6
- import { useContext, useMemo, useRef, useEffect, useState, useCallback, useLayoutEffect } from 'preact/hooks';
6
+ import { useContext, useMemo, useRef, useCallback, useEffect, useState, useLayoutEffect } from 'preact/hooks';
7
7
  import { createContext, createElement, Fragment as Fragment$1, render } from 'preact';
8
8
  import isEqual from 'lodash/isEqual';
9
9
  import flatpickr from 'flatpickr';
@@ -772,20 +772,59 @@ function useCondition(condition) {
772
772
  }, [conditionChecker, condition, expressionContextInfo]);
773
773
  }
774
774
 
775
- // parses the options data from the provided form field and form data
776
- function getOptionsData(formField, formData) {
775
+ /**
776
+ * Returns the options data for the provided if they can be simply determined, ignoring expression defined options.
777
+ *
778
+ * @param {object} formField
779
+ * @param {object} formData
780
+ */
781
+ function getSimpleOptionsData(formField, formData) {
777
782
  const {
783
+ valuesExpression: optionsExpression,
778
784
  valuesKey: optionsKey,
779
785
  values: staticOptions
780
786
  } = formField;
787
+ if (optionsExpression) {
788
+ return null;
789
+ }
781
790
  return optionsKey ? get(formData, [optionsKey]) : staticOptions;
782
791
  }
783
792
 
784
- // transforms the provided options into a normalized format, trimming invalid options
793
+ /**
794
+ * Normalizes the provided options data to a format that can be used by the select components.
795
+ * If the options data is not valid, it is filtered out.
796
+ *
797
+ * @param {any[]} optionsData
798
+ *
799
+ * @returns {object[]}
800
+ */
785
801
  function normalizeOptionsData(optionsData) {
786
802
  return optionsData.filter(_isAllowedValue).map(_normalizeOption).filter(o => !isNil(o));
787
803
  }
788
804
 
805
+ /**
806
+ * Creates an options object with default values if no options are provided.
807
+ *
808
+ * @param {object} options
809
+ *
810
+ * @returns {object}
811
+ */
812
+ function createEmptyOptions(options = {}) {
813
+ const defaults = {};
814
+
815
+ // provide default options if valuesKey and valuesExpression are not set
816
+ if (!options.valuesKey && !options.valuesExpression) {
817
+ defaults.values = [{
818
+ label: 'Value',
819
+ value: 'value'
820
+ }];
821
+ }
822
+ return {
823
+ ...defaults,
824
+ ...options
825
+ };
826
+ }
827
+
789
828
  /**
790
829
  * Converts the provided option to a normalized format.
791
830
  * If the option is not valid, null is returned.
@@ -836,21 +875,6 @@ function _isAllowedValue(value) {
836
875
  }
837
876
  return _isAllowedPrimitive(value);
838
877
  }
839
- function createEmptyOptions(options = {}) {
840
- const defaults = {};
841
-
842
- // provide default options if valuesKey and valuesExpression are not set
843
- if (!options.valuesKey && !options.valuesExpression) {
844
- defaults.values = [{
845
- label: 'Value',
846
- value: 'value'
847
- }];
848
- }
849
- return {
850
- ...defaults,
851
- ...options
852
- };
853
- }
854
878
 
855
879
  /**
856
880
  * Evaluate a string reactively based on the expressionLanguage and form data.
@@ -871,29 +895,19 @@ function useExpressionEvaluation(value) {
871
895
  }, [expressionLanguage, expressionContextInfo, value]);
872
896
  }
873
897
 
874
- function usePrevious(value, defaultValue = null) {
875
- const ref = useRef(defaultValue);
876
- useEffect(() => ref.current = value, [value]);
877
- return ref.current;
878
- }
879
-
880
898
  /**
881
899
  * A custom hook to manage state changes with deep comparison.
882
900
  *
883
- * @param {any} value - The current value to manage.
884
- * @param {any} defaultValue - The initial default value for the state.
885
- * @returns {any} - Returns the current state.
901
+ * @template T
902
+ * @param {T} value - The current value to manage.
903
+ * @returns {T} - Returns the current state.
886
904
  */
887
- function useDeepCompareState(value, defaultValue) {
888
- const [state, setState] = useState(defaultValue);
889
- const previous = usePrevious(value, defaultValue);
890
- const changed = !isEqual(previous, value);
891
- useEffect(() => {
892
- if (changed) {
893
- setState(value);
894
- }
895
- }, [changed, value]);
896
- return state;
905
+ function useDeepCompareMemoize(value) {
906
+ const ref = useRef();
907
+ if (!isEqual(value, ref.current)) {
908
+ ref.current = value;
909
+ }
910
+ return ref.current;
897
911
  }
898
912
 
899
913
  /**
@@ -923,15 +937,10 @@ function useOptionsAsync(field) {
923
937
  valuesKey: optionsKey,
924
938
  values: staticOptions
925
939
  } = field;
926
- const [optionsGetter, setOptionsGetter] = useState({
927
- options: [],
928
- error: undefined,
929
- loadState: LOAD_STATES.LOADING
930
- });
931
940
  const initialData = useService('form')._getState().initialData;
932
941
  const expressionEvaluation = useExpressionEvaluation(optionsExpression);
933
- const evaluatedOptions = useDeepCompareState(expressionEvaluation || [], []);
934
- useEffect(() => {
942
+ const evaluatedOptions = useDeepCompareMemoize(expressionEvaluation || []);
943
+ const optionsGetter = useMemo(() => {
935
944
  let options = [];
936
945
 
937
946
  // dynamic options
@@ -946,18 +955,16 @@ function useOptionsAsync(field) {
946
955
  options = Array.isArray(staticOptions) ? staticOptions : [];
947
956
 
948
957
  // expression
949
- } else if (optionsExpression) {
950
- if (evaluatedOptions && Array.isArray(evaluatedOptions)) {
951
- options = evaluatedOptions;
952
- }
958
+ } else if (optionsExpression && evaluatedOptions && Array.isArray(evaluatedOptions)) {
959
+ options = evaluatedOptions;
960
+
961
+ // error case
953
962
  } else {
954
- setOptionsGetter(buildErrorState('No options source defined in the form definition'));
955
- return;
963
+ return buildErrorState('No options source defined in the form definition');
956
964
  }
957
965
 
958
966
  // normalize data to support primitives and partially defined objects
959
- options = normalizeOptionsData(options);
960
- setOptionsGetter(buildLoadedState(options));
967
+ return buildLoadedState(normalizeOptionsData(options));
961
968
  }, [optionsKey, staticOptions, initialData, optionsExpression, evaluatedOptions]);
962
969
  return optionsGetter;
963
970
  }
@@ -1002,14 +1009,14 @@ const getDOMPurifyConfig = sanitizeStyleTags => {
1002
1009
  };
1003
1010
  };
1004
1011
 
1005
- /**
1006
- * A custom hook to build up security attributes from form configuration.
1007
- *
1008
- * @param {Object} security - The security configuration.
1009
- * @returns {Array} - Returns a tuple with sandbox and allow attributes.
1012
+ /**
1013
+ * A custom hook to build up security attributes from form configuration.
1014
+ *
1015
+ * @param {Object} security - The security configuration.
1016
+ * @returns {Array} - Returns a tuple with sandbox and allow attributes.
1010
1017
  */
1011
1018
  function useSecurityAttributesMap(security) {
1012
- const securityMemoized = useDeepCompareState(security);
1019
+ const securityMemoized = useDeepCompareMemoize(security);
1013
1020
  const sandbox = useMemo(() => SECURITY_ATTRIBUTES_DEFINITIONS.filter(({
1014
1021
  attribute
1015
1022
  }) => attribute === SANDBOX_ATTRIBUTE).filter(({
@@ -1181,6 +1188,12 @@ function useReadonly(formField, properties = {}) {
1181
1188
  return readonly || false;
1182
1189
  }
1183
1190
 
1191
+ function usePrevious(value, defaultValue = null) {
1192
+ const ref = useRef(defaultValue);
1193
+ useEffect(() => ref.current = value, [value]);
1194
+ return ref.current;
1195
+ }
1196
+
1184
1197
  function useFlushDebounce(func) {
1185
1198
  const timeoutRef = useRef(null);
1186
1199
  const lastArgsRef = useRef(null);
@@ -1445,8 +1458,16 @@ function sanitizeSingleSelectValue(options) {
1445
1458
  data,
1446
1459
  value
1447
1460
  } = options;
1461
+ const {
1462
+ valuesExpression: optionsExpression
1463
+ } = formField;
1448
1464
  try {
1449
- const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1465
+ // if options are expression evaluated, we don't need to sanitize the value against the options
1466
+ // and defer to the field's internal validation
1467
+ if (optionsExpression) {
1468
+ return value;
1469
+ }
1470
+ const validValues = normalizeOptionsData(getSimpleOptionsData(formField, data)).map(v => v.value);
1450
1471
  return hasEqualValue(value, validValues) ? value : null;
1451
1472
  } catch (error) {
1452
1473
  // use default value in case of formatting error
@@ -1460,8 +1481,16 @@ function sanitizeMultiSelectValue(options) {
1460
1481
  data,
1461
1482
  value
1462
1483
  } = options;
1484
+ const {
1485
+ valuesExpression: optionsExpression
1486
+ } = formField;
1463
1487
  try {
1464
- const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1488
+ // if options are expression evaluated, we don't need to sanitize the values against the options
1489
+ // and defer to the field's internal validation
1490
+ if (optionsExpression) {
1491
+ return value;
1492
+ }
1493
+ const validValues = normalizeOptionsData(getSimpleOptionsData(formField, data)).map(v => v.value);
1465
1494
  return value.filter(v => hasEqualValue(v, validValues));
1466
1495
  } catch (error) {
1467
1496
  // use default value in case of formatting error
@@ -1549,7 +1578,7 @@ function useCleanupMultiSelectValue(props) {
1549
1578
  onChange,
1550
1579
  values
1551
1580
  } = props;
1552
- const memoizedValues = useDeepCompareState(values, []);
1581
+ const memoizedValues = useDeepCompareMemoize(values || []);
1553
1582
 
1554
1583
  // ensures that the values are always a subset of the possible options
1555
1584
  useEffect(() => {
@@ -2237,7 +2266,7 @@ function Datepicker(props) {
2237
2266
  const [forceFocusCalendar, setForceFocusCalendar] = useState(false);
2238
2267
 
2239
2268
  // ensures we render based on date value instead of reference
2240
- const date = useDeepCompareState(dateObject, null);
2269
+ const date = useDeepCompareMemoize(dateObject);
2241
2270
 
2242
2271
  // shorts the date value back to the source
2243
2272
  useEffect(() => {
@@ -3673,8 +3702,8 @@ function Numberfield(props) {
3673
3702
  'fjs-readonly': readonly
3674
3703
  }),
3675
3704
  children: [jsx("button", {
3676
- class: "fjs-number-arrow-up",
3677
3705
  type: "button",
3706
+ class: "fjs-number-arrow-up",
3678
3707
  "aria-label": "Increment",
3679
3708
  onClick: () => increment(),
3680
3709
  tabIndex: -1,
@@ -3682,8 +3711,8 @@ function Numberfield(props) {
3682
3711
  }), jsx("div", {
3683
3712
  class: "fjs-number-arrow-separator"
3684
3713
  }), jsx("button", {
3685
- class: "fjs-number-arrow-down",
3686
3714
  type: "button",
3715
+ class: "fjs-number-arrow-down",
3687
3716
  "aria-label": "Decrement",
3688
3717
  onClick: () => decrement(),
3689
3718
  tabIndex: -1,
@@ -4354,7 +4383,7 @@ function Taglist(props) {
4354
4383
  } = useOptionsAsync(field);
4355
4384
 
4356
4385
  // ensures we render based on array content instead of reference
4357
- const values = useDeepCompareState(value || [], []);
4386
+ const values = useDeepCompareMemoize(value || []);
4358
4387
  useCleanupMultiSelectValue({
4359
4388
  field,
4360
4389
  loadState,
@@ -6668,18 +6697,18 @@ class RepeatRenderManager {
6668
6697
  'fjs-remove-allowed': repeaterField.allowAddRemove
6669
6698
  }),
6670
6699
  children: [showAdd ? jsx("button", {
6700
+ type: "button",
6671
6701
  readOnly: readonly,
6672
6702
  disabled: disabled || readonly,
6673
6703
  class: "fjs-repeat-render-add",
6674
- type: "button",
6675
6704
  ref: addButtonRef,
6676
6705
  onClick: onAddItem,
6677
6706
  children: jsxs(Fragment, {
6678
6707
  children: [jsx(AddSvg, {}), " ", 'Add new']
6679
6708
  })
6680
6709
  }) : null, collapseEnabled ? jsx("button", {
6681
- class: "fjs-repeat-render-collapse",
6682
6710
  type: "button",
6711
+ class: "fjs-repeat-render-collapse",
6683
6712
  onClick: toggle,
6684
6713
  children: isCollapsed ? jsxs(Fragment, {
6685
6714
  children: [jsx(ExpandSvg, {}), " ", `Expand all (${values.length})`]
@@ -6753,8 +6782,8 @@ const RepetitionScaffold = props => {
6753
6782
  })
6754
6783
  })
6755
6784
  }), jsx("button", {
6756
- class: "fjs-repeat-row-remove",
6757
6785
  type: "button",
6786
+ class: "fjs-repeat-row-remove",
6758
6787
  "aria-label": `Remove list item ${index + 1}`,
6759
6788
  onClick: () => onDeleteItem(index),
6760
6789
  children: jsx("div", {