@bolttech/form-engine-core 0.0.1-beta.9 → 0.0.2-beta.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/README.md +40 -36
- package/index.esm.js +591 -324
- package/package.json +2 -2
- package/src/constants/constants.d.ts +6 -1
- package/src/helpers/SafeSubject.d.ts +11 -0
- package/src/helpers/helpers.d.ts +4 -24
- package/src/helpers/validation.d.ts +27 -0
- package/src/interfaces/schema.d.ts +27 -8
- package/src/managers/field.d.ts +17 -23
- package/src/managers/form.d.ts +50 -31
- package/src/managers/formGroup.d.ts +9 -1
- package/src/types/event.d.ts +2 -2
- package/src/types/form.d.ts +0 -5
- package/src/types/schema.d.ts +87 -40
- package/src/types/template.d.ts +19 -1
- package/src/types/utility.d.ts +2 -0
- package/src/validations/date.d.ts +1 -0
- package/src/validations/handler.d.ts +2 -2
- package/src/validations/multiple.d.ts +2 -2
- package/src/validations/namedRule.d.ts +22 -0
- package/src/validations/number.d.ts +30 -0
package/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Subject, Subscription, combineLatest, startWith, groupBy, mergeMap, debounceTime, filter, map } from 'rxjs';
|
|
2
2
|
import creditCardType from 'credit-card-type';
|
|
3
|
-
import { isNumber as isNumber$1,
|
|
3
|
+
import { isNumber as isNumber$1, isFunction, cloneDeep, isEqual, get, isNil, set } from 'lodash';
|
|
4
4
|
import { getCurrencySymbol } from '@gaignoux/currency';
|
|
5
5
|
|
|
6
6
|
var TMutationEnum;
|
|
@@ -44,6 +44,14 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
44
44
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
const DEFAULT_API_DEBOUNCE_TIME = 1000;
|
|
48
|
+
const DEFAULT_STATE_REFRESH_TIME = 100;
|
|
49
|
+
const TEMPLATE_REGEX_STRING_CONCATENATION_DETECTOR = /^\$\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}$/;
|
|
50
|
+
const TEMPLATE_REGEX_DELIMITATOR = /\$\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*)\}/g;
|
|
51
|
+
const TEMPLATE_REGEX_OPERATOR_SPLITTER = /\s*(\|\||&&|!)\s*/g;
|
|
52
|
+
const TEMPLATE_REGEX_OPERATOR_MATCHER = /^\|\||&&|!$/;
|
|
53
|
+
const TEMPLATE_AVALIABLE_SCOPES = ['fields', 'iVars'];
|
|
54
|
+
|
|
47
55
|
/**
|
|
48
56
|
* Makes an HTTP request using XMLHttpRequest.
|
|
49
57
|
*
|
|
@@ -96,15 +104,18 @@ function makeRequest(method, url, headers, body) {
|
|
|
96
104
|
* ```
|
|
97
105
|
*/
|
|
98
106
|
function extractFieldKeys(expression) {
|
|
99
|
-
const regex =
|
|
107
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
100
108
|
const extractedValues = [];
|
|
101
109
|
let match;
|
|
102
110
|
while ((match = regex.exec(expression)) !== null) {
|
|
103
111
|
extractedValues.push(match[1]);
|
|
104
112
|
}
|
|
113
|
+
const operatorRegex = TEMPLATE_REGEX_OPERATOR_SPLITTER;
|
|
114
|
+
const splittedString = extractedValues.map(el => el.split(operatorRegex).filter(item => !operatorRegex.test(item))).flat().filter(el => el.split('.').length > 1);
|
|
105
115
|
return {
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
originScopeKeys: Array.from(new Set(splittedString.map(el => el.split('.')[0]))),
|
|
117
|
+
originFieldKeys: Array.from(new Set(splittedString.map(el => el.split('.')[1]))),
|
|
118
|
+
originPropertyKeys: Array.from(new Set(splittedString.map(el => el.split('.')[2])))
|
|
108
119
|
};
|
|
109
120
|
}
|
|
110
121
|
/**
|
|
@@ -132,95 +143,40 @@ function extractFieldKeys(expression) {
|
|
|
132
143
|
* // expressions will contain an array of objects with origin expressions, field keys, and destination paths.
|
|
133
144
|
* ```
|
|
134
145
|
*/
|
|
135
|
-
function traverseObject(
|
|
146
|
+
function traverseObject(element, path) {
|
|
136
147
|
const result = [];
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (String(item).includes('$')) {
|
|
146
|
-
// const extractedPath = item.replace(/\$|{|}/g, '').split('.');
|
|
147
|
-
const extractedOriginPath = `${path ? `${path}.` : ``}${key}`.split('.');
|
|
148
|
-
result.push(Object.assign(Object.assign({
|
|
149
|
-
originExpression: item
|
|
150
|
-
}, extractFieldKeys(item)), {
|
|
151
|
-
destinationKey: extractedOriginPath[0],
|
|
152
|
-
destinationProperty: extractedOriginPath[1],
|
|
153
|
-
destinationPath: extractedOriginPath.slice(2)
|
|
154
|
-
}));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
} else if (typeof value === 'object') {
|
|
148
|
+
if (Array.isArray(element)) {
|
|
149
|
+
element.forEach((item, index) => {
|
|
150
|
+
result.push(...traverseObject(item, `${path ? `${path}.` : ``}${index}`));
|
|
151
|
+
});
|
|
152
|
+
} else if (typeof element === 'object') {
|
|
153
|
+
for (const key in element) {
|
|
154
|
+
if (Object.prototype.hasOwnProperty.call(element, key)) {
|
|
155
|
+
const value = element[key];
|
|
159
156
|
result.push(...traverseObject(value, `${path ? `${path}.` : ``}${key}`));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else if (typeof element === 'string') {
|
|
160
|
+
if (element.includes('${')) {
|
|
161
|
+
// const extractedPath = value.replace(/\$|{|}/g, '').split('.');
|
|
162
|
+
const destinationPath = (path ? path : '').split('.');
|
|
163
|
+
// if(destinationPath[0])
|
|
164
|
+
const originfieldKeys = extractFieldKeys(element);
|
|
165
|
+
if (!originfieldKeys.originScopeKeys.every(scopeKey => TEMPLATE_AVALIABLE_SCOPES.includes(scopeKey))) {
|
|
166
|
+
console.warn(`scope malformed on this expression: ${element}, ignoring..`);
|
|
167
|
+
} else {
|
|
168
|
+
result.push(Object.assign(Object.assign({
|
|
169
|
+
originExpression: element
|
|
170
|
+
}, extractFieldKeys(element)), {
|
|
171
|
+
destinationKey: destinationPath[0],
|
|
172
|
+
destinationProperty: destinationPath[1],
|
|
173
|
+
destinationPath: destinationPath.slice(2)
|
|
174
|
+
}));
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
return result;
|
|
176
179
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Recursively creates a nested object structure based on the given parts array,
|
|
179
|
-
* setting the specified value at the appropriate nested level.
|
|
180
|
-
*
|
|
181
|
-
* @param {string[]} parts - Array of strings representing the keys for the nested object.
|
|
182
|
-
* @param {Record<string, unknown>} subObject - The sub-object to be updated or used for recursion.
|
|
183
|
-
* @param {unknown} value - The value to be set at the final nested level.
|
|
184
|
-
* @returns {Record<string, unknown>} - The updated object with the new nested structure and value.
|
|
185
|
-
*/
|
|
186
|
-
const getNewPart = (parts, subObject, value) => {
|
|
187
|
-
const clonedParts = [...parts];
|
|
188
|
-
clonedParts.splice(0, 1);
|
|
189
|
-
const part = parts.length !== 1 ? getNewPart(clonedParts, subObject ? subObject[parts[0]] : {}, value) : value;
|
|
190
|
-
return Object.assign(Object.assign({}, subObject), {
|
|
191
|
-
[parts[0]]: part
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
/**
|
|
195
|
-
* Encapsulates in a given object, at a given path the provided value
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ORIGINAL object
|
|
199
|
-
*
|
|
200
|
-
* encapsulateIn({maintain: 'spread_me'}, 'a.b.c','test')
|
|
201
|
-
*
|
|
202
|
-
* RESULT
|
|
203
|
-
*
|
|
204
|
-
* {
|
|
205
|
-
* maintain: 'spread_me',
|
|
206
|
-
* c: 'test
|
|
207
|
-
* }
|
|
208
|
-
* }
|
|
209
|
-
*
|
|
210
|
-
* @param origin - The original object where the new value will be appended
|
|
211
|
-
* @param path - The path at which the new value will be placed
|
|
212
|
-
* @param value - The new value
|
|
213
|
-
* @returns One new object with the new value at the provided path merged with the given object
|
|
214
|
-
*/
|
|
215
|
-
const encapsulateIn = (origin, path, value) => {
|
|
216
|
-
const parts = path.split('.');
|
|
217
|
-
if (parts.length === 1) {
|
|
218
|
-
return Object.assign(Object.assign({}, origin), {
|
|
219
|
-
[path]: value
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
return getNewPart(parts, origin, value);
|
|
223
|
-
};
|
|
224
180
|
|
|
225
181
|
/**
|
|
226
182
|
* Splits a string by inserting specified values at given positions.
|
|
@@ -769,10 +725,11 @@ const custom = (value, masks) => {
|
|
|
769
725
|
if (!masks.custom || !value) return value;
|
|
770
726
|
let mask = '';
|
|
771
727
|
let index = 0;
|
|
728
|
+
const convertedValue = value.replace(/[^\w\s]/gi, '');
|
|
772
729
|
for (let i = 0; i < masks.custom.length; i++) {
|
|
773
730
|
if (masks.custom[i] === '#') {
|
|
774
|
-
if (index <
|
|
775
|
-
mask +=
|
|
731
|
+
if (index < convertedValue.length) {
|
|
732
|
+
mask += convertedValue[index];
|
|
776
733
|
index++;
|
|
777
734
|
} else {
|
|
778
735
|
break;
|
|
@@ -1274,6 +1231,44 @@ const between = (value, validations) => {
|
|
|
1274
1231
|
const num = Number(value);
|
|
1275
1232
|
return !Number.isNaN(num) && !(+num >= validations.between.start && +num <= validations.between.end);
|
|
1276
1233
|
};
|
|
1234
|
+
/**
|
|
1235
|
+
* Checks if a given value is less than a specified threshold.
|
|
1236
|
+
*
|
|
1237
|
+
* @param value - The value to be checked. This value will be converted to a number.
|
|
1238
|
+
* @param validations - An object containing validation methods, specifically the `lessThan` property which holds the threshold value.
|
|
1239
|
+
* @returns Returns `false` if the `lessThan` threshold is not provided or if the value is not a valid number. Otherwise, returns `true` if the value is greater than or equal to the `lessThan` threshold.
|
|
1240
|
+
*
|
|
1241
|
+
* @example
|
|
1242
|
+
* ```typescript
|
|
1243
|
+
* const validations = { lessThan: 10 };
|
|
1244
|
+
* console.log(lessThan(5, validations)); // true
|
|
1245
|
+
* console.log(lessThan(15, validations)); // false
|
|
1246
|
+
* ```
|
|
1247
|
+
*/
|
|
1248
|
+
const lessThan = (value, validations) => {
|
|
1249
|
+
const number = Number(value);
|
|
1250
|
+
if (!validations.lessThan || Number.isNaN(number)) return false;
|
|
1251
|
+
return number >= validations.lessThan;
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* Checks if a given value is greater than a specified threshold.
|
|
1255
|
+
*
|
|
1256
|
+
* @param value - The value to be checked. This value will be converted to a number.
|
|
1257
|
+
* @param validations - An object containing validation methods, specifically the `greaterThan` property which holds the threshold value.
|
|
1258
|
+
* @returns Returns `false` if the `greaterThan` threshold is not provided or if the value is not a valid number. Otherwise, returns `true` if the value is less than or equal to the `greaterThan` threshold.
|
|
1259
|
+
*
|
|
1260
|
+
* @example
|
|
1261
|
+
* ```typescript
|
|
1262
|
+
* const validations = { greaterThan: 10 };
|
|
1263
|
+
* console.log(greaterThan(15, validations)); // true
|
|
1264
|
+
* console.log(greaterThan(5, validations)); // false
|
|
1265
|
+
* ```
|
|
1266
|
+
*/
|
|
1267
|
+
const greaterThan = (value, validations) => {
|
|
1268
|
+
const number = Number(value);
|
|
1269
|
+
if (!validations.greaterThan || Number.isNaN(number)) return false;
|
|
1270
|
+
return number <= validations.greaterThan;
|
|
1271
|
+
};
|
|
1277
1272
|
/**
|
|
1278
1273
|
* Validates if a value contains sequential numbers.
|
|
1279
1274
|
*
|
|
@@ -1968,41 +1963,44 @@ const conditions = (value, validations) => {
|
|
|
1968
1963
|
};
|
|
1969
1964
|
|
|
1970
1965
|
/**
|
|
1971
|
-
*
|
|
1966
|
+
* @internal
|
|
1967
|
+
* Validates if a date string matches a specific date format.
|
|
1968
|
+
* The function accepts strings with '/' or '-' separators and removes them before validating the format.
|
|
1972
1969
|
*
|
|
1973
|
-
* @param {string} value - The date
|
|
1974
|
-
* @param {
|
|
1975
|
-
*
|
|
1970
|
+
* @param {string} value - The date string to be validated. It can contain '/' or '-' separators.
|
|
1971
|
+
* @param {string} format - The expected date format. It can be one of the following:
|
|
1972
|
+
* 'DDMMYYYY', 'YYYYMMDD', 'YYYYDDMM', 'MMDDYYYY',
|
|
1973
|
+
* 'DMYYYY', 'YYYYMD', 'YYYYDM', 'MDYYYY'.
|
|
1974
|
+
* @returns {boolean} - Returns `false` if the date string matches the specified format, otherwise `true`.
|
|
1976
1975
|
*
|
|
1977
1976
|
* @example
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
* betweenDates: [
|
|
1981
|
-
* { origin: { value: '2023-01-01', format: 'yyyy-MM-dd' }, operator: '>=' },
|
|
1982
|
-
* { origin: { value: '2023-12-31', format: 'yyyy-MM-dd' }, operator: '<=' }
|
|
1983
|
-
* ]
|
|
1984
|
-
* };
|
|
1977
|
+
* // Returns false
|
|
1978
|
+
* invalidStringDate("25/07/1997", "DDMMYYYY");
|
|
1985
1979
|
*
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
1980
|
+
* @example
|
|
1981
|
+
* // Returns false
|
|
1982
|
+
* invalidStringDate("1997-07-25", "YYYYMMDD");
|
|
1988
1983
|
*
|
|
1989
|
-
*
|
|
1990
|
-
*
|
|
1991
|
-
*
|
|
1984
|
+
* @example
|
|
1985
|
+
* // Returns true, as the format does not match
|
|
1986
|
+
* invalidStringDate("1997/25/07", "MMDDYYYY");
|
|
1992
1987
|
*/
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
if (((_a = validations.betweenDates) === null || _a === void 0 ? void 0 : _a.length) != 2) return false;
|
|
1997
|
-
for (const validation of validations.betweenDates) {
|
|
1998
|
-
if (date(value, {
|
|
1999
|
-
date: validation
|
|
2000
|
-
})) {
|
|
2001
|
-
fail = true;
|
|
2002
|
-
break;
|
|
2003
|
-
}
|
|
1988
|
+
const invalidStringDate = (value, format) => {
|
|
1989
|
+
if (!value.includes('/') && !value.includes('-') || !format) {
|
|
1990
|
+
return true;
|
|
2004
1991
|
}
|
|
2005
|
-
|
|
1992
|
+
const valueParts = value.replace(/[-/]/g, '');
|
|
1993
|
+
const dateMapper = {
|
|
1994
|
+
DDMMYYYY: /^(\d{2})(\d{2})(\d{4})$/,
|
|
1995
|
+
YYYYMMDD: /^(\d{4})(\d{2})(\d{2})$/,
|
|
1996
|
+
YYYYDDMM: /^(\d{4})(\d{2})(\d{2})$/,
|
|
1997
|
+
MMDDYYYY: /^(\d{2})(\d{2})(\d{4})$/,
|
|
1998
|
+
DMYYYY: /^(\d{1,2})(\d{1,2})(\d{4})$/,
|
|
1999
|
+
YYYYMD: /^(\d{4})(\d{1,2})(\d{1,2})$/,
|
|
2000
|
+
YYYYDM: /^(\d{4})(\d{1,2})(\d{1,2})$/,
|
|
2001
|
+
MDYYYY: /^(\d{1,2})(\d{1,2})(\d{4})$/
|
|
2002
|
+
};
|
|
2003
|
+
return !dateMapper[format].test(valueParts);
|
|
2006
2004
|
};
|
|
2007
2005
|
/**
|
|
2008
2006
|
* @internal
|
|
@@ -2055,6 +2053,44 @@ const dateRearrangeMapper = {
|
|
|
2055
2053
|
MMDDYYYY: value => value,
|
|
2056
2054
|
timestamp: value => new Date(value).toString()
|
|
2057
2055
|
};
|
|
2056
|
+
/**
|
|
2057
|
+
* @function betweenDates
|
|
2058
|
+
* Validates if a date value falls between two specified dates.
|
|
2059
|
+
*
|
|
2060
|
+
* @param {string} value - The date value to be validated in string format.
|
|
2061
|
+
* @param {TValidationMethods} validations - The validation methods object containing the betweenDates validation rules.
|
|
2062
|
+
* @returns {boolean} - Returns `true` if the date value fails the betweenDates validation, otherwise `false`.
|
|
2063
|
+
*
|
|
2064
|
+
* @example
|
|
2065
|
+
* ```typescript
|
|
2066
|
+
* const validations = {
|
|
2067
|
+
* betweenDates: [
|
|
2068
|
+
* { origin: { value: '2023-01-01', format: 'yyyy-MM-dd' }, operator: '>=' },
|
|
2069
|
+
* { origin: { value: '2023-12-31', format: 'yyyy-MM-dd' }, operator: '<=' }
|
|
2070
|
+
* ]
|
|
2071
|
+
* };
|
|
2072
|
+
*
|
|
2073
|
+
* const result1 = betweenDates('2023-06-01', validations);
|
|
2074
|
+
* console.log(result1); // false (date is within the range)
|
|
2075
|
+
*
|
|
2076
|
+
* const result2 = betweenDates('2024-01-01', validations);
|
|
2077
|
+
* console.log(result2); // true (date is outside the range)
|
|
2078
|
+
* ```
|
|
2079
|
+
*/
|
|
2080
|
+
const betweenDates = (value, validations) => {
|
|
2081
|
+
var _a;
|
|
2082
|
+
let fail = false;
|
|
2083
|
+
if (((_a = validations.betweenDates) === null || _a === void 0 ? void 0 : _a.length) != 2) return false;
|
|
2084
|
+
for (const validation of validations.betweenDates) {
|
|
2085
|
+
if (date(value, {
|
|
2086
|
+
date: validation
|
|
2087
|
+
})) {
|
|
2088
|
+
fail = true;
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return fail;
|
|
2093
|
+
};
|
|
2058
2094
|
/**
|
|
2059
2095
|
* @function date
|
|
2060
2096
|
* Validates a date value based on various date conditions and intervals.
|
|
@@ -2088,12 +2124,13 @@ const dateRearrangeMapper = {
|
|
|
2088
2124
|
* ```
|
|
2089
2125
|
*/
|
|
2090
2126
|
const date = (value, validations) => {
|
|
2091
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
2127
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2092
2128
|
if (!((_b = (_a = validations.date) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.value) && !((_d = (_c = validations.date) === null || _c === void 0 ? void 0 : _c.origin) === null || _d === void 0 ? void 0 : _d.intervals)) {
|
|
2093
2129
|
return false;
|
|
2094
2130
|
}
|
|
2095
2131
|
const originValue = validations.date.origin.value || value;
|
|
2096
|
-
|
|
2132
|
+
const mappedValue = dateRearrangeMapper[(_e = validations.date) === null || _e === void 0 ? void 0 : _e.origin.format](originValue).toString();
|
|
2133
|
+
let originDate = new Date(mappedValue);
|
|
2097
2134
|
let targetDate = new Date();
|
|
2098
2135
|
let target = new Date();
|
|
2099
2136
|
if ((_g = (_f = validations.date) === null || _f === void 0 ? void 0 : _f.target) === null || _g === void 0 ? void 0 : _g.format) {
|
|
@@ -2112,6 +2149,9 @@ const date = (value, validations) => {
|
|
|
2112
2149
|
if (validations.date.onlyValidDate && (!(targetDate instanceof Date && isFinite(targetDate)) || !(targetDate instanceof Date && isFinite(originDate)) || originValue.length < 8)) {
|
|
2113
2150
|
return true;
|
|
2114
2151
|
}
|
|
2152
|
+
if (invalidStringDate(mappedValue, (_l = validations.date) === null || _l === void 0 ? void 0 : _l.origin.format)) {
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2115
2155
|
const originTimestamp = originDate.getTime();
|
|
2116
2156
|
const targetTimestamp = targetDate.getTime();
|
|
2117
2157
|
const operationsMapper = {
|
|
@@ -2122,7 +2162,7 @@ const date = (value, validations) => {
|
|
|
2122
2162
|
'===': originTimestamp === targetTimestamp,
|
|
2123
2163
|
'!==': originTimestamp !== targetTimestamp
|
|
2124
2164
|
};
|
|
2125
|
-
return operationsMapper[(
|
|
2165
|
+
return operationsMapper[(_m = validations.date) === null || _m === void 0 ? void 0 : _m.operator];
|
|
2126
2166
|
};
|
|
2127
2167
|
/**
|
|
2128
2168
|
* @function validDate
|
|
@@ -2151,11 +2191,74 @@ const validDate = (value, validations) => {
|
|
|
2151
2191
|
const month = parseInt(dateParts[0], 10) - 1; // Month is zero-based
|
|
2152
2192
|
const day = parseInt(dateParts[1], 10);
|
|
2153
2193
|
const date = new Date(year, month, day);
|
|
2194
|
+
/*
|
|
2195
|
+
* Motivation: due to the scenario in which a date field may be 'typeable',
|
|
2196
|
+
* we need to ensure that dates less than a thousand and such years are valid.
|
|
2197
|
+
* E.g.: user types 13-07-199 (without this condition, it becomes a valid date, as 199 is a valid year)
|
|
2198
|
+
*
|
|
2199
|
+
* Ps: 150 is a valid value for now, as we still cannot live more than that, from the
|
|
2200
|
+
* the moment we start to live longer, we can rethink this.
|
|
2201
|
+
*/
|
|
2202
|
+
const today = new Date();
|
|
2203
|
+
today.setFullYear(today.getFullYear() - 150);
|
|
2204
|
+
if (date.getFullYear() < today.getFullYear()) {
|
|
2205
|
+
return true;
|
|
2206
|
+
}
|
|
2154
2207
|
// Check if the date is valid
|
|
2155
2208
|
const isValidDate = date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
|
|
2156
2209
|
return !isValidDate;
|
|
2157
2210
|
};
|
|
2158
2211
|
|
|
2212
|
+
/**
|
|
2213
|
+
* @internal
|
|
2214
|
+
* Runs a set of validation handlers against a given value.
|
|
2215
|
+
*
|
|
2216
|
+
* @param {unknown} value - The value to be validated.
|
|
2217
|
+
* @param {TValidationMethods} handlers - An object containing validation methods to be applied.
|
|
2218
|
+
* @param {TValidationHandler} validations - An object containing every validation methods to be executed.
|
|
2219
|
+
* @returns {boolean[]} - An array of boolean results for each validation method.
|
|
2220
|
+
*
|
|
2221
|
+
* @example
|
|
2222
|
+
* const handlers = {
|
|
2223
|
+
* max: { max: 10 },
|
|
2224
|
+
* required: true,
|
|
2225
|
+
* email: true
|
|
2226
|
+
* };
|
|
2227
|
+
* const results = run('test@example.com', handlers);
|
|
2228
|
+
* console.log(results); // [false, false, true] (value fails 'max', passes 'required', passes 'email')
|
|
2229
|
+
*/
|
|
2230
|
+
function run$1(value, handlers, validations) {
|
|
2231
|
+
const runner = [];
|
|
2232
|
+
Object.keys(handlers).forEach(rule => {
|
|
2233
|
+
runner.push(validations[rule](value, {
|
|
2234
|
+
[rule]: handlers[rule]
|
|
2235
|
+
}));
|
|
2236
|
+
});
|
|
2237
|
+
return runner;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Validates a given value based on specified validation methods inside a custom named validation.
|
|
2241
|
+
*
|
|
2242
|
+
* @param {unknown} value - The value to be validated.
|
|
2243
|
+
* @param {TValidationMethods} methods - The validation methods to be applied.
|
|
2244
|
+
* @param {TValidationHandler} validations - An object containing every validation methods to be executed.
|
|
2245
|
+
* @returns {boolean} - Returns true if any of the validation methods pass, otherwise false.
|
|
2246
|
+
*
|
|
2247
|
+
* @example
|
|
2248
|
+
* const value = 'example@example.com';
|
|
2249
|
+
* const methods = {
|
|
2250
|
+
* required: true,
|
|
2251
|
+
* email: true
|
|
2252
|
+
* };
|
|
2253
|
+
*
|
|
2254
|
+
* const isValid = validateValue(value, methods);
|
|
2255
|
+
* console.log(isValid); // Output: true
|
|
2256
|
+
*/
|
|
2257
|
+
var namedRule = ((value, methods, validations) => {
|
|
2258
|
+
if (!methods) return false;
|
|
2259
|
+
return run$1(value, methods, validations).some(validation => validation);
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2159
2262
|
/**
|
|
2160
2263
|
* @internal
|
|
2161
2264
|
* An object mapping validation keys to their respective validation functions.
|
|
@@ -2169,6 +2272,8 @@ const validDate = (value, validations) => {
|
|
|
2169
2272
|
const validations$1 = {
|
|
2170
2273
|
max,
|
|
2171
2274
|
min,
|
|
2275
|
+
lessThan,
|
|
2276
|
+
greaterThan,
|
|
2172
2277
|
length,
|
|
2173
2278
|
regex,
|
|
2174
2279
|
url,
|
|
@@ -2190,7 +2295,6 @@ const validations$1 = {
|
|
|
2190
2295
|
notEmpty,
|
|
2191
2296
|
bool,
|
|
2192
2297
|
exists,
|
|
2193
|
-
greaterThan: () => true,
|
|
2194
2298
|
isNumber,
|
|
2195
2299
|
conditions,
|
|
2196
2300
|
validDate,
|
|
@@ -2214,20 +2318,26 @@ const validations$1 = {
|
|
|
2214
2318
|
* const results = run('test@example.com', handlers);
|
|
2215
2319
|
* console.log(results); // [false, false, true] (value fails 'max', passes 'required', passes 'email')
|
|
2216
2320
|
*/
|
|
2217
|
-
|
|
2321
|
+
function run(value, handlers) {
|
|
2218
2322
|
const runner = [];
|
|
2219
2323
|
Object.keys(handlers).forEach(rule => {
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2324
|
+
let handler;
|
|
2325
|
+
if (isFunction(validations$1[rule])) {
|
|
2326
|
+
handler = validations$1[rule](value, {
|
|
2327
|
+
[rule]: handlers[rule]
|
|
2328
|
+
});
|
|
2329
|
+
} else {
|
|
2330
|
+
handler = namedRule(value, handlers[rule], validations$1);
|
|
2331
|
+
}
|
|
2332
|
+
runner.push(handler);
|
|
2223
2333
|
});
|
|
2224
2334
|
return runner;
|
|
2225
|
-
}
|
|
2335
|
+
}
|
|
2226
2336
|
/**
|
|
2227
2337
|
* Validates that a value meets multiple validation rules.
|
|
2228
2338
|
*
|
|
2229
2339
|
* @param {number | string | boolean} value - The value to be validated.
|
|
2230
|
-
* @param {TValidationMethods}
|
|
2340
|
+
* @param {TValidationMethods} methods - The validation methods object containing the multipleValidations rule set.
|
|
2231
2341
|
* @returns {boolean} - Returns `true` if the value meets the specified multiple validation rules, otherwise `false`.
|
|
2232
2342
|
*
|
|
2233
2343
|
* @example
|
|
@@ -2252,20 +2362,22 @@ const run = (value, handlers) => {
|
|
|
2252
2362
|
* console.log(result2); // false
|
|
2253
2363
|
* ```
|
|
2254
2364
|
*/
|
|
2255
|
-
const multipleValidations = (value,
|
|
2256
|
-
if (!
|
|
2257
|
-
const runner = run(value,
|
|
2365
|
+
const multipleValidations = (value, methods) => {
|
|
2366
|
+
if (!methods.multipleValidations) return false;
|
|
2367
|
+
const runner = run(value, methods.multipleValidations.validations);
|
|
2258
2368
|
const rulesMapper = {
|
|
2259
2369
|
AND: () => runner.every(validation => validation),
|
|
2260
2370
|
OR: () => runner.some(validation => validation),
|
|
2261
2371
|
NOT: () => !runner.every(validation => validation)
|
|
2262
2372
|
};
|
|
2263
|
-
return rulesMapper[
|
|
2373
|
+
return rulesMapper[methods.multipleValidations.rule]();
|
|
2264
2374
|
};
|
|
2265
2375
|
|
|
2266
2376
|
const validations = {
|
|
2267
2377
|
max,
|
|
2268
2378
|
min,
|
|
2379
|
+
lessThan,
|
|
2380
|
+
greaterThan,
|
|
2269
2381
|
length,
|
|
2270
2382
|
regex,
|
|
2271
2383
|
url,
|
|
@@ -2287,7 +2399,6 @@ const validations = {
|
|
|
2287
2399
|
notEmpty,
|
|
2288
2400
|
bool,
|
|
2289
2401
|
exists,
|
|
2290
|
-
greaterThan: () => true,
|
|
2291
2402
|
isNumber,
|
|
2292
2403
|
conditions,
|
|
2293
2404
|
multipleValidations,
|
|
@@ -2296,8 +2407,52 @@ const validations = {
|
|
|
2296
2407
|
validDate
|
|
2297
2408
|
};
|
|
2298
2409
|
|
|
2299
|
-
|
|
2300
|
-
|
|
2410
|
+
/**
|
|
2411
|
+
* Custom RXJS Subject to gracefully handle errors on unsubscribed Subjects
|
|
2412
|
+
* that were unmounted due to adapter external handling such as visibility
|
|
2413
|
+
*/
|
|
2414
|
+
class SafeSubject extends Subject {
|
|
2415
|
+
constructor(isMounted) {
|
|
2416
|
+
super();
|
|
2417
|
+
this.isMounted = isMounted;
|
|
2418
|
+
}
|
|
2419
|
+
next(value) {
|
|
2420
|
+
if (this.isMounted()) {
|
|
2421
|
+
super.next(value);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* @internal
|
|
2428
|
+
* Handles the validation of a given value based on specified validation methods and rules.
|
|
2429
|
+
*
|
|
2430
|
+
* @param {string | number | boolean | unknown} value - The value to be validated.
|
|
2431
|
+
* @param {TSchemaValidation} validations - The schema validations to be applied.
|
|
2432
|
+
* @param {TValidationHandler} methods - The validation handler methods.
|
|
2433
|
+
* @param {keyof TValidationMethods} key - The specific key of the validation method to be used.
|
|
2434
|
+
* @returns {boolean} - Returns true if the value passes the validation, otherwise false.
|
|
2435
|
+
*
|
|
2436
|
+
* @example
|
|
2437
|
+
* const value = 'example@example.com';
|
|
2438
|
+
* const validations = {
|
|
2439
|
+
* required: true,
|
|
2440
|
+
* customName: { email: true }
|
|
2441
|
+
* };
|
|
2442
|
+
* const methods = {
|
|
2443
|
+
* email: (value) => /\S+@\S+\.\S+/.test(value)
|
|
2444
|
+
* };
|
|
2445
|
+
* const key = 'required';
|
|
2446
|
+
*
|
|
2447
|
+
* const isValid = handleValidation(value, validations, methods, key);
|
|
2448
|
+
* console.log(isValid); // Output: true
|
|
2449
|
+
*/
|
|
2450
|
+
function handleValidation(value, validations, methods, key) {
|
|
2451
|
+
if (isFunction(methods[key])) {
|
|
2452
|
+
return methods[key](value, validations);
|
|
2453
|
+
}
|
|
2454
|
+
return namedRule(value, validations[key], methods);
|
|
2455
|
+
}
|
|
2301
2456
|
|
|
2302
2457
|
/**
|
|
2303
2458
|
* Represents a form field with observables for managing form state, validations, and API requests.
|
|
@@ -2330,8 +2485,9 @@ class FormField {
|
|
|
2330
2485
|
mapper
|
|
2331
2486
|
}) {
|
|
2332
2487
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
2488
|
+
this.valueSubscription$ = new Subscription();
|
|
2333
2489
|
this.fieldStateSubscription$ = new Subscription();
|
|
2334
|
-
this.originalSchema = schemaComponent;
|
|
2490
|
+
this.originalSchema = cloneDeep(schemaComponent);
|
|
2335
2491
|
this.config = {
|
|
2336
2492
|
defaultAPIdebounceTimeMS: Number(config === null || config === void 0 ? void 0 : config.defaultAPIdebounceTimeMS) ? Number(config === null || config === void 0 ? void 0 : config.defaultAPIdebounceTimeMS) : DEFAULT_API_DEBOUNCE_TIME,
|
|
2337
2493
|
defaultStateRefreshTimeMS: Number(config === null || config === void 0 ? void 0 : config.defaultStateRefreshTimeMS) ? Number(config === null || config === void 0 ? void 0 : config.defaultStateRefreshTimeMS) : DEFAULT_STATE_REFRESH_TIME
|
|
@@ -2342,14 +2498,14 @@ class FormField {
|
|
|
2342
2498
|
this.path = path;
|
|
2343
2499
|
this.children = children;
|
|
2344
2500
|
this.validations = schemaComponent.validations;
|
|
2345
|
-
this.errorMessages = schemaComponent.
|
|
2501
|
+
this.errorMessages = (_a = schemaComponent.validations) === null || _a === void 0 ? void 0 : _a.messages;
|
|
2346
2502
|
this.visibilityConditions = schemaComponent.visibilityConditions;
|
|
2347
2503
|
this.resetValues = schemaComponent.resetValues;
|
|
2348
2504
|
this.apiSchema = schemaComponent.api;
|
|
2349
2505
|
this.formatters = schemaComponent.formatters;
|
|
2350
2506
|
this.masks = schemaComponent.masks;
|
|
2351
2507
|
if (mapper.valueChangeEvent) this.valueChangeEvent = mapper.valueChangeEvent;
|
|
2352
|
-
if ((
|
|
2508
|
+
if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) this.valuePropName = mapper.events.setValue;
|
|
2353
2509
|
this.mapper = mapper;
|
|
2354
2510
|
this.validateVisibility = validateVisibility;
|
|
2355
2511
|
this.resetValue = resetValue;
|
|
@@ -2357,10 +2513,6 @@ class FormField {
|
|
|
2357
2513
|
this.fieldEventSubject$ = fieldEventSubject$;
|
|
2358
2514
|
this.dataSubject$ = dataSubject$;
|
|
2359
2515
|
this._props = schemaComponent.props || {};
|
|
2360
|
-
this._value = '';
|
|
2361
|
-
this._stateValue = ((_b = this.mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) ? {
|
|
2362
|
-
[this.mapper.events.setValue]: ''
|
|
2363
|
-
} : {};
|
|
2364
2516
|
this._metadata = '';
|
|
2365
2517
|
this.errorsString = '';
|
|
2366
2518
|
this.errorsList = [];
|
|
@@ -2380,7 +2532,9 @@ class FormField {
|
|
|
2380
2532
|
};
|
|
2381
2533
|
this._errors = {};
|
|
2382
2534
|
this._valid = false;
|
|
2535
|
+
this._mounted = true;
|
|
2383
2536
|
this.initializeObservers();
|
|
2537
|
+
this.value = this.initialValue || '';
|
|
2384
2538
|
}
|
|
2385
2539
|
/**
|
|
2386
2540
|
* method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
|
|
@@ -2388,25 +2542,25 @@ class FormField {
|
|
|
2388
2542
|
initializeObservers() {
|
|
2389
2543
|
var _a;
|
|
2390
2544
|
if (!this.valueSubject$ || this.valueSubject$.closed) {
|
|
2391
|
-
this.valueSubject$ = new
|
|
2545
|
+
this.valueSubject$ = new SafeSubject(() => this._mounted);
|
|
2392
2546
|
}
|
|
2393
2547
|
if (!this.errorSubject$ || this.errorSubject$.closed) {
|
|
2394
|
-
this.errorSubject$ = new
|
|
2548
|
+
this.errorSubject$ = new SafeSubject(() => this._mounted);
|
|
2395
2549
|
}
|
|
2396
2550
|
if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
|
|
2397
|
-
this.visibilitySubject$ = new
|
|
2551
|
+
this.visibilitySubject$ = new SafeSubject(() => this._mounted);
|
|
2398
2552
|
}
|
|
2399
2553
|
if (!this.apiSubject$ || this.apiSubject$.closed) {
|
|
2400
|
-
this.apiSubject$ = new
|
|
2554
|
+
this.apiSubject$ = new SafeSubject(() => this._mounted);
|
|
2401
2555
|
}
|
|
2402
2556
|
if (!this.propsSubject$ || this.propsSubject$.closed) {
|
|
2403
|
-
this.propsSubject$ = new
|
|
2557
|
+
this.propsSubject$ = new SafeSubject(() => this._mounted);
|
|
2404
2558
|
}
|
|
2405
2559
|
if (!this.fieldStateSubscription$ || this.fieldStateSubscription$.closed) {
|
|
2406
2560
|
this.fieldStateSubscription$ = new Subscription();
|
|
2407
2561
|
}
|
|
2408
2562
|
if (!this.apiEventQueueSubject$ || this.apiEventQueueSubject$.closed) {
|
|
2409
|
-
this.apiEventQueueSubject$ = new
|
|
2563
|
+
this.apiEventQueueSubject$ = new SafeSubject(() => this._mounted);
|
|
2410
2564
|
}
|
|
2411
2565
|
this.fieldState$ = combineLatest({
|
|
2412
2566
|
visibility: this.visibilitySubject$.pipe(startWith(this._visibility)),
|
|
@@ -2420,9 +2574,6 @@ class FormField {
|
|
|
2420
2574
|
}) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
|
|
2421
2575
|
this.apiRequest(payload);
|
|
2422
2576
|
});
|
|
2423
|
-
if (!isNil(this.initialValue)) {
|
|
2424
|
-
this.value = this.initialValue;
|
|
2425
|
-
}
|
|
2426
2577
|
}
|
|
2427
2578
|
/**
|
|
2428
2579
|
* Retrieves the properties associated with the form field.
|
|
@@ -2442,6 +2593,7 @@ class FormField {
|
|
|
2442
2593
|
this._props = props;
|
|
2443
2594
|
this.propsSubject$.next(this.props);
|
|
2444
2595
|
this.templateSubject$.next({
|
|
2596
|
+
scope: 'fields',
|
|
2445
2597
|
key: this.name,
|
|
2446
2598
|
event: 'ON_PROPS'
|
|
2447
2599
|
});
|
|
@@ -2505,6 +2657,7 @@ class FormField {
|
|
|
2505
2657
|
}
|
|
2506
2658
|
this.stateValue && this.valueSubject$.next(this.stateValue);
|
|
2507
2659
|
this.templateSubject$.next({
|
|
2660
|
+
scope: 'fields',
|
|
2508
2661
|
key: this.name,
|
|
2509
2662
|
event: 'ON_VALUE'
|
|
2510
2663
|
});
|
|
@@ -2525,8 +2678,26 @@ class FormField {
|
|
|
2525
2678
|
set visibility(visible) {
|
|
2526
2679
|
if (typeof visible === 'undefined' || visible === this.visibility) return;
|
|
2527
2680
|
this._visibility = visible;
|
|
2681
|
+
/**
|
|
2682
|
+
* I was sure I would not require to gambiarra, but..
|
|
2683
|
+
* in order to ignore an hidden value on a form submit
|
|
2684
|
+
* or revalidate it when it comes back to visibility
|
|
2685
|
+
* I needed to...
|
|
2686
|
+
* I don't recommend setting private properties like this
|
|
2687
|
+
* this will force the field to be valid when it's hidden
|
|
2688
|
+
* and trigger the validation when it's visible
|
|
2689
|
+
*/
|
|
2690
|
+
if (!this.visibility) {
|
|
2691
|
+
this.value = '';
|
|
2692
|
+
this._valid = true;
|
|
2693
|
+
} else {
|
|
2694
|
+
this.setFieldValidity({
|
|
2695
|
+
event: 'ON_FIELD_MOUNT'
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2528
2698
|
this.visibilitySubject$.next(this.visibility);
|
|
2529
2699
|
this.templateSubject$.next({
|
|
2700
|
+
scope: 'fields',
|
|
2530
2701
|
key: this.name,
|
|
2531
2702
|
event: 'ON_VISIBILITY'
|
|
2532
2703
|
});
|
|
@@ -2559,12 +2730,13 @@ class FormField {
|
|
|
2559
2730
|
this.errorsList = Object.values(this.errors);
|
|
2560
2731
|
this.errorsString = this.errorsList.join(', ');
|
|
2561
2732
|
/**
|
|
2562
|
-
* if any error
|
|
2733
|
+
* if any error receives a list of errors, set a prop for it, currently only supporting a single string
|
|
2563
2734
|
*/
|
|
2564
2735
|
((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && this.errorSubject$.next({
|
|
2565
2736
|
[this.mapper.events.setErrorMessage]: this.errorsString
|
|
2566
2737
|
});
|
|
2567
2738
|
this.templateSubject$.next({
|
|
2739
|
+
scope: 'fields',
|
|
2568
2740
|
key: this.name,
|
|
2569
2741
|
event: 'ON_PROPS'
|
|
2570
2742
|
});
|
|
@@ -2587,6 +2759,7 @@ class FormField {
|
|
|
2587
2759
|
this._api = response;
|
|
2588
2760
|
this.apiSubject$.next(this.api);
|
|
2589
2761
|
this.templateSubject$.next({
|
|
2762
|
+
scope: 'fields',
|
|
2590
2763
|
key: this.name,
|
|
2591
2764
|
event: 'ON_API'
|
|
2592
2765
|
});
|
|
@@ -2610,6 +2783,7 @@ class FormField {
|
|
|
2610
2783
|
this.initializeObservers();
|
|
2611
2784
|
this.subscribeValue(valueSubscription);
|
|
2612
2785
|
this.subscribeState(propsSubscription);
|
|
2786
|
+
this._mounted = true;
|
|
2613
2787
|
}
|
|
2614
2788
|
/**
|
|
2615
2789
|
* Sets the value of the form field and emits associated events.
|
|
@@ -2666,54 +2840,43 @@ class FormField {
|
|
|
2666
2840
|
setFieldValidity({
|
|
2667
2841
|
event
|
|
2668
2842
|
}) {
|
|
2669
|
-
var _a;
|
|
2670
|
-
if (!this.validations) {
|
|
2843
|
+
var _a, _b, _c, _d;
|
|
2844
|
+
if (!this.validations || !this.visibility) {
|
|
2845
|
+
this.errors = {};
|
|
2671
2846
|
this._valid = true;
|
|
2672
2847
|
return;
|
|
2673
2848
|
}
|
|
2674
|
-
/*
|
|
2675
|
-
@TODO evaluate if _valid flag needs to be updated on all events, this condition saves resources,
|
|
2676
|
-
currently form submition needs to be controlled with form instance submit property function that
|
|
2677
|
-
will evaluate if all fields are valid regardless the events that triggers error messages
|
|
2678
|
-
*/
|
|
2679
|
-
if (!this.validations.events.includes(event) && event !== 'ON_FORM_SUBMIT') return;
|
|
2680
2849
|
let valid = true;
|
|
2681
|
-
const errors =
|
|
2682
|
-
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.
|
|
2850
|
+
const errors = {};
|
|
2851
|
+
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.methods;
|
|
2683
2852
|
schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
|
|
2684
|
-
|
|
2685
|
-
const error = validations[validationKey](this.value, schemaValidations);
|
|
2853
|
+
const error = handleValidation(this.value, schemaValidations, validations, validationKey);
|
|
2686
2854
|
// setting valid flag
|
|
2687
2855
|
valid = !error && valid;
|
|
2688
2856
|
// setting error messages
|
|
2689
|
-
if (
|
|
2690
|
-
if (
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
errors[validationKey] = this.errorMessages.default;
|
|
2696
|
-
}
|
|
2697
|
-
} else {
|
|
2698
|
-
delete errors[validationKey];
|
|
2857
|
+
if (error && this.errorMessages) {
|
|
2858
|
+
if (validationKey in this.errorMessages) {
|
|
2859
|
+
const messages = this.errorMessages;
|
|
2860
|
+
errors[validationKey] = messages[validationKey];
|
|
2861
|
+
} else if ('default' in this.errorMessages) {
|
|
2862
|
+
errors[validationKey] = this.errorMessages.default;
|
|
2699
2863
|
}
|
|
2864
|
+
} else {
|
|
2865
|
+
delete errors[validationKey];
|
|
2700
2866
|
}
|
|
2701
2867
|
});
|
|
2702
2868
|
this._valid = valid;
|
|
2703
|
-
this.
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
valid = !error && valid;
|
|
2715
|
-
});
|
|
2716
|
-
this._valid = valid;
|
|
2869
|
+
if ((_c = (_b = this.validations) === null || _b === void 0 ? void 0 : _b.eventMessages) === null || _c === void 0 ? void 0 : _c[event]) {
|
|
2870
|
+
const eventMessages = {};
|
|
2871
|
+
(_d = this.validations.eventMessages[event]) === null || _d === void 0 ? void 0 : _d.forEach(method => {
|
|
2872
|
+
if (method in errors) {
|
|
2873
|
+
eventMessages[method] = errors[method];
|
|
2874
|
+
}
|
|
2875
|
+
});
|
|
2876
|
+
this.errors = eventMessages;
|
|
2877
|
+
} else if (event === 'ON_FORM_SUBMIT') {
|
|
2878
|
+
this.errors = errors;
|
|
2879
|
+
}
|
|
2717
2880
|
}
|
|
2718
2881
|
/**
|
|
2719
2882
|
* Formats the field value using the specified formatters, if available.
|
|
@@ -2747,7 +2910,7 @@ class FormField {
|
|
|
2747
2910
|
let valid = true;
|
|
2748
2911
|
const preConditions = config.preConditions;
|
|
2749
2912
|
preConditions && Object.keys(preConditions).forEach(validationKey => {
|
|
2750
|
-
const error =
|
|
2913
|
+
const error = handleValidation(this.value, preConditions, validations, validationKey);
|
|
2751
2914
|
valid = valid && !error;
|
|
2752
2915
|
});
|
|
2753
2916
|
if (config.blockRequestWhenInvalid) {
|
|
@@ -2766,6 +2929,17 @@ class FormField {
|
|
|
2766
2929
|
}) {
|
|
2767
2930
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
2768
2931
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2932
|
+
const configRequest = config => __awaiter(this, void 0, void 0, function* () {
|
|
2933
|
+
try {
|
|
2934
|
+
const responseData = yield makeRequest(config.method, config.url, config.headers, config.body);
|
|
2935
|
+
const apiResponseData = JSON.parse(String(responseData));
|
|
2936
|
+
const response = config.resultPath ? get(apiResponseData, config.resultPath) : apiResponseData;
|
|
2937
|
+
// this.apiResponseData = { response };
|
|
2938
|
+
return response;
|
|
2939
|
+
} catch (e) {
|
|
2940
|
+
return !isNil(config === null || config === void 0 ? void 0 : config.fallbackValue) ? config.fallbackValue : 'error';
|
|
2941
|
+
}
|
|
2942
|
+
});
|
|
2769
2943
|
if (!((_b = (_a = this.apiSchema) === null || _a === void 0 ? void 0 : _a.defaultConfig) === null || _b === void 0 ? void 0 : _b.events.includes(event)) && !(((_c = this.apiSchema) === null || _c === void 0 ? void 0 : _c.configs) && Object.keys((_d = this.apiSchema) === null || _d === void 0 ? void 0 : _d.configs).some(key => {
|
|
2770
2944
|
var _a, _b;
|
|
2771
2945
|
return (_b = (_a = this.apiSchema) === null || _a === void 0 ? void 0 : _a.configs) === null || _b === void 0 ? void 0 : _b[key].events.includes(event);
|
|
@@ -2776,19 +2950,10 @@ class FormField {
|
|
|
2776
2950
|
};
|
|
2777
2951
|
const config = (_e = this.apiSchema.defaultConfig) === null || _e === void 0 ? void 0 : _e.config;
|
|
2778
2952
|
if (config && ((_g = (_f = this.apiSchema) === null || _f === void 0 ? void 0 : _f.defaultConfig) === null || _g === void 0 ? void 0 : _g.events.includes(event)) && this.checkApiRequestValidations(config)) {
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
// this.apiResponseData = { response };
|
|
2784
|
-
responses.default = {
|
|
2785
|
-
response
|
|
2786
|
-
};
|
|
2787
|
-
} catch (e) {
|
|
2788
|
-
responses.default = {
|
|
2789
|
-
response: !isNil(config === null || config === void 0 ? void 0 : config.fallbackValue) ? config.fallbackValue : 'error'
|
|
2790
|
-
};
|
|
2791
|
-
}
|
|
2953
|
+
const response = yield configRequest(config);
|
|
2954
|
+
responses.default = {
|
|
2955
|
+
response
|
|
2956
|
+
};
|
|
2792
2957
|
}
|
|
2793
2958
|
if (((_h = this.apiSchema) === null || _h === void 0 ? void 0 : _h.configs) && Object.keys((_j = this.apiSchema) === null || _j === void 0 ? void 0 : _j.configs).some(key => {
|
|
2794
2959
|
var _a, _b;
|
|
@@ -2799,38 +2964,21 @@ class FormField {
|
|
|
2799
2964
|
@TODO handle promises with error
|
|
2800
2965
|
*/
|
|
2801
2966
|
const result = yield Promise.all(Object.keys(this.apiSchema.configs).map(configKey => __awaiter(this, void 0, void 0, function* () {
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
res({
|
|
2811
|
-
name: configKey,
|
|
2812
|
-
result: {
|
|
2813
|
-
response
|
|
2814
|
-
}
|
|
2815
|
-
});
|
|
2816
|
-
// responses.named![configKey] = { response };
|
|
2817
|
-
});
|
|
2818
|
-
} catch (e) {
|
|
2819
|
-
res({
|
|
2820
|
-
name: configKey,
|
|
2821
|
-
result: {
|
|
2822
|
-
response: !isNil(config.fallbackValue) ? config.fallbackValue : 'error'
|
|
2823
|
-
}
|
|
2824
|
-
});
|
|
2967
|
+
var _k, _l, _m, _o;
|
|
2968
|
+
const config = (_l = (_k = this.apiSchema) === null || _k === void 0 ? void 0 : _k.configs) === null || _l === void 0 ? void 0 : _l[configKey].config;
|
|
2969
|
+
if (config && ((_o = (_m = this.apiSchema) === null || _m === void 0 ? void 0 : _m.configs) === null || _o === void 0 ? void 0 : _o[configKey].events.includes(event)) && this.checkApiRequestValidations(config)) {
|
|
2970
|
+
const response = yield configRequest(config);
|
|
2971
|
+
return {
|
|
2972
|
+
name: configKey,
|
|
2973
|
+
result: {
|
|
2974
|
+
response
|
|
2825
2975
|
}
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
return null;
|
|
2828
2979
|
})));
|
|
2829
|
-
result.forEach(
|
|
2830
|
-
name
|
|
2831
|
-
result
|
|
2832
|
-
}) => {
|
|
2833
|
-
if (responses.named) responses.named[name] = result;
|
|
2980
|
+
result.forEach(payload => {
|
|
2981
|
+
if (payload && responses.named) responses.named[payload.name] = payload.result;
|
|
2834
2982
|
});
|
|
2835
2983
|
}
|
|
2836
2984
|
}
|
|
@@ -2843,7 +2991,8 @@ class FormField {
|
|
|
2843
2991
|
* @returns {void}
|
|
2844
2992
|
*/
|
|
2845
2993
|
destroyField() {
|
|
2846
|
-
this.
|
|
2994
|
+
this._mounted = false;
|
|
2995
|
+
this.valueSubscription$.unsubscribe();
|
|
2847
2996
|
this.visibilitySubject$.unsubscribe();
|
|
2848
2997
|
this.fieldStateSubscription$.unsubscribe();
|
|
2849
2998
|
this.propsSubject$.unsubscribe();
|
|
@@ -2869,13 +3018,12 @@ class FormField {
|
|
|
2869
3018
|
* @returns {void}
|
|
2870
3019
|
*/
|
|
2871
3020
|
subscribeValue(callback) {
|
|
2872
|
-
this.valueSubject$.subscribe({
|
|
3021
|
+
this.valueSubscription$ = this.valueSubject$.subscribe({
|
|
2873
3022
|
next: callback
|
|
2874
3023
|
});
|
|
2875
3024
|
}
|
|
2876
3025
|
}
|
|
2877
3026
|
|
|
2878
|
-
const IVARPROPNAME = 'iVars';
|
|
2879
3027
|
/**
|
|
2880
3028
|
* Represents the core logic for managing a form, including field management, validation, and submission.
|
|
2881
3029
|
*/
|
|
@@ -2889,38 +3037,39 @@ class FormCore {
|
|
|
2889
3037
|
* @param {string} [entry.action] - The action attribute of the form.
|
|
2890
3038
|
* @param {string} [entry.method] - The method attribute of the form.
|
|
2891
3039
|
* @param {IFormSchema.iVars} [entry.iVars] - The internal variables of the form.
|
|
2892
|
-
* @param {(data: TFormValues) => void} [entry.onSubmit] - A callback function to handle form submission.
|
|
2893
3040
|
* @param {((payload: {field: string;data: TFormValues;}) => void) | undefined} [entry.onData] - A callback function to handle data emission.
|
|
2894
3041
|
*/
|
|
2895
3042
|
constructor(entry) {
|
|
2896
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3043
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
3044
|
+
this.templateSubscription$ = new Subscription();
|
|
3045
|
+
this.mappers = new Map();
|
|
2897
3046
|
this.schema = entry.schema;
|
|
2898
3047
|
this.fields = new Map();
|
|
2899
3048
|
this.initialValues = entry.initialValues || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.initialValues);
|
|
2900
3049
|
this.action = entry.action || ((_b = entry.schema) === null || _b === void 0 ? void 0 : _b.action);
|
|
2901
3050
|
this.method = entry.method || ((_c = entry.schema) === null || _c === void 0 ? void 0 : _c.method);
|
|
2902
3051
|
this._iVars = entry.iVars || ((_d = entry.schema) === null || _d === void 0 ? void 0 : _d.iVars) || {};
|
|
2903
|
-
this.onSubmit = entry.onSubmit;
|
|
2904
3052
|
this.config = {
|
|
2905
3053
|
defaultAPIdebounceTimeMS: Number((_e = entry.config) === null || _e === void 0 ? void 0 : _e.defaultAPIdebounceTimeMS) ? Number((_f = entry.config) === null || _f === void 0 ? void 0 : _f.defaultAPIdebounceTimeMS) : DEFAULT_API_DEBOUNCE_TIME,
|
|
2906
3054
|
defaultStateRefreshTimeMS: Number((_g = entry.config) === null || _g === void 0 ? void 0 : _g.defaultStateRefreshTimeMS) ? Number((_h = entry.config) === null || _h === void 0 ? void 0 : _h.defaultStateRefreshTimeMS) : DEFAULT_STATE_REFRESH_TIME
|
|
2907
3055
|
};
|
|
2908
|
-
|
|
3056
|
+
(_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
|
|
3057
|
+
this.mappers.set(mapper.componentName, mapper);
|
|
3058
|
+
});
|
|
2909
3059
|
this.schema && FormCore.checkIndexes(this.schema.components);
|
|
2910
3060
|
this.templateSubject$ = new Subject();
|
|
2911
3061
|
this.submitSubject$ = new Subject();
|
|
2912
3062
|
this.fieldEventSubject$ = new Subject();
|
|
2913
3063
|
this.dataSubject$ = new Subject();
|
|
2914
|
-
this.
|
|
3064
|
+
this.mountSubject$ = new Subject();
|
|
2915
3065
|
this.subscribedTemplates = [];
|
|
2916
3066
|
this.schema && this.serializeStructure(this.schema.components);
|
|
2917
3067
|
this.schema && this.subscribeTemplates();
|
|
2918
|
-
this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
|
|
3068
|
+
this.templateSubscription$ = this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
|
|
2919
3069
|
this.templateSubject$.next({
|
|
2920
|
-
|
|
3070
|
+
scope: 'iVars',
|
|
2921
3071
|
event: 'ON_IVARS'
|
|
2922
3072
|
});
|
|
2923
|
-
entry.onData && this.subscribeData(entry.onData);
|
|
2924
3073
|
/*
|
|
2925
3074
|
mount events needs to occur on form level, only when all the fields are instantiated
|
|
2926
3075
|
is it possible to apply all the side effects that occur globally, same effect occur
|
|
@@ -2931,6 +3080,7 @@ class FormCore {
|
|
|
2931
3080
|
event: 'ON_FIELD_MOUNT'
|
|
2932
3081
|
});
|
|
2933
3082
|
this.refreshTemplates({
|
|
3083
|
+
scope: 'fields',
|
|
2934
3084
|
key,
|
|
2935
3085
|
event: 'ON_FIELDS'
|
|
2936
3086
|
});
|
|
@@ -2952,7 +3102,7 @@ class FormCore {
|
|
|
2952
3102
|
set iVars(payload) {
|
|
2953
3103
|
this._iVars = payload;
|
|
2954
3104
|
this.templateSubject$.next({
|
|
2955
|
-
|
|
3105
|
+
scope: 'iVars',
|
|
2956
3106
|
event: 'ON_IVARS'
|
|
2957
3107
|
});
|
|
2958
3108
|
}
|
|
@@ -2980,7 +3130,6 @@ class FormCore {
|
|
|
2980
3130
|
validations,
|
|
2981
3131
|
visibilityConditions,
|
|
2982
3132
|
resetValues,
|
|
2983
|
-
errorMessages,
|
|
2984
3133
|
api
|
|
2985
3134
|
}
|
|
2986
3135
|
}, key) => {
|
|
@@ -2991,26 +3140,11 @@ class FormCore {
|
|
|
2991
3140
|
validations,
|
|
2992
3141
|
visibilityConditions,
|
|
2993
3142
|
resetValues,
|
|
2994
|
-
errorMessages,
|
|
2995
3143
|
apiSchema: api
|
|
2996
3144
|
};
|
|
2997
3145
|
traverseObject(template, key).forEach(element => this.subscribedTemplates.push(element));
|
|
2998
3146
|
});
|
|
2999
3147
|
}
|
|
3000
|
-
/**
|
|
3001
|
-
*
|
|
3002
|
-
* @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call on data
|
|
3003
|
-
*/
|
|
3004
|
-
subscribeData(callback) {
|
|
3005
|
-
this.dataCallbackSubscription$ = this.dataSubject$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS), map(({
|
|
3006
|
-
key
|
|
3007
|
-
}) => ({
|
|
3008
|
-
field: key,
|
|
3009
|
-
data: this.getFormValues()
|
|
3010
|
-
}))).subscribe({
|
|
3011
|
-
next: callback
|
|
3012
|
-
});
|
|
3013
|
-
}
|
|
3014
3148
|
/**
|
|
3015
3149
|
* Gets the value of a property from a field.
|
|
3016
3150
|
*
|
|
@@ -3021,20 +3155,27 @@ class FormCore {
|
|
|
3021
3155
|
* @returns {unknown | undefined} The value of the property, or undefined if the field doesn't exist.
|
|
3022
3156
|
*/
|
|
3023
3157
|
getValue({
|
|
3158
|
+
scope,
|
|
3024
3159
|
key,
|
|
3025
3160
|
property,
|
|
3026
3161
|
path
|
|
3027
3162
|
}) {
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3163
|
+
switch (scope) {
|
|
3164
|
+
case 'iVars':
|
|
3165
|
+
{
|
|
3166
|
+
const value = get(this.iVars, [key, ...(property ? [property] : []), ...path]);
|
|
3167
|
+
return value;
|
|
3168
|
+
}
|
|
3169
|
+
case 'fields':
|
|
3170
|
+
{
|
|
3171
|
+
const field = this.fields.get(key);
|
|
3172
|
+
if (!field) return console.warn(`failed to get value from ${key}`);
|
|
3173
|
+
if (property === 'props' && path[0] === field.valuePropName) {
|
|
3174
|
+
return field.stateValue;
|
|
3175
|
+
}
|
|
3176
|
+
return path.length > 0 ? get(field[property], path) : field[property];
|
|
3177
|
+
}
|
|
3036
3178
|
}
|
|
3037
|
-
return path.length > 0 ? get(field[property], path) : field[property];
|
|
3038
3179
|
}
|
|
3039
3180
|
/**
|
|
3040
3181
|
* Sets the value of a property in a field.
|
|
@@ -3097,27 +3238,26 @@ class FormCore {
|
|
|
3097
3238
|
* @returns {string[]} An array of extracted parameters.
|
|
3098
3239
|
*/
|
|
3099
3240
|
extractParams(expression) {
|
|
3100
|
-
const regex = /\${(.*?)}/g;
|
|
3101
3241
|
const extractedValues = [];
|
|
3102
3242
|
let match;
|
|
3103
|
-
while (!isNil(match =
|
|
3243
|
+
while (!isNil(match = TEMPLATE_REGEX_DELIMITATOR.exec(expression))) {
|
|
3104
3244
|
extractedValues.push(match[1]);
|
|
3105
3245
|
}
|
|
3106
|
-
const
|
|
3107
|
-
const splittedString = extractedValues.map(el => el.split(operatorRegex));
|
|
3246
|
+
const splittedString = extractedValues.map(el => el.split(TEMPLATE_REGEX_OPERATOR_SPLITTER));
|
|
3108
3247
|
const result = splittedString.map(splittedStringVal => {
|
|
3109
3248
|
return splittedStringVal.filter(Boolean).reduce((acc, curr) => {
|
|
3110
|
-
if (curr.match(
|
|
3249
|
+
if (curr.match(TEMPLATE_REGEX_OPERATOR_MATCHER)) {
|
|
3111
3250
|
return `${acc}${curr}`;
|
|
3112
3251
|
}
|
|
3113
3252
|
let value;
|
|
3114
3253
|
// check if element is in dot notation to get from field instances
|
|
3115
3254
|
const element = curr.split('.');
|
|
3116
3255
|
const currElementContent = element.length > 1 ? this.getValue({
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3256
|
+
scope: element[0],
|
|
3257
|
+
key: element[1],
|
|
3258
|
+
property: element[2],
|
|
3259
|
+
path: element.slice(3)
|
|
3260
|
+
}) : curr;
|
|
3121
3261
|
let currValue;
|
|
3122
3262
|
// if any parsable content was passed to the conditions, parse them
|
|
3123
3263
|
// required to be able to apply conditions
|
|
@@ -3138,6 +3278,11 @@ class FormCore {
|
|
|
3138
3278
|
case 'object':
|
|
3139
3279
|
if (currValue === null) {
|
|
3140
3280
|
value = null;
|
|
3281
|
+
break;
|
|
3282
|
+
}
|
|
3283
|
+
if (currValue instanceof Date) {
|
|
3284
|
+
value = `new Date(\`${currValue}\`)`;
|
|
3285
|
+
break;
|
|
3141
3286
|
}
|
|
3142
3287
|
value = JSON.stringify(currValue);
|
|
3143
3288
|
break;
|
|
@@ -3164,8 +3309,8 @@ class FormCore {
|
|
|
3164
3309
|
* @returns {string} The expression string with the replacements made.
|
|
3165
3310
|
*/
|
|
3166
3311
|
replaceExpression(expression, values) {
|
|
3167
|
-
const regex =
|
|
3168
|
-
return expression.replace(regex, () => values.shift() || '');
|
|
3312
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
3313
|
+
return expression.replace(regex, () => String(values.shift()) || '');
|
|
3169
3314
|
}
|
|
3170
3315
|
/**
|
|
3171
3316
|
* Checks if an expression string contains string concatenation within a marked expression.
|
|
@@ -3174,8 +3319,7 @@ class FormCore {
|
|
|
3174
3319
|
* @returns {boolean} True if the expression contains string concatenation, otherwise false.
|
|
3175
3320
|
*/
|
|
3176
3321
|
hasStringConcatenation(expression) {
|
|
3177
|
-
|
|
3178
|
-
return !regex.test(expression);
|
|
3322
|
+
return !TEMPLATE_REGEX_STRING_CONCATENATION_DETECTOR.test(expression);
|
|
3179
3323
|
}
|
|
3180
3324
|
/**
|
|
3181
3325
|
* Refreshes templates with updated values.
|
|
@@ -3195,7 +3339,7 @@ class FormCore {
|
|
|
3195
3339
|
originExpression,
|
|
3196
3340
|
originFieldKeys
|
|
3197
3341
|
}) => {
|
|
3198
|
-
if (originFieldKeys.includes(key)) {
|
|
3342
|
+
if (!key || originFieldKeys.includes(key)) {
|
|
3199
3343
|
const originExpressions = this.extractParams(originExpression);
|
|
3200
3344
|
let originValue;
|
|
3201
3345
|
if (this.hasStringConcatenation(originExpression)) {
|
|
@@ -3204,6 +3348,7 @@ class FormCore {
|
|
|
3204
3348
|
originValue = originExpressions === null || originExpressions === void 0 ? void 0 : originExpressions[0];
|
|
3205
3349
|
}
|
|
3206
3350
|
const destinationValue = this.getValue({
|
|
3351
|
+
scope: 'fields',
|
|
3207
3352
|
key: destinationKey,
|
|
3208
3353
|
property: destinationProperty,
|
|
3209
3354
|
path: destinationPath
|
|
@@ -3221,6 +3366,26 @@ class FormCore {
|
|
|
3221
3366
|
}
|
|
3222
3367
|
});
|
|
3223
3368
|
}
|
|
3369
|
+
/**
|
|
3370
|
+
* @internal
|
|
3371
|
+
* Update field visibility accordingly.
|
|
3372
|
+
*
|
|
3373
|
+
* @param {string} field - Field name to be updated.
|
|
3374
|
+
* @param {boolean} hasError - Condition to be used as visibility.
|
|
3375
|
+
* @param {boolean|undefined} showOnlyIfTrue - Flag to be considered when update field visibility. If it's true, then considered error, if it's false, always considered the opposite.
|
|
3376
|
+
*/
|
|
3377
|
+
setFieldVisibility(field, hasError, showOnlyIfTrue) {
|
|
3378
|
+
if (!this.fields.has(field)) {
|
|
3379
|
+
/*
|
|
3380
|
+
* check with João the need for a global mount event for the form
|
|
3381
|
+
* to avoid not displaying this message in some cases (AsFormField)
|
|
3382
|
+
* suggestion: create subscriber to targetField.ON_FIELD_MOUNT
|
|
3383
|
+
*/
|
|
3384
|
+
console.warn(`failed to update visibility onto field ${field}`);
|
|
3385
|
+
} else {
|
|
3386
|
+
this.fields.get(field).visibility = showOnlyIfTrue ? hasError : !hasError;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3224
3389
|
/**
|
|
3225
3390
|
* Validates visibility conditions for a given event and updates field visibility accordingly.
|
|
3226
3391
|
*
|
|
@@ -3234,21 +3399,44 @@ class FormCore {
|
|
|
3234
3399
|
}) {
|
|
3235
3400
|
const field = this.fields.get(key);
|
|
3236
3401
|
const structVisibility = field === null || field === void 0 ? void 0 : field.visibilityConditions;
|
|
3237
|
-
if (!structVisibility || !(structVisibility === null || structVisibility === void 0 ? void 0 : structVisibility.some(config => config.events.includes(event))))
|
|
3402
|
+
if (!structVisibility || !(structVisibility === null || structVisibility === void 0 ? void 0 : structVisibility.some(config => config.events.includes(event)))) {
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3238
3405
|
structVisibility.forEach(structElement => {
|
|
3239
3406
|
if (!structElement.events.includes(event)) return;
|
|
3240
3407
|
Object.keys(structElement.validations).forEach(validationKey => {
|
|
3241
|
-
const error =
|
|
3408
|
+
const error = handleValidation(field.value, structElement.validations, validations, validationKey);
|
|
3242
3409
|
if (Array.isArray(structElement.fields)) {
|
|
3243
3410
|
structElement.fields.forEach(fieldKey => {
|
|
3244
|
-
|
|
3411
|
+
this.setFieldVisibility(fieldKey, error, !!(field.value && structElement.showOnlyIfTrue));
|
|
3245
3412
|
});
|
|
3246
3413
|
} else if (structElement.fields) {
|
|
3247
|
-
|
|
3414
|
+
this.setFieldVisibility(structElement.fields, error, !!(field.value && structElement.showOnlyIfTrue));
|
|
3248
3415
|
}
|
|
3249
3416
|
});
|
|
3250
3417
|
});
|
|
3251
3418
|
}
|
|
3419
|
+
/**
|
|
3420
|
+
* @internal
|
|
3421
|
+
* Update field value and emit change and cleared event.
|
|
3422
|
+
*
|
|
3423
|
+
* @param {string} key - Field name to be updated.
|
|
3424
|
+
* @param {unknown} value - Value to be inserted into field.
|
|
3425
|
+
*/
|
|
3426
|
+
setResetFieldValue(key, value) {
|
|
3427
|
+
if (!this.fields.has(key)) {
|
|
3428
|
+
console.warn(`failed to reset value onto field ${key}`);
|
|
3429
|
+
} else {
|
|
3430
|
+
const field = this.fields.get(key);
|
|
3431
|
+
field.emitValue({
|
|
3432
|
+
value: value,
|
|
3433
|
+
event: 'ON_FIELD_CHANGE'
|
|
3434
|
+
});
|
|
3435
|
+
field.emitEvents({
|
|
3436
|
+
event: 'ON_FIELD_CLEARED'
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3252
3440
|
/**
|
|
3253
3441
|
* Resets field values based on reset conditions defined in the schema.
|
|
3254
3442
|
*
|
|
@@ -3265,23 +3453,23 @@ class FormCore {
|
|
|
3265
3453
|
if (!structResetValue || !(structResetValue === null || structResetValue === void 0 ? void 0 : structResetValue.some(config => config.events.includes(event)))) return;
|
|
3266
3454
|
structResetValue.forEach(structElement => {
|
|
3267
3455
|
if (!structElement.events.includes(event)) return;
|
|
3456
|
+
const handleFieldRestoration = () => {
|
|
3457
|
+
if (Array.isArray(structElement.fields)) {
|
|
3458
|
+
structElement.fields.forEach((fieldKey, index) => {
|
|
3459
|
+
const resettledValue = Array.isArray(structElement.resettledValue) ? structElement.resettledValue[index] : structElement.resettledValue;
|
|
3460
|
+
this.setResetFieldValue(fieldKey, resettledValue);
|
|
3461
|
+
});
|
|
3462
|
+
} else if (structElement.fields) {
|
|
3463
|
+
this.setResetFieldValue(structElement.fields, structElement.resettledValue);
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
if (!structElement.validations) {
|
|
3467
|
+
return handleFieldRestoration();
|
|
3468
|
+
}
|
|
3268
3469
|
Object.keys(structElement.validations).forEach(validationKey => {
|
|
3269
|
-
const error =
|
|
3470
|
+
const error = handleValidation(field.value, structElement.validations, validations, validationKey);
|
|
3270
3471
|
if (!error) {
|
|
3271
|
-
|
|
3272
|
-
structElement.fields.forEach((fieldKey, index) => {
|
|
3273
|
-
const resettledValue = Array.isArray(structElement.resettledValue) ? structElement.resettledValue[index] : structElement.resettledValue;
|
|
3274
|
-
if (!this.fields.has(fieldKey)) console.warn(`failed to reset value onto field ${fieldKey}`);else this.fields.get(fieldKey).emitValue({
|
|
3275
|
-
value: resettledValue,
|
|
3276
|
-
event: 'ON_FIELD_CHANGE'
|
|
3277
|
-
});
|
|
3278
|
-
});
|
|
3279
|
-
} else if (structElement.fields) {
|
|
3280
|
-
if (!this.fields.has(structElement.fields)) console.warn(`failed to reset value onto field ${structElement.fields}`);else this.fields.get(structElement.fields).emitValue({
|
|
3281
|
-
value: structElement.resettledValue,
|
|
3282
|
-
event: 'ON_FIELD_CHANGE'
|
|
3283
|
-
});
|
|
3284
|
-
}
|
|
3472
|
+
handleFieldRestoration();
|
|
3285
3473
|
}
|
|
3286
3474
|
});
|
|
3287
3475
|
});
|
|
@@ -3290,6 +3478,7 @@ class FormCore {
|
|
|
3290
3478
|
* Adds a field onto the form instance regardless there is a schema or not
|
|
3291
3479
|
*
|
|
3292
3480
|
* @param fieldSchema
|
|
3481
|
+
* @param mapperElement
|
|
3293
3482
|
*/
|
|
3294
3483
|
addField({
|
|
3295
3484
|
fieldSchema,
|
|
@@ -3299,7 +3488,7 @@ class FormCore {
|
|
|
3299
3488
|
if (this.fields.has(fieldSchema.name)) {
|
|
3300
3489
|
throw new Error(`field name ${fieldSchema.name} already defined`);
|
|
3301
3490
|
}
|
|
3302
|
-
const mapper = mapperElement || ((_a = this.mappers) === null || _a === void 0 ? void 0 : _a.
|
|
3491
|
+
const mapper = mapperElement || ((_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(fieldSchema.component));
|
|
3303
3492
|
if (!mapper) throw new Error(`mapper not found for ${fieldSchema.component}, add it to the mappers configuration`);
|
|
3304
3493
|
this.fields.set(fieldSchema.name, new FormField({
|
|
3305
3494
|
schemaComponent: fieldSchema,
|
|
@@ -3315,6 +3504,7 @@ class FormCore {
|
|
|
3315
3504
|
}));
|
|
3316
3505
|
this.subscribeTemplates();
|
|
3317
3506
|
this.refreshTemplates({
|
|
3507
|
+
scope: 'fields',
|
|
3318
3508
|
event: 'ON_FIELDS',
|
|
3319
3509
|
key: fieldSchema.name
|
|
3320
3510
|
});
|
|
@@ -3330,6 +3520,7 @@ class FormCore {
|
|
|
3330
3520
|
this.fields.delete(key);
|
|
3331
3521
|
this.subscribeTemplates();
|
|
3332
3522
|
this.templateSubject$.next({
|
|
3523
|
+
scope: 'fields',
|
|
3333
3524
|
key,
|
|
3334
3525
|
event: 'ON_FIELDS'
|
|
3335
3526
|
});
|
|
@@ -3346,7 +3537,12 @@ class FormCore {
|
|
|
3346
3537
|
var _a, _b, _c;
|
|
3347
3538
|
const currField = this.fields.get(structElement.name);
|
|
3348
3539
|
if (!currField) {
|
|
3349
|
-
|
|
3540
|
+
let mapper;
|
|
3541
|
+
if (structElement === null || structElement === void 0 ? void 0 : structElement.mapper) {
|
|
3542
|
+
mapper = structElement === null || structElement === void 0 ? void 0 : structElement.mapper;
|
|
3543
|
+
} else {
|
|
3544
|
+
mapper = (_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(structElement.component);
|
|
3545
|
+
}
|
|
3350
3546
|
if (!mapper) throw new Error(`mapper not found for ${structElement.component}, add it to the mappers configuration`);
|
|
3351
3547
|
this.fields.set(structElement.name, new FormField({
|
|
3352
3548
|
schemaComponent: structElement,
|
|
@@ -3364,10 +3560,11 @@ class FormCore {
|
|
|
3364
3560
|
} else {
|
|
3365
3561
|
currField.children = ((_c = structElement === null || structElement === void 0 ? void 0 : structElement.children) === null || _c === void 0 ? void 0 : _c.map(el => el.name)) || (currField === null || currField === void 0 ? void 0 : currField.children) || [];
|
|
3366
3562
|
currField.path = path;
|
|
3563
|
+
currField.originalSchema = structElement;
|
|
3367
3564
|
currField.templateSubject$ = this.templateSubject$;
|
|
3368
3565
|
}
|
|
3369
3566
|
if (structElement.children) {
|
|
3370
|
-
|
|
3567
|
+
this.serializeStructure(structElement.children, `${path ? `${path}.` : ``}${structElement.name}`);
|
|
3371
3568
|
}
|
|
3372
3569
|
});
|
|
3373
3570
|
}
|
|
@@ -3399,6 +3596,7 @@ class FormCore {
|
|
|
3399
3596
|
this.subscribedTemplates.forEach(el => {
|
|
3400
3597
|
el.originFieldKeys.forEach(field => {
|
|
3401
3598
|
this.templateSubject$.next({
|
|
3599
|
+
scope: 'fields',
|
|
3402
3600
|
key: field,
|
|
3403
3601
|
event: 'ON_FIELDS'
|
|
3404
3602
|
});
|
|
@@ -3424,10 +3622,10 @@ class FormCore {
|
|
|
3424
3622
|
const values = {};
|
|
3425
3623
|
this.fields.forEach((val, key) => {
|
|
3426
3624
|
if (val.value) {
|
|
3427
|
-
values
|
|
3625
|
+
set(values, val.nameToSubmit || key, val.value);
|
|
3428
3626
|
}
|
|
3429
3627
|
});
|
|
3430
|
-
console.
|
|
3628
|
+
console.table(values);
|
|
3431
3629
|
}
|
|
3432
3630
|
/**
|
|
3433
3631
|
* Gets the current values of all form fields.
|
|
@@ -3435,11 +3633,11 @@ class FormCore {
|
|
|
3435
3633
|
* @returns {TFormValues} The current form values.
|
|
3436
3634
|
*/
|
|
3437
3635
|
getFormValues() {
|
|
3438
|
-
|
|
3636
|
+
const values = {};
|
|
3439
3637
|
const erroredFields = [];
|
|
3440
3638
|
this.fields.forEach((val, key) => {
|
|
3441
3639
|
if (val.value) {
|
|
3442
|
-
|
|
3640
|
+
set(values, val.nameToSubmit || key, val.value);
|
|
3443
3641
|
}
|
|
3444
3642
|
if (!val.valid) {
|
|
3445
3643
|
erroredFields.push(key);
|
|
@@ -3459,6 +3657,45 @@ class FormCore {
|
|
|
3459
3657
|
});
|
|
3460
3658
|
return sub;
|
|
3461
3659
|
}
|
|
3660
|
+
subscribeOnMount(callback) {
|
|
3661
|
+
const sub = this.mountSubject$.pipe(map(() => this.getFormValues())).subscribe({
|
|
3662
|
+
next: callback
|
|
3663
|
+
});
|
|
3664
|
+
return sub;
|
|
3665
|
+
}
|
|
3666
|
+
/**
|
|
3667
|
+
*
|
|
3668
|
+
* @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call on data
|
|
3669
|
+
*/
|
|
3670
|
+
subscribeData(callback) {
|
|
3671
|
+
const sub = this.dataSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), map(({
|
|
3672
|
+
key
|
|
3673
|
+
}) => ({
|
|
3674
|
+
field: key,
|
|
3675
|
+
data: this.getFormValues()
|
|
3676
|
+
}))).subscribe({
|
|
3677
|
+
next: callback
|
|
3678
|
+
});
|
|
3679
|
+
return sub;
|
|
3680
|
+
}
|
|
3681
|
+
subscribeOnSubmit(callback) {
|
|
3682
|
+
const sub = this.submitSubject$.pipe(map(() => this.getFormValues())).subscribe({
|
|
3683
|
+
next: callback
|
|
3684
|
+
});
|
|
3685
|
+
return sub;
|
|
3686
|
+
}
|
|
3687
|
+
/**
|
|
3688
|
+
* Submits the form by triggering form field events and invoking the onSubmit callback.
|
|
3689
|
+
*/
|
|
3690
|
+
mounted() {
|
|
3691
|
+
this.fields.forEach(field => {
|
|
3692
|
+
field.emitEvents({
|
|
3693
|
+
event: 'ON_FORM_MOUNT'
|
|
3694
|
+
});
|
|
3695
|
+
});
|
|
3696
|
+
const values = this.getFormValues();
|
|
3697
|
+
this.mountSubject$.next(values);
|
|
3698
|
+
}
|
|
3462
3699
|
/**
|
|
3463
3700
|
* Submits the form by triggering form field events and invoking the onSubmit callback.
|
|
3464
3701
|
*/
|
|
@@ -3471,11 +3708,10 @@ class FormCore {
|
|
|
3471
3708
|
if (!this.isValid) return;
|
|
3472
3709
|
const values = this.getFormValues();
|
|
3473
3710
|
this.submitSubject$.next(values);
|
|
3474
|
-
this.onSubmit && this.onSubmit(values);
|
|
3475
3711
|
}
|
|
3476
3712
|
destroy() {
|
|
3477
3713
|
this.submitSubject$.unsubscribe();
|
|
3478
|
-
this.
|
|
3714
|
+
this.templateSubscription$.unsubscribe();
|
|
3479
3715
|
this.fieldEventSubject$.unsubscribe();
|
|
3480
3716
|
this.dataSubject$.unsubscribe();
|
|
3481
3717
|
this.fields.forEach(field => field.destroyField());
|
|
@@ -3492,15 +3728,19 @@ class FormCore {
|
|
|
3492
3728
|
*/
|
|
3493
3729
|
FormCore.checkIndexes = (struct, indexes = []) => {
|
|
3494
3730
|
if (!struct) return indexes;
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
return FormCore.checkIndexes(structElement.children, indexes);
|
|
3731
|
+
const helper = (struct, indexes) => {
|
|
3732
|
+
for (let i = 0; i < struct.length; i++) {
|
|
3733
|
+
const structElement = struct[i];
|
|
3734
|
+
indexes.push(structElement.name);
|
|
3735
|
+
if (structElement.children) {
|
|
3736
|
+
helper(structElement.children, indexes);
|
|
3737
|
+
}
|
|
3503
3738
|
}
|
|
3739
|
+
};
|
|
3740
|
+
helper(struct, indexes);
|
|
3741
|
+
const duppedIndexes = indexes.filter((item, index) => indexes.indexOf(item) !== index);
|
|
3742
|
+
if (duppedIndexes.length > 0) {
|
|
3743
|
+
throw new Error(`duplicated indexes found on schema: ${JSON.stringify(duppedIndexes)}`);
|
|
3504
3744
|
}
|
|
3505
3745
|
return indexes;
|
|
3506
3746
|
};
|
|
@@ -3608,29 +3848,56 @@ class FormGroup {
|
|
|
3608
3848
|
* Prints the form group instance to the console.
|
|
3609
3849
|
*/
|
|
3610
3850
|
printFormGroupInstance() {
|
|
3611
|
-
console.
|
|
3851
|
+
console.table(this.forms);
|
|
3612
3852
|
}
|
|
3613
3853
|
/**
|
|
3614
3854
|
* Prototype submit function to multiple forms
|
|
3615
3855
|
* @param {string[]} indexes form indexes to be submitted
|
|
3616
3856
|
* @returns
|
|
3617
3857
|
*/
|
|
3618
|
-
submitMultipleFormsByIndex(indexes) {
|
|
3858
|
+
submitMultipleFormsByIndex(indexes, callback) {
|
|
3619
3859
|
let isValid = true;
|
|
3620
3860
|
let values = {};
|
|
3621
3861
|
let erroredFields = [];
|
|
3622
3862
|
indexes.forEach(index => {
|
|
3623
|
-
var _a;
|
|
3624
|
-
|
|
3863
|
+
var _a, _b;
|
|
3864
|
+
(_a = this.forms.get(index)) === null || _a === void 0 ? void 0 : _a.submit();
|
|
3865
|
+
const res = (_b = this.forms.get(index)) === null || _b === void 0 ? void 0 : _b.getFormValues();
|
|
3625
3866
|
isValid = isValid && ((res === null || res === void 0 ? void 0 : res.isValid) || false);
|
|
3626
3867
|
values = Object.assign(Object.assign({}, values), (res === null || res === void 0 ? void 0 : res.values) || {});
|
|
3627
3868
|
erroredFields = [...erroredFields, ...((res === null || res === void 0 ? void 0 : res.erroredFields) || [])];
|
|
3628
3869
|
});
|
|
3629
|
-
|
|
3870
|
+
isValid && callback && callback({
|
|
3630
3871
|
erroredFields,
|
|
3631
3872
|
isValid,
|
|
3632
3873
|
values
|
|
3633
|
-
};
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
onDataSubscription({
|
|
3877
|
+
ids,
|
|
3878
|
+
callback
|
|
3879
|
+
}) {
|
|
3880
|
+
const subs = ids.reduce((acc, formId) => {
|
|
3881
|
+
var _a;
|
|
3882
|
+
// @TODO add config on debounceTime on this events
|
|
3883
|
+
const sub = (_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.dataSubject$.pipe(groupBy(payload => `${formId}.${payload.event}`), mergeMap(group$ => group$.pipe(debounceTime(100))), map(({
|
|
3884
|
+
key
|
|
3885
|
+
}) => {
|
|
3886
|
+
var _a;
|
|
3887
|
+
return {
|
|
3888
|
+
formField: key,
|
|
3889
|
+
values: (_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.getFormValues()
|
|
3890
|
+
};
|
|
3891
|
+
}));
|
|
3892
|
+
if (sub) {
|
|
3893
|
+
acc[formId] = sub;
|
|
3894
|
+
} else {
|
|
3895
|
+
console.warn(`failed to register form id ${formId}`);
|
|
3896
|
+
}
|
|
3897
|
+
return acc;
|
|
3898
|
+
}, {});
|
|
3899
|
+
const sub = combineLatest(subs).subscribe(callback);
|
|
3900
|
+
return sub;
|
|
3634
3901
|
}
|
|
3635
3902
|
}
|
|
3636
3903
|
|