@evoke-platform/ui-components 1.6.0-dev.14 → 1.6.0-dev.15

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.
@@ -0,0 +1,3 @@
1
+ import { FormEntry, InputParameter } from '@evoke-platform/context';
2
+ import { FieldValues, UseFormRegister } from 'react-hook-form';
3
+ export declare const handleValidation: (entries: FormEntry[], register: UseFormRegister<FieldValues>, formValues: FieldValues, parameters?: InputParameter[], instance?: FieldValues) => void;
@@ -0,0 +1,164 @@
1
+ import { isArray } from 'lodash';
2
+ import { DateTime } from 'luxon';
3
+ import Handlebars from 'no-eval-handlebars';
4
+ export const handleValidation = (entries, register, formValues, parameters, instance) => {
5
+ entries?.forEach((entry) => {
6
+ if (entry.type === 'sections' || entry.type === 'columns') {
7
+ const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
8
+ subEntries.forEach((subEntry) => {
9
+ if (subEntry.entries) {
10
+ handleValidation(subEntry.entries, register, formValues, parameters, instance);
11
+ }
12
+ });
13
+ return;
14
+ }
15
+ else if (entry.type !== 'input' && entry.type !== 'inputField') {
16
+ return;
17
+ }
18
+ const display = entry?.display;
19
+ const parameter = entry.type === 'input'
20
+ ? parameters?.find((param) => param.id === entry.parameterId)
21
+ : entry.type === 'inputField'
22
+ ? entry.input
23
+ : undefined;
24
+ const validation = parameter?.validation || {};
25
+ const fieldName = display?.label;
26
+ const errorMsg = validation?.errorMessage;
27
+ const validationRules = {};
28
+ // Required fields
29
+ if (entry.type !== 'inputField' && parameter?.required) {
30
+ validationRules.required = `${fieldName} is required`;
31
+ }
32
+ if (parameter?.type === 'boolean' && parameter?.strictlyTrue) {
33
+ validationRules.required = {
34
+ value: true,
35
+ message: display?.booleanDisplay === 'switch'
36
+ ? `${fieldName} must be toggled on`
37
+ : `${fieldName} must be checked`,
38
+ };
39
+ }
40
+ // Min/max char string fields
41
+ if (typeof validation.minLength === 'number') {
42
+ validationRules.minLength = {
43
+ value: validation.minLength,
44
+ message: `${fieldName} must have at least ${validation.minLength} characters`,
45
+ };
46
+ }
47
+ if (typeof validation.maxLength === 'number') {
48
+ validationRules.maxLength = {
49
+ value: validation.maxLength,
50
+ message: `${fieldName} must have no more than ${validation.maxLength} characters`,
51
+ };
52
+ }
53
+ // Min/max number fields
54
+ if (typeof validation.maximum === 'number') {
55
+ validationRules.max = {
56
+ value: validation.maximum,
57
+ message: errorMsg || `${fieldName} must have a value under ${validation.maximum}`,
58
+ };
59
+ }
60
+ if (typeof validation.minimum === 'number') {
61
+ validationRules.min = {
62
+ value: validation.minimum,
63
+ message: errorMsg || `${fieldName} must have a value over ${validation.minimum}`,
64
+ };
65
+ }
66
+ validationRules.validate = (value) => {
67
+ if (!value)
68
+ return true;
69
+ // Document validation
70
+ if (validation.maxDocuments || validation.minDocuments) {
71
+ const amountOfDocuments = isArray(value) ? value.length : 0;
72
+ const min = validation.minDocuments;
73
+ const max = validation.maxDocuments;
74
+ if (max && min && (amountOfDocuments > max || amountOfDocuments < min)) {
75
+ return (errorMsg ||
76
+ `Please select between ${validation.minDocuments} and ${validation.maxDocuments} document${max > 1 ? 's' : ''}`);
77
+ }
78
+ else if (min && amountOfDocuments < min) {
79
+ return errorMsg || `Please select at least ${min} document${min > 1 ? 's' : ''}`;
80
+ }
81
+ else if (max && amountOfDocuments > max) {
82
+ return errorMsg || `Please select no more than ${max} document${max > 1 ? 's' : ''}`;
83
+ }
84
+ }
85
+ // Date and Time validation
86
+ if (validation.from || validation.to) {
87
+ const data = {
88
+ __today__: DateTime.now().toISODate(),
89
+ input: { ...instance, ...formValues },
90
+ };
91
+ if (validation.from) {
92
+ let earliestAllowed = validation.from;
93
+ if (/{{[\w.]+(?:\s+[\w.]+)*(?:\s+\d+)?}}/.test(earliestAllowed)) {
94
+ earliestAllowed = Handlebars.compileAST(earliestAllowed)(data);
95
+ }
96
+ if (earliestAllowed && value < earliestAllowed) {
97
+ return (errorMsg ||
98
+ `${fieldName} must be ${parameter?.type === 'time' ? 'at' : 'on'} or later than ${earliestAllowed || 'a field with no value'}`);
99
+ }
100
+ }
101
+ if (validation.to) {
102
+ let latestAllowed = validation.to;
103
+ if (/{{[\w.]+(?:\s+[\w.]+)*(?:\s+\d+)?}}/.test(latestAllowed)) {
104
+ latestAllowed = Handlebars.compileAST(latestAllowed)(data);
105
+ }
106
+ if (latestAllowed && value > latestAllowed) {
107
+ return (errorMsg ||
108
+ `${fieldName} must be ${parameter?.type === 'time' ? 'at' : 'on'} or before ${latestAllowed || 'a field with no value'}`);
109
+ }
110
+ }
111
+ }
112
+ // Regex validation
113
+ if (validation.rules) {
114
+ const rules = validation.rules;
115
+ const failedRules = rules.filter((rule) => {
116
+ const regex = new RegExp(rule.regex);
117
+ return !regex.test(value);
118
+ });
119
+ const operator = validation.operator;
120
+ if (failedRules.length > 0) {
121
+ if (operator === 'all') {
122
+ const messages = failedRules
123
+ .map((rule) => rule.errorMessage || 'Property is not in a valid format')
124
+ .join(' and ');
125
+ return `${fieldName}: ${messages}`;
126
+ }
127
+ if (operator === 'any' && failedRules.length < rules.length) {
128
+ return true; // passes if at least one rule passed
129
+ }
130
+ const messages = failedRules
131
+ .map((rule) => rule.errorMessage || 'Property is not in a valid format')
132
+ .join(' or ');
133
+ return `${fieldName}: ${messages}`;
134
+ }
135
+ }
136
+ // Integer check
137
+ if (parameter?.type === 'integer') {
138
+ if (value && !Number.isInteger(value)) {
139
+ return `${fieldName} must be an integer`;
140
+ }
141
+ }
142
+ // Invalid date check
143
+ if (value?.invalid && value?.dateText) {
144
+ return `Invalid Date`;
145
+ }
146
+ return true;
147
+ };
148
+ register(entry.parameterId || entry.input?.id, validationRules);
149
+ });
150
+ };
151
+ Handlebars.registerHelper('addDays', function (addend1, addend2) {
152
+ const dateAddend1 = DateTime.fromISO(addend1);
153
+ if (dateAddend1.isValid) {
154
+ return dateAddend1.plus({ days: addend2 }).toISODate();
155
+ }
156
+ return undefined;
157
+ });
158
+ Handlebars.registerHelper('subDays', function (minuend, subtrahend) {
159
+ const dateMinuend = DateTime.fromISO(minuend);
160
+ if (dateMinuend.isValid) {
161
+ return dateMinuend.minus({ days: subtrahend }).toISODate();
162
+ }
163
+ return undefined;
164
+ });
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { FieldErrors } from 'react-hook-form';
3
+ export type ValidationErrorDisplayProps = {
4
+ errors: FieldErrors;
5
+ show: boolean;
6
+ formId: string;
7
+ title?: string;
8
+ };
9
+ declare function ValidationErrorDisplay(props: ValidationErrorDisplayProps): React.JSX.Element | null;
10
+ export default ValidationErrorDisplay;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { useResponsive } from '../../../../../theme';
3
+ import { List, ListItem, Typography } from '../../../../core';
4
+ import { Box } from '../../../../layout';
5
+ function ValidationErrorDisplay(props) {
6
+ const { errors, show, formId, title } = props;
7
+ const { isSm, isXs } = useResponsive();
8
+ function extractErrorMessages(errors) {
9
+ const messages = [];
10
+ for (const key in errors) {
11
+ const error = errors[key];
12
+ if (error?.message) {
13
+ messages.push(error.message);
14
+ }
15
+ else if (error) {
16
+ for (const nestedKey in error) {
17
+ const nestedError = error[nestedKey];
18
+ if (nestedError?.message) {
19
+ messages.push(nestedError.message);
20
+ }
21
+ }
22
+ }
23
+ }
24
+ return messages;
25
+ }
26
+ const errorMessages = extractErrorMessages(errors);
27
+ return show && errorMessages.length > 0 ? (React.createElement(Box, { id: `validation-error-display-${formId}`, sx: {
28
+ backgroundColor: '#f8d7da',
29
+ borderColor: '#f5c6cb',
30
+ color: '#721c24',
31
+ border: '1px solid #721c24',
32
+ padding: '8px 24px',
33
+ borderRadius: '4px',
34
+ marginBottom: isSm || isXs ? 2 : 3,
35
+ marginTop: !title ? (isSm || isXs ? -2 : -3) : undefined,
36
+ } },
37
+ React.createElement(Typography, { sx: { color: '#721c24', mt: '16px', mb: '8px' } }, "Please fix the following errors before submitting:"),
38
+ React.createElement(List, { sx: {
39
+ listStyleType: 'disc',
40
+ paddingLeft: '40px',
41
+ mb: '8px',
42
+ fontFamily: 'Arial, Helvetica, sans-serif',
43
+ } }, errorMessages.map((msg, index) => (React.createElement(ListItem, { key: index, sx: { display: 'list-item', p: 0 } }, msg)))))) : null;
44
+ }
45
+ export default ValidationErrorDisplay;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.6.0-dev.14",
3
+ "version": "1.6.0-dev.15",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",