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

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,
@@ -6615,9 +6644,9 @@ class RepeatRenderManager {
6615
6644
  };
6616
6645
  const parentExpressionContextInfo = hooks.useContext(LocalExpressionContext);
6617
6646
  return jsxRuntime.jsx(jsxRuntime.Fragment, {
6618
- children: displayValues.map((value, index) => jsxRuntime.jsx(RepetitionScaffold, {
6619
- index: index,
6620
- value: value,
6647
+ children: displayValues.map((itemValue, itemIndex) => jsxRuntime.jsx(RepetitionScaffold, {
6648
+ itemIndex: itemIndex,
6649
+ itemValue: itemValue,
6621
6650
  parentExpressionContextInfo: parentExpressionContextInfo,
6622
6651
  repeaterField: repeaterField,
6623
6652
  RowsRenderer: RowsRenderer,
@@ -6625,7 +6654,7 @@ class RepeatRenderManager {
6625
6654
  onDeleteItem: onDeleteItem,
6626
6655
  showRemove: showRemove,
6627
6656
  ...restProps
6628
- }, index))
6657
+ }, itemIndex))
6629
6658
  });
6630
6659
  }
6631
6660
  RepeatFooter(props) {
@@ -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})`]
@@ -6722,8 +6751,8 @@ class RepeatRenderManager {
6722
6751
  * Individual repetition of a repeated field and context scaffolding.
6723
6752
  *
6724
6753
  * @param {Object} props
6725
- * @param {number} props.index
6726
- * @param {Object} props.value
6754
+ * @param {number} props.itemIndex
6755
+ * @param {Object} props.itemValue
6727
6756
  * @param {Object} props.parentExpressionContextInfo
6728
6757
  * @param {Object} props.repeaterField
6729
6758
  * @param {Function} props.RowsRenderer
@@ -6734,8 +6763,8 @@ class RepeatRenderManager {
6734
6763
 
6735
6764
  const RepetitionScaffold = props => {
6736
6765
  const {
6737
- index,
6738
- value,
6766
+ itemIndex,
6767
+ itemValue,
6739
6768
  parentExpressionContextInfo,
6740
6769
  repeaterField,
6741
6770
  RowsRenderer,
@@ -6748,15 +6777,15 @@ const RepetitionScaffold = props => {
6748
6777
  ...restProps,
6749
6778
  indexes: {
6750
6779
  ...(indexes || {}),
6751
- [repeaterField.id]: index
6780
+ [repeaterField.id]: itemIndex
6752
6781
  }
6753
- }), [index, indexes, repeaterField.id, restProps]);
6782
+ }), [itemIndex, indexes, repeaterField.id, restProps]);
6754
6783
  const localExpressionContextInfo = hooks.useMemo(() => ({
6755
6784
  data: parentExpressionContextInfo.data,
6756
- this: value,
6785
+ this: itemValue,
6757
6786
  parent: buildExpressionContext(parentExpressionContextInfo),
6758
- i: [...parentExpressionContextInfo.i, index + 1]
6759
- }), [index, parentExpressionContextInfo, value]);
6787
+ i: [...parentExpressionContextInfo.i, itemIndex + 1]
6788
+ }), [itemIndex, parentExpressionContextInfo, itemValue]);
6760
6789
  return !showRemove ? jsxRuntime.jsx(LocalExpressionContext.Provider, {
6761
6790
  value: localExpressionContextInfo,
6762
6791
  children: jsxRuntime.jsx(RowsRenderer, {
@@ -6773,10 +6802,10 @@ const RepetitionScaffold = props => {
6773
6802
  })
6774
6803
  })
6775
6804
  }), jsxRuntime.jsx("button", {
6776
- class: "fjs-repeat-row-remove",
6777
6805
  type: "button",
6778
- "aria-label": `Remove list item ${index + 1}`,
6779
- onClick: () => onDeleteItem(index),
6806
+ class: "fjs-repeat-row-remove",
6807
+ "aria-label": `Remove list item ${itemIndex + 1}`,
6808
+ onClick: () => onDeleteItem(itemIndex),
6780
6809
  children: jsxRuntime.jsx("div", {
6781
6810
  class: "fjs-repeat-row-remove-icon-container",
6782
6811
  children: jsxRuntime.jsx(DeleteSvg, {})