@bpmn-io/form-js-viewer 1.11.0 → 1.11.1
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 +307 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +307 -119
- 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.cjs
CHANGED
|
@@ -677,6 +677,12 @@ const FormContext = preact.createContext({
|
|
|
677
677
|
formId: null
|
|
678
678
|
});
|
|
679
679
|
|
|
680
|
+
/**
|
|
681
|
+
* @template T
|
|
682
|
+
* @param {string} type
|
|
683
|
+
* @param {boolean} [strict=true]
|
|
684
|
+
* @returns {T | null}
|
|
685
|
+
*/
|
|
680
686
|
function useService(type, strict) {
|
|
681
687
|
const {
|
|
682
688
|
getService
|
|
@@ -755,11 +761,10 @@ function buildExpressionContext(context) {
|
|
|
755
761
|
}
|
|
756
762
|
|
|
757
763
|
/**
|
|
758
|
-
*
|
|
759
|
-
* If the string is not an expression, it is returned as is.
|
|
764
|
+
* If the value is a valid expression, it is evaluated and returned. Otherwise, it is returned as-is.
|
|
760
765
|
*
|
|
761
766
|
* @param {any} expressionLanguage - The expression language to use.
|
|
762
|
-
* @param {
|
|
767
|
+
* @param {any} value - The static value or expression to evaluate.
|
|
763
768
|
* @param {Object} expressionContextInfo - The context information to use.
|
|
764
769
|
* @returns {any} - Evaluated value or the original value if not an expression.
|
|
765
770
|
*/
|
|
@@ -890,11 +895,10 @@ function _isAllowedValue(value) {
|
|
|
890
895
|
}
|
|
891
896
|
|
|
892
897
|
/**
|
|
893
|
-
*
|
|
894
|
-
* If the string is not an expression, it is returned as is.
|
|
898
|
+
* If the value is a valid expression, it is evaluated and returned. Otherwise, it is returned as-is.
|
|
895
899
|
* The function is memoized to minimize re-renders.
|
|
896
900
|
*
|
|
897
|
-
* @param {
|
|
901
|
+
* @param {any} value - A static value or expression to evaluate.
|
|
898
902
|
* @returns {any} - Evaluated value or the original value if not an expression.
|
|
899
903
|
*/
|
|
900
904
|
function useExpressionEvaluation(value) {
|
|
@@ -1135,7 +1139,7 @@ function _isElementScrollable(el) {
|
|
|
1135
1139
|
}
|
|
1136
1140
|
|
|
1137
1141
|
const EMPTY_OBJECT = {};
|
|
1138
|
-
const EMPTY_ARRAY = [];
|
|
1142
|
+
const EMPTY_ARRAY$2 = [];
|
|
1139
1143
|
|
|
1140
1144
|
/**
|
|
1141
1145
|
* Custom hook to scroll an element within a scrollable container.
|
|
@@ -1151,7 +1155,7 @@ const EMPTY_ARRAY = [];
|
|
|
1151
1155
|
*/
|
|
1152
1156
|
function useScrollIntoView(scrolledElementRef, deps, scrollOptions, flagRefs) {
|
|
1153
1157
|
const _scrollOptions = scrollOptions || EMPTY_OBJECT;
|
|
1154
|
-
const _flagRefs = flagRefs || EMPTY_ARRAY;
|
|
1158
|
+
const _flagRefs = flagRefs || EMPTY_ARRAY$2;
|
|
1155
1159
|
hooks.useEffect(() => {
|
|
1156
1160
|
// return early if flags are not raised, or component is not mounted
|
|
1157
1161
|
if (minDash.some(_flagRefs, ref => !ref.current) || !scrolledElementRef.current) {
|
|
@@ -1205,6 +1209,23 @@ function _getTopOffset(item, scrollContainer, options) {
|
|
|
1205
1209
|
return 0;
|
|
1206
1210
|
}
|
|
1207
1211
|
|
|
1212
|
+
/**
|
|
1213
|
+
* If the value is a valid expression, we evaluate it. Otherwise, we continue with the value as-is.
|
|
1214
|
+
* If the resulting value isn't a boolean, we return 'false'
|
|
1215
|
+
* The function is memoized to minimize re-renders.
|
|
1216
|
+
*
|
|
1217
|
+
* @param {boolean | string} value - A static boolean or expression to evaluate.
|
|
1218
|
+
* @returns {boolean} - Evaluated boolean result.
|
|
1219
|
+
*/
|
|
1220
|
+
function useBooleanExpressionEvaluation(value) {
|
|
1221
|
+
const expressionLanguage = useService('expressionLanguage');
|
|
1222
|
+
const expressionContextInfo = hooks.useContext(LocalExpressionContext);
|
|
1223
|
+
return hooks.useMemo(() => {
|
|
1224
|
+
const evaluationResult = runExpressionEvaluation(expressionLanguage, value, expressionContextInfo);
|
|
1225
|
+
return typeof evaluationResult === 'boolean' ? evaluationResult : false;
|
|
1226
|
+
}, [expressionLanguage, expressionContextInfo, value]);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1208
1229
|
/**
|
|
1209
1230
|
* Returns the conditionally filtered data of a form reactively.
|
|
1210
1231
|
* Memoised to minimize re-renders
|
|
@@ -1986,7 +2007,9 @@ Checklist.config = {
|
|
|
1986
2007
|
};
|
|
1987
2008
|
|
|
1988
2009
|
const noop$1 = () => false;
|
|
2010
|
+
const ids$2 = new Ids([32, 36, 1]);
|
|
1989
2011
|
function FormField(props) {
|
|
2012
|
+
const instanceIdRef = hooks.useRef(ids$2.next());
|
|
1990
2013
|
const {
|
|
1991
2014
|
field,
|
|
1992
2015
|
indexes,
|
|
@@ -2032,22 +2055,28 @@ function FormField(props) {
|
|
|
2032
2055
|
// add precedence: global readonly > form field disabled
|
|
2033
2056
|
const disabled = !properties.readOnly && (properties.disabled || field.disabled || false);
|
|
2034
2057
|
const hidden = useCondition(field.conditional && field.conditional.hide || null);
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2058
|
+
const instanceId = hooks.useMemo(() => {
|
|
2059
|
+
if (!formFieldInstanceRegistry) {
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
return formFieldInstanceRegistry.syncInstance(instanceIdRef.current, {
|
|
2063
|
+
id: field.id,
|
|
2064
|
+
expressionContextInfo: localExpressionContext,
|
|
2065
|
+
valuePath,
|
|
2066
|
+
value,
|
|
2067
|
+
indexes,
|
|
2068
|
+
hidden
|
|
2069
|
+
});
|
|
2070
|
+
}, [formFieldInstanceRegistry, field.id, localExpressionContext, valuePath, value, indexes, hidden]);
|
|
2071
|
+
const fieldInstance = instanceId ? formFieldInstanceRegistry.get(instanceId) : null;
|
|
2041
2072
|
|
|
2042
|
-
//
|
|
2073
|
+
// cleanup the instance on unmount
|
|
2043
2074
|
hooks.useEffect(() => {
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
return () =>
|
|
2047
|
-
formFieldInstanceRegistry.remove(instanceId);
|
|
2048
|
-
};
|
|
2075
|
+
const instanceId = instanceIdRef.current;
|
|
2076
|
+
if (formFieldInstanceRegistry) {
|
|
2077
|
+
return () => formFieldInstanceRegistry.cleanupInstance(instanceId);
|
|
2049
2078
|
}
|
|
2050
|
-
}, [
|
|
2079
|
+
}, [formFieldInstanceRegistry]);
|
|
2051
2080
|
|
|
2052
2081
|
// ensures the initial validation behavior can be re-triggered upon form reset
|
|
2053
2082
|
hooks.useEffect(() => {
|
|
@@ -2066,7 +2095,7 @@ function FormField(props) {
|
|
|
2066
2095
|
}, [eventBus, viewerCommands]);
|
|
2067
2096
|
hooks.useEffect(() => {
|
|
2068
2097
|
const hasInitialValue = initialValue && !isEqual(initialValue, []);
|
|
2069
|
-
if (initialValidationTrigger && hasInitialValue) {
|
|
2098
|
+
if (initialValidationTrigger && hasInitialValue && fieldInstance) {
|
|
2070
2099
|
setInitialValidationTrigger(false);
|
|
2071
2100
|
viewerCommands.updateFieldInstanceValidation(fieldInstance, initialValue);
|
|
2072
2101
|
}
|
|
@@ -5686,7 +5715,11 @@ function getHeaderAriaLabel(sortBy, key, label) {
|
|
|
5686
5715
|
return `Click to sort by ${label} ascending`;
|
|
5687
5716
|
}
|
|
5688
5717
|
|
|
5718
|
+
const FILE_PICKER_FILE_KEY_PREFIX = 'files::';
|
|
5719
|
+
|
|
5689
5720
|
const type = 'filepicker';
|
|
5721
|
+
const ids$1 = new Ids();
|
|
5722
|
+
const EMPTY_ARRAY$1 = [];
|
|
5690
5723
|
|
|
5691
5724
|
/**
|
|
5692
5725
|
* @typedef Props
|
|
@@ -5700,7 +5733,8 @@ const type = 'filepicker';
|
|
|
5700
5733
|
* @property {string} field.id
|
|
5701
5734
|
* @property {string} [field.label]
|
|
5702
5735
|
* @property {string} [field.accept]
|
|
5703
|
-
* @property {boolean} [field.multiple]
|
|
5736
|
+
* @property {string|boolean} [field.multiple]
|
|
5737
|
+
* @property {string} [value]
|
|
5704
5738
|
*
|
|
5705
5739
|
* @param {Props} props
|
|
5706
5740
|
* @returns {import("preact").JSX.Element}
|
|
@@ -5708,9 +5742,8 @@ const type = 'filepicker';
|
|
|
5708
5742
|
function FilePicker(props) {
|
|
5709
5743
|
/** @type {import("preact/hooks").Ref<HTMLInputElement>} */
|
|
5710
5744
|
const fileInputRef = hooks.useRef(null);
|
|
5711
|
-
/** @type {
|
|
5712
|
-
const
|
|
5713
|
-
const eventBus = useService('eventBus');
|
|
5745
|
+
/** @type {import('../../FileRegistry').FileRegistry} */
|
|
5746
|
+
const fileRegistry = useService('fileRegistry', false);
|
|
5714
5747
|
const {
|
|
5715
5748
|
field,
|
|
5716
5749
|
onChange,
|
|
@@ -5718,33 +5751,59 @@ function FilePicker(props) {
|
|
|
5718
5751
|
errors = [],
|
|
5719
5752
|
disabled,
|
|
5720
5753
|
readonly,
|
|
5721
|
-
required
|
|
5754
|
+
required,
|
|
5755
|
+
value: filesKey = ''
|
|
5722
5756
|
} = props;
|
|
5723
5757
|
const {
|
|
5724
5758
|
label,
|
|
5725
|
-
multiple =
|
|
5726
|
-
accept = ''
|
|
5727
|
-
id
|
|
5759
|
+
multiple = false,
|
|
5760
|
+
accept = ''
|
|
5728
5761
|
} = field;
|
|
5762
|
+
/** @type {string} */
|
|
5729
5763
|
const evaluatedAccept = useSingleLineTemplateEvaluation(accept);
|
|
5730
|
-
const evaluatedMultiple =
|
|
5764
|
+
const evaluatedMultiple = useBooleanExpressionEvaluation(multiple);
|
|
5731
5765
|
const errorMessageId = `${domId}-error-message`;
|
|
5766
|
+
/** @type {File[]} */
|
|
5767
|
+
const selectedFiles = fileRegistry === null ? EMPTY_ARRAY$1 : fileRegistry.getFiles(filesKey);
|
|
5732
5768
|
hooks.useEffect(() => {
|
|
5733
|
-
|
|
5734
|
-
setSelectedFiles([]);
|
|
5769
|
+
if (filesKey && fileRegistry !== null && !fileRegistry.hasKey(filesKey)) {
|
|
5735
5770
|
onChange({
|
|
5736
5771
|
value: null
|
|
5737
5772
|
});
|
|
5738
|
-
}
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5773
|
+
}
|
|
5774
|
+
}, [fileRegistry, filesKey, onChange, selectedFiles.length]);
|
|
5775
|
+
hooks.useEffect(() => {
|
|
5776
|
+
const data = new DataTransfer();
|
|
5777
|
+
selectedFiles.forEach(file => data.items.add(file));
|
|
5778
|
+
fileInputRef.current.files = data.files;
|
|
5779
|
+
}, [selectedFiles]);
|
|
5780
|
+
|
|
5781
|
+
/**
|
|
5782
|
+
* @type import("preact").JSX.GenericEventHandler<HTMLInputElement>
|
|
5783
|
+
*/
|
|
5784
|
+
const onFileChange = event => {
|
|
5785
|
+
const input = /** @type {HTMLInputElement} */event.target;
|
|
5786
|
+
|
|
5787
|
+
// if we have an associated file key but no files are selected, clear the file key and associated files
|
|
5788
|
+
if ((input.files === null || input.files.length === 0) && filesKey !== '') {
|
|
5789
|
+
fileRegistry.deleteFiles(filesKey);
|
|
5790
|
+
onChange({
|
|
5791
|
+
value: null
|
|
5792
|
+
});
|
|
5793
|
+
return;
|
|
5794
|
+
}
|
|
5795
|
+
const files = Array.from(input.files);
|
|
5796
|
+
|
|
5797
|
+
// ensure fileKey exists
|
|
5798
|
+
const updatedFilesKey = filesKey || ids$1.nextPrefixed(FILE_PICKER_FILE_KEY_PREFIX);
|
|
5799
|
+
fileRegistry.setFiles(updatedFilesKey, files);
|
|
5800
|
+
onChange({
|
|
5801
|
+
value: updatedFilesKey
|
|
5802
|
+
});
|
|
5803
|
+
};
|
|
5804
|
+
const isInputDisabled = disabled || readonly || fileRegistry === null;
|
|
5746
5805
|
return jsxRuntime.jsxs("div", {
|
|
5747
|
-
|
|
5806
|
+
className: formFieldClasses(type, {
|
|
5748
5807
|
errors,
|
|
5749
5808
|
disabled,
|
|
5750
5809
|
readonly
|
|
@@ -5759,29 +5818,17 @@ function FilePicker(props) {
|
|
|
5759
5818
|
ref: fileInputRef,
|
|
5760
5819
|
id: domId,
|
|
5761
5820
|
name: domId,
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
if (input.files === null || input.files.length === 0) {
|
|
5767
|
-
onChange({
|
|
5768
|
-
value: null
|
|
5769
|
-
});
|
|
5770
|
-
return;
|
|
5771
|
-
}
|
|
5772
|
-
const files = Array.from(input.files);
|
|
5773
|
-
onChange({
|
|
5774
|
-
value: `${id}_value_key`
|
|
5775
|
-
});
|
|
5776
|
-
setSelectedFiles(files);
|
|
5777
|
-
}
|
|
5821
|
+
disabled: isInputDisabled,
|
|
5822
|
+
multiple: evaluatedMultiple || undefined,
|
|
5823
|
+
accept: evaluatedAccept || undefined,
|
|
5824
|
+
onChange: onFileChange
|
|
5778
5825
|
}), jsxRuntime.jsxs("div", {
|
|
5779
5826
|
className: "fjs-filepicker-container",
|
|
5780
5827
|
children: [jsxRuntime.jsx("button", {
|
|
5781
5828
|
type: "button",
|
|
5782
|
-
disabled:
|
|
5829
|
+
disabled: isInputDisabled,
|
|
5783
5830
|
readonly: readonly,
|
|
5784
|
-
|
|
5831
|
+
className: "fjs-button fjs-filepicker-button",
|
|
5785
5832
|
onClick: () => {
|
|
5786
5833
|
fileInputRef.current.click();
|
|
5787
5834
|
},
|
|
@@ -5802,11 +5849,6 @@ FilePicker.config = {
|
|
|
5802
5849
|
label: 'File picker',
|
|
5803
5850
|
group: 'basic-input',
|
|
5804
5851
|
emptyValue: null,
|
|
5805
|
-
sanitizeValue: ({
|
|
5806
|
-
value
|
|
5807
|
-
}) => {
|
|
5808
|
-
return value;
|
|
5809
|
-
},
|
|
5810
5852
|
create: (options = {}) => ({
|
|
5811
5853
|
...options
|
|
5812
5854
|
})
|
|
@@ -6203,11 +6245,21 @@ class ConditionChecker {
|
|
|
6203
6245
|
// if we have a hidden repeatable field, and the data structure allows, we clear it directly at the root and stop recursion
|
|
6204
6246
|
if (context.isHidden && isRepeatable) {
|
|
6205
6247
|
context.preventRecursion = true;
|
|
6248
|
+
this._eventBus.fire('conditionChecker.remove', {
|
|
6249
|
+
item: {
|
|
6250
|
+
[field.key]: minDash.get(workingData, getFilterPath(field, indexes))
|
|
6251
|
+
}
|
|
6252
|
+
});
|
|
6206
6253
|
this._cleanlyClearDataAtPath(getFilterPath(field, indexes), workingData);
|
|
6207
6254
|
}
|
|
6208
6255
|
|
|
6209
6256
|
// for simple leaf fields, we always clear
|
|
6210
6257
|
if (context.isHidden && isClosed) {
|
|
6258
|
+
this._eventBus.fire('conditionChecker.remove', {
|
|
6259
|
+
item: {
|
|
6260
|
+
[field.key]: minDash.get(workingData, getFilterPath(field, indexes))
|
|
6261
|
+
}
|
|
6262
|
+
});
|
|
6211
6263
|
this._cleanlyClearDataAtPath(getFilterPath(field, indexes), workingData);
|
|
6212
6264
|
}
|
|
6213
6265
|
});
|
|
@@ -7001,11 +7053,16 @@ var SvgDelete = function SvgDelete(props) {
|
|
|
7001
7053
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
7002
7054
|
|
|
7003
7055
|
class RepeatRenderManager {
|
|
7004
|
-
constructor(form, formFields, formFieldRegistry, pathRegistry) {
|
|
7056
|
+
constructor(form, formFields, formFieldRegistry, pathRegistry, eventBus) {
|
|
7005
7057
|
this._form = form;
|
|
7058
|
+
/** @type {import('../../render/FormFields').FormFields} */
|
|
7006
7059
|
this._formFields = formFields;
|
|
7060
|
+
/** @type {import('../../core/FormFieldRegistry').FormFieldRegistry} */
|
|
7007
7061
|
this._formFieldRegistry = formFieldRegistry;
|
|
7062
|
+
/** @type {import('../../core/PathRegistry').PathRegistry} */
|
|
7008
7063
|
this._pathRegistry = pathRegistry;
|
|
7064
|
+
/** @type {import('../../core/EventBus').EventBus} */
|
|
7065
|
+
this._eventBus = eventBus;
|
|
7009
7066
|
this.Repeater = this.Repeater.bind(this);
|
|
7010
7067
|
this.RepeatFooter = this.RepeatFooter.bind(this);
|
|
7011
7068
|
}
|
|
@@ -7045,11 +7102,18 @@ class RepeatRenderManager {
|
|
|
7045
7102
|
const isCollapsed = collapseEnabled && sharedRepeatState.isCollapsed;
|
|
7046
7103
|
const hasChildren = repeaterField.components && repeaterField.components.length > 0;
|
|
7047
7104
|
const showRemove = repeaterField.allowAddRemove && hasChildren;
|
|
7048
|
-
|
|
7049
|
-
|
|
7105
|
+
|
|
7106
|
+
/**
|
|
7107
|
+
* @param {number} index
|
|
7108
|
+
*/
|
|
7050
7109
|
const onDeleteItem = index => {
|
|
7051
7110
|
const updatedValues = values.slice();
|
|
7052
|
-
updatedValues.splice(index, 1);
|
|
7111
|
+
const removedItem = updatedValues.splice(index, 1)[0];
|
|
7112
|
+
this._eventBus.fire('repeatRenderManager.remove', {
|
|
7113
|
+
dataPath,
|
|
7114
|
+
index,
|
|
7115
|
+
item: removedItem
|
|
7116
|
+
});
|
|
7053
7117
|
props.onChange({
|
|
7054
7118
|
field: repeaterField,
|
|
7055
7119
|
value: updatedValues,
|
|
@@ -7057,21 +7121,13 @@ class RepeatRenderManager {
|
|
|
7057
7121
|
});
|
|
7058
7122
|
};
|
|
7059
7123
|
const parentExpressionContextInfo = hooks.useContext(LocalExpressionContext);
|
|
7060
|
-
return jsxRuntime.
|
|
7061
|
-
children:
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
indexes: indexes,
|
|
7068
|
-
onDeleteItem: onDeleteItem,
|
|
7069
|
-
showRemove: showRemove,
|
|
7070
|
-
...restProps
|
|
7071
|
-
}, itemIndex)), hiddenValues.length > 0 ? jsxRuntime.jsx("div", {
|
|
7072
|
-
className: "fjs-repeat-row-collapsed",
|
|
7073
|
-
children: hiddenValues.map((itemValue, itemIndex) => jsxRuntime.jsx(RepetitionScaffold, {
|
|
7074
|
-
itemIndex: itemIndex + nonCollapsedItems,
|
|
7124
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, {
|
|
7125
|
+
children: values.map((itemValue, itemIndex) => jsxRuntime.jsx("div", {
|
|
7126
|
+
class: classNames({
|
|
7127
|
+
'fjs-repeat-row-collapsed': isCollapsed ? itemIndex >= nonCollapsedItems : false
|
|
7128
|
+
}),
|
|
7129
|
+
children: jsxRuntime.jsx(RepetitionScaffold, {
|
|
7130
|
+
itemIndex: itemIndex,
|
|
7075
7131
|
itemValue: itemValue,
|
|
7076
7132
|
parentExpressionContextInfo: parentExpressionContextInfo,
|
|
7077
7133
|
repeaterField: repeaterField,
|
|
@@ -7080,8 +7136,8 @@ class RepeatRenderManager {
|
|
|
7080
7136
|
onDeleteItem: onDeleteItem,
|
|
7081
7137
|
showRemove: showRemove,
|
|
7082
7138
|
...restProps
|
|
7083
|
-
}
|
|
7084
|
-
})
|
|
7139
|
+
})
|
|
7140
|
+
}))
|
|
7085
7141
|
});
|
|
7086
7142
|
}
|
|
7087
7143
|
RepeatFooter(props) {
|
|
@@ -7124,6 +7180,11 @@ class RepeatRenderManager {
|
|
|
7124
7180
|
});
|
|
7125
7181
|
updatedValues.push(newItem);
|
|
7126
7182
|
shouldScroll.current = true;
|
|
7183
|
+
this._eventBus.fire('repeatRenderManager.add', {
|
|
7184
|
+
dataPath,
|
|
7185
|
+
index: updatedValues.length - 1,
|
|
7186
|
+
item: newItem
|
|
7187
|
+
});
|
|
7127
7188
|
props.onChange({
|
|
7128
7189
|
value: updatedValues
|
|
7129
7190
|
});
|
|
@@ -7156,7 +7217,7 @@ class RepeatRenderManager {
|
|
|
7156
7217
|
class: "fjs-repeat-render-collapse",
|
|
7157
7218
|
onClick: toggle,
|
|
7158
7219
|
children: isCollapsed ? jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
7159
|
-
children: [jsxRuntime.jsx(SvgExpand, {}), " ", `Expand all (${values.length})`]
|
|
7220
|
+
children: [jsxRuntime.jsx(SvgExpand, {}), " ", `Expand all (${values.length - 1})`]
|
|
7160
7221
|
}) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
7161
7222
|
children: [jsxRuntime.jsx(SvgCollapse, {}), " ", 'Collapse']
|
|
7162
7223
|
})
|
|
@@ -7241,7 +7302,7 @@ const RepetitionScaffold = props => {
|
|
|
7241
7302
|
})]
|
|
7242
7303
|
});
|
|
7243
7304
|
};
|
|
7244
|
-
RepeatRenderManager.$inject = ['form', 'formFields', 'formFieldRegistry', 'pathRegistry'];
|
|
7305
|
+
RepeatRenderManager.$inject = ['form', 'formFields', 'formFieldRegistry', 'pathRegistry', 'eventBus'];
|
|
7245
7306
|
|
|
7246
7307
|
const RepeatRenderModule = {
|
|
7247
7308
|
__init__: ['repeatRenderManager'],
|
|
@@ -8631,39 +8692,52 @@ class FormFieldInstanceRegistry {
|
|
|
8631
8692
|
this._formFieldInstances = {};
|
|
8632
8693
|
eventBus.on('form.clear', () => this.clear());
|
|
8633
8694
|
}
|
|
8634
|
-
|
|
8695
|
+
syncInstance(instanceId, formFieldInfo) {
|
|
8635
8696
|
const {
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8697
|
+
hidden,
|
|
8698
|
+
...restInfo
|
|
8699
|
+
} = formFieldInfo;
|
|
8700
|
+
const isInstanceExpected = !hidden;
|
|
8701
|
+
const doesInstanceExist = this._formFieldInstances[instanceId];
|
|
8702
|
+
if (isInstanceExpected && !doesInstanceExist) {
|
|
8703
|
+
this._formFieldInstances[instanceId] = {
|
|
8704
|
+
instanceId,
|
|
8705
|
+
...restInfo
|
|
8706
|
+
};
|
|
8707
|
+
this._eventBus.fire('formFieldInstance.added', {
|
|
8708
|
+
instanceId
|
|
8709
|
+
});
|
|
8710
|
+
} else if (!isInstanceExpected && doesInstanceExist) {
|
|
8711
|
+
delete this._formFieldInstances[instanceId];
|
|
8712
|
+
this._eventBus.fire('formFieldInstance.removed', {
|
|
8713
|
+
instanceId
|
|
8714
|
+
});
|
|
8715
|
+
} else if (isInstanceExpected && doesInstanceExist) {
|
|
8716
|
+
const wasInstanceChanged = Object.keys(restInfo).some(key => {
|
|
8717
|
+
return this._formFieldInstances[instanceId][key] !== restInfo[key];
|
|
8718
|
+
});
|
|
8719
|
+
if (wasInstanceChanged) {
|
|
8720
|
+
this._formFieldInstances[instanceId] = {
|
|
8721
|
+
instanceId,
|
|
8722
|
+
...restInfo
|
|
8723
|
+
};
|
|
8724
|
+
this._eventBus.fire('formFieldInstance.changed', {
|
|
8725
|
+
instanceId
|
|
8726
|
+
});
|
|
8727
|
+
}
|
|
8644
8728
|
}
|
|
8645
|
-
this._formFieldInstances[instanceId] = {
|
|
8646
|
-
id,
|
|
8647
|
-
instanceId,
|
|
8648
|
-
expressionContextInfo,
|
|
8649
|
-
valuePath,
|
|
8650
|
-
indexes
|
|
8651
|
-
};
|
|
8652
|
-
this._eventBus.fire('formFieldInstanceRegistry.changed', {
|
|
8653
|
-
instanceId,
|
|
8654
|
-
action: 'added'
|
|
8655
|
-
});
|
|
8656
8729
|
return instanceId;
|
|
8657
8730
|
}
|
|
8658
|
-
|
|
8659
|
-
if (
|
|
8660
|
-
|
|
8731
|
+
cleanupInstance(instanceId) {
|
|
8732
|
+
if (this._formFieldInstances[instanceId]) {
|
|
8733
|
+
delete this._formFieldInstances[instanceId];
|
|
8734
|
+
this._eventBus.fire('formFieldInstance.removed', {
|
|
8735
|
+
instanceId
|
|
8736
|
+
});
|
|
8661
8737
|
}
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
action: 'removed'
|
|
8666
|
-
});
|
|
8738
|
+
}
|
|
8739
|
+
get(instanceId) {
|
|
8740
|
+
return this._formFieldInstances[instanceId];
|
|
8667
8741
|
}
|
|
8668
8742
|
getAll() {
|
|
8669
8743
|
return Object.values(this._formFieldInstances);
|
|
@@ -8743,10 +8817,122 @@ function Renderer(config, eventBus, form, injector) {
|
|
|
8743
8817
|
}
|
|
8744
8818
|
Renderer.$inject = ['config.renderer', 'eventBus', 'form', 'injector'];
|
|
8745
8819
|
|
|
8820
|
+
/**
|
|
8821
|
+
* @typedef {Record<PropertyKey, unknown>} RemovedData
|
|
8822
|
+
* @param {RemovedData} removedData
|
|
8823
|
+
* @returns {string[]}
|
|
8824
|
+
*/
|
|
8825
|
+
const extractFileReferencesFromRemovedData = removedData => {
|
|
8826
|
+
/** @type {string[]} */
|
|
8827
|
+
const fileReferences = [];
|
|
8828
|
+
if (removedData === null) {
|
|
8829
|
+
return fileReferences;
|
|
8830
|
+
}
|
|
8831
|
+
Object.values(removedData).forEach(value => {
|
|
8832
|
+
if (value === null) {
|
|
8833
|
+
return;
|
|
8834
|
+
}
|
|
8835
|
+
if (typeof value === 'object') {
|
|
8836
|
+
fileReferences.push(...extractFileReferencesFromRemovedData(/** @type {RemovedData} */value));
|
|
8837
|
+
} else if (Array.isArray(value)) {
|
|
8838
|
+
fileReferences.push(...value.map(extractFileReferencesFromRemovedData).flat());
|
|
8839
|
+
} else if (typeof value === 'string' && value.startsWith(FILE_PICKER_FILE_KEY_PREFIX)) {
|
|
8840
|
+
fileReferences.push(value);
|
|
8841
|
+
}
|
|
8842
|
+
});
|
|
8843
|
+
return fileReferences;
|
|
8844
|
+
};
|
|
8845
|
+
|
|
8846
|
+
const fileRegistry = Symbol('fileRegistry');
|
|
8847
|
+
const eventBusSymbol = Symbol('eventBus');
|
|
8848
|
+
const formFieldRegistrySymbol = Symbol('formFieldRegistry');
|
|
8849
|
+
const formFieldInstanceRegistrySymbol = Symbol('formFieldInstanceRegistry');
|
|
8850
|
+
const EMPTY_ARRAY = [];
|
|
8851
|
+
class FileRegistry {
|
|
8852
|
+
/**
|
|
8853
|
+
* @param {import('../core/EventBus').EventBus} eventBus
|
|
8854
|
+
* @param {import('../core/FormFieldRegistry').FormFieldRegistry} formFieldRegistry
|
|
8855
|
+
* @param {import('../core/FormFieldInstanceRegistry').FormFieldInstanceRegistry} formFieldInstanceRegistry
|
|
8856
|
+
*/
|
|
8857
|
+
constructor(eventBus, formFieldRegistry, formFieldInstanceRegistry) {
|
|
8858
|
+
/** @type {Map<string, File[]>} */
|
|
8859
|
+
this[fileRegistry] = new Map();
|
|
8860
|
+
/** @type {import('../core/EventBus').EventBus} */
|
|
8861
|
+
this[eventBusSymbol] = eventBus;
|
|
8862
|
+
/** @type {import('../core/FormFieldRegistry').FormFieldRegistry} */
|
|
8863
|
+
this[formFieldRegistrySymbol] = formFieldRegistry;
|
|
8864
|
+
/** @type {import('../core/FormFieldInstanceRegistry').FormFieldInstanceRegistry} */
|
|
8865
|
+
this[formFieldInstanceRegistrySymbol] = formFieldInstanceRegistry;
|
|
8866
|
+
const removeFileHandler = ({
|
|
8867
|
+
item
|
|
8868
|
+
}) => {
|
|
8869
|
+
const fileReferences = extractFileReferencesFromRemovedData(item);
|
|
8870
|
+
|
|
8871
|
+
// Remove all file references from the registry
|
|
8872
|
+
fileReferences.forEach(fileReference => {
|
|
8873
|
+
this.deleteFiles(fileReference);
|
|
8874
|
+
});
|
|
8875
|
+
};
|
|
8876
|
+
eventBus.on('form.clear', () => this.clear());
|
|
8877
|
+
eventBus.on('conditionChecker.remove', removeFileHandler);
|
|
8878
|
+
eventBus.on('repeatRenderManager.remove', removeFileHandler);
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8881
|
+
/**
|
|
8882
|
+
* @param {string} id
|
|
8883
|
+
* @param {File[]} files
|
|
8884
|
+
*/
|
|
8885
|
+
setFiles(id, files) {
|
|
8886
|
+
this[fileRegistry].set(id, files);
|
|
8887
|
+
}
|
|
8888
|
+
|
|
8889
|
+
/**
|
|
8890
|
+
* @param {string} id
|
|
8891
|
+
* @returns {File[]}
|
|
8892
|
+
*/
|
|
8893
|
+
getFiles(id) {
|
|
8894
|
+
return this[fileRegistry].get(id) || EMPTY_ARRAY;
|
|
8895
|
+
}
|
|
8896
|
+
|
|
8897
|
+
/**
|
|
8898
|
+
* @returns {string[]}
|
|
8899
|
+
*/
|
|
8900
|
+
getKeys() {
|
|
8901
|
+
return Array.from(this[fileRegistry].keys());
|
|
8902
|
+
}
|
|
8903
|
+
|
|
8904
|
+
/**
|
|
8905
|
+
* @param {string} id
|
|
8906
|
+
* @returns {boolean}
|
|
8907
|
+
*/
|
|
8908
|
+
hasKey(id) {
|
|
8909
|
+
return this[fileRegistry].has(id);
|
|
8910
|
+
}
|
|
8911
|
+
|
|
8912
|
+
/**
|
|
8913
|
+
* @param {string} id
|
|
8914
|
+
*/
|
|
8915
|
+
deleteFiles(id) {
|
|
8916
|
+
this[fileRegistry].delete(id);
|
|
8917
|
+
}
|
|
8918
|
+
|
|
8919
|
+
/**
|
|
8920
|
+
* @returns {Map<string, File[]>}
|
|
8921
|
+
*/
|
|
8922
|
+
getAllFiles() {
|
|
8923
|
+
return new Map(this[fileRegistry]);
|
|
8924
|
+
}
|
|
8925
|
+
clear() {
|
|
8926
|
+
this[fileRegistry].clear();
|
|
8927
|
+
}
|
|
8928
|
+
}
|
|
8929
|
+
FileRegistry.$inject = ['eventBus', 'formFieldRegistry', 'formFieldInstanceRegistry'];
|
|
8930
|
+
|
|
8746
8931
|
const RenderModule = {
|
|
8747
8932
|
__init__: ['formFields', 'renderer'],
|
|
8748
8933
|
formFields: ['type', FormFields],
|
|
8749
|
-
renderer: ['type', Renderer]
|
|
8934
|
+
renderer: ['type', Renderer],
|
|
8935
|
+
fileRegistry: ['type', FileRegistry]
|
|
8750
8936
|
};
|
|
8751
8937
|
|
|
8752
8938
|
const CoreModule = {
|
|
@@ -8899,7 +9085,7 @@ class Form {
|
|
|
8899
9085
|
/**
|
|
8900
9086
|
* Submit the form, triggering all field validations.
|
|
8901
9087
|
*
|
|
8902
|
-
* @returns { { data: Data, errors: Errors } }
|
|
9088
|
+
* @returns { { data: Data, errors: Errors, files: Map<string, File[]> } }
|
|
8903
9089
|
*/
|
|
8904
9090
|
submit() {
|
|
8905
9091
|
const {
|
|
@@ -8911,9 +9097,11 @@ class Form {
|
|
|
8911
9097
|
this._emit('presubmit');
|
|
8912
9098
|
const data = this._getSubmitData();
|
|
8913
9099
|
const errors = this.validate();
|
|
9100
|
+
const files = this.get('fileRegistry').getAllFiles();
|
|
8914
9101
|
const result = {
|
|
8915
9102
|
data,
|
|
8916
|
-
errors
|
|
9103
|
+
errors,
|
|
9104
|
+
files
|
|
8917
9105
|
};
|
|
8918
9106
|
this._emit('submit', result);
|
|
8919
9107
|
return result;
|