@bpmn-io/form-js-viewer 1.11.0 → 1.11.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 +308 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +308 -120
- package/dist/index.es.js.map +1 -1
- package/dist/types/Form.d.ts +2 -1
- package/dist/types/core/FormFieldInstanceRegistry.d.ts +3 -2
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/features/repeatRender/RepeatRenderManager.d.ts +9 -4
- package/dist/types/render/FileRegistry.d.ts +52 -0
- package/dist/types/render/components/form-fields/FilePicker.d.ts +4 -5
- package/dist/types/render/hooks/index.d.ts +1 -0
- package/dist/types/render/hooks/useBooleanExpressionEvaluation.d.ts +9 -0
- package/dist/types/render/hooks/useExpressionEvaluation.d.ts +3 -4
- package/dist/types/render/hooks/useService.d.ts +7 -1
- package/dist/types/render/index.d.ts +2 -0
- package/dist/types/util/constants/FilePickerConstants.d.ts +1 -0
- package/dist/types/util/expressions.d.ts +3 -4
- package/dist/types/util/extractFileReferencesFromRemovedData.d.ts +7 -0
- package/package.json +2 -2
package/dist/index.es.js
CHANGED
|
@@ -657,6 +657,12 @@ const FormContext = createContext({
|
|
|
657
657
|
formId: null
|
|
658
658
|
});
|
|
659
659
|
|
|
660
|
+
/**
|
|
661
|
+
* @template T
|
|
662
|
+
* @param {string} type
|
|
663
|
+
* @param {boolean} [strict=true]
|
|
664
|
+
* @returns {T | null}
|
|
665
|
+
*/
|
|
660
666
|
function useService(type, strict) {
|
|
661
667
|
const {
|
|
662
668
|
getService
|
|
@@ -735,11 +741,10 @@ function buildExpressionContext(context) {
|
|
|
735
741
|
}
|
|
736
742
|
|
|
737
743
|
/**
|
|
738
|
-
*
|
|
739
|
-
* If the string is not an expression, it is returned as is.
|
|
744
|
+
* If the value is a valid expression, it is evaluated and returned. Otherwise, it is returned as-is.
|
|
740
745
|
*
|
|
741
746
|
* @param {any} expressionLanguage - The expression language to use.
|
|
742
|
-
* @param {
|
|
747
|
+
* @param {any} value - The static value or expression to evaluate.
|
|
743
748
|
* @param {Object} expressionContextInfo - The context information to use.
|
|
744
749
|
* @returns {any} - Evaluated value or the original value if not an expression.
|
|
745
750
|
*/
|
|
@@ -870,11 +875,10 @@ function _isAllowedValue(value) {
|
|
|
870
875
|
}
|
|
871
876
|
|
|
872
877
|
/**
|
|
873
|
-
*
|
|
874
|
-
* If the string is not an expression, it is returned as is.
|
|
878
|
+
* If the value is a valid expression, it is evaluated and returned. Otherwise, it is returned as-is.
|
|
875
879
|
* The function is memoized to minimize re-renders.
|
|
876
880
|
*
|
|
877
|
-
* @param {
|
|
881
|
+
* @param {any} value - A static value or expression to evaluate.
|
|
878
882
|
* @returns {any} - Evaluated value or the original value if not an expression.
|
|
879
883
|
*/
|
|
880
884
|
function useExpressionEvaluation(value) {
|
|
@@ -1115,7 +1119,7 @@ function _isElementScrollable(el) {
|
|
|
1115
1119
|
}
|
|
1116
1120
|
|
|
1117
1121
|
const EMPTY_OBJECT = {};
|
|
1118
|
-
const EMPTY_ARRAY = [];
|
|
1122
|
+
const EMPTY_ARRAY$2 = [];
|
|
1119
1123
|
|
|
1120
1124
|
/**
|
|
1121
1125
|
* Custom hook to scroll an element within a scrollable container.
|
|
@@ -1131,7 +1135,7 @@ const EMPTY_ARRAY = [];
|
|
|
1131
1135
|
*/
|
|
1132
1136
|
function useScrollIntoView(scrolledElementRef, deps, scrollOptions, flagRefs) {
|
|
1133
1137
|
const _scrollOptions = scrollOptions || EMPTY_OBJECT;
|
|
1134
|
-
const _flagRefs = flagRefs || EMPTY_ARRAY;
|
|
1138
|
+
const _flagRefs = flagRefs || EMPTY_ARRAY$2;
|
|
1135
1139
|
useEffect(() => {
|
|
1136
1140
|
// return early if flags are not raised, or component is not mounted
|
|
1137
1141
|
if (some(_flagRefs, ref => !ref.current) || !scrolledElementRef.current) {
|
|
@@ -1185,6 +1189,23 @@ function _getTopOffset(item, scrollContainer, options) {
|
|
|
1185
1189
|
return 0;
|
|
1186
1190
|
}
|
|
1187
1191
|
|
|
1192
|
+
/**
|
|
1193
|
+
* If the value is a valid expression, we evaluate it. Otherwise, we continue with the value as-is.
|
|
1194
|
+
* If the resulting value isn't a boolean, we return 'false'
|
|
1195
|
+
* The function is memoized to minimize re-renders.
|
|
1196
|
+
*
|
|
1197
|
+
* @param {boolean | string} value - A static boolean or expression to evaluate.
|
|
1198
|
+
* @returns {boolean} - Evaluated boolean result.
|
|
1199
|
+
*/
|
|
1200
|
+
function useBooleanExpressionEvaluation(value) {
|
|
1201
|
+
const expressionLanguage = useService('expressionLanguage');
|
|
1202
|
+
const expressionContextInfo = useContext(LocalExpressionContext);
|
|
1203
|
+
return useMemo(() => {
|
|
1204
|
+
const evaluationResult = runExpressionEvaluation(expressionLanguage, value, expressionContextInfo);
|
|
1205
|
+
return typeof evaluationResult === 'boolean' ? evaluationResult : false;
|
|
1206
|
+
}, [expressionLanguage, expressionContextInfo, value]);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1188
1209
|
/**
|
|
1189
1210
|
* Returns the conditionally filtered data of a form reactively.
|
|
1190
1211
|
* Memoised to minimize re-renders
|
|
@@ -1966,7 +1987,9 @@ Checklist.config = {
|
|
|
1966
1987
|
};
|
|
1967
1988
|
|
|
1968
1989
|
const noop$1 = () => false;
|
|
1990
|
+
const ids$2 = new Ids([32, 36, 1]);
|
|
1969
1991
|
function FormField(props) {
|
|
1992
|
+
const instanceIdRef = useRef(ids$2.next());
|
|
1970
1993
|
const {
|
|
1971
1994
|
field,
|
|
1972
1995
|
indexes,
|
|
@@ -2012,22 +2035,28 @@ function FormField(props) {
|
|
|
2012
2035
|
// add precedence: global readonly > form field disabled
|
|
2013
2036
|
const disabled = !properties.readOnly && (properties.disabled || field.disabled || false);
|
|
2014
2037
|
const hidden = useCondition(field.conditional && field.conditional.hide || null);
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2038
|
+
const instanceId = useMemo(() => {
|
|
2039
|
+
if (!formFieldInstanceRegistry) {
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
return formFieldInstanceRegistry.syncInstance(instanceIdRef.current, {
|
|
2043
|
+
id: field.id,
|
|
2044
|
+
expressionContextInfo: localExpressionContext,
|
|
2045
|
+
valuePath,
|
|
2046
|
+
value,
|
|
2047
|
+
indexes,
|
|
2048
|
+
hidden
|
|
2049
|
+
});
|
|
2050
|
+
}, [formFieldInstanceRegistry, field.id, localExpressionContext, valuePath, value, indexes, hidden]);
|
|
2051
|
+
const fieldInstance = instanceId ? formFieldInstanceRegistry.get(instanceId) : null;
|
|
2021
2052
|
|
|
2022
|
-
//
|
|
2053
|
+
// cleanup the instance on unmount
|
|
2023
2054
|
useEffect(() => {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
return () =>
|
|
2027
|
-
formFieldInstanceRegistry.remove(instanceId);
|
|
2028
|
-
};
|
|
2055
|
+
const instanceId = instanceIdRef.current;
|
|
2056
|
+
if (formFieldInstanceRegistry) {
|
|
2057
|
+
return () => formFieldInstanceRegistry.cleanupInstance(instanceId);
|
|
2029
2058
|
}
|
|
2030
|
-
}, [
|
|
2059
|
+
}, [formFieldInstanceRegistry]);
|
|
2031
2060
|
|
|
2032
2061
|
// ensures the initial validation behavior can be re-triggered upon form reset
|
|
2033
2062
|
useEffect(() => {
|
|
@@ -2046,7 +2075,7 @@ function FormField(props) {
|
|
|
2046
2075
|
}, [eventBus, viewerCommands]);
|
|
2047
2076
|
useEffect(() => {
|
|
2048
2077
|
const hasInitialValue = initialValue && !isEqual(initialValue, []);
|
|
2049
|
-
if (initialValidationTrigger && hasInitialValue) {
|
|
2078
|
+
if (initialValidationTrigger && hasInitialValue && fieldInstance) {
|
|
2050
2079
|
setInitialValidationTrigger(false);
|
|
2051
2080
|
viewerCommands.updateFieldInstanceValidation(fieldInstance, initialValue);
|
|
2052
2081
|
}
|
|
@@ -5666,7 +5695,11 @@ function getHeaderAriaLabel(sortBy, key, label) {
|
|
|
5666
5695
|
return `Click to sort by ${label} ascending`;
|
|
5667
5696
|
}
|
|
5668
5697
|
|
|
5698
|
+
const FILE_PICKER_FILE_KEY_PREFIX = 'files::';
|
|
5699
|
+
|
|
5669
5700
|
const type = 'filepicker';
|
|
5701
|
+
const ids$1 = new Ids();
|
|
5702
|
+
const EMPTY_ARRAY$1 = [];
|
|
5670
5703
|
|
|
5671
5704
|
/**
|
|
5672
5705
|
* @typedef Props
|
|
@@ -5680,7 +5713,8 @@ const type = 'filepicker';
|
|
|
5680
5713
|
* @property {string} field.id
|
|
5681
5714
|
* @property {string} [field.label]
|
|
5682
5715
|
* @property {string} [field.accept]
|
|
5683
|
-
* @property {boolean} [field.multiple]
|
|
5716
|
+
* @property {string|boolean} [field.multiple]
|
|
5717
|
+
* @property {string} [value]
|
|
5684
5718
|
*
|
|
5685
5719
|
* @param {Props} props
|
|
5686
5720
|
* @returns {import("preact").JSX.Element}
|
|
@@ -5688,9 +5722,8 @@ const type = 'filepicker';
|
|
|
5688
5722
|
function FilePicker(props) {
|
|
5689
5723
|
/** @type {import("preact/hooks").Ref<HTMLInputElement>} */
|
|
5690
5724
|
const fileInputRef = useRef(null);
|
|
5691
|
-
/** @type {
|
|
5692
|
-
const
|
|
5693
|
-
const eventBus = useService('eventBus');
|
|
5725
|
+
/** @type {import('../../FileRegistry').FileRegistry} */
|
|
5726
|
+
const fileRegistry = useService('fileRegistry', false);
|
|
5694
5727
|
const {
|
|
5695
5728
|
field,
|
|
5696
5729
|
onChange,
|
|
@@ -5698,33 +5731,59 @@ function FilePicker(props) {
|
|
|
5698
5731
|
errors = [],
|
|
5699
5732
|
disabled,
|
|
5700
5733
|
readonly,
|
|
5701
|
-
required
|
|
5734
|
+
required,
|
|
5735
|
+
value: filesKey = ''
|
|
5702
5736
|
} = props;
|
|
5703
5737
|
const {
|
|
5704
5738
|
label,
|
|
5705
|
-
multiple =
|
|
5706
|
-
accept = ''
|
|
5707
|
-
id
|
|
5739
|
+
multiple = false,
|
|
5740
|
+
accept = ''
|
|
5708
5741
|
} = field;
|
|
5742
|
+
/** @type {string} */
|
|
5709
5743
|
const evaluatedAccept = useSingleLineTemplateEvaluation(accept);
|
|
5710
|
-
const evaluatedMultiple =
|
|
5744
|
+
const evaluatedMultiple = useBooleanExpressionEvaluation(multiple);
|
|
5711
5745
|
const errorMessageId = `${domId}-error-message`;
|
|
5746
|
+
/** @type {File[]} */
|
|
5747
|
+
const selectedFiles = fileRegistry === null ? EMPTY_ARRAY$1 : fileRegistry.getFiles(filesKey);
|
|
5712
5748
|
useEffect(() => {
|
|
5713
|
-
|
|
5714
|
-
setSelectedFiles([]);
|
|
5749
|
+
if (filesKey && fileRegistry !== null && !fileRegistry.hasKey(filesKey)) {
|
|
5715
5750
|
onChange({
|
|
5716
5751
|
value: null
|
|
5717
5752
|
});
|
|
5718
|
-
}
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5753
|
+
}
|
|
5754
|
+
}, [fileRegistry, filesKey, onChange, selectedFiles.length]);
|
|
5755
|
+
useEffect(() => {
|
|
5756
|
+
const data = new DataTransfer();
|
|
5757
|
+
selectedFiles.forEach(file => data.items.add(file));
|
|
5758
|
+
fileInputRef.current.files = data.files;
|
|
5759
|
+
}, [selectedFiles]);
|
|
5760
|
+
|
|
5761
|
+
/**
|
|
5762
|
+
* @type import("preact").JSX.GenericEventHandler<HTMLInputElement>
|
|
5763
|
+
*/
|
|
5764
|
+
const onFileChange = event => {
|
|
5765
|
+
const input = /** @type {HTMLInputElement} */event.target;
|
|
5766
|
+
|
|
5767
|
+
// if we have an associated file key but no files are selected, clear the file key and associated files
|
|
5768
|
+
if ((input.files === null || input.files.length === 0) && filesKey !== '') {
|
|
5769
|
+
fileRegistry.deleteFiles(filesKey);
|
|
5770
|
+
onChange({
|
|
5771
|
+
value: null
|
|
5772
|
+
});
|
|
5773
|
+
return;
|
|
5774
|
+
}
|
|
5775
|
+
const files = Array.from(input.files);
|
|
5776
|
+
|
|
5777
|
+
// ensure fileKey exists
|
|
5778
|
+
const updatedFilesKey = filesKey || ids$1.nextPrefixed(FILE_PICKER_FILE_KEY_PREFIX);
|
|
5779
|
+
fileRegistry.setFiles(updatedFilesKey, files);
|
|
5780
|
+
onChange({
|
|
5781
|
+
value: updatedFilesKey
|
|
5782
|
+
});
|
|
5783
|
+
};
|
|
5784
|
+
const isInputDisabled = disabled || readonly || fileRegistry === null;
|
|
5726
5785
|
return jsxs("div", {
|
|
5727
|
-
|
|
5786
|
+
className: formFieldClasses(type, {
|
|
5728
5787
|
errors,
|
|
5729
5788
|
disabled,
|
|
5730
5789
|
readonly
|
|
@@ -5739,29 +5798,17 @@ function FilePicker(props) {
|
|
|
5739
5798
|
ref: fileInputRef,
|
|
5740
5799
|
id: domId,
|
|
5741
5800
|
name: domId,
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
if (input.files === null || input.files.length === 0) {
|
|
5747
|
-
onChange({
|
|
5748
|
-
value: null
|
|
5749
|
-
});
|
|
5750
|
-
return;
|
|
5751
|
-
}
|
|
5752
|
-
const files = Array.from(input.files);
|
|
5753
|
-
onChange({
|
|
5754
|
-
value: `${id}_value_key`
|
|
5755
|
-
});
|
|
5756
|
-
setSelectedFiles(files);
|
|
5757
|
-
}
|
|
5801
|
+
disabled: isInputDisabled,
|
|
5802
|
+
multiple: evaluatedMultiple || undefined,
|
|
5803
|
+
accept: evaluatedAccept || undefined,
|
|
5804
|
+
onChange: onFileChange
|
|
5758
5805
|
}), jsxs("div", {
|
|
5759
5806
|
className: "fjs-filepicker-container",
|
|
5760
5807
|
children: [jsx("button", {
|
|
5761
5808
|
type: "button",
|
|
5762
|
-
disabled:
|
|
5809
|
+
disabled: isInputDisabled,
|
|
5763
5810
|
readonly: readonly,
|
|
5764
|
-
|
|
5811
|
+
className: "fjs-button fjs-filepicker-button",
|
|
5765
5812
|
onClick: () => {
|
|
5766
5813
|
fileInputRef.current.click();
|
|
5767
5814
|
},
|
|
@@ -5782,11 +5829,6 @@ FilePicker.config = {
|
|
|
5782
5829
|
label: 'File picker',
|
|
5783
5830
|
group: 'basic-input',
|
|
5784
5831
|
emptyValue: null,
|
|
5785
|
-
sanitizeValue: ({
|
|
5786
|
-
value
|
|
5787
|
-
}) => {
|
|
5788
|
-
return value;
|
|
5789
|
-
},
|
|
5790
5832
|
create: (options = {}) => ({
|
|
5791
5833
|
...options
|
|
5792
5834
|
})
|
|
@@ -5965,7 +6007,7 @@ class FormFields {
|
|
|
5965
6007
|
}
|
|
5966
6008
|
}
|
|
5967
6009
|
|
|
5968
|
-
const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression', 'url', 'dataSource', 'columnsExpression', 'expression'];
|
|
6010
|
+
const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression', 'url', 'dataSource', 'columnsExpression', 'expression', 'multiple', 'accept'];
|
|
5969
6011
|
const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text', 'content', 'url'];
|
|
5970
6012
|
|
|
5971
6013
|
/**
|
|
@@ -6183,11 +6225,21 @@ class ConditionChecker {
|
|
|
6183
6225
|
// if we have a hidden repeatable field, and the data structure allows, we clear it directly at the root and stop recursion
|
|
6184
6226
|
if (context.isHidden && isRepeatable) {
|
|
6185
6227
|
context.preventRecursion = true;
|
|
6228
|
+
this._eventBus.fire('conditionChecker.remove', {
|
|
6229
|
+
item: {
|
|
6230
|
+
[field.key]: get(workingData, getFilterPath(field, indexes))
|
|
6231
|
+
}
|
|
6232
|
+
});
|
|
6186
6233
|
this._cleanlyClearDataAtPath(getFilterPath(field, indexes), workingData);
|
|
6187
6234
|
}
|
|
6188
6235
|
|
|
6189
6236
|
// for simple leaf fields, we always clear
|
|
6190
6237
|
if (context.isHidden && isClosed) {
|
|
6238
|
+
this._eventBus.fire('conditionChecker.remove', {
|
|
6239
|
+
item: {
|
|
6240
|
+
[field.key]: get(workingData, getFilterPath(field, indexes))
|
|
6241
|
+
}
|
|
6242
|
+
});
|
|
6191
6243
|
this._cleanlyClearDataAtPath(getFilterPath(field, indexes), workingData);
|
|
6192
6244
|
}
|
|
6193
6245
|
});
|
|
@@ -6981,11 +7033,16 @@ var SvgDelete = function SvgDelete(props) {
|
|
|
6981
7033
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
6982
7034
|
|
|
6983
7035
|
class RepeatRenderManager {
|
|
6984
|
-
constructor(form, formFields, formFieldRegistry, pathRegistry) {
|
|
7036
|
+
constructor(form, formFields, formFieldRegistry, pathRegistry, eventBus) {
|
|
6985
7037
|
this._form = form;
|
|
7038
|
+
/** @type {import('../../render/FormFields').FormFields} */
|
|
6986
7039
|
this._formFields = formFields;
|
|
7040
|
+
/** @type {import('../../core/FormFieldRegistry').FormFieldRegistry} */
|
|
6987
7041
|
this._formFieldRegistry = formFieldRegistry;
|
|
7042
|
+
/** @type {import('../../core/PathRegistry').PathRegistry} */
|
|
6988
7043
|
this._pathRegistry = pathRegistry;
|
|
7044
|
+
/** @type {import('../../core/EventBus').EventBus} */
|
|
7045
|
+
this._eventBus = eventBus;
|
|
6989
7046
|
this.Repeater = this.Repeater.bind(this);
|
|
6990
7047
|
this.RepeatFooter = this.RepeatFooter.bind(this);
|
|
6991
7048
|
}
|
|
@@ -7025,11 +7082,18 @@ class RepeatRenderManager {
|
|
|
7025
7082
|
const isCollapsed = collapseEnabled && sharedRepeatState.isCollapsed;
|
|
7026
7083
|
const hasChildren = repeaterField.components && repeaterField.components.length > 0;
|
|
7027
7084
|
const showRemove = repeaterField.allowAddRemove && hasChildren;
|
|
7028
|
-
|
|
7029
|
-
|
|
7085
|
+
|
|
7086
|
+
/**
|
|
7087
|
+
* @param {number} index
|
|
7088
|
+
*/
|
|
7030
7089
|
const onDeleteItem = index => {
|
|
7031
7090
|
const updatedValues = values.slice();
|
|
7032
|
-
updatedValues.splice(index, 1);
|
|
7091
|
+
const removedItem = updatedValues.splice(index, 1)[0];
|
|
7092
|
+
this._eventBus.fire('repeatRenderManager.remove', {
|
|
7093
|
+
dataPath,
|
|
7094
|
+
index,
|
|
7095
|
+
item: removedItem
|
|
7096
|
+
});
|
|
7033
7097
|
props.onChange({
|
|
7034
7098
|
field: repeaterField,
|
|
7035
7099
|
value: updatedValues,
|
|
@@ -7037,21 +7101,13 @@ class RepeatRenderManager {
|
|
|
7037
7101
|
});
|
|
7038
7102
|
};
|
|
7039
7103
|
const parentExpressionContextInfo = useContext(LocalExpressionContext);
|
|
7040
|
-
return
|
|
7041
|
-
children:
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
indexes: indexes,
|
|
7048
|
-
onDeleteItem: onDeleteItem,
|
|
7049
|
-
showRemove: showRemove,
|
|
7050
|
-
...restProps
|
|
7051
|
-
}, itemIndex)), hiddenValues.length > 0 ? jsx("div", {
|
|
7052
|
-
className: "fjs-repeat-row-collapsed",
|
|
7053
|
-
children: hiddenValues.map((itemValue, itemIndex) => jsx(RepetitionScaffold, {
|
|
7054
|
-
itemIndex: itemIndex + nonCollapsedItems,
|
|
7104
|
+
return jsx(Fragment, {
|
|
7105
|
+
children: values.map((itemValue, itemIndex) => jsx("div", {
|
|
7106
|
+
class: classNames({
|
|
7107
|
+
'fjs-repeat-row-collapsed': isCollapsed ? itemIndex >= nonCollapsedItems : false
|
|
7108
|
+
}),
|
|
7109
|
+
children: jsx(RepetitionScaffold, {
|
|
7110
|
+
itemIndex: itemIndex,
|
|
7055
7111
|
itemValue: itemValue,
|
|
7056
7112
|
parentExpressionContextInfo: parentExpressionContextInfo,
|
|
7057
7113
|
repeaterField: repeaterField,
|
|
@@ -7060,8 +7116,8 @@ class RepeatRenderManager {
|
|
|
7060
7116
|
onDeleteItem: onDeleteItem,
|
|
7061
7117
|
showRemove: showRemove,
|
|
7062
7118
|
...restProps
|
|
7063
|
-
}
|
|
7064
|
-
})
|
|
7119
|
+
})
|
|
7120
|
+
}))
|
|
7065
7121
|
});
|
|
7066
7122
|
}
|
|
7067
7123
|
RepeatFooter(props) {
|
|
@@ -7104,6 +7160,11 @@ class RepeatRenderManager {
|
|
|
7104
7160
|
});
|
|
7105
7161
|
updatedValues.push(newItem);
|
|
7106
7162
|
shouldScroll.current = true;
|
|
7163
|
+
this._eventBus.fire('repeatRenderManager.add', {
|
|
7164
|
+
dataPath,
|
|
7165
|
+
index: updatedValues.length - 1,
|
|
7166
|
+
item: newItem
|
|
7167
|
+
});
|
|
7107
7168
|
props.onChange({
|
|
7108
7169
|
value: updatedValues
|
|
7109
7170
|
});
|
|
@@ -7136,7 +7197,7 @@ class RepeatRenderManager {
|
|
|
7136
7197
|
class: "fjs-repeat-render-collapse",
|
|
7137
7198
|
onClick: toggle,
|
|
7138
7199
|
children: isCollapsed ? jsxs(Fragment, {
|
|
7139
|
-
children: [jsx(SvgExpand, {}), " ", `Expand all (${values.length})`]
|
|
7200
|
+
children: [jsx(SvgExpand, {}), " ", `Expand all (${values.length - 1})`]
|
|
7140
7201
|
}) : jsxs(Fragment, {
|
|
7141
7202
|
children: [jsx(SvgCollapse, {}), " ", 'Collapse']
|
|
7142
7203
|
})
|
|
@@ -7221,7 +7282,7 @@ const RepetitionScaffold = props => {
|
|
|
7221
7282
|
})]
|
|
7222
7283
|
});
|
|
7223
7284
|
};
|
|
7224
|
-
RepeatRenderManager.$inject = ['form', 'formFields', 'formFieldRegistry', 'pathRegistry'];
|
|
7285
|
+
RepeatRenderManager.$inject = ['form', 'formFields', 'formFieldRegistry', 'pathRegistry', 'eventBus'];
|
|
7225
7286
|
|
|
7226
7287
|
const RepeatRenderModule = {
|
|
7227
7288
|
__init__: ['repeatRenderManager'],
|
|
@@ -8611,39 +8672,52 @@ class FormFieldInstanceRegistry {
|
|
|
8611
8672
|
this._formFieldInstances = {};
|
|
8612
8673
|
eventBus.on('form.clear', () => this.clear());
|
|
8613
8674
|
}
|
|
8614
|
-
|
|
8675
|
+
syncInstance(instanceId, formFieldInfo) {
|
|
8615
8676
|
const {
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8677
|
+
hidden,
|
|
8678
|
+
...restInfo
|
|
8679
|
+
} = formFieldInfo;
|
|
8680
|
+
const isInstanceExpected = !hidden;
|
|
8681
|
+
const doesInstanceExist = this._formFieldInstances[instanceId];
|
|
8682
|
+
if (isInstanceExpected && !doesInstanceExist) {
|
|
8683
|
+
this._formFieldInstances[instanceId] = {
|
|
8684
|
+
instanceId,
|
|
8685
|
+
...restInfo
|
|
8686
|
+
};
|
|
8687
|
+
this._eventBus.fire('formFieldInstance.added', {
|
|
8688
|
+
instanceId
|
|
8689
|
+
});
|
|
8690
|
+
} else if (!isInstanceExpected && doesInstanceExist) {
|
|
8691
|
+
delete this._formFieldInstances[instanceId];
|
|
8692
|
+
this._eventBus.fire('formFieldInstance.removed', {
|
|
8693
|
+
instanceId
|
|
8694
|
+
});
|
|
8695
|
+
} else if (isInstanceExpected && doesInstanceExist) {
|
|
8696
|
+
const wasInstanceChanged = Object.keys(restInfo).some(key => {
|
|
8697
|
+
return this._formFieldInstances[instanceId][key] !== restInfo[key];
|
|
8698
|
+
});
|
|
8699
|
+
if (wasInstanceChanged) {
|
|
8700
|
+
this._formFieldInstances[instanceId] = {
|
|
8701
|
+
instanceId,
|
|
8702
|
+
...restInfo
|
|
8703
|
+
};
|
|
8704
|
+
this._eventBus.fire('formFieldInstance.changed', {
|
|
8705
|
+
instanceId
|
|
8706
|
+
});
|
|
8707
|
+
}
|
|
8624
8708
|
}
|
|
8625
|
-
this._formFieldInstances[instanceId] = {
|
|
8626
|
-
id,
|
|
8627
|
-
instanceId,
|
|
8628
|
-
expressionContextInfo,
|
|
8629
|
-
valuePath,
|
|
8630
|
-
indexes
|
|
8631
|
-
};
|
|
8632
|
-
this._eventBus.fire('formFieldInstanceRegistry.changed', {
|
|
8633
|
-
instanceId,
|
|
8634
|
-
action: 'added'
|
|
8635
|
-
});
|
|
8636
8709
|
return instanceId;
|
|
8637
8710
|
}
|
|
8638
|
-
|
|
8639
|
-
if (
|
|
8640
|
-
|
|
8711
|
+
cleanupInstance(instanceId) {
|
|
8712
|
+
if (this._formFieldInstances[instanceId]) {
|
|
8713
|
+
delete this._formFieldInstances[instanceId];
|
|
8714
|
+
this._eventBus.fire('formFieldInstance.removed', {
|
|
8715
|
+
instanceId
|
|
8716
|
+
});
|
|
8641
8717
|
}
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
action: 'removed'
|
|
8646
|
-
});
|
|
8718
|
+
}
|
|
8719
|
+
get(instanceId) {
|
|
8720
|
+
return this._formFieldInstances[instanceId];
|
|
8647
8721
|
}
|
|
8648
8722
|
getAll() {
|
|
8649
8723
|
return Object.values(this._formFieldInstances);
|
|
@@ -8723,10 +8797,122 @@ function Renderer(config, eventBus, form, injector) {
|
|
|
8723
8797
|
}
|
|
8724
8798
|
Renderer.$inject = ['config.renderer', 'eventBus', 'form', 'injector'];
|
|
8725
8799
|
|
|
8800
|
+
/**
|
|
8801
|
+
* @typedef {Record<PropertyKey, unknown>} RemovedData
|
|
8802
|
+
* @param {RemovedData} removedData
|
|
8803
|
+
* @returns {string[]}
|
|
8804
|
+
*/
|
|
8805
|
+
const extractFileReferencesFromRemovedData = removedData => {
|
|
8806
|
+
/** @type {string[]} */
|
|
8807
|
+
const fileReferences = [];
|
|
8808
|
+
if (removedData === null) {
|
|
8809
|
+
return fileReferences;
|
|
8810
|
+
}
|
|
8811
|
+
Object.values(removedData).forEach(value => {
|
|
8812
|
+
if (value === null) {
|
|
8813
|
+
return;
|
|
8814
|
+
}
|
|
8815
|
+
if (typeof value === 'object') {
|
|
8816
|
+
fileReferences.push(...extractFileReferencesFromRemovedData(/** @type {RemovedData} */value));
|
|
8817
|
+
} else if (Array.isArray(value)) {
|
|
8818
|
+
fileReferences.push(...value.map(extractFileReferencesFromRemovedData).flat());
|
|
8819
|
+
} else if (typeof value === 'string' && value.startsWith(FILE_PICKER_FILE_KEY_PREFIX)) {
|
|
8820
|
+
fileReferences.push(value);
|
|
8821
|
+
}
|
|
8822
|
+
});
|
|
8823
|
+
return fileReferences;
|
|
8824
|
+
};
|
|
8825
|
+
|
|
8826
|
+
const fileRegistry = Symbol('fileRegistry');
|
|
8827
|
+
const eventBusSymbol = Symbol('eventBus');
|
|
8828
|
+
const formFieldRegistrySymbol = Symbol('formFieldRegistry');
|
|
8829
|
+
const formFieldInstanceRegistrySymbol = Symbol('formFieldInstanceRegistry');
|
|
8830
|
+
const EMPTY_ARRAY = [];
|
|
8831
|
+
class FileRegistry {
|
|
8832
|
+
/**
|
|
8833
|
+
* @param {import('../core/EventBus').EventBus} eventBus
|
|
8834
|
+
* @param {import('../core/FormFieldRegistry').FormFieldRegistry} formFieldRegistry
|
|
8835
|
+
* @param {import('../core/FormFieldInstanceRegistry').FormFieldInstanceRegistry} formFieldInstanceRegistry
|
|
8836
|
+
*/
|
|
8837
|
+
constructor(eventBus, formFieldRegistry, formFieldInstanceRegistry) {
|
|
8838
|
+
/** @type {Map<string, File[]>} */
|
|
8839
|
+
this[fileRegistry] = new Map();
|
|
8840
|
+
/** @type {import('../core/EventBus').EventBus} */
|
|
8841
|
+
this[eventBusSymbol] = eventBus;
|
|
8842
|
+
/** @type {import('../core/FormFieldRegistry').FormFieldRegistry} */
|
|
8843
|
+
this[formFieldRegistrySymbol] = formFieldRegistry;
|
|
8844
|
+
/** @type {import('../core/FormFieldInstanceRegistry').FormFieldInstanceRegistry} */
|
|
8845
|
+
this[formFieldInstanceRegistrySymbol] = formFieldInstanceRegistry;
|
|
8846
|
+
const removeFileHandler = ({
|
|
8847
|
+
item
|
|
8848
|
+
}) => {
|
|
8849
|
+
const fileReferences = extractFileReferencesFromRemovedData(item);
|
|
8850
|
+
|
|
8851
|
+
// Remove all file references from the registry
|
|
8852
|
+
fileReferences.forEach(fileReference => {
|
|
8853
|
+
this.deleteFiles(fileReference);
|
|
8854
|
+
});
|
|
8855
|
+
};
|
|
8856
|
+
eventBus.on('form.clear', () => this.clear());
|
|
8857
|
+
eventBus.on('conditionChecker.remove', removeFileHandler);
|
|
8858
|
+
eventBus.on('repeatRenderManager.remove', removeFileHandler);
|
|
8859
|
+
}
|
|
8860
|
+
|
|
8861
|
+
/**
|
|
8862
|
+
* @param {string} id
|
|
8863
|
+
* @param {File[]} files
|
|
8864
|
+
*/
|
|
8865
|
+
setFiles(id, files) {
|
|
8866
|
+
this[fileRegistry].set(id, files);
|
|
8867
|
+
}
|
|
8868
|
+
|
|
8869
|
+
/**
|
|
8870
|
+
* @param {string} id
|
|
8871
|
+
* @returns {File[]}
|
|
8872
|
+
*/
|
|
8873
|
+
getFiles(id) {
|
|
8874
|
+
return this[fileRegistry].get(id) || EMPTY_ARRAY;
|
|
8875
|
+
}
|
|
8876
|
+
|
|
8877
|
+
/**
|
|
8878
|
+
* @returns {string[]}
|
|
8879
|
+
*/
|
|
8880
|
+
getKeys() {
|
|
8881
|
+
return Array.from(this[fileRegistry].keys());
|
|
8882
|
+
}
|
|
8883
|
+
|
|
8884
|
+
/**
|
|
8885
|
+
* @param {string} id
|
|
8886
|
+
* @returns {boolean}
|
|
8887
|
+
*/
|
|
8888
|
+
hasKey(id) {
|
|
8889
|
+
return this[fileRegistry].has(id);
|
|
8890
|
+
}
|
|
8891
|
+
|
|
8892
|
+
/**
|
|
8893
|
+
* @param {string} id
|
|
8894
|
+
*/
|
|
8895
|
+
deleteFiles(id) {
|
|
8896
|
+
this[fileRegistry].delete(id);
|
|
8897
|
+
}
|
|
8898
|
+
|
|
8899
|
+
/**
|
|
8900
|
+
* @returns {Map<string, File[]>}
|
|
8901
|
+
*/
|
|
8902
|
+
getAllFiles() {
|
|
8903
|
+
return new Map(this[fileRegistry]);
|
|
8904
|
+
}
|
|
8905
|
+
clear() {
|
|
8906
|
+
this[fileRegistry].clear();
|
|
8907
|
+
}
|
|
8908
|
+
}
|
|
8909
|
+
FileRegistry.$inject = ['eventBus', 'formFieldRegistry', 'formFieldInstanceRegistry'];
|
|
8910
|
+
|
|
8726
8911
|
const RenderModule = {
|
|
8727
8912
|
__init__: ['formFields', 'renderer'],
|
|
8728
8913
|
formFields: ['type', FormFields],
|
|
8729
|
-
renderer: ['type', Renderer]
|
|
8914
|
+
renderer: ['type', Renderer],
|
|
8915
|
+
fileRegistry: ['type', FileRegistry]
|
|
8730
8916
|
};
|
|
8731
8917
|
|
|
8732
8918
|
const CoreModule = {
|
|
@@ -8879,7 +9065,7 @@ class Form {
|
|
|
8879
9065
|
/**
|
|
8880
9066
|
* Submit the form, triggering all field validations.
|
|
8881
9067
|
*
|
|
8882
|
-
* @returns { { data: Data, errors: Errors } }
|
|
9068
|
+
* @returns { { data: Data, errors: Errors, files: Map<string, File[]> } }
|
|
8883
9069
|
*/
|
|
8884
9070
|
submit() {
|
|
8885
9071
|
const {
|
|
@@ -8891,9 +9077,11 @@ class Form {
|
|
|
8891
9077
|
this._emit('presubmit');
|
|
8892
9078
|
const data = this._getSubmitData();
|
|
8893
9079
|
const errors = this.validate();
|
|
9080
|
+
const files = this.get('fileRegistry').getAllFiles();
|
|
8894
9081
|
const result = {
|
|
8895
9082
|
data,
|
|
8896
|
-
errors
|
|
9083
|
+
errors,
|
|
9084
|
+
files
|
|
8897
9085
|
};
|
|
8898
9086
|
this._emit('submit', result);
|
|
8899
9087
|
return result;
|