@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.
Files changed (58) hide show
  1. package/dist/published/components/custom/Form/Common/Form.d.ts +38 -0
  2. package/dist/published/components/custom/Form/Common/Form.js +413 -0
  3. package/dist/published/components/custom/Form/Common/FormComponentWrapper.d.ts +26 -0
  4. package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +79 -0
  5. package/dist/published/components/custom/Form/Common/index.d.ts +2 -0
  6. package/dist/published/components/custom/Form/Common/index.js +2 -0
  7. package/dist/published/components/custom/Form/FormComponents/ButtonComponent.d.ts +37 -0
  8. package/dist/published/components/custom/Form/FormComponents/ButtonComponent.js +150 -0
  9. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.d.ts +17 -0
  10. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +80 -0
  11. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.d.ts +23 -0
  12. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.js +154 -0
  13. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.d.ts +15 -0
  14. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +172 -0
  15. package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.d.ts +41 -0
  16. package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.js +409 -0
  17. package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.d.ts +15 -0
  18. package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.js +111 -0
  19. package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.d.ts +23 -0
  20. package/dist/published/components/custom/Form/FormComponents/ImageComponent/ImageComponent.js +112 -0
  21. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/InstanceLookup.d.ts +20 -0
  22. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/InstanceLookup.js +229 -0
  23. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.d.ts +34 -0
  24. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js +150 -0
  25. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.d.ts +3 -0
  26. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +306 -0
  27. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.d.ts +24 -0
  28. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +126 -0
  29. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +21 -0
  30. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +96 -0
  31. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.d.ts +15 -0
  32. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.js +158 -0
  33. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.d.ts +39 -0
  34. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.js +89 -0
  35. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.d.ts +12 -0
  36. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +369 -0
  37. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.d.ts +20 -0
  38. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +57 -0
  39. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserComponent.d.ts +26 -0
  40. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserComponent.js +99 -0
  41. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.d.ts +23 -0
  42. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.js +115 -0
  43. package/dist/published/components/custom/Form/FormComponents/ViewOnlyComponent.d.ts +20 -0
  44. package/dist/published/components/custom/Form/FormComponents/ViewOnlyComponent.js +83 -0
  45. package/dist/published/components/custom/Form/FormComponents/index.d.ts +8 -0
  46. package/dist/published/components/custom/Form/FormComponents/index.js +8 -0
  47. package/dist/published/components/custom/Form/index.d.ts +3 -0
  48. package/dist/published/components/custom/Form/index.js +3 -0
  49. package/dist/published/components/custom/Form/types.d.ts +109 -0
  50. package/dist/published/components/custom/Form/types.js +1 -0
  51. package/dist/published/components/custom/Form/utils.d.ts +45 -0
  52. package/dist/published/components/custom/Form/utils.js +1036 -0
  53. package/dist/published/components/custom/index.d.ts +1 -0
  54. package/dist/published/components/custom/index.js +1 -0
  55. package/dist/published/index.d.ts +1 -1
  56. package/dist/published/index.js +1 -1
  57. package/dist/published/styles/form-component.css +152 -0
  58. 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
+ };
@@ -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
+ }