@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/LICENSE +22 -22
- package/README.md +189 -189
- package/dist/index.cjs +97 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +98 -69
- package/dist/index.es.js.map +1 -1
- package/dist/types/render/components/index.d.ts +1 -1
- package/dist/types/render/components/util/optionsUtil.d.ts +24 -8
- package/dist/types/render/hooks/index.d.ts +1 -1
- package/dist/types/render/hooks/useDeepCompareMemoize.d.ts +8 -0
- package/dist/types/types.d.ts +35 -35
- package/package.json +2 -2
- package/dist/types/render/hooks/useDeepCompareState.d.ts +0 -8
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,
|
|
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
|
-
|
|
776
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
884
|
-
* @param {
|
|
885
|
-
* @returns {
|
|
901
|
+
* @template T
|
|
902
|
+
* @param {T} value - The current value to manage.
|
|
903
|
+
* @returns {T} - Returns the current state.
|
|
886
904
|
*/
|
|
887
|
-
function
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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 =
|
|
934
|
-
|
|
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
|
-
|
|
951
|
-
|
|
952
|
-
|
|
958
|
+
} else if (optionsExpression && evaluatedOptions && Array.isArray(evaluatedOptions)) {
|
|
959
|
+
options = evaluatedOptions;
|
|
960
|
+
|
|
961
|
+
// error case
|
|
953
962
|
} else {
|
|
954
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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", {
|