@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.cjs CHANGED
@@ -792,20 +792,59 @@ function useCondition(condition) {
792
792
  }, [conditionChecker, condition, expressionContextInfo]);
793
793
  }
794
794
 
795
- // parses the options data from the provided form field and form data
796
- function getOptionsData(formField, formData) {
795
+ /**
796
+ * Returns the options data for the provided if they can be simply determined, ignoring expression defined options.
797
+ *
798
+ * @param {object} formField
799
+ * @param {object} formData
800
+ */
801
+ function getSimpleOptionsData(formField, formData) {
797
802
  const {
803
+ valuesExpression: optionsExpression,
798
804
  valuesKey: optionsKey,
799
805
  values: staticOptions
800
806
  } = formField;
807
+ if (optionsExpression) {
808
+ return null;
809
+ }
801
810
  return optionsKey ? minDash.get(formData, [optionsKey]) : staticOptions;
802
811
  }
803
812
 
804
- // transforms the provided options into a normalized format, trimming invalid options
813
+ /**
814
+ * Normalizes the provided options data to a format that can be used by the select components.
815
+ * If the options data is not valid, it is filtered out.
816
+ *
817
+ * @param {any[]} optionsData
818
+ *
819
+ * @returns {object[]}
820
+ */
805
821
  function normalizeOptionsData(optionsData) {
806
822
  return optionsData.filter(_isAllowedValue).map(_normalizeOption).filter(o => !minDash.isNil(o));
807
823
  }
808
824
 
825
+ /**
826
+ * Creates an options object with default values if no options are provided.
827
+ *
828
+ * @param {object} options
829
+ *
830
+ * @returns {object}
831
+ */
832
+ function createEmptyOptions(options = {}) {
833
+ const defaults = {};
834
+
835
+ // provide default options if valuesKey and valuesExpression are not set
836
+ if (!options.valuesKey && !options.valuesExpression) {
837
+ defaults.values = [{
838
+ label: 'Value',
839
+ value: 'value'
840
+ }];
841
+ }
842
+ return {
843
+ ...defaults,
844
+ ...options
845
+ };
846
+ }
847
+
809
848
  /**
810
849
  * Converts the provided option to a normalized format.
811
850
  * If the option is not valid, null is returned.
@@ -856,21 +895,6 @@ function _isAllowedValue(value) {
856
895
  }
857
896
  return _isAllowedPrimitive(value);
858
897
  }
859
- function createEmptyOptions(options = {}) {
860
- const defaults = {};
861
-
862
- // provide default options if valuesKey and valuesExpression are not set
863
- if (!options.valuesKey && !options.valuesExpression) {
864
- defaults.values = [{
865
- label: 'Value',
866
- value: 'value'
867
- }];
868
- }
869
- return {
870
- ...defaults,
871
- ...options
872
- };
873
- }
874
898
 
875
899
  /**
876
900
  * Evaluate a string reactively based on the expressionLanguage and form data.
@@ -891,29 +915,19 @@ function useExpressionEvaluation(value) {
891
915
  }, [expressionLanguage, expressionContextInfo, value]);
892
916
  }
893
917
 
894
- function usePrevious(value, defaultValue = null) {
895
- const ref = hooks.useRef(defaultValue);
896
- hooks.useEffect(() => ref.current = value, [value]);
897
- return ref.current;
898
- }
899
-
900
918
  /**
901
919
  * A custom hook to manage state changes with deep comparison.
902
920
  *
903
- * @param {any} value - The current value to manage.
904
- * @param {any} defaultValue - The initial default value for the state.
905
- * @returns {any} - Returns the current state.
921
+ * @template T
922
+ * @param {T} value - The current value to manage.
923
+ * @returns {T} - Returns the current state.
906
924
  */
907
- function useDeepCompareState(value, defaultValue) {
908
- const [state, setState] = hooks.useState(defaultValue);
909
- const previous = usePrevious(value, defaultValue);
910
- const changed = !isEqual(previous, value);
911
- hooks.useEffect(() => {
912
- if (changed) {
913
- setState(value);
914
- }
915
- }, [changed, value]);
916
- return state;
925
+ function useDeepCompareMemoize(value) {
926
+ const ref = hooks.useRef();
927
+ if (!isEqual(value, ref.current)) {
928
+ ref.current = value;
929
+ }
930
+ return ref.current;
917
931
  }
918
932
 
919
933
  /**
@@ -943,15 +957,10 @@ function useOptionsAsync(field) {
943
957
  valuesKey: optionsKey,
944
958
  values: staticOptions
945
959
  } = field;
946
- const [optionsGetter, setOptionsGetter] = hooks.useState({
947
- options: [],
948
- error: undefined,
949
- loadState: LOAD_STATES.LOADING
950
- });
951
960
  const initialData = useService('form')._getState().initialData;
952
961
  const expressionEvaluation = useExpressionEvaluation(optionsExpression);
953
- const evaluatedOptions = useDeepCompareState(expressionEvaluation || [], []);
954
- hooks.useEffect(() => {
962
+ const evaluatedOptions = useDeepCompareMemoize(expressionEvaluation || []);
963
+ const optionsGetter = hooks.useMemo(() => {
955
964
  let options = [];
956
965
 
957
966
  // dynamic options
@@ -966,18 +975,16 @@ function useOptionsAsync(field) {
966
975
  options = Array.isArray(staticOptions) ? staticOptions : [];
967
976
 
968
977
  // expression
969
- } else if (optionsExpression) {
970
- if (evaluatedOptions && Array.isArray(evaluatedOptions)) {
971
- options = evaluatedOptions;
972
- }
978
+ } else if (optionsExpression && evaluatedOptions && Array.isArray(evaluatedOptions)) {
979
+ options = evaluatedOptions;
980
+
981
+ // error case
973
982
  } else {
974
- setOptionsGetter(buildErrorState('No options source defined in the form definition'));
975
- return;
983
+ return buildErrorState('No options source defined in the form definition');
976
984
  }
977
985
 
978
986
  // normalize data to support primitives and partially defined objects
979
- options = normalizeOptionsData(options);
980
- setOptionsGetter(buildLoadedState(options));
987
+ return buildLoadedState(normalizeOptionsData(options));
981
988
  }, [optionsKey, staticOptions, initialData, optionsExpression, evaluatedOptions]);
982
989
  return optionsGetter;
983
990
  }
@@ -1022,14 +1029,14 @@ const getDOMPurifyConfig = sanitizeStyleTags => {
1022
1029
  };
1023
1030
  };
1024
1031
 
1025
- /**
1026
- * A custom hook to build up security attributes from form configuration.
1027
- *
1028
- * @param {Object} security - The security configuration.
1029
- * @returns {Array} - Returns a tuple with sandbox and allow attributes.
1032
+ /**
1033
+ * A custom hook to build up security attributes from form configuration.
1034
+ *
1035
+ * @param {Object} security - The security configuration.
1036
+ * @returns {Array} - Returns a tuple with sandbox and allow attributes.
1030
1037
  */
1031
1038
  function useSecurityAttributesMap(security) {
1032
- const securityMemoized = useDeepCompareState(security);
1039
+ const securityMemoized = useDeepCompareMemoize(security);
1033
1040
  const sandbox = hooks.useMemo(() => SECURITY_ATTRIBUTES_DEFINITIONS.filter(({
1034
1041
  attribute
1035
1042
  }) => attribute === SANDBOX_ATTRIBUTE).filter(({
@@ -1201,6 +1208,12 @@ function useReadonly(formField, properties = {}) {
1201
1208
  return readonly || false;
1202
1209
  }
1203
1210
 
1211
+ function usePrevious(value, defaultValue = null) {
1212
+ const ref = hooks.useRef(defaultValue);
1213
+ hooks.useEffect(() => ref.current = value, [value]);
1214
+ return ref.current;
1215
+ }
1216
+
1204
1217
  function useFlushDebounce(func) {
1205
1218
  const timeoutRef = hooks.useRef(null);
1206
1219
  const lastArgsRef = hooks.useRef(null);
@@ -1465,8 +1478,16 @@ function sanitizeSingleSelectValue(options) {
1465
1478
  data,
1466
1479
  value
1467
1480
  } = options;
1481
+ const {
1482
+ valuesExpression: optionsExpression
1483
+ } = formField;
1468
1484
  try {
1469
- const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1485
+ // if options are expression evaluated, we don't need to sanitize the value against the options
1486
+ // and defer to the field's internal validation
1487
+ if (optionsExpression) {
1488
+ return value;
1489
+ }
1490
+ const validValues = normalizeOptionsData(getSimpleOptionsData(formField, data)).map(v => v.value);
1470
1491
  return hasEqualValue(value, validValues) ? value : null;
1471
1492
  } catch (error) {
1472
1493
  // use default value in case of formatting error
@@ -1480,8 +1501,16 @@ function sanitizeMultiSelectValue(options) {
1480
1501
  data,
1481
1502
  value
1482
1503
  } = options;
1504
+ const {
1505
+ valuesExpression: optionsExpression
1506
+ } = formField;
1483
1507
  try {
1484
- const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1508
+ // if options are expression evaluated, we don't need to sanitize the values against the options
1509
+ // and defer to the field's internal validation
1510
+ if (optionsExpression) {
1511
+ return value;
1512
+ }
1513
+ const validValues = normalizeOptionsData(getSimpleOptionsData(formField, data)).map(v => v.value);
1485
1514
  return value.filter(v => hasEqualValue(v, validValues));
1486
1515
  } catch (error) {
1487
1516
  // use default value in case of formatting error
@@ -1569,7 +1598,7 @@ function useCleanupMultiSelectValue(props) {
1569
1598
  onChange,
1570
1599
  values
1571
1600
  } = props;
1572
- const memoizedValues = useDeepCompareState(values, []);
1601
+ const memoizedValues = useDeepCompareMemoize(values || []);
1573
1602
 
1574
1603
  // ensures that the values are always a subset of the possible options
1575
1604
  hooks.useEffect(() => {
@@ -2257,7 +2286,7 @@ function Datepicker(props) {
2257
2286
  const [forceFocusCalendar, setForceFocusCalendar] = hooks.useState(false);
2258
2287
 
2259
2288
  // ensures we render based on date value instead of reference
2260
- const date = useDeepCompareState(dateObject, null);
2289
+ const date = useDeepCompareMemoize(dateObject);
2261
2290
 
2262
2291
  // shorts the date value back to the source
2263
2292
  hooks.useEffect(() => {
@@ -3693,8 +3722,8 @@ function Numberfield(props) {
3693
3722
  'fjs-readonly': readonly
3694
3723
  }),
3695
3724
  children: [jsxRuntime.jsx("button", {
3696
- class: "fjs-number-arrow-up",
3697
3725
  type: "button",
3726
+ class: "fjs-number-arrow-up",
3698
3727
  "aria-label": "Increment",
3699
3728
  onClick: () => increment(),
3700
3729
  tabIndex: -1,
@@ -3702,8 +3731,8 @@ function Numberfield(props) {
3702
3731
  }), jsxRuntime.jsx("div", {
3703
3732
  class: "fjs-number-arrow-separator"
3704
3733
  }), jsxRuntime.jsx("button", {
3705
- class: "fjs-number-arrow-down",
3706
3734
  type: "button",
3735
+ class: "fjs-number-arrow-down",
3707
3736
  "aria-label": "Decrement",
3708
3737
  onClick: () => decrement(),
3709
3738
  tabIndex: -1,
@@ -4374,7 +4403,7 @@ function Taglist(props) {
4374
4403
  } = useOptionsAsync(field);
4375
4404
 
4376
4405
  // ensures we render based on array content instead of reference
4377
- const values = useDeepCompareState(value || [], []);
4406
+ const values = useDeepCompareMemoize(value || []);
4378
4407
  useCleanupMultiSelectValue({
4379
4408
  field,
4380
4409
  loadState,
@@ -6688,18 +6717,18 @@ class RepeatRenderManager {
6688
6717
  'fjs-remove-allowed': repeaterField.allowAddRemove
6689
6718
  }),
6690
6719
  children: [showAdd ? jsxRuntime.jsx("button", {
6720
+ type: "button",
6691
6721
  readOnly: readonly,
6692
6722
  disabled: disabled || readonly,
6693
6723
  class: "fjs-repeat-render-add",
6694
- type: "button",
6695
6724
  ref: addButtonRef,
6696
6725
  onClick: onAddItem,
6697
6726
  children: jsxRuntime.jsxs(jsxRuntime.Fragment, {
6698
6727
  children: [jsxRuntime.jsx(AddSvg, {}), " ", 'Add new']
6699
6728
  })
6700
6729
  }) : null, collapseEnabled ? jsxRuntime.jsx("button", {
6701
- class: "fjs-repeat-render-collapse",
6702
6730
  type: "button",
6731
+ class: "fjs-repeat-render-collapse",
6703
6732
  onClick: toggle,
6704
6733
  children: isCollapsed ? jsxRuntime.jsxs(jsxRuntime.Fragment, {
6705
6734
  children: [jsxRuntime.jsx(ExpandSvg, {}), " ", `Expand all (${values.length})`]
@@ -6773,8 +6802,8 @@ const RepetitionScaffold = props => {
6773
6802
  })
6774
6803
  })
6775
6804
  }), jsxRuntime.jsx("button", {
6776
- class: "fjs-repeat-row-remove",
6777
6805
  type: "button",
6806
+ class: "fjs-repeat-row-remove",
6778
6807
  "aria-label": `Remove list item ${index + 1}`,
6779
6808
  onClick: () => onDeleteItem(index),
6780
6809
  children: jsxRuntime.jsx("div", {