@evoke-platform/ui-components 1.0.0-dev.217 → 1.0.0-dev.218
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/published/components/custom/Form/Common/Form.d.ts +38 -0
- package/dist/published/components/custom/Form/Common/Form.js +413 -0
- package/dist/published/components/custom/Form/Common/FormComponentWrapper.d.ts +26 -0
- package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +79 -0
- package/dist/published/components/custom/Form/Common/index.d.ts +2 -0
- package/dist/published/components/custom/Form/Common/index.js +2 -0
- package/dist/published/components/custom/Form/FormComponents/ButtonComponent.d.ts +37 -0
- package/dist/published/components/custom/Form/FormComponents/ButtonComponent.js +150 -0
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.d.ts +17 -0
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +80 -0
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.d.ts +23 -0
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.js +154 -0
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.d.ts +15 -0
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +172 -0
- package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.d.ts +41 -0
- package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.js +409 -0
- package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.d.ts +15 -0
- package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.js +111 -0
- package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.d.ts +23 -0
- package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.js +112 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/InstanceLookup.d.ts +20 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/InstanceLookup.js +229 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.d.ts +34 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js +150 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.d.ts +3 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +306 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.d.ts +24 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +126 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +21 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +96 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.d.ts +15 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.js +158 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.d.ts +39 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.js +89 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.d.ts +12 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +369 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.d.ts +20 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +57 -0
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserComponent.d.ts +26 -0
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserComponent.js +99 -0
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.d.ts +23 -0
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.js +115 -0
- package/dist/published/components/custom/Form/FormComponents/ViewOnlyComponent.d.ts +20 -0
- package/dist/published/components/custom/Form/FormComponents/ViewOnlyComponent.js +83 -0
- package/dist/published/components/custom/Form/FormComponents/index.d.ts +8 -0
- package/dist/published/components/custom/Form/FormComponents/index.js +8 -0
- package/dist/published/components/custom/Form/index.d.ts +3 -0
- package/dist/published/components/custom/Form/index.js +3 -0
- package/dist/published/components/custom/Form/types.d.ts +109 -0
- package/dist/published/components/custom/Form/types.js +1 -0
- package/dist/published/components/custom/Form/utils.d.ts +45 -0
- package/dist/published/components/custom/Form/utils.js +1036 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +1 -1
- package/dist/published/index.js +1 -1
- package/dist/published/styles/form-component.css +152 -0
- package/package.json +18 -5
@@ -0,0 +1,409 @@
|
|
1
|
+
import { ReactComponent } from '@formio/react';
|
2
|
+
import Handlebars from 'handlebars';
|
3
|
+
import { isArray, isEmpty, isNil } from 'lodash';
|
4
|
+
import { DateTime } from 'luxon';
|
5
|
+
import React from 'react';
|
6
|
+
import ReactDOM from 'react-dom';
|
7
|
+
import { FormField } from '../../../custom';
|
8
|
+
import { FormComponentWrapper } from '../Common';
|
9
|
+
import { isPropertyVisible } from '../utils';
|
10
|
+
Handlebars.registerHelper('addDays', function (addend1, addend2) {
|
11
|
+
const dateAddend1 = DateTime.fromISO(addend1);
|
12
|
+
if (dateAddend1.isValid) {
|
13
|
+
return dateAddend1.plus({ days: addend2 }).toISODate();
|
14
|
+
}
|
15
|
+
return undefined;
|
16
|
+
});
|
17
|
+
Handlebars.registerHelper('subDays', function (minuend, subtrahend) {
|
18
|
+
const dateMinuend = DateTime.fromISO(minuend);
|
19
|
+
if (dateMinuend.isValid) {
|
20
|
+
return dateMinuend.minus({ days: subtrahend }).toISODate();
|
21
|
+
}
|
22
|
+
return undefined;
|
23
|
+
});
|
24
|
+
export class FormFieldComponent extends ReactComponent {
|
25
|
+
/**
|
26
|
+
* Called when the component has been instantiated. This is useful to define
|
27
|
+
* default instance variable values.
|
28
|
+
*
|
29
|
+
* @param component - The JSON representation of the component created.
|
30
|
+
* @param options - The global options for the renderer
|
31
|
+
* @param data - The contextual data object (model) used for this component.
|
32
|
+
*/
|
33
|
+
constructor(component, options, data) {
|
34
|
+
var _a;
|
35
|
+
const { property } = component;
|
36
|
+
component.property = Object.assign(Object.assign({}, component.property), { type: ((property === null || property === void 0 ? void 0 : property.type) === 'string' && (property === null || property === void 0 ? void 0 : property.enum)) || component.type === 'Select'
|
37
|
+
? 'choices'
|
38
|
+
: property === null || property === void 0 ? void 0 : property.type });
|
39
|
+
let selectOptions = [];
|
40
|
+
if (!isEmpty(component.data)) {
|
41
|
+
selectOptions = component.data.values;
|
42
|
+
}
|
43
|
+
else if (property === null || property === void 0 ? void 0 : property.enum) {
|
44
|
+
selectOptions = (_a = property === null || property === void 0 ? void 0 : property.enum) === null || _a === void 0 ? void 0 : _a.map((val) => ({ label: val, value: val }));
|
45
|
+
}
|
46
|
+
else if (component.type === 'Select') {
|
47
|
+
selectOptions = [
|
48
|
+
{ label: 'Portal', value: 'Portal' },
|
49
|
+
{ label: 'Private', value: 'Private' },
|
50
|
+
{ label: 'Public', value: 'Public' },
|
51
|
+
];
|
52
|
+
}
|
53
|
+
super(Object.assign(Object.assign({}, component), { hideLabel: true, selectOptions, inputMaskPlaceholderChar: component.inputMaskPlaceholderChar || '_' }), options, data);
|
54
|
+
this.handleComponentChange = (components, value) => {
|
55
|
+
if (isArray(components)) {
|
56
|
+
if (components.filter((component) => Object.hasOwnProperty.call(component, 'components'))) {
|
57
|
+
components
|
58
|
+
.filter((component) => Object.hasOwnProperty.call(component, 'components'))
|
59
|
+
.forEach((comp) => {
|
60
|
+
this.handleComponentChange(comp.components, value);
|
61
|
+
});
|
62
|
+
}
|
63
|
+
components
|
64
|
+
.filter((comp) => [
|
65
|
+
`${this.component.addressPropertyId}.city`,
|
66
|
+
`${this.component.addressPropertyId}.county`,
|
67
|
+
`${this.component.addressPropertyId}.state`,
|
68
|
+
`${this.component.addressPropertyId}.zipCode`,
|
69
|
+
].includes(comp.key))
|
70
|
+
.forEach((comp) => {
|
71
|
+
this.emit('changed-' + comp.key, value[comp.key.replace(`${this.component.addressPropertyId}.`, '')]);
|
72
|
+
});
|
73
|
+
}
|
74
|
+
};
|
75
|
+
this.handleChange = (key, value) => {
|
76
|
+
let selectedValue, label;
|
77
|
+
if (this.component.isAddressLine1) {
|
78
|
+
if (typeof value === 'string') {
|
79
|
+
selectedValue = value;
|
80
|
+
label = value;
|
81
|
+
}
|
82
|
+
else {
|
83
|
+
selectedValue = value.line1;
|
84
|
+
label = value.line1;
|
85
|
+
this.handleComponentChange(this.root.components, value);
|
86
|
+
this.root.components
|
87
|
+
.filter((component) => Object.prototype.hasOwnProperty.call(component, 'components'))
|
88
|
+
.forEach((section) => {
|
89
|
+
this.handleComponentChange(section.components, value);
|
90
|
+
});
|
91
|
+
}
|
92
|
+
}
|
93
|
+
else if (this.component.property.type === 'choices' || this.component.property.type === 'array') {
|
94
|
+
selectedValue =
|
95
|
+
typeof value === 'object' && value !== null && Object.prototype.hasOwnProperty.call(value, 'value')
|
96
|
+
? value.value
|
97
|
+
: value;
|
98
|
+
label = selectedValue;
|
99
|
+
}
|
100
|
+
else {
|
101
|
+
selectedValue =
|
102
|
+
typeof value === 'object' && value !== null && Object.prototype.hasOwnProperty.call(value, 'value')
|
103
|
+
? value.value
|
104
|
+
: value;
|
105
|
+
label =
|
106
|
+
typeof value === 'object' && value !== null && Object.prototype.hasOwnProperty.call(value, 'label')
|
107
|
+
? value.label
|
108
|
+
: value;
|
109
|
+
}
|
110
|
+
this.setValue(selectedValue !== null && selectedValue !== void 0 ? selectedValue : '');
|
111
|
+
this.updateValue(label, { modified: true });
|
112
|
+
this.handleValidation(selectedValue);
|
113
|
+
!['TextField', 'Decimal', 'Integer'].includes(this.component.type) &&
|
114
|
+
this.emit('changed-' + this.component.key, label);
|
115
|
+
this.attach(this.element);
|
116
|
+
if (this.component.isAddressLine1 &&
|
117
|
+
this.component.addressPropertyId &&
|
118
|
+
typeof value === 'object' &&
|
119
|
+
this.component.autoSave) {
|
120
|
+
this.component.autoSave({
|
121
|
+
[this.component.addressPropertyId]: this.root.data[this.component.addressPropertyId],
|
122
|
+
});
|
123
|
+
}
|
124
|
+
if (!['TextField', 'Decimal', 'Integer'].includes(this.component.type) && this.component.autoSave) {
|
125
|
+
if (key && isEmpty(this.errorDetails)) {
|
126
|
+
this.component.autoSave({ [key]: selectedValue });
|
127
|
+
}
|
128
|
+
else if (this.component.key && isEmpty(this.errorDetails)) {
|
129
|
+
this.component.autoSave(this.root.data);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
};
|
133
|
+
this.errorDetails = {};
|
134
|
+
this.handleChange = this.handleChange.bind(this);
|
135
|
+
}
|
136
|
+
init() {
|
137
|
+
var _a, _b, _c, _d;
|
138
|
+
this.on('changed-' + this.component.conditional.when, (value) => {
|
139
|
+
//set default value when conditional field is shown
|
140
|
+
if (this.component.defaultValue && value === this.component.conditional.eq) {
|
141
|
+
this.setValue(this.component.defaultValue);
|
142
|
+
this.updateValue(this.component.defaultValue, { modified: true });
|
143
|
+
}
|
144
|
+
//clear data and errors when a true conditional field is hidden
|
145
|
+
if (value !== this.component.conditional.eq) {
|
146
|
+
this.setValue('');
|
147
|
+
this.updateValue('', { modified: true });
|
148
|
+
this.clearErrors();
|
149
|
+
super.detach();
|
150
|
+
}
|
151
|
+
});
|
152
|
+
if (this.component.key.includes('.city') ||
|
153
|
+
this.component.key.includes('.county') ||
|
154
|
+
this.component.key.includes('.state') ||
|
155
|
+
this.component.key.includes('.zipCode')) {
|
156
|
+
this.on('changed-' + this.component.key, (value) => {
|
157
|
+
this.setValue(value !== null && value !== void 0 ? value : '');
|
158
|
+
this.updateValue(value, { modified: true });
|
159
|
+
this.attach(this.element);
|
160
|
+
});
|
161
|
+
}
|
162
|
+
if (this.component.type === 'Date') {
|
163
|
+
const inputProps = [];
|
164
|
+
// When date validation use handlebars. i.e {{input.datePropertyId}} or {{addDays input.datePropertyId 10}}
|
165
|
+
// We need to retrieve the property id to create a listener when those properties changed
|
166
|
+
// Then revalidate the current property.
|
167
|
+
if (((_a = this.component.validate) === null || _a === void 0 ? void 0 : _a.minDate) && /^{{.*}}$/.test((_b = this.component.validate) === null || _b === void 0 ? void 0 : _b.minDate)) {
|
168
|
+
const matches = this.component.validate.minDate.match(/^{{.*input\.([a-z][a-zA-Z0-9_]*).*}}$/);
|
169
|
+
if ((matches === null || matches === void 0 ? void 0 : matches[1]) && !inputProps.includes(matches[1])) {
|
170
|
+
inputProps.push(matches[1]);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
if (((_c = this.component.validate) === null || _c === void 0 ? void 0 : _c.maxDate) && /^{{.*}}$/.test((_d = this.component.validate) === null || _d === void 0 ? void 0 : _d.maxDate)) {
|
174
|
+
const matches = this.component.validate.maxDate.match(/^{{.*input\.([a-z][a-zA-Z0-9_]*).*}}$/);
|
175
|
+
if ((matches === null || matches === void 0 ? void 0 : matches[1]) && !inputProps.includes(matches[1])) {
|
176
|
+
inputProps.push(matches[1]);
|
177
|
+
}
|
178
|
+
}
|
179
|
+
for (const inputProp of inputProps) {
|
180
|
+
this.on(`changed-${inputProp}`, (value) => {
|
181
|
+
var _a;
|
182
|
+
if (this.dataValue) {
|
183
|
+
this.handleValidation(this.dataValue);
|
184
|
+
this.setValue((_a = this.dataValue) !== null && _a !== void 0 ? _a : '');
|
185
|
+
this.updateValue(this.dataValue, { modified: true });
|
186
|
+
this.attach(this.element);
|
187
|
+
}
|
188
|
+
});
|
189
|
+
}
|
190
|
+
}
|
191
|
+
this.on(`error-${this.component.key}`, (details) => {
|
192
|
+
const error = details.find((detail) => detail.code === 'errorMessage');
|
193
|
+
if (error) {
|
194
|
+
if (!this.root.customErrors.find((err) => err.formattedKeyOrPath === this.component.key && err.message === error.message)) {
|
195
|
+
this.root.customErrors = [
|
196
|
+
...this.root.customErrors,
|
197
|
+
Object.assign(Object.assign({}, error), { component: this.component, formattedKeyOrPath: this.component.key }),
|
198
|
+
];
|
199
|
+
}
|
200
|
+
this.errorDetails['date'] = error === null || error === void 0 ? void 0 : error.message;
|
201
|
+
this.attach(this.element);
|
202
|
+
this.errorDetails = {};
|
203
|
+
}
|
204
|
+
});
|
205
|
+
}
|
206
|
+
clearErrors() {
|
207
|
+
this.errorDetails = {};
|
208
|
+
this.root.customErrors = this.root.customErrors.filter((error) => error.formattedKeyOrPath !== this.component.key);
|
209
|
+
}
|
210
|
+
handleValidation(value) {
|
211
|
+
var _a;
|
212
|
+
const { validate } = this.component;
|
213
|
+
if (!isPropertyVisible(this.component.conditional, this.root.data)) {
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
const emptyMask = this.component.inputMaskPlaceholderChar &&
|
217
|
+
((_a = this.component.inputMask) === null || _a === void 0 ? void 0 : _a.replaceAll('9', this.component.inputMaskPlaceholderChar).replaceAll('a', this.component.inputMaskPlaceholderChar).replaceAll('*', this.component.inputMaskPlaceholderChar));
|
218
|
+
const emptyField = value === undefined || value === null || value === '' || isNil(value) || value === emptyMask;
|
219
|
+
if (emptyField &&
|
220
|
+
!validate.required &&
|
221
|
+
this.component.type !== 'Date' &&
|
222
|
+
this.component.type !== 'DateTime' &&
|
223
|
+
isEmpty(validate.regexes) &&
|
224
|
+
!(validate.min || validate.max)) {
|
225
|
+
delete this.errorDetails['rootError'];
|
226
|
+
return;
|
227
|
+
}
|
228
|
+
if (this.component.type == 'Date' && value && typeof value === 'object' && value['invalid']) {
|
229
|
+
this.errorDetails['invalidDate'] = 'Invalid Date';
|
230
|
+
}
|
231
|
+
else {
|
232
|
+
delete this.errorDetails['invalidDate'];
|
233
|
+
}
|
234
|
+
if (this.component.type == 'DateTime' && value && typeof value === 'object' && value['invalid']) {
|
235
|
+
this.errorDetails['invalidDateTime'] = 'Invalid Date Time';
|
236
|
+
}
|
237
|
+
else {
|
238
|
+
delete this.errorDetails['invalidDateTime'];
|
239
|
+
}
|
240
|
+
if (this.component.type == 'Time' && value && typeof value === 'object' && value['invalid']) {
|
241
|
+
this.errorDetails['invalidTime'] = 'Invalid Time';
|
242
|
+
}
|
243
|
+
else {
|
244
|
+
delete this.errorDetails['invalidTime'];
|
245
|
+
}
|
246
|
+
if (!isEmpty(validate.regexes) && !emptyField) {
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
248
|
+
const regexes = validate.regexes;
|
249
|
+
const failedRules = regexes.filter((rule) => {
|
250
|
+
if (!RegExp(rule.regex).test(value)) {
|
251
|
+
return true;
|
252
|
+
}
|
253
|
+
return false;
|
254
|
+
});
|
255
|
+
if (failedRules.length === 0) {
|
256
|
+
delete this.errorDetails['regexes'];
|
257
|
+
}
|
258
|
+
else {
|
259
|
+
if (validate.operator === 'all') {
|
260
|
+
this.errorDetails['regexes'] =
|
261
|
+
this.component.label +
|
262
|
+
': ' +
|
263
|
+
failedRules
|
264
|
+
.map((rule) => rule.errorMessage)
|
265
|
+
.join(' and ');
|
266
|
+
}
|
267
|
+
else if (validate.operator === 'any') {
|
268
|
+
if (regexes.length > failedRules.length) {
|
269
|
+
delete this.errorDetails['regexes'];
|
270
|
+
}
|
271
|
+
else {
|
272
|
+
this.errorDetails['regexes'] =
|
273
|
+
this.component.label +
|
274
|
+
': ' +
|
275
|
+
failedRules
|
276
|
+
.map((rule) => rule.errorMessage)
|
277
|
+
.join(' or ');
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
else {
|
283
|
+
delete this.errorDetails['regexes'];
|
284
|
+
}
|
285
|
+
if (this.component.validate.minDate || this.component.validate.maxDate) {
|
286
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
287
|
+
const data = {
|
288
|
+
input: Object.assign({}, this.root.data),
|
289
|
+
__today__: DateTime.now().toISODate(),
|
290
|
+
};
|
291
|
+
let { minDate, maxDate } = validate;
|
292
|
+
if (minDate && !dateRegex.test(minDate)) {
|
293
|
+
try {
|
294
|
+
minDate = Handlebars.compile(minDate)(data);
|
295
|
+
}
|
296
|
+
catch (err) {
|
297
|
+
console.log(err);
|
298
|
+
}
|
299
|
+
}
|
300
|
+
if (maxDate && !dateRegex.test(maxDate)) {
|
301
|
+
try {
|
302
|
+
maxDate = Handlebars.compile(maxDate)(data);
|
303
|
+
}
|
304
|
+
catch (err) {
|
305
|
+
console.log(err);
|
306
|
+
}
|
307
|
+
}
|
308
|
+
if ((minDate && value < minDate) || (maxDate && value > maxDate)) {
|
309
|
+
this.errorDetails['date'] = validate.customMessage;
|
310
|
+
}
|
311
|
+
else {
|
312
|
+
delete this.errorDetails['date'];
|
313
|
+
}
|
314
|
+
}
|
315
|
+
if (this.component.validate.minTime || this.component.validate.maxTime) {
|
316
|
+
if ((validate.minTime && value < validate.minTime) || (validate.maxTime && value > validate.maxTime)) {
|
317
|
+
this.errorDetails['time'] = validate.customMessage;
|
318
|
+
}
|
319
|
+
else {
|
320
|
+
delete this.errorDetails['time'];
|
321
|
+
}
|
322
|
+
}
|
323
|
+
if (validate.min || validate.max) {
|
324
|
+
if (!isNil(value) &&
|
325
|
+
value !== '' &&
|
326
|
+
((validate.min && value < validate.min) || (validate.max && value > validate.max))) {
|
327
|
+
this.errorDetails['min-max'] = validate.customMessage;
|
328
|
+
}
|
329
|
+
else {
|
330
|
+
delete this.errorDetails['min-max'];
|
331
|
+
}
|
332
|
+
}
|
333
|
+
//check for out-of-the-box formio errors which store on this.root.errors
|
334
|
+
this.checkValidity(this.dataValue, true, this.data);
|
335
|
+
this.manageFormErrors();
|
336
|
+
}
|
337
|
+
hasErrors() {
|
338
|
+
return !isEmpty(this.errorDetails);
|
339
|
+
}
|
340
|
+
errorMessages() {
|
341
|
+
return Object.values(this.errorDetails).join(', ');
|
342
|
+
}
|
343
|
+
/**
|
344
|
+
* Synchronizes out-of-the-box formio errors with this field's errorDetails object
|
345
|
+
*/
|
346
|
+
manageFormErrors() {
|
347
|
+
var _a;
|
348
|
+
const outOfTheBoxError = (_a = this.root.errors.find((error) => {
|
349
|
+
return error.component.key === this.component.key;
|
350
|
+
})) === null || _a === void 0 ? void 0 : _a.message;
|
351
|
+
//add OoB formio error to errorDetails object to show under field
|
352
|
+
if (outOfTheBoxError) {
|
353
|
+
this.errorDetails['rootError'] = outOfTheBoxError;
|
354
|
+
}
|
355
|
+
else {
|
356
|
+
delete this.errorDetails['rootError'];
|
357
|
+
}
|
358
|
+
//add custom errors to be read by the Buttons component to show above the form
|
359
|
+
Object.values(this.errorDetails).forEach((error, index) => {
|
360
|
+
if (!this.root.errors.some((err) => err.message === error) &&
|
361
|
+
!this.root.customErrors.some((err) => err.message === error)) {
|
362
|
+
this.root.customErrors = [
|
363
|
+
...this.root.customErrors,
|
364
|
+
{ component: this.component, message: error, formattedKeyOrPath: this.component.key },
|
365
|
+
];
|
366
|
+
}
|
367
|
+
});
|
368
|
+
//remove custom errors that are no longer relevant
|
369
|
+
if (isEmpty(this.errorDetails)) {
|
370
|
+
this.root.customErrors = this.root.customErrors.filter((err) => err.formattedKeyOrPath !== this.component.key);
|
371
|
+
}
|
372
|
+
}
|
373
|
+
beforeSubmit() {
|
374
|
+
this.handleValidation(this.dataValue);
|
375
|
+
this.element && this.attach(this.element);
|
376
|
+
}
|
377
|
+
attachReact(element) {
|
378
|
+
var _a, _b, _c;
|
379
|
+
const { id, defaultValue } = this.component;
|
380
|
+
let root = ReactDOM.findDOMNode(element);
|
381
|
+
if (!root) {
|
382
|
+
root = element;
|
383
|
+
}
|
384
|
+
// FormIO uses id for an enclosing div, so we need to give the input field a different id.
|
385
|
+
const inputId = `${id}-input`;
|
386
|
+
/* TODO: You'll see warnings to upgrade to React 18's createRoot();
|
387
|
+
* It'll cause issues with: field-level errors not showing up, conditional visibility not working, focus moving out of the form on keypress
|
388
|
+
* Will need to be revisited later. Possibly look into using this.ref */
|
389
|
+
return ReactDOM.render(React.createElement("div", null,
|
390
|
+
React.createElement(FormComponentWrapper, Object.assign({}, this.component, { inputId: inputId, errorMessage: this.errorMessages(), value: (_a = this.dataValue) !== null && _a !== void 0 ? _a : defaultValue }),
|
391
|
+
React.createElement(FormField, Object.assign({ onChange: this.handleChange, onBlur: (e) => {
|
392
|
+
// no mask errors when field is empty and not required
|
393
|
+
const componentError = this.root.errors.find((error) => error.component.key === this.component.key);
|
394
|
+
const maskMessage = componentError === null || componentError === void 0 ? void 0 : componentError.messages.find((message) => message.context.validator === 'mask');
|
395
|
+
const falsePositiveMaskError = !(!!maskMessage &&
|
396
|
+
!this.component.validate.required &&
|
397
|
+
this.root.data[this.component.key] === maskMessage.context.value);
|
398
|
+
['TextField', 'Decimal', 'Integer'].includes(this.component.type) &&
|
399
|
+
falsePositiveMaskError &&
|
400
|
+
this.component.autoSave &&
|
401
|
+
isEmpty(this.errorDetails) &&
|
402
|
+
this.component.autoSave(this.root.data);
|
403
|
+
['TextField', 'Decimal', 'Integer'].includes(this.component.type) &&
|
404
|
+
falsePositiveMaskError &&
|
405
|
+
isEmpty(this.errorDetails) &&
|
406
|
+
this.emit('changed-' + this.component.key, e.target.value);
|
407
|
+
} }, this.component, { id: inputId, defaultValue: (_b = this.dataValue) !== null && _b !== void 0 ? _b : defaultValue, mask: this.component.inputMask, error: this.hasErrors(), size: (_c = this.component.fieldHeight) !== null && _c !== void 0 ? _c : 'medium' })))), root);
|
408
|
+
}
|
409
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/// <reference types="react" />
|
2
|
+
import { ObjectInstance } from '@evoke-platform/context';
|
3
|
+
export declare function blobToDataUrl(blob: Blob): Promise<string>;
|
4
|
+
declare type ImageProps = {
|
5
|
+
id: string;
|
6
|
+
handleChange: (propertyId: string, value: string | null) => void;
|
7
|
+
property: {
|
8
|
+
id: string;
|
9
|
+
};
|
10
|
+
instance: ObjectInstance;
|
11
|
+
canUpdateProperty: boolean;
|
12
|
+
error: boolean;
|
13
|
+
};
|
14
|
+
export declare const Image: (props: ImageProps) => JSX.Element;
|
15
|
+
export {};
|
@@ -0,0 +1,111 @@
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
|
+
});
|
9
|
+
};
|
10
|
+
import { BackupOutlined, ClearRounded } from '@mui/icons-material';
|
11
|
+
import React, { useEffect, useState } from 'react';
|
12
|
+
import { useDropzone } from 'react-dropzone';
|
13
|
+
import { CardMedia, IconButton, Typography } from '../../../../core';
|
14
|
+
import { Box, Grid } from '../../../../layout';
|
15
|
+
export function blobToDataUrl(blob) {
|
16
|
+
const reader = new FileReader();
|
17
|
+
return new Promise((resolve) => {
|
18
|
+
reader.onloadend = () => resolve(reader.result);
|
19
|
+
reader.readAsDataURL(blob);
|
20
|
+
});
|
21
|
+
}
|
22
|
+
const styles = {
|
23
|
+
imageContainer: {
|
24
|
+
margin: '5px 0',
|
25
|
+
height: '160px',
|
26
|
+
borderRadius: '8px',
|
27
|
+
maxWidth: '100%',
|
28
|
+
},
|
29
|
+
dropzoneContainer: {
|
30
|
+
margin: '5px 0',
|
31
|
+
height: '160px',
|
32
|
+
borderRadius: '8px',
|
33
|
+
display: 'flex',
|
34
|
+
justifyContent: 'center',
|
35
|
+
alignItems: 'center',
|
36
|
+
border: '1px dashed #858585',
|
37
|
+
position: 'relative',
|
38
|
+
cursor: 'pointer',
|
39
|
+
},
|
40
|
+
icon: {
|
41
|
+
color: '#fff',
|
42
|
+
zIndex: 40,
|
43
|
+
fontSize: '16px',
|
44
|
+
},
|
45
|
+
deleteIcon: {
|
46
|
+
borderRadius: '50%',
|
47
|
+
padding: '3px',
|
48
|
+
backgroundColor: '#212B36',
|
49
|
+
':hover': { backgroundColor: '#212B36', cursor: 'pointer' },
|
50
|
+
color: '#fff',
|
51
|
+
right: '29px',
|
52
|
+
bottom: '138px',
|
53
|
+
},
|
54
|
+
image: {
|
55
|
+
borderRadius: '8px',
|
56
|
+
width: 'fit-content',
|
57
|
+
maxWidth: '95%',
|
58
|
+
height: '160px',
|
59
|
+
position: 'relative',
|
60
|
+
display: 'inline-block',
|
61
|
+
objectFit: 'contain',
|
62
|
+
},
|
63
|
+
};
|
64
|
+
export const Image = (props) => {
|
65
|
+
const { id, handleChange, property, instance, canUpdateProperty, error } = props;
|
66
|
+
const [image, setImage] = useState();
|
67
|
+
useEffect(() => {
|
68
|
+
const value = instance === null || instance === void 0 ? void 0 : instance[property.id];
|
69
|
+
if (typeof value === 'string') {
|
70
|
+
setImage(value);
|
71
|
+
}
|
72
|
+
}, [property]);
|
73
|
+
const handleUpload = (file) => __awaiter(void 0, void 0, void 0, function* () {
|
74
|
+
if ((file === null || file === void 0 ? void 0 : file.size) && file.size <= 300000) {
|
75
|
+
const dataUrl = file ? yield blobToDataUrl(file) : null;
|
76
|
+
setImage(dataUrl);
|
77
|
+
handleChange(property.id, dataUrl);
|
78
|
+
}
|
79
|
+
});
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
81
|
+
const handleRemove = (e) => {
|
82
|
+
setImage(null);
|
83
|
+
handleChange(property.id, '');
|
84
|
+
e.stopPropagation();
|
85
|
+
};
|
86
|
+
const { getRootProps, getInputProps, open } = useDropzone({
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
88
|
+
onDrop: (files) => handleUpload(files === null || files === void 0 ? void 0 : files[0]),
|
89
|
+
accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.svg'] },
|
90
|
+
});
|
91
|
+
return (React.createElement(React.Fragment, null, image ? (React.createElement(Box, { sx: styles.imageContainer },
|
92
|
+
React.createElement(Box, { sx: { position: 'relative', left: 0, zIndex: 5 } },
|
93
|
+
React.createElement(CardMedia, { component: "img", image: image, sx: styles.image }),
|
94
|
+
canUpdateProperty && (React.createElement(IconButton, { onClick: handleRemove, "aria-label": "remove image", sx: styles.deleteIcon },
|
95
|
+
React.createElement(ClearRounded, { sx: styles.icon })))))) : canUpdateProperty ? (React.createElement(Box, Object.assign({ sx: Object.assign(Object.assign({}, styles.dropzoneContainer), { borderColor: error ? 'red' : '#858585' }) }, getRootProps(), { onClick: open }),
|
96
|
+
React.createElement("input", Object.assign({}, getInputProps({ id }), { multiple: false })),
|
97
|
+
React.createElement(Grid, { container: true, sx: { width: '100%' } },
|
98
|
+
React.createElement(Grid, { item: true, xs: 12, sx: { display: 'flex', justifyContent: 'center', paddingBottom: '5px' } },
|
99
|
+
React.createElement(BackupOutlined, { sx: { color: '#919EAB', height: '1.5em', width: '1.5em' } })),
|
100
|
+
React.createElement(Grid, { item: true, xs: 12 },
|
101
|
+
React.createElement(Typography, { variant: "body2", sx: { color: '#212B36', textAlign: 'center' } },
|
102
|
+
"Drag and drop or",
|
103
|
+
' ',
|
104
|
+
React.createElement(Typography, { component: 'span', sx: { color: '#0075A7', fontSize: '14px' } }, "select a file"),
|
105
|
+
' ',
|
106
|
+
"to upload")),
|
107
|
+
React.createElement(Grid, { item: true, xs: 12 },
|
108
|
+
React.createElement(Typography, { variant: "body2", sx: { color: '#637381', textAlign: 'center' } }, "Max file size of 300KB")),
|
109
|
+
React.createElement(Grid, { item: true, xs: 12 },
|
110
|
+
React.createElement(Typography, { variant: "body2", sx: { color: '#637381', textAlign: 'center' } }, "JPG, PNG, or GIF"))))) : (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No image"))));
|
111
|
+
};
|
package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.d.ts
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
import { ReactComponent } from '@formio/react';
|
2
|
+
import { Root } from 'react-dom/client';
|
3
|
+
import { BaseFormComponentProps } from '../../types';
|
4
|
+
export declare class ImageComponent extends ReactComponent {
|
5
|
+
[x: string]: any;
|
6
|
+
static schema: any;
|
7
|
+
component: BaseFormComponentProps;
|
8
|
+
errorDetails: any;
|
9
|
+
componentRoot?: Root;
|
10
|
+
constructor(component: BaseFormComponentProps, options: any, data: any);
|
11
|
+
init(): void;
|
12
|
+
clearErrors(): void;
|
13
|
+
handleValidation(): void;
|
14
|
+
hasErrors(): boolean;
|
15
|
+
errorMessages(): string;
|
16
|
+
/**
|
17
|
+
* Synchronizes out-of-the-box formio errors with this field's errorDetails object
|
18
|
+
*/
|
19
|
+
manageFormErrors(): void;
|
20
|
+
handleChange: (key: string, value: string | null) => void;
|
21
|
+
beforeSubmit(): void;
|
22
|
+
attachReact(element: Element): void;
|
23
|
+
}
|
@@ -0,0 +1,112 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { ReactComponent } from '@formio/react';
|
3
|
+
import { isEmpty } from 'lodash';
|
4
|
+
import React from 'react';
|
5
|
+
import { createRoot } from 'react-dom/client';
|
6
|
+
import { FormComponentWrapper } from '../../Common';
|
7
|
+
import { isPropertyVisible } from '../../utils';
|
8
|
+
import { Image } from './Image';
|
9
|
+
export class ImageComponent extends ReactComponent {
|
10
|
+
constructor(component, options, data) {
|
11
|
+
super(Object.assign(Object.assign({}, component), { canUpdateProperty: !component.readOnly, hideLabel: true }), options, data);
|
12
|
+
this.handleChange = (key, value) => {
|
13
|
+
delete this.errorDetails['api-error'];
|
14
|
+
this.setValue(value);
|
15
|
+
this.updateValue(value, { modified: true });
|
16
|
+
this.handleValidation();
|
17
|
+
this.emit('changed-' + this.component.key, value);
|
18
|
+
this.attach(this.element);
|
19
|
+
if (this.component.autoSave) {
|
20
|
+
this.component.autoSave({ [key]: value });
|
21
|
+
}
|
22
|
+
};
|
23
|
+
this.errorDetails = {};
|
24
|
+
this.handleChange = this.handleChange.bind(this);
|
25
|
+
}
|
26
|
+
init() {
|
27
|
+
this.on('changed-' + this.component.conditional.when, (value) => {
|
28
|
+
//set default value when conditional field is shown
|
29
|
+
if (this.component.defaultValue && value === this.component.conditional.eq) {
|
30
|
+
this.setValue(this.component.defaultValue);
|
31
|
+
this.updateValue(this.component.defaultValue, { modified: true });
|
32
|
+
}
|
33
|
+
//clear data and errors when a true conditional field is hidden
|
34
|
+
if (this.component.conditional.show && value !== this.component.conditional.eq) {
|
35
|
+
this.setValue('');
|
36
|
+
this.updateValue('', { modified: true });
|
37
|
+
this.clearErrors();
|
38
|
+
super.detach();
|
39
|
+
// Detach the componentRoot when the component is hidden
|
40
|
+
if (this.componentRoot) {
|
41
|
+
this.componentRoot.unmount();
|
42
|
+
this.componentRoot = undefined;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
});
|
46
|
+
this.on(`api-error`, (details) => {
|
47
|
+
const error = details.find((detail) => detail.code === 'errorMessage' && detail.path.replace('/', '') === this.component.key);
|
48
|
+
if (error) {
|
49
|
+
if (!this.root.customErrors.find((err) => err.formattedKeyOrPath === this.component.key && err.message === error.message)) {
|
50
|
+
this.root.customErrors = [
|
51
|
+
...this.root.customErrors,
|
52
|
+
Object.assign(Object.assign({}, error), { code: 'api-error', component: this.component, formattedKeyOrPath: this.component.key }),
|
53
|
+
];
|
54
|
+
}
|
55
|
+
this.errorDetails['api-error'] = error === null || error === void 0 ? void 0 : error.message;
|
56
|
+
}
|
57
|
+
else {
|
58
|
+
this.root.customErrors = this.root.customErrors.filter((item) => item.formattedKeyOrPath !== this.component.key);
|
59
|
+
delete this.errorDetails['api-error'];
|
60
|
+
}
|
61
|
+
this.attach(this.element);
|
62
|
+
this.attachReact(this.element);
|
63
|
+
});
|
64
|
+
}
|
65
|
+
clearErrors() {
|
66
|
+
this.errorDetails = {};
|
67
|
+
this.root.customErrors = this.root.customErrors.filter((error) => error.formattedKeyOrPath !== this.component.key);
|
68
|
+
}
|
69
|
+
handleValidation() {
|
70
|
+
if (!isPropertyVisible(this.component.conditional, this.root.data)) {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
// check for out-of-the-box formio errors which store on this.root.errors
|
74
|
+
this.checkValidity(this.dataValue, true, this.data);
|
75
|
+
this.manageFormErrors();
|
76
|
+
}
|
77
|
+
hasErrors() {
|
78
|
+
return !isEmpty(this.errorDetails);
|
79
|
+
}
|
80
|
+
errorMessages() {
|
81
|
+
return Object.values(this.errorDetails).join(', ');
|
82
|
+
}
|
83
|
+
/**
|
84
|
+
* Synchronizes out-of-the-box formio errors with this field's errorDetails object
|
85
|
+
*/
|
86
|
+
manageFormErrors() {
|
87
|
+
var _a;
|
88
|
+
const outOfTheBoxError = (_a = this.root.errors.find((error) => {
|
89
|
+
return error.component.key === this.component.key;
|
90
|
+
})) === null || _a === void 0 ? void 0 : _a.message;
|
91
|
+
// add OoB formio error to errorDetails object to show under field
|
92
|
+
if (outOfTheBoxError) {
|
93
|
+
this.errorDetails['rootError'] = outOfTheBoxError;
|
94
|
+
}
|
95
|
+
else {
|
96
|
+
delete this.errorDetails['rootError'];
|
97
|
+
}
|
98
|
+
}
|
99
|
+
beforeSubmit() {
|
100
|
+
this.handleValidation();
|
101
|
+
this.element && this.attach(this.element);
|
102
|
+
}
|
103
|
+
attachReact(element) {
|
104
|
+
if (!this.componentRoot) {
|
105
|
+
this.componentRoot = createRoot(element);
|
106
|
+
}
|
107
|
+
// FormIO uses id for an enclosing div, so we need to give the input field a different id.
|
108
|
+
const inputId = `${this.component.id}-input`;
|
109
|
+
return this.componentRoot.render(React.createElement("div", null, !this.component.hidden ? (React.createElement(FormComponentWrapper, Object.assign({}, this.component, { inputId: inputId, viewOnly: !this.component.canUpdateProperty, errorMessage: this.errorMessages() }),
|
110
|
+
React.createElement(Image, Object.assign({}, this.component, { id: inputId, handleChange: this.handleChange, error: this.hasErrors() })))) : null));
|
111
|
+
}
|
112
|
+
}
|