@bolttech/form-engine-core 0.0.1-beta.4 → 0.0.1-beta.40
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 +548 -285
- package/package.json +2 -2
- package/src/constants/constants.d.ts +6 -0
- package/src/helpers/SafeSubject.d.ts +11 -0
- package/src/helpers/validation.d.ts +27 -0
- package/src/interfaces/schema.d.ts +30 -8
- package/src/interfaces/state.d.ts +1 -6
- package/src/managers/field.d.ts +24 -54
- package/src/managers/form.d.ts +56 -35
- package/src/managers/formGroup.d.ts +13 -5
- package/src/types/event.d.ts +8 -2
- package/src/types/form.d.ts +6 -11
- package/src/types/schema.d.ts +82 -40
- 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/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, isEqual, get, isNil, set } from 'lodash';
|
|
4
4
|
import { getCurrencySymbol } from '@gaignoux/currency';
|
|
5
5
|
|
|
6
6
|
var TMutationEnum;
|
|
@@ -44,6 +44,12 @@ 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_DELIMITATOR = /\${(.*?)}/g;
|
|
50
|
+
const TEMPLATE_REGEX_OPERATOR_SPLITTER = /\s*(\|\||&&|!)\s*/g;
|
|
51
|
+
const TEMPLATE_REGEX_OPERATOR_MATCHER = /^\|\||&&|!$/;
|
|
52
|
+
|
|
47
53
|
/**
|
|
48
54
|
* Makes an HTTP request using XMLHttpRequest.
|
|
49
55
|
*
|
|
@@ -96,15 +102,17 @@ function makeRequest(method, url, headers, body) {
|
|
|
96
102
|
* ```
|
|
97
103
|
*/
|
|
98
104
|
function extractFieldKeys(expression) {
|
|
99
|
-
const regex =
|
|
105
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
100
106
|
const extractedValues = [];
|
|
101
107
|
let match;
|
|
102
108
|
while ((match = regex.exec(expression)) !== null) {
|
|
103
109
|
extractedValues.push(match[1]);
|
|
104
110
|
}
|
|
111
|
+
const operatorRegex = TEMPLATE_REGEX_OPERATOR_SPLITTER;
|
|
112
|
+
const splittedString = extractedValues.map(el => el.split(operatorRegex).filter(item => !operatorRegex.test(item))).flat().filter(el => el.split('.').length > 1);
|
|
105
113
|
return {
|
|
106
|
-
originFieldKeys: Array.from(new Set(
|
|
107
|
-
originPropertyKeys: Array.from(new Set(
|
|
114
|
+
originFieldKeys: Array.from(new Set(splittedString.map(el => el.split('.')[0]))),
|
|
115
|
+
originPropertyKeys: Array.from(new Set(splittedString.map(el => el.split('.')[1])))
|
|
108
116
|
};
|
|
109
117
|
}
|
|
110
118
|
/**
|
|
@@ -142,7 +150,7 @@ function traverseObject(obj, path) {
|
|
|
142
150
|
if (typeof item === 'object') {
|
|
143
151
|
result.push(...traverseObject(item, `${path ? `${path}.` : ``}${key}.${index}`));
|
|
144
152
|
} else if (typeof item === 'string') {
|
|
145
|
-
if (String(item).includes('$')) {
|
|
153
|
+
if (String(item).includes('${')) {
|
|
146
154
|
// const extractedPath = item.replace(/\$|{|}/g, '').split('.');
|
|
147
155
|
const extractedOriginPath = `${path ? `${path}.` : ``}${key}`.split('.');
|
|
148
156
|
result.push(Object.assign(Object.assign({
|
|
@@ -158,7 +166,7 @@ function traverseObject(obj, path) {
|
|
|
158
166
|
} else if (typeof value === 'object') {
|
|
159
167
|
result.push(...traverseObject(value, `${path ? `${path}.` : ``}${key}`));
|
|
160
168
|
} else if (typeof value === 'string') {
|
|
161
|
-
if (value.includes('$')) {
|
|
169
|
+
if (value.includes('${')) {
|
|
162
170
|
// const extractedPath = value.replace(/\$|{|}/g, '').split('.');
|
|
163
171
|
const destinationPath = `${path ? `${path}.` : ``}${key}`.split('.');
|
|
164
172
|
result.push(Object.assign(Object.assign({
|
|
@@ -681,16 +689,9 @@ const currency = (value, masks) => {
|
|
|
681
689
|
if (integerPart === '') {
|
|
682
690
|
integerPart = '0';
|
|
683
691
|
}
|
|
684
|
-
console.log('Before', {
|
|
685
|
-
convertedValue
|
|
686
|
-
});
|
|
687
692
|
if (align === 'right' && String(value).endsWith(' ')) {
|
|
688
693
|
convertedValue = convertedValue.slice(0, -1);
|
|
689
694
|
}
|
|
690
|
-
console.log('After', {
|
|
691
|
-
value,
|
|
692
|
-
convertedValue
|
|
693
|
-
});
|
|
694
695
|
let newRawValue = integerPart;
|
|
695
696
|
let decimalPart = convertedValue.slice(convertedValue.length - precision);
|
|
696
697
|
if (precision > 0) {
|
|
@@ -729,10 +730,11 @@ const custom = (value, masks) => {
|
|
|
729
730
|
if (!masks.custom || !value) return value;
|
|
730
731
|
let mask = '';
|
|
731
732
|
let index = 0;
|
|
733
|
+
const convertedValue = value.replace(/[^\w\s]/gi, '');
|
|
732
734
|
for (let i = 0; i < masks.custom.length; i++) {
|
|
733
735
|
if (masks.custom[i] === '#') {
|
|
734
|
-
if (index <
|
|
735
|
-
mask +=
|
|
736
|
+
if (index < convertedValue.length) {
|
|
737
|
+
mask += convertedValue[index];
|
|
736
738
|
index++;
|
|
737
739
|
} else {
|
|
738
740
|
break;
|
|
@@ -1928,41 +1930,44 @@ const conditions = (value, validations) => {
|
|
|
1928
1930
|
};
|
|
1929
1931
|
|
|
1930
1932
|
/**
|
|
1931
|
-
*
|
|
1933
|
+
* @internal
|
|
1934
|
+
* Validates if a date string matches a specific date format.
|
|
1935
|
+
* The function accepts strings with '/' or '-' separators and removes them before validating the format.
|
|
1932
1936
|
*
|
|
1933
|
-
* @param {string} value - The date
|
|
1934
|
-
* @param {
|
|
1935
|
-
*
|
|
1937
|
+
* @param {string} value - The date string to be validated. It can contain '/' or '-' separators.
|
|
1938
|
+
* @param {string} format - The expected date format. It can be one of the following:
|
|
1939
|
+
* 'DDMMYYYY', 'YYYYMMDD', 'YYYYDDMM', 'MMDDYYYY',
|
|
1940
|
+
* 'DMYYYY', 'YYYYMD', 'YYYYDM', 'MDYYYY'.
|
|
1941
|
+
* @returns {boolean} - Returns `false` if the date string matches the specified format, otherwise `true`.
|
|
1936
1942
|
*
|
|
1937
1943
|
* @example
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
* betweenDates: [
|
|
1941
|
-
* { origin: { value: '2023-01-01', format: 'yyyy-MM-dd' }, operator: '>=' },
|
|
1942
|
-
* { origin: { value: '2023-12-31', format: 'yyyy-MM-dd' }, operator: '<=' }
|
|
1943
|
-
* ]
|
|
1944
|
-
* };
|
|
1944
|
+
* // Returns false
|
|
1945
|
+
* invalidStringDate("25/07/1997", "DDMMYYYY");
|
|
1945
1946
|
*
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
1947
|
+
* @example
|
|
1948
|
+
* // Returns false
|
|
1949
|
+
* invalidStringDate("1997-07-25", "YYYYMMDD");
|
|
1948
1950
|
*
|
|
1949
|
-
*
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
1951
|
+
* @example
|
|
1952
|
+
* // Returns true, as the format does not match
|
|
1953
|
+
* invalidStringDate("1997/25/07", "MMDDYYYY");
|
|
1952
1954
|
*/
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
if (((_a = validations.betweenDates) === null || _a === void 0 ? void 0 : _a.length) != 2) return false;
|
|
1957
|
-
for (const validation of validations.betweenDates) {
|
|
1958
|
-
if (date(value, {
|
|
1959
|
-
date: validation
|
|
1960
|
-
})) {
|
|
1961
|
-
fail = true;
|
|
1962
|
-
break;
|
|
1963
|
-
}
|
|
1955
|
+
const invalidStringDate = (value, format) => {
|
|
1956
|
+
if (!value.includes('/') && !value.includes('-') || !format) {
|
|
1957
|
+
return true;
|
|
1964
1958
|
}
|
|
1965
|
-
|
|
1959
|
+
const valueParts = value.replace(/[-/]/g, '');
|
|
1960
|
+
const dateMapper = {
|
|
1961
|
+
DDMMYYYY: /^(\d{2})(\d{2})(\d{4})$/,
|
|
1962
|
+
YYYYMMDD: /^(\d{4})(\d{2})(\d{2})$/,
|
|
1963
|
+
YYYYDDMM: /^(\d{4})(\d{2})(\d{2})$/,
|
|
1964
|
+
MMDDYYYY: /^(\d{2})(\d{2})(\d{4})$/,
|
|
1965
|
+
DMYYYY: /^(\d{1,2})(\d{1,2})(\d{4})$/,
|
|
1966
|
+
YYYYMD: /^(\d{4})(\d{1,2})(\d{1,2})$/,
|
|
1967
|
+
YYYYDM: /^(\d{4})(\d{1,2})(\d{1,2})$/,
|
|
1968
|
+
MDYYYY: /^(\d{1,2})(\d{1,2})(\d{4})$/
|
|
1969
|
+
};
|
|
1970
|
+
return !dateMapper[format].test(valueParts);
|
|
1966
1971
|
};
|
|
1967
1972
|
/**
|
|
1968
1973
|
* @internal
|
|
@@ -2015,6 +2020,44 @@ const dateRearrangeMapper = {
|
|
|
2015
2020
|
MMDDYYYY: value => value,
|
|
2016
2021
|
timestamp: value => new Date(value).toString()
|
|
2017
2022
|
};
|
|
2023
|
+
/**
|
|
2024
|
+
* @function betweenDates
|
|
2025
|
+
* Validates if a date value falls between two specified dates.
|
|
2026
|
+
*
|
|
2027
|
+
* @param {string} value - The date value to be validated in string format.
|
|
2028
|
+
* @param {TValidationMethods} validations - The validation methods object containing the betweenDates validation rules.
|
|
2029
|
+
* @returns {boolean} - Returns `true` if the date value fails the betweenDates validation, otherwise `false`.
|
|
2030
|
+
*
|
|
2031
|
+
* @example
|
|
2032
|
+
* ```typescript
|
|
2033
|
+
* const validations = {
|
|
2034
|
+
* betweenDates: [
|
|
2035
|
+
* { origin: { value: '2023-01-01', format: 'yyyy-MM-dd' }, operator: '>=' },
|
|
2036
|
+
* { origin: { value: '2023-12-31', format: 'yyyy-MM-dd' }, operator: '<=' }
|
|
2037
|
+
* ]
|
|
2038
|
+
* };
|
|
2039
|
+
*
|
|
2040
|
+
* const result1 = betweenDates('2023-06-01', validations);
|
|
2041
|
+
* console.log(result1); // false (date is within the range)
|
|
2042
|
+
*
|
|
2043
|
+
* const result2 = betweenDates('2024-01-01', validations);
|
|
2044
|
+
* console.log(result2); // true (date is outside the range)
|
|
2045
|
+
* ```
|
|
2046
|
+
*/
|
|
2047
|
+
const betweenDates = (value, validations) => {
|
|
2048
|
+
var _a;
|
|
2049
|
+
let fail = false;
|
|
2050
|
+
if (((_a = validations.betweenDates) === null || _a === void 0 ? void 0 : _a.length) != 2) return false;
|
|
2051
|
+
for (const validation of validations.betweenDates) {
|
|
2052
|
+
if (date(value, {
|
|
2053
|
+
date: validation
|
|
2054
|
+
})) {
|
|
2055
|
+
fail = true;
|
|
2056
|
+
break;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return fail;
|
|
2060
|
+
};
|
|
2018
2061
|
/**
|
|
2019
2062
|
* @function date
|
|
2020
2063
|
* Validates a date value based on various date conditions and intervals.
|
|
@@ -2048,12 +2091,13 @@ const dateRearrangeMapper = {
|
|
|
2048
2091
|
* ```
|
|
2049
2092
|
*/
|
|
2050
2093
|
const date = (value, validations) => {
|
|
2051
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
2094
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2052
2095
|
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)) {
|
|
2053
2096
|
return false;
|
|
2054
2097
|
}
|
|
2055
2098
|
const originValue = validations.date.origin.value || value;
|
|
2056
|
-
|
|
2099
|
+
const mappedValue = dateRearrangeMapper[(_e = validations.date) === null || _e === void 0 ? void 0 : _e.origin.format](originValue).toString();
|
|
2100
|
+
let originDate = new Date(mappedValue);
|
|
2057
2101
|
let targetDate = new Date();
|
|
2058
2102
|
let target = new Date();
|
|
2059
2103
|
if ((_g = (_f = validations.date) === null || _f === void 0 ? void 0 : _f.target) === null || _g === void 0 ? void 0 : _g.format) {
|
|
@@ -2072,6 +2116,9 @@ const date = (value, validations) => {
|
|
|
2072
2116
|
if (validations.date.onlyValidDate && (!(targetDate instanceof Date && isFinite(targetDate)) || !(targetDate instanceof Date && isFinite(originDate)) || originValue.length < 8)) {
|
|
2073
2117
|
return true;
|
|
2074
2118
|
}
|
|
2119
|
+
if (invalidStringDate(mappedValue, (_l = validations.date) === null || _l === void 0 ? void 0 : _l.origin.format)) {
|
|
2120
|
+
return false;
|
|
2121
|
+
}
|
|
2075
2122
|
const originTimestamp = originDate.getTime();
|
|
2076
2123
|
const targetTimestamp = targetDate.getTime();
|
|
2077
2124
|
const operationsMapper = {
|
|
@@ -2082,7 +2129,7 @@ const date = (value, validations) => {
|
|
|
2082
2129
|
'===': originTimestamp === targetTimestamp,
|
|
2083
2130
|
'!==': originTimestamp !== targetTimestamp
|
|
2084
2131
|
};
|
|
2085
|
-
return operationsMapper[(
|
|
2132
|
+
return operationsMapper[(_m = validations.date) === null || _m === void 0 ? void 0 : _m.operator];
|
|
2086
2133
|
};
|
|
2087
2134
|
/**
|
|
2088
2135
|
* @function validDate
|
|
@@ -2111,11 +2158,74 @@ const validDate = (value, validations) => {
|
|
|
2111
2158
|
const month = parseInt(dateParts[0], 10) - 1; // Month is zero-based
|
|
2112
2159
|
const day = parseInt(dateParts[1], 10);
|
|
2113
2160
|
const date = new Date(year, month, day);
|
|
2161
|
+
/*
|
|
2162
|
+
* Motivation: due to the scenario in which a date field may be 'typeable',
|
|
2163
|
+
* we need to ensure that dates less than a thousand and such years are valid.
|
|
2164
|
+
* E.g.: user types 13-07-199 (without this condition, it becomes a valid date, as 199 is a valid year)
|
|
2165
|
+
*
|
|
2166
|
+
* Ps: 150 is a valid value for now, as we still cannot live more than that, from the
|
|
2167
|
+
* the moment we start to live longer, we can rethink this.
|
|
2168
|
+
*/
|
|
2169
|
+
const today = new Date();
|
|
2170
|
+
today.setFullYear(today.getFullYear() - 150);
|
|
2171
|
+
if (date.getFullYear() < today.getFullYear()) {
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2114
2174
|
// Check if the date is valid
|
|
2115
2175
|
const isValidDate = date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
|
|
2116
2176
|
return !isValidDate;
|
|
2117
2177
|
};
|
|
2118
2178
|
|
|
2179
|
+
/**
|
|
2180
|
+
* @internal
|
|
2181
|
+
* Runs a set of validation handlers against a given value.
|
|
2182
|
+
*
|
|
2183
|
+
* @param {unknown} value - The value to be validated.
|
|
2184
|
+
* @param {TValidationMethods} handlers - An object containing validation methods to be applied.
|
|
2185
|
+
* @param {TValidationHandler} validations - An object containing every validation methods to be executed.
|
|
2186
|
+
* @returns {boolean[]} - An array of boolean results for each validation method.
|
|
2187
|
+
*
|
|
2188
|
+
* @example
|
|
2189
|
+
* const handlers = {
|
|
2190
|
+
* max: { max: 10 },
|
|
2191
|
+
* required: true,
|
|
2192
|
+
* email: true
|
|
2193
|
+
* };
|
|
2194
|
+
* const results = run('test@example.com', handlers);
|
|
2195
|
+
* console.log(results); // [false, false, true] (value fails 'max', passes 'required', passes 'email')
|
|
2196
|
+
*/
|
|
2197
|
+
function run$1(value, handlers, validations) {
|
|
2198
|
+
const runner = [];
|
|
2199
|
+
Object.keys(handlers).forEach(rule => {
|
|
2200
|
+
runner.push(validations[rule](value, {
|
|
2201
|
+
[rule]: handlers[rule]
|
|
2202
|
+
}));
|
|
2203
|
+
});
|
|
2204
|
+
return runner;
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Validates a given value based on specified validation methods inside a custom named validation.
|
|
2208
|
+
*
|
|
2209
|
+
* @param {unknown} value - The value to be validated.
|
|
2210
|
+
* @param {TValidationMethods} methods - The validation methods to be applied.
|
|
2211
|
+
* @param {TValidationHandler} validations - An object containing every validation methods to be executed.
|
|
2212
|
+
* @returns {boolean} - Returns true if any of the validation methods pass, otherwise false.
|
|
2213
|
+
*
|
|
2214
|
+
* @example
|
|
2215
|
+
* const value = 'example@example.com';
|
|
2216
|
+
* const methods = {
|
|
2217
|
+
* required: true,
|
|
2218
|
+
* email: true
|
|
2219
|
+
* };
|
|
2220
|
+
*
|
|
2221
|
+
* const isValid = validateValue(value, methods);
|
|
2222
|
+
* console.log(isValid); // Output: true
|
|
2223
|
+
*/
|
|
2224
|
+
var namedRule = ((value, methods, validations) => {
|
|
2225
|
+
if (!methods) return false;
|
|
2226
|
+
return run$1(value, methods, validations).some(validation => validation);
|
|
2227
|
+
});
|
|
2228
|
+
|
|
2119
2229
|
/**
|
|
2120
2230
|
* @internal
|
|
2121
2231
|
* An object mapping validation keys to their respective validation functions.
|
|
@@ -2150,7 +2260,6 @@ const validations$1 = {
|
|
|
2150
2260
|
notEmpty,
|
|
2151
2261
|
bool,
|
|
2152
2262
|
exists,
|
|
2153
|
-
greaterThan: () => true,
|
|
2154
2263
|
isNumber,
|
|
2155
2264
|
conditions,
|
|
2156
2265
|
validDate,
|
|
@@ -2174,20 +2283,26 @@ const validations$1 = {
|
|
|
2174
2283
|
* const results = run('test@example.com', handlers);
|
|
2175
2284
|
* console.log(results); // [false, false, true] (value fails 'max', passes 'required', passes 'email')
|
|
2176
2285
|
*/
|
|
2177
|
-
|
|
2286
|
+
function run(value, handlers) {
|
|
2178
2287
|
const runner = [];
|
|
2179
2288
|
Object.keys(handlers).forEach(rule => {
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2289
|
+
let handler;
|
|
2290
|
+
if (isFunction(validations$1[rule])) {
|
|
2291
|
+
handler = validations$1[rule](value, {
|
|
2292
|
+
[rule]: handlers[rule]
|
|
2293
|
+
});
|
|
2294
|
+
} else {
|
|
2295
|
+
handler = namedRule(value, handlers[rule], validations$1);
|
|
2296
|
+
}
|
|
2297
|
+
runner.push(handler);
|
|
2183
2298
|
});
|
|
2184
2299
|
return runner;
|
|
2185
|
-
}
|
|
2300
|
+
}
|
|
2186
2301
|
/**
|
|
2187
2302
|
* Validates that a value meets multiple validation rules.
|
|
2188
2303
|
*
|
|
2189
2304
|
* @param {number | string | boolean} value - The value to be validated.
|
|
2190
|
-
* @param {TValidationMethods}
|
|
2305
|
+
* @param {TValidationMethods} methods - The validation methods object containing the multipleValidations rule set.
|
|
2191
2306
|
* @returns {boolean} - Returns `true` if the value meets the specified multiple validation rules, otherwise `false`.
|
|
2192
2307
|
*
|
|
2193
2308
|
* @example
|
|
@@ -2212,15 +2327,15 @@ const run = (value, handlers) => {
|
|
|
2212
2327
|
* console.log(result2); // false
|
|
2213
2328
|
* ```
|
|
2214
2329
|
*/
|
|
2215
|
-
const multipleValidations = (value,
|
|
2216
|
-
if (!
|
|
2217
|
-
const runner = run(value,
|
|
2330
|
+
const multipleValidations = (value, methods) => {
|
|
2331
|
+
if (!methods.multipleValidations) return false;
|
|
2332
|
+
const runner = run(value, methods.multipleValidations.validations);
|
|
2218
2333
|
const rulesMapper = {
|
|
2219
2334
|
AND: () => runner.every(validation => validation),
|
|
2220
2335
|
OR: () => runner.some(validation => validation),
|
|
2221
2336
|
NOT: () => !runner.every(validation => validation)
|
|
2222
2337
|
};
|
|
2223
|
-
return rulesMapper[
|
|
2338
|
+
return rulesMapper[methods.multipleValidations.rule]();
|
|
2224
2339
|
};
|
|
2225
2340
|
|
|
2226
2341
|
const validations = {
|
|
@@ -2247,7 +2362,6 @@ const validations = {
|
|
|
2247
2362
|
notEmpty,
|
|
2248
2363
|
bool,
|
|
2249
2364
|
exists,
|
|
2250
|
-
greaterThan: () => true,
|
|
2251
2365
|
isNumber,
|
|
2252
2366
|
conditions,
|
|
2253
2367
|
multipleValidations,
|
|
@@ -2256,6 +2370,53 @@ const validations = {
|
|
|
2256
2370
|
validDate
|
|
2257
2371
|
};
|
|
2258
2372
|
|
|
2373
|
+
/**
|
|
2374
|
+
* Custom RXJS Subject to gracefully handle errors on unsubscribed Subjects
|
|
2375
|
+
* that were unmounted due to adapter external handling such as visibility
|
|
2376
|
+
*/
|
|
2377
|
+
class SafeSubject extends Subject {
|
|
2378
|
+
constructor(isMounted) {
|
|
2379
|
+
super();
|
|
2380
|
+
this.isMounted = isMounted;
|
|
2381
|
+
}
|
|
2382
|
+
next(value) {
|
|
2383
|
+
if (this.isMounted()) {
|
|
2384
|
+
super.next(value);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
/**
|
|
2390
|
+
* @internal
|
|
2391
|
+
* Handles the validation of a given value based on specified validation methods and rules.
|
|
2392
|
+
*
|
|
2393
|
+
* @param {string | number | boolean | unknown} value - The value to be validated.
|
|
2394
|
+
* @param {TSchemaValidation} validations - The schema validations to be applied.
|
|
2395
|
+
* @param {TValidationHandler} methods - The validation handler methods.
|
|
2396
|
+
* @param {keyof TValidationMethods} key - The specific key of the validation method to be used.
|
|
2397
|
+
* @returns {boolean} - Returns true if the value passes the validation, otherwise false.
|
|
2398
|
+
*
|
|
2399
|
+
* @example
|
|
2400
|
+
* const value = 'example@example.com';
|
|
2401
|
+
* const validations = {
|
|
2402
|
+
* required: true,
|
|
2403
|
+
* customName: { email: true }
|
|
2404
|
+
* };
|
|
2405
|
+
* const methods = {
|
|
2406
|
+
* email: (value) => /\S+@\S+\.\S+/.test(value)
|
|
2407
|
+
* };
|
|
2408
|
+
* const key = 'required';
|
|
2409
|
+
*
|
|
2410
|
+
* const isValid = handleValidation(value, validations, methods, key);
|
|
2411
|
+
* console.log(isValid); // Output: true
|
|
2412
|
+
*/
|
|
2413
|
+
function handleValidation(value, validations, methods, key) {
|
|
2414
|
+
if (isFunction(methods[key])) {
|
|
2415
|
+
return methods[key](value, validations);
|
|
2416
|
+
}
|
|
2417
|
+
return namedRule(value, validations[key], methods);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2259
2420
|
/**
|
|
2260
2421
|
* Represents a form field with observables for managing form state, validations, and API requests.
|
|
2261
2422
|
*/
|
|
@@ -2282,40 +2443,41 @@ class FormField {
|
|
|
2282
2443
|
resetValue,
|
|
2283
2444
|
initialValue,
|
|
2284
2445
|
templateSubject$,
|
|
2285
|
-
|
|
2446
|
+
fieldEventSubject$,
|
|
2286
2447
|
dataSubject$,
|
|
2287
2448
|
mapper
|
|
2288
2449
|
}) {
|
|
2289
2450
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
2290
2451
|
this.fieldStateSubscription$ = new Subscription();
|
|
2452
|
+
this.originalSchema = schemaComponent;
|
|
2291
2453
|
this.config = {
|
|
2292
|
-
defaultAPIdebounceTimeMS: Number(config === null || config === void 0 ? void 0 : config.defaultAPIdebounceTimeMS) ? Number(config === null || config === void 0 ? void 0 : config.defaultAPIdebounceTimeMS) :
|
|
2293
|
-
defaultStateRefreshTimeMS: Number(config === null || config === void 0 ? void 0 : config.defaultStateRefreshTimeMS) ? Number(config === null || config === void 0 ? void 0 : config.defaultStateRefreshTimeMS) :
|
|
2454
|
+
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,
|
|
2455
|
+
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
|
|
2294
2456
|
};
|
|
2295
2457
|
this.name = schemaComponent.name;
|
|
2458
|
+
this.nameToSubmit = schemaComponent.nameToSubmit;
|
|
2296
2459
|
this.component = schemaComponent.component;
|
|
2297
2460
|
this.path = path;
|
|
2298
2461
|
this.children = children;
|
|
2299
2462
|
this.validations = schemaComponent.validations;
|
|
2300
|
-
this.errorMessages = schemaComponent.
|
|
2463
|
+
this.errorMessages = (_a = schemaComponent.validations) === null || _a === void 0 ? void 0 : _a.messages;
|
|
2301
2464
|
this.visibilityConditions = schemaComponent.visibilityConditions;
|
|
2302
2465
|
this.resetValues = schemaComponent.resetValues;
|
|
2303
2466
|
this.apiSchema = schemaComponent.api;
|
|
2304
2467
|
this.formatters = schemaComponent.formatters;
|
|
2305
2468
|
this.masks = schemaComponent.masks;
|
|
2306
2469
|
if (mapper.valueChangeEvent) this.valueChangeEvent = mapper.valueChangeEvent;
|
|
2307
|
-
if ((
|
|
2308
|
-
if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setErrorMessage) this.errorMessagePropName = mapper.events.setErrorMessage;
|
|
2470
|
+
if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) this.valuePropName = mapper.events.setValue;
|
|
2309
2471
|
this.mapper = mapper;
|
|
2310
2472
|
this.validateVisibility = validateVisibility;
|
|
2311
2473
|
this.resetValue = resetValue;
|
|
2312
2474
|
this.templateSubject$ = templateSubject$;
|
|
2313
|
-
this.
|
|
2475
|
+
this.fieldEventSubject$ = fieldEventSubject$;
|
|
2314
2476
|
this.dataSubject$ = dataSubject$;
|
|
2315
2477
|
this._props = schemaComponent.props || {};
|
|
2316
|
-
this._value = '';
|
|
2317
|
-
this._stateValue = '';
|
|
2318
2478
|
this._metadata = '';
|
|
2479
|
+
this.errorsString = '';
|
|
2480
|
+
this.errorsList = [];
|
|
2319
2481
|
this.initialValue = initialValue;
|
|
2320
2482
|
this._visibility = true;
|
|
2321
2483
|
this._api = {
|
|
@@ -2331,9 +2493,10 @@ class FormField {
|
|
|
2331
2493
|
}, {})
|
|
2332
2494
|
};
|
|
2333
2495
|
this._errors = {};
|
|
2334
|
-
this._errorsString = '';
|
|
2335
2496
|
this._valid = false;
|
|
2497
|
+
this._mounted = true;
|
|
2336
2498
|
this.initializeObservers();
|
|
2499
|
+
this.value = this.initialValue || '';
|
|
2337
2500
|
}
|
|
2338
2501
|
/**
|
|
2339
2502
|
* method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
|
|
@@ -2341,53 +2504,38 @@ class FormField {
|
|
|
2341
2504
|
initializeObservers() {
|
|
2342
2505
|
var _a;
|
|
2343
2506
|
if (!this.valueSubject$ || this.valueSubject$.closed) {
|
|
2344
|
-
this.valueSubject$ = new
|
|
2507
|
+
this.valueSubject$ = new SafeSubject(() => this._mounted);
|
|
2345
2508
|
}
|
|
2346
2509
|
if (!this.errorSubject$ || this.errorSubject$.closed) {
|
|
2347
|
-
this.errorSubject$ = new
|
|
2510
|
+
this.errorSubject$ = new SafeSubject(() => this._mounted);
|
|
2348
2511
|
}
|
|
2349
2512
|
if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
|
|
2350
|
-
this.visibilitySubject$ = new
|
|
2513
|
+
this.visibilitySubject$ = new SafeSubject(() => this._mounted);
|
|
2351
2514
|
}
|
|
2352
2515
|
if (!this.apiSubject$ || this.apiSubject$.closed) {
|
|
2353
|
-
this.apiSubject$ = new
|
|
2516
|
+
this.apiSubject$ = new SafeSubject(() => this._mounted);
|
|
2354
2517
|
}
|
|
2355
2518
|
if (!this.propsSubject$ || this.propsSubject$.closed) {
|
|
2356
|
-
this.propsSubject$ = new
|
|
2519
|
+
this.propsSubject$ = new SafeSubject(() => this._mounted);
|
|
2357
2520
|
}
|
|
2358
2521
|
if (!this.fieldStateSubscription$ || this.fieldStateSubscription$.closed) {
|
|
2359
2522
|
this.fieldStateSubscription$ = new Subscription();
|
|
2360
2523
|
}
|
|
2361
2524
|
if (!this.apiEventQueueSubject$ || this.apiEventQueueSubject$.closed) {
|
|
2362
|
-
this.apiEventQueueSubject$ = new
|
|
2525
|
+
this.apiEventQueueSubject$ = new SafeSubject(() => this._mounted);
|
|
2363
2526
|
}
|
|
2364
2527
|
this.fieldState$ = combineLatest({
|
|
2365
|
-
errors: this.errorSubject$.pipe(startWith([])),
|
|
2366
2528
|
visibility: this.visibilitySubject$.pipe(startWith(this._visibility)),
|
|
2367
|
-
|
|
2368
|
-
|
|
2529
|
+
props: this.propsSubject$.pipe(startWith(this._props)),
|
|
2530
|
+
errors: this.errorSubject$.pipe(startWith(Object.assign({}, ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && {
|
|
2531
|
+
[this.mapper.events.setErrorMessage]: this.errorsString
|
|
2532
|
+
})))
|
|
2369
2533
|
});
|
|
2370
|
-
!this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(
|
|
2534
|
+
!this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(groupBy(({
|
|
2371
2535
|
event
|
|
2372
|
-
}) => event, (
|
|
2536
|
+
}) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
|
|
2373
2537
|
this.apiRequest(payload);
|
|
2374
2538
|
});
|
|
2375
|
-
if (!isNil(this.initialValue)) {
|
|
2376
|
-
this.value = this.initialValue;
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
/**
|
|
2380
|
-
* Observable function to emit api events debounced and distinct for each event type,
|
|
2381
|
-
* avoiding previous events being cancelled by new events if they occur inside the debounce time interval
|
|
2382
|
-
*
|
|
2383
|
-
* @param {(event: { event: TEvents }) => TEvents} keyExtractor function that will pass the event key to the groupBy operator
|
|
2384
|
-
* @param {number} debounceTimeMs time to wait for each individual event emmited
|
|
2385
|
-
* @returns
|
|
2386
|
-
*/
|
|
2387
|
-
debounceDistinct(keyExtractor, debounceTimeMs) {
|
|
2388
|
-
return source$ => source$.pipe(groupBy(keyExtractor), mergeMap(group$ => group$.pipe(debounceTime(debounceTimeMs), map(() => ({
|
|
2389
|
-
event: group$.key
|
|
2390
|
-
})))));
|
|
2391
2539
|
}
|
|
2392
2540
|
/**
|
|
2393
2541
|
* Retrieves the properties associated with the form field.
|
|
@@ -2414,7 +2562,7 @@ class FormField {
|
|
|
2414
2562
|
/**
|
|
2415
2563
|
* Retrieves the current state value of the form field.
|
|
2416
2564
|
*
|
|
2417
|
-
* @returns {unknown} - The current state value of the form field.
|
|
2565
|
+
* @returns {Record<string,unknown>} - The current state value of the form field.
|
|
2418
2566
|
*/
|
|
2419
2567
|
get stateValue() {
|
|
2420
2568
|
return this._stateValue;
|
|
@@ -2422,14 +2570,6 @@ class FormField {
|
|
|
2422
2570
|
get metadata() {
|
|
2423
2571
|
return this._metadata;
|
|
2424
2572
|
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Retrieves the concatenated string of errors associated with the form field.
|
|
2427
|
-
*
|
|
2428
|
-
* @returns {string} - The concatenated string of errors.
|
|
2429
|
-
*/
|
|
2430
|
-
get errorsString() {
|
|
2431
|
-
return this._errorsString;
|
|
2432
|
-
}
|
|
2433
2573
|
/**
|
|
2434
2574
|
* Retrieves the current value of the form field.
|
|
2435
2575
|
*
|
|
@@ -2444,6 +2584,7 @@ class FormField {
|
|
|
2444
2584
|
* @param {unknown} value - The new value to be set.
|
|
2445
2585
|
*/
|
|
2446
2586
|
set value(value) {
|
|
2587
|
+
var _a, _b, _c;
|
|
2447
2588
|
/*
|
|
2448
2589
|
too much unstable, if the valueChangeEvent parses the template event
|
|
2449
2590
|
value, might occur unexpected results
|
|
@@ -2463,22 +2604,19 @@ class FormField {
|
|
|
2463
2604
|
if (typeof val === 'undefined' || val === null) return;
|
|
2464
2605
|
if (typeof val === 'object' && '_value' in val && '_metadata' in val) {
|
|
2465
2606
|
this._value = this.formatValue(val['_value']);
|
|
2466
|
-
this._stateValue =
|
|
2607
|
+
this._stateValue = ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setValue) ? {
|
|
2608
|
+
[this.mapper.events.setValue]: this.maskValue(this.formatValue(val['_value']))
|
|
2609
|
+
} : {};
|
|
2467
2610
|
this._metadata = val._metadata;
|
|
2468
2611
|
} else {
|
|
2469
2612
|
this._value = this.formatValue(val);
|
|
2470
|
-
this._stateValue =
|
|
2613
|
+
this._stateValue = ((_b = this.mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) ? {
|
|
2614
|
+
[(_c = this.mapper.events) === null || _c === void 0 ? void 0 : _c.setValue]: this.maskValue(this.formatValue(val))
|
|
2615
|
+
} : {};
|
|
2616
|
+
this.maskValue(this.formatValue(val));
|
|
2471
2617
|
this._metadata = val;
|
|
2472
2618
|
}
|
|
2473
|
-
|
|
2474
|
-
update prop value attribute to sync with templating
|
|
2475
|
-
currently doesn't need prop Subject emission since it's synced with value
|
|
2476
|
-
to avoid excessive prop subject emissions on each keystroke
|
|
2477
|
-
*/
|
|
2478
|
-
if (this.valuePropName) this._props = Object.assign(Object.assign({}, this.props), {
|
|
2479
|
-
[this.valuePropName]: this.value
|
|
2480
|
-
});
|
|
2481
|
-
this.valueSubject$.next(this._stateValue);
|
|
2619
|
+
this.stateValue && this.valueSubject$.next(this.stateValue);
|
|
2482
2620
|
this.templateSubject$.next({
|
|
2483
2621
|
key: this.name,
|
|
2484
2622
|
event: 'ON_VALUE'
|
|
@@ -2500,6 +2638,23 @@ class FormField {
|
|
|
2500
2638
|
set visibility(visible) {
|
|
2501
2639
|
if (typeof visible === 'undefined' || visible === this.visibility) return;
|
|
2502
2640
|
this._visibility = visible;
|
|
2641
|
+
/**
|
|
2642
|
+
* I was sure I would not require to gambiarra, but..
|
|
2643
|
+
* in order to ignore an hidden value on a form submit
|
|
2644
|
+
* or revalidate it when it comes back to visibility
|
|
2645
|
+
* I needed to...
|
|
2646
|
+
* I don't recommend setting private properties like this
|
|
2647
|
+
* this will force the field to be valid when it's hidden
|
|
2648
|
+
* and trigger the validation when it's visible
|
|
2649
|
+
*/
|
|
2650
|
+
if (!this.visibility) {
|
|
2651
|
+
this.value = '';
|
|
2652
|
+
this._valid = true;
|
|
2653
|
+
} else {
|
|
2654
|
+
this.setFieldValidity({
|
|
2655
|
+
event: 'ON_FIELD_MOUNT'
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2503
2658
|
this.visibilitySubject$.next(this.visibility);
|
|
2504
2659
|
this.templateSubject$.next({
|
|
2505
2660
|
key: this.name,
|
|
@@ -2528,10 +2683,17 @@ class FormField {
|
|
|
2528
2683
|
* @param {TErrorMessages} errors - The new error messages to be set.
|
|
2529
2684
|
*/
|
|
2530
2685
|
set errors(errors) {
|
|
2686
|
+
var _a;
|
|
2531
2687
|
if (typeof errors === 'undefined' || isEqual(errors, this.errors)) return;
|
|
2532
2688
|
this._errors = errors;
|
|
2533
|
-
this.
|
|
2534
|
-
this.
|
|
2689
|
+
this.errorsList = Object.values(this.errors);
|
|
2690
|
+
this.errorsString = this.errorsList.join(', ');
|
|
2691
|
+
/**
|
|
2692
|
+
* if any error receives a list of errors, set a prop for it, currently only supporting a single string
|
|
2693
|
+
*/
|
|
2694
|
+
((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && this.errorSubject$.next({
|
|
2695
|
+
[this.mapper.events.setErrorMessage]: this.errorsString
|
|
2696
|
+
});
|
|
2535
2697
|
this.templateSubject$.next({
|
|
2536
2698
|
key: this.name,
|
|
2537
2699
|
event: 'ON_PROPS'
|
|
@@ -2567,11 +2729,8 @@ class FormField {
|
|
|
2567
2729
|
* Mounts the form field by initializing necessary subjects and combining their streams.
|
|
2568
2730
|
*
|
|
2569
2731
|
* @param {object} mountOpts - Adapter mount options.
|
|
2570
|
-
* @param {string} prop.valuePropName - Adapter value property name.
|
|
2571
|
-
* @param {(event: unknown) => unknown} prop.valueChangeEvent - Adapter change event handler function
|
|
2572
2732
|
* @param {(value: unknown) => unknown} prop.valueSubscription - Adapter value change function
|
|
2573
2733
|
* @param {(payload: Partial<IState>) => unknown} prop.propsSubscription - Adapter prop change function
|
|
2574
|
-
* @param {string} prop.errorMessagePropName - error message property name to set errors onto component
|
|
2575
2734
|
* @returns {void}
|
|
2576
2735
|
*/
|
|
2577
2736
|
mountField({
|
|
@@ -2581,6 +2740,7 @@ class FormField {
|
|
|
2581
2740
|
this.initializeObservers();
|
|
2582
2741
|
this.subscribeValue(valueSubscription);
|
|
2583
2742
|
this.subscribeState(propsSubscription);
|
|
2743
|
+
this._mounted = true;
|
|
2584
2744
|
}
|
|
2585
2745
|
/**
|
|
2586
2746
|
* Sets the value of the form field and emits associated events.
|
|
@@ -2622,6 +2782,11 @@ class FormField {
|
|
|
2622
2782
|
this.apiEventQueueSubject$.next({
|
|
2623
2783
|
event
|
|
2624
2784
|
});
|
|
2785
|
+
this.fieldEventSubject$.next({
|
|
2786
|
+
event,
|
|
2787
|
+
fieldName: this.name,
|
|
2788
|
+
fieldInstance: this
|
|
2789
|
+
});
|
|
2625
2790
|
}
|
|
2626
2791
|
/**
|
|
2627
2792
|
* Sets the validity state of the field based on the provided validation rules and triggers error message updates.
|
|
@@ -2632,53 +2797,43 @@ class FormField {
|
|
|
2632
2797
|
setFieldValidity({
|
|
2633
2798
|
event
|
|
2634
2799
|
}) {
|
|
2635
|
-
var _a;
|
|
2636
|
-
if (!this.validations) {
|
|
2800
|
+
var _a, _b, _c, _d;
|
|
2801
|
+
if (!this.validations || !this.visibility) {
|
|
2802
|
+
this.errors = {};
|
|
2637
2803
|
this._valid = true;
|
|
2638
2804
|
return;
|
|
2639
2805
|
}
|
|
2640
|
-
/*
|
|
2641
|
-
@TODO evaluate if _valid flag needs to be updated on all events, this condition saves resources,
|
|
2642
|
-
currently form submition needs to be controlled with form instance submit property function that
|
|
2643
|
-
will evaluate if all fields are valid regardless the events that triggers error messages
|
|
2644
|
-
*/
|
|
2645
|
-
if (!this.validations.events.includes(event) && event !== 'ON_FORM_SUBMIT') return;
|
|
2646
2806
|
let valid = true;
|
|
2647
|
-
const errors =
|
|
2648
|
-
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.
|
|
2807
|
+
const errors = {};
|
|
2808
|
+
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.methods;
|
|
2649
2809
|
schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
|
|
2650
|
-
|
|
2651
|
-
const error = validations[validationKey](this.value, schemaValidations);
|
|
2810
|
+
const error = handleValidation(this.value, schemaValidations, validations, validationKey);
|
|
2652
2811
|
// setting valid flag
|
|
2653
2812
|
valid = !error && valid;
|
|
2654
2813
|
// setting error messages
|
|
2655
|
-
if (
|
|
2656
|
-
if (
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2814
|
+
if (error && this.errorMessages) {
|
|
2815
|
+
if (validationKey in this.errorMessages) {
|
|
2816
|
+
const messages = this.errorMessages;
|
|
2817
|
+
errors[validationKey] = messages[validationKey];
|
|
2818
|
+
} else if ('default' in this.errorMessages) {
|
|
2819
|
+
errors[validationKey] = this.errorMessages.default;
|
|
2660
2820
|
}
|
|
2821
|
+
} else {
|
|
2822
|
+
delete errors[validationKey];
|
|
2661
2823
|
}
|
|
2662
2824
|
});
|
|
2663
2825
|
this._valid = valid;
|
|
2664
|
-
this.
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
let valid = true;
|
|
2676
|
-
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.config;
|
|
2677
|
-
schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
|
|
2678
|
-
const error = validations[validationKey](this.value, schemaValidations);
|
|
2679
|
-
valid = !error && valid;
|
|
2680
|
-
});
|
|
2681
|
-
this._valid = valid;
|
|
2826
|
+
if ((_c = (_b = this.validations) === null || _b === void 0 ? void 0 : _b.eventMessages) === null || _c === void 0 ? void 0 : _c[event]) {
|
|
2827
|
+
const eventMessages = {};
|
|
2828
|
+
(_d = this.validations.eventMessages[event]) === null || _d === void 0 ? void 0 : _d.forEach(method => {
|
|
2829
|
+
if (method in errors) {
|
|
2830
|
+
eventMessages[method] = errors[method];
|
|
2831
|
+
}
|
|
2832
|
+
});
|
|
2833
|
+
this.errors = eventMessages;
|
|
2834
|
+
} else if (event === 'ON_FORM_SUBMIT') {
|
|
2835
|
+
this.errors = errors;
|
|
2836
|
+
}
|
|
2682
2837
|
}
|
|
2683
2838
|
/**
|
|
2684
2839
|
* Formats the field value using the specified formatters, if available.
|
|
@@ -2712,7 +2867,7 @@ class FormField {
|
|
|
2712
2867
|
let valid = true;
|
|
2713
2868
|
const preConditions = config.preConditions;
|
|
2714
2869
|
preConditions && Object.keys(preConditions).forEach(validationKey => {
|
|
2715
|
-
const error =
|
|
2870
|
+
const error = handleValidation(this.value, preConditions, validations, validationKey);
|
|
2716
2871
|
valid = valid && !error;
|
|
2717
2872
|
});
|
|
2718
2873
|
if (config.blockRequestWhenInvalid) {
|
|
@@ -2808,7 +2963,8 @@ class FormField {
|
|
|
2808
2963
|
* @returns {void}
|
|
2809
2964
|
*/
|
|
2810
2965
|
destroyField() {
|
|
2811
|
-
this.
|
|
2966
|
+
this._mounted = false;
|
|
2967
|
+
this.valueSubscription$.unsubscribe();
|
|
2812
2968
|
this.visibilitySubject$.unsubscribe();
|
|
2813
2969
|
this.fieldStateSubscription$.unsubscribe();
|
|
2814
2970
|
this.propsSubject$.unsubscribe();
|
|
@@ -2834,7 +2990,7 @@ class FormField {
|
|
|
2834
2990
|
* @returns {void}
|
|
2835
2991
|
*/
|
|
2836
2992
|
subscribeValue(callback) {
|
|
2837
|
-
this.valueSubject$.subscribe({
|
|
2993
|
+
this.valueSubscription$ = this.valueSubject$.subscribe({
|
|
2838
2994
|
next: callback
|
|
2839
2995
|
});
|
|
2840
2996
|
}
|
|
@@ -2854,36 +3010,38 @@ class FormCore {
|
|
|
2854
3010
|
* @param {string} [entry.action] - The action attribute of the form.
|
|
2855
3011
|
* @param {string} [entry.method] - The method attribute of the form.
|
|
2856
3012
|
* @param {IFormSchema.iVars} [entry.iVars] - The internal variables of the form.
|
|
2857
|
-
* @param {(data: TFormValues) => void} [entry.onSubmit] - A callback function to handle form submission.
|
|
2858
3013
|
* @param {((payload: {field: string;data: TFormValues;}) => void) | undefined} [entry.onData] - A callback function to handle data emission.
|
|
2859
3014
|
*/
|
|
2860
3015
|
constructor(entry) {
|
|
2861
|
-
var _a, _b, _c, _d;
|
|
3016
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
3017
|
+
this.mappers = new Map();
|
|
2862
3018
|
this.schema = entry.schema;
|
|
2863
3019
|
this.fields = new Map();
|
|
2864
3020
|
this.initialValues = entry.initialValues || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.initialValues);
|
|
2865
3021
|
this.action = entry.action || ((_b = entry.schema) === null || _b === void 0 ? void 0 : _b.action);
|
|
2866
3022
|
this.method = entry.method || ((_c = entry.schema) === null || _c === void 0 ? void 0 : _c.method);
|
|
2867
3023
|
this._iVars = entry.iVars || ((_d = entry.schema) === null || _d === void 0 ? void 0 : _d.iVars) || {};
|
|
2868
|
-
this.
|
|
2869
|
-
|
|
2870
|
-
|
|
3024
|
+
this.config = {
|
|
3025
|
+
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,
|
|
3026
|
+
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
|
|
3027
|
+
};
|
|
3028
|
+
(_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
|
|
3029
|
+
this.mappers.set(mapper.componentName, mapper);
|
|
3030
|
+
});
|
|
2871
3031
|
this.schema && FormCore.checkIndexes(this.schema.components);
|
|
2872
3032
|
this.templateSubject$ = new Subject();
|
|
2873
3033
|
this.submitSubject$ = new Subject();
|
|
2874
|
-
this.
|
|
3034
|
+
this.fieldEventSubject$ = new Subject();
|
|
2875
3035
|
this.dataSubject$ = new Subject();
|
|
2876
|
-
this.
|
|
3036
|
+
this.mountSubject$ = new Subject();
|
|
2877
3037
|
this.subscribedTemplates = [];
|
|
2878
3038
|
this.schema && this.serializeStructure(this.schema.components);
|
|
2879
3039
|
this.schema && this.subscribeTemplates();
|
|
2880
|
-
this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
|
|
3040
|
+
this.templateSubscription = this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
|
|
2881
3041
|
this.templateSubject$.next({
|
|
2882
3042
|
key: IVARPROPNAME,
|
|
2883
3043
|
event: 'ON_IVARS'
|
|
2884
3044
|
});
|
|
2885
|
-
this.apiResponseSubject$.subscribe(this.refreshApi.bind(this));
|
|
2886
|
-
entry.onData && this.subscribeData(entry.onData);
|
|
2887
3045
|
/*
|
|
2888
3046
|
mount events needs to occur on form level, only when all the fields are instantiated
|
|
2889
3047
|
is it possible to apply all the side effects that occur globally, same effect occur
|
|
@@ -2934,20 +3092,17 @@ class FormCore {
|
|
|
2934
3092
|
* Subscribes to templates for dynamic updates.
|
|
2935
3093
|
*/
|
|
2936
3094
|
subscribeTemplates() {
|
|
2937
|
-
|
|
2938
|
-
@TODO fix removal of templates of removed fields, they are kept
|
|
2939
|
-
tried: this.subscribedTemplates = [] and only stores the last one..
|
|
2940
|
-
*/
|
|
3095
|
+
this.subscribedTemplates = [];
|
|
2941
3096
|
this.fields.forEach(({
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
3097
|
+
originalSchema: {
|
|
3098
|
+
component,
|
|
3099
|
+
props,
|
|
3100
|
+
name,
|
|
3101
|
+
validations,
|
|
3102
|
+
visibilityConditions,
|
|
3103
|
+
resetValues,
|
|
3104
|
+
api
|
|
3105
|
+
}
|
|
2951
3106
|
}, key) => {
|
|
2952
3107
|
const template = {
|
|
2953
3108
|
component,
|
|
@@ -2956,28 +3111,10 @@ class FormCore {
|
|
|
2956
3111
|
validations,
|
|
2957
3112
|
visibilityConditions,
|
|
2958
3113
|
resetValues,
|
|
2959
|
-
|
|
2960
|
-
apiSchema,
|
|
2961
|
-
metadata
|
|
3114
|
+
apiSchema: api
|
|
2962
3115
|
};
|
|
2963
3116
|
traverseObject(template, key).forEach(element => this.subscribedTemplates.push(element));
|
|
2964
3117
|
});
|
|
2965
|
-
// console.log(subscribedProps);
|
|
2966
|
-
}
|
|
2967
|
-
/**
|
|
2968
|
-
*
|
|
2969
|
-
* @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call on data
|
|
2970
|
-
*/
|
|
2971
|
-
subscribeData(callback) {
|
|
2972
|
-
var _a, _b;
|
|
2973
|
-
this.dataCallbackSubscription$ = this.dataSubject$.pipe(debounceTime(Number((_a = this.config) === null || _a === void 0 ? void 0 : _a.defaultStateRefreshTimeMS) ? Number((_b = this.config) === null || _b === void 0 ? void 0 : _b.defaultStateRefreshTimeMS) : 100), map(({
|
|
2974
|
-
key
|
|
2975
|
-
}) => ({
|
|
2976
|
-
field: key,
|
|
2977
|
-
data: this.getFormValues()
|
|
2978
|
-
}))).subscribe({
|
|
2979
|
-
next: callback
|
|
2980
|
-
});
|
|
2981
3118
|
}
|
|
2982
3119
|
/**
|
|
2983
3120
|
* Gets the value of a property from a field.
|
|
@@ -2997,8 +3134,12 @@ class FormCore {
|
|
|
2997
3134
|
const value = get(this.iVars, [property, ...path]);
|
|
2998
3135
|
return value;
|
|
2999
3136
|
}
|
|
3000
|
-
|
|
3001
|
-
return
|
|
3137
|
+
const field = this.fields.get(key);
|
|
3138
|
+
if (!field) return console.warn(`failed to get value from ${key}`);
|
|
3139
|
+
if (property === 'props' && path[0] === field.valuePropName) {
|
|
3140
|
+
return field.stateValue;
|
|
3141
|
+
}
|
|
3142
|
+
return path.length > 0 ? get(field[property], path) : field[property];
|
|
3002
3143
|
}
|
|
3003
3144
|
/**
|
|
3004
3145
|
* Sets the value of a property in a field.
|
|
@@ -3031,7 +3172,6 @@ class FormCore {
|
|
|
3031
3172
|
now using key !== originKey, check if any recursion error occurs
|
|
3032
3173
|
**/
|
|
3033
3174
|
if (property === 'props' && path[0] === field.valuePropName && key !== originKey) {
|
|
3034
|
-
// field.value = value;
|
|
3035
3175
|
field.emitValue({
|
|
3036
3176
|
event: 'ON_FIELD_CHANGE',
|
|
3037
3177
|
value
|
|
@@ -3062,18 +3202,17 @@ class FormCore {
|
|
|
3062
3202
|
* @returns {string[]} An array of extracted parameters.
|
|
3063
3203
|
*/
|
|
3064
3204
|
extractParams(expression) {
|
|
3065
|
-
const regex =
|
|
3205
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
3066
3206
|
const extractedValues = [];
|
|
3067
3207
|
let match;
|
|
3068
3208
|
while (!isNil(match = regex.exec(expression))) {
|
|
3069
3209
|
extractedValues.push(match[1]);
|
|
3070
3210
|
}
|
|
3071
|
-
const operatorRegex =
|
|
3211
|
+
const operatorRegex = TEMPLATE_REGEX_OPERATOR_SPLITTER;
|
|
3072
3212
|
const splittedString = extractedValues.map(el => el.split(operatorRegex));
|
|
3073
3213
|
const result = splittedString.map(splittedStringVal => {
|
|
3074
|
-
// console.log(splittedStringVal)
|
|
3075
3214
|
return splittedStringVal.filter(Boolean).reduce((acc, curr) => {
|
|
3076
|
-
if (curr.match(
|
|
3215
|
+
if (curr.match(TEMPLATE_REGEX_OPERATOR_MATCHER)) {
|
|
3077
3216
|
return `${acc}${curr}`;
|
|
3078
3217
|
}
|
|
3079
3218
|
let value;
|
|
@@ -3104,6 +3243,11 @@ class FormCore {
|
|
|
3104
3243
|
case 'object':
|
|
3105
3244
|
if (currValue === null) {
|
|
3106
3245
|
value = null;
|
|
3246
|
+
break;
|
|
3247
|
+
}
|
|
3248
|
+
if (currValue instanceof Date) {
|
|
3249
|
+
value = `new Date(\`${currValue}\`)`;
|
|
3250
|
+
break;
|
|
3107
3251
|
}
|
|
3108
3252
|
value = JSON.stringify(currValue);
|
|
3109
3253
|
break;
|
|
@@ -3115,7 +3259,6 @@ class FormCore {
|
|
|
3115
3259
|
});
|
|
3116
3260
|
return result.map(el => {
|
|
3117
3261
|
try {
|
|
3118
|
-
// console.log(el);
|
|
3119
3262
|
return new Function(`return ${el}`)();
|
|
3120
3263
|
} catch (e) {
|
|
3121
3264
|
console.log(e);
|
|
@@ -3131,8 +3274,8 @@ class FormCore {
|
|
|
3131
3274
|
* @returns {string} The expression string with the replacements made.
|
|
3132
3275
|
*/
|
|
3133
3276
|
replaceExpression(expression, values) {
|
|
3134
|
-
const regex =
|
|
3135
|
-
return expression.replace(regex, () => values.shift() || '');
|
|
3277
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
3278
|
+
return expression.replace(regex, () => String(values.shift()) || '');
|
|
3136
3279
|
}
|
|
3137
3280
|
/**
|
|
3138
3281
|
* Checks if an expression string contains string concatenation within a marked expression.
|
|
@@ -3189,32 +3332,24 @@ class FormCore {
|
|
|
3189
3332
|
});
|
|
3190
3333
|
}
|
|
3191
3334
|
/**
|
|
3192
|
-
*
|
|
3335
|
+
* @internal
|
|
3336
|
+
* Update field visibility accordingly.
|
|
3193
3337
|
*
|
|
3194
|
-
* @param {
|
|
3195
|
-
* @param {
|
|
3338
|
+
* @param {string} field - Field name to be updated.
|
|
3339
|
+
* @param {boolean} hasError - Condition to be used as visibility.
|
|
3340
|
+
* @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.
|
|
3196
3341
|
*/
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
// template.originFieldKeys.includes(key) &&
|
|
3209
|
-
// template.originPropertyKeys.includes('api') &&
|
|
3210
|
-
// !emmittedFields.includes(template.destinationKey)
|
|
3211
|
-
// ) {
|
|
3212
|
-
// emmittedFields.push(template.destinationKey);
|
|
3213
|
-
// this.fields
|
|
3214
|
-
// .get(template.destinationKey)
|
|
3215
|
-
// ?.emitEvents({ event: 'ON_API_RESPONSE' });
|
|
3216
|
-
// }
|
|
3217
|
-
// });
|
|
3342
|
+
setFieldVisibility(field, hasError, showOnlyIfTrue) {
|
|
3343
|
+
if (!this.fields.has(field)) {
|
|
3344
|
+
/*
|
|
3345
|
+
* check with João the need for a global mount event for the form
|
|
3346
|
+
* to avoid not displaying this message in some cases (AsFormField)
|
|
3347
|
+
* suggestion: create subscriber to targetField.ON_FIELD_MOUNT
|
|
3348
|
+
*/
|
|
3349
|
+
console.warn(`failed to update visibility onto field ${field}`);
|
|
3350
|
+
} else {
|
|
3351
|
+
this.fields.get(field).visibility = showOnlyIfTrue ? hasError : !hasError;
|
|
3352
|
+
}
|
|
3218
3353
|
}
|
|
3219
3354
|
/**
|
|
3220
3355
|
* Validates visibility conditions for a given event and updates field visibility accordingly.
|
|
@@ -3229,21 +3364,44 @@ class FormCore {
|
|
|
3229
3364
|
}) {
|
|
3230
3365
|
const field = this.fields.get(key);
|
|
3231
3366
|
const structVisibility = field === null || field === void 0 ? void 0 : field.visibilityConditions;
|
|
3232
|
-
if (!structVisibility || !(structVisibility === null || structVisibility === void 0 ? void 0 : structVisibility.some(config => config.events.includes(event))))
|
|
3367
|
+
if (!structVisibility || !(structVisibility === null || structVisibility === void 0 ? void 0 : structVisibility.some(config => config.events.includes(event)))) {
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3233
3370
|
structVisibility.forEach(structElement => {
|
|
3234
3371
|
if (!structElement.events.includes(event)) return;
|
|
3235
3372
|
Object.keys(structElement.validations).forEach(validationKey => {
|
|
3236
|
-
const error =
|
|
3373
|
+
const error = handleValidation(field.value, structElement.validations, validations, validationKey);
|
|
3237
3374
|
if (Array.isArray(structElement.fields)) {
|
|
3238
3375
|
structElement.fields.forEach(fieldKey => {
|
|
3239
|
-
|
|
3376
|
+
this.setFieldVisibility(fieldKey, error, !!(field.value && structElement.showOnlyIfTrue));
|
|
3240
3377
|
});
|
|
3241
3378
|
} else if (structElement.fields) {
|
|
3242
|
-
|
|
3379
|
+
this.setFieldVisibility(structElement.fields, error, !!(field.value && structElement.showOnlyIfTrue));
|
|
3243
3380
|
}
|
|
3244
3381
|
});
|
|
3245
3382
|
});
|
|
3246
3383
|
}
|
|
3384
|
+
/**
|
|
3385
|
+
* @internal
|
|
3386
|
+
* Update field value and emit change and cleared event.
|
|
3387
|
+
*
|
|
3388
|
+
* @param {string} key - Field name to be updated.
|
|
3389
|
+
* @param {unknown} value - Value to be inserted into field.
|
|
3390
|
+
*/
|
|
3391
|
+
setResetFieldValue(key, value) {
|
|
3392
|
+
if (!this.fields.has(key)) {
|
|
3393
|
+
console.warn(`failed to reset value onto field ${key}`);
|
|
3394
|
+
} else {
|
|
3395
|
+
const field = this.fields.get(key);
|
|
3396
|
+
field.emitValue({
|
|
3397
|
+
value: value,
|
|
3398
|
+
event: 'ON_FIELD_CHANGE'
|
|
3399
|
+
});
|
|
3400
|
+
field.emitEvents({
|
|
3401
|
+
event: 'ON_FIELD_CLEARED'
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3247
3405
|
/**
|
|
3248
3406
|
* Resets field values based on reset conditions defined in the schema.
|
|
3249
3407
|
*
|
|
@@ -3260,23 +3418,23 @@ class FormCore {
|
|
|
3260
3418
|
if (!structResetValue || !(structResetValue === null || structResetValue === void 0 ? void 0 : structResetValue.some(config => config.events.includes(event)))) return;
|
|
3261
3419
|
structResetValue.forEach(structElement => {
|
|
3262
3420
|
if (!structElement.events.includes(event)) return;
|
|
3421
|
+
const handleFieldRestoration = () => {
|
|
3422
|
+
if (Array.isArray(structElement.fields)) {
|
|
3423
|
+
structElement.fields.forEach((fieldKey, index) => {
|
|
3424
|
+
const resettledValue = Array.isArray(structElement.resettledValue) ? structElement.resettledValue[index] : structElement.resettledValue;
|
|
3425
|
+
this.setResetFieldValue(fieldKey, resettledValue);
|
|
3426
|
+
});
|
|
3427
|
+
} else if (structElement.fields) {
|
|
3428
|
+
this.setResetFieldValue(structElement.fields, structElement.resettledValue);
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
3431
|
+
if (!structElement.validations) {
|
|
3432
|
+
return handleFieldRestoration();
|
|
3433
|
+
}
|
|
3263
3434
|
Object.keys(structElement.validations).forEach(validationKey => {
|
|
3264
|
-
const error =
|
|
3435
|
+
const error = handleValidation(field.value, structElement.validations, validations, validationKey);
|
|
3265
3436
|
if (!error) {
|
|
3266
|
-
|
|
3267
|
-
structElement.fields.forEach((fieldKey, index) => {
|
|
3268
|
-
const resettledValue = Array.isArray(structElement.resettledValue) ? structElement.resettledValue[index] : structElement.resettledValue;
|
|
3269
|
-
if (!this.fields.has(fieldKey)) console.warn(`failed to reset value onto field ${fieldKey}`);else this.fields.get(fieldKey).emitValue({
|
|
3270
|
-
value: resettledValue,
|
|
3271
|
-
event: 'ON_FIELD_CHANGE'
|
|
3272
|
-
});
|
|
3273
|
-
});
|
|
3274
|
-
} else if (structElement.fields) {
|
|
3275
|
-
if (!this.fields.has(structElement.fields)) console.warn(`failed to reset value onto field ${structElement.fields}`);else this.fields.get(structElement.fields).emitValue({
|
|
3276
|
-
value: structElement.resettledValue,
|
|
3277
|
-
event: 'ON_FIELD_CHANGE'
|
|
3278
|
-
});
|
|
3279
|
-
}
|
|
3437
|
+
handleFieldRestoration();
|
|
3280
3438
|
}
|
|
3281
3439
|
});
|
|
3282
3440
|
});
|
|
@@ -3285,13 +3443,17 @@ class FormCore {
|
|
|
3285
3443
|
* Adds a field onto the form instance regardless there is a schema or not
|
|
3286
3444
|
*
|
|
3287
3445
|
* @param fieldSchema
|
|
3446
|
+
* @param mapperElement
|
|
3288
3447
|
*/
|
|
3289
|
-
addField(
|
|
3290
|
-
|
|
3448
|
+
addField({
|
|
3449
|
+
fieldSchema,
|
|
3450
|
+
mapperElement
|
|
3451
|
+
}) {
|
|
3452
|
+
var _a, _b, _c;
|
|
3291
3453
|
if (this.fields.has(fieldSchema.name)) {
|
|
3292
3454
|
throw new Error(`field name ${fieldSchema.name} already defined`);
|
|
3293
3455
|
}
|
|
3294
|
-
const mapper = this.mappers
|
|
3456
|
+
const mapper = mapperElement || ((_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(fieldSchema.component));
|
|
3295
3457
|
if (!mapper) throw new Error(`mapper not found for ${fieldSchema.component}, add it to the mappers configuration`);
|
|
3296
3458
|
this.fields.set(fieldSchema.name, new FormField({
|
|
3297
3459
|
schemaComponent: fieldSchema,
|
|
@@ -3299,9 +3461,9 @@ class FormCore {
|
|
|
3299
3461
|
children: fieldSchema.children ? fieldSchema.children.map(el => el.name) : [],
|
|
3300
3462
|
validateVisibility: this.validateVisibility.bind(this),
|
|
3301
3463
|
resetValue: this.resetValue.bind(this),
|
|
3302
|
-
initialValue: (
|
|
3464
|
+
initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[fieldSchema.name],
|
|
3303
3465
|
templateSubject$: this.templateSubject$,
|
|
3304
|
-
|
|
3466
|
+
fieldEventSubject$: this.fieldEventSubject$,
|
|
3305
3467
|
dataSubject$: this.dataSubject$,
|
|
3306
3468
|
config: this.config
|
|
3307
3469
|
}));
|
|
@@ -3310,6 +3472,21 @@ class FormCore {
|
|
|
3310
3472
|
event: 'ON_FIELDS',
|
|
3311
3473
|
key: fieldSchema.name
|
|
3312
3474
|
});
|
|
3475
|
+
(_c = this.fields.get(fieldSchema.name)) === null || _c === void 0 ? void 0 : _c.emitEvents({
|
|
3476
|
+
event: 'ON_FIELD_MOUNT'
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
removeField({
|
|
3480
|
+
key
|
|
3481
|
+
}) {
|
|
3482
|
+
var _a;
|
|
3483
|
+
(_a = this.fields.get(key)) === null || _a === void 0 ? void 0 : _a.destroyField();
|
|
3484
|
+
this.fields.delete(key);
|
|
3485
|
+
this.subscribeTemplates();
|
|
3486
|
+
this.templateSubject$.next({
|
|
3487
|
+
key,
|
|
3488
|
+
event: 'ON_FIELDS'
|
|
3489
|
+
});
|
|
3313
3490
|
}
|
|
3314
3491
|
/**
|
|
3315
3492
|
* Serializes the schema structure to create form fields.
|
|
@@ -3320,10 +3497,15 @@ class FormCore {
|
|
|
3320
3497
|
serializeStructure(struct, path) {
|
|
3321
3498
|
if (!struct) return;
|
|
3322
3499
|
struct.forEach(structElement => {
|
|
3323
|
-
var _a, _b;
|
|
3500
|
+
var _a, _b, _c;
|
|
3324
3501
|
const currField = this.fields.get(structElement.name);
|
|
3325
3502
|
if (!currField) {
|
|
3326
|
-
|
|
3503
|
+
let mapper;
|
|
3504
|
+
if (structElement === null || structElement === void 0 ? void 0 : structElement.mapper) {
|
|
3505
|
+
mapper = structElement === null || structElement === void 0 ? void 0 : structElement.mapper;
|
|
3506
|
+
} else {
|
|
3507
|
+
mapper = (_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(structElement.component);
|
|
3508
|
+
}
|
|
3327
3509
|
if (!mapper) throw new Error(`mapper not found for ${structElement.component}, add it to the mappers configuration`);
|
|
3328
3510
|
this.fields.set(structElement.name, new FormField({
|
|
3329
3511
|
schemaComponent: structElement,
|
|
@@ -3332,19 +3514,20 @@ class FormCore {
|
|
|
3332
3514
|
children: structElement.children ? structElement.children.map(el => el.name) : [],
|
|
3333
3515
|
validateVisibility: this.validateVisibility.bind(this),
|
|
3334
3516
|
resetValue: this.resetValue.bind(this),
|
|
3335
|
-
initialValue: (
|
|
3517
|
+
initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[structElement.name],
|
|
3336
3518
|
templateSubject$: this.templateSubject$,
|
|
3337
|
-
|
|
3519
|
+
fieldEventSubject$: this.fieldEventSubject$,
|
|
3338
3520
|
dataSubject$: this.dataSubject$,
|
|
3339
3521
|
config: this.config
|
|
3340
3522
|
}));
|
|
3341
3523
|
} else {
|
|
3342
|
-
currField.children = ((
|
|
3524
|
+
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) || [];
|
|
3343
3525
|
currField.path = path;
|
|
3526
|
+
currField.originalSchema = structElement;
|
|
3344
3527
|
currField.templateSubject$ = this.templateSubject$;
|
|
3345
3528
|
}
|
|
3346
3529
|
if (structElement.children) {
|
|
3347
|
-
|
|
3530
|
+
this.serializeStructure(structElement.children, `${path ? `${path}.` : ``}${structElement.name}`);
|
|
3348
3531
|
}
|
|
3349
3532
|
});
|
|
3350
3533
|
}
|
|
@@ -3401,10 +3584,10 @@ class FormCore {
|
|
|
3401
3584
|
const values = {};
|
|
3402
3585
|
this.fields.forEach((val, key) => {
|
|
3403
3586
|
if (val.value) {
|
|
3404
|
-
values
|
|
3587
|
+
set(values, val.nameToSubmit || key, val.value);
|
|
3405
3588
|
}
|
|
3406
3589
|
});
|
|
3407
|
-
console.
|
|
3590
|
+
console.table(values);
|
|
3408
3591
|
}
|
|
3409
3592
|
/**
|
|
3410
3593
|
* Gets the current values of all form fields.
|
|
@@ -3416,7 +3599,7 @@ class FormCore {
|
|
|
3416
3599
|
const erroredFields = [];
|
|
3417
3600
|
this.fields.forEach((val, key) => {
|
|
3418
3601
|
if (val.value) {
|
|
3419
|
-
values
|
|
3602
|
+
set(values, val.nameToSubmit || key, val.value);
|
|
3420
3603
|
}
|
|
3421
3604
|
if (!val.valid) {
|
|
3422
3605
|
erroredFields.push(key);
|
|
@@ -3428,6 +3611,53 @@ class FormCore {
|
|
|
3428
3611
|
isValid: this.isValid
|
|
3429
3612
|
};
|
|
3430
3613
|
}
|
|
3614
|
+
subscribeFieldEvent({
|
|
3615
|
+
callback
|
|
3616
|
+
}) {
|
|
3617
|
+
const sub = this.fieldEventSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS)))).subscribe({
|
|
3618
|
+
next: callback
|
|
3619
|
+
});
|
|
3620
|
+
return sub;
|
|
3621
|
+
}
|
|
3622
|
+
subscribeOnMount(callback) {
|
|
3623
|
+
const sub = this.mountSubject$.pipe(map(() => this.getFormValues())).subscribe({
|
|
3624
|
+
next: callback
|
|
3625
|
+
});
|
|
3626
|
+
return sub;
|
|
3627
|
+
}
|
|
3628
|
+
/**
|
|
3629
|
+
*
|
|
3630
|
+
* @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call on data
|
|
3631
|
+
*/
|
|
3632
|
+
subscribeData(callback) {
|
|
3633
|
+
const sub = this.dataSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), map(({
|
|
3634
|
+
key
|
|
3635
|
+
}) => ({
|
|
3636
|
+
field: key,
|
|
3637
|
+
data: this.getFormValues()
|
|
3638
|
+
}))).subscribe({
|
|
3639
|
+
next: callback
|
|
3640
|
+
});
|
|
3641
|
+
return sub;
|
|
3642
|
+
}
|
|
3643
|
+
subscribeOnSubmit(callback) {
|
|
3644
|
+
const sub = this.submitSubject$.pipe(map(() => this.getFormValues())).subscribe({
|
|
3645
|
+
next: callback
|
|
3646
|
+
});
|
|
3647
|
+
return sub;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Submits the form by triggering form field events and invoking the onSubmit callback.
|
|
3651
|
+
*/
|
|
3652
|
+
mounted() {
|
|
3653
|
+
this.fields.forEach(field => {
|
|
3654
|
+
field.emitEvents({
|
|
3655
|
+
event: 'ON_FORM_MOUNT'
|
|
3656
|
+
});
|
|
3657
|
+
});
|
|
3658
|
+
const values = this.getFormValues();
|
|
3659
|
+
this.mountSubject$.next(values);
|
|
3660
|
+
}
|
|
3431
3661
|
/**
|
|
3432
3662
|
* Submits the form by triggering form field events and invoking the onSubmit callback.
|
|
3433
3663
|
*/
|
|
@@ -3440,12 +3670,11 @@ class FormCore {
|
|
|
3440
3670
|
if (!this.isValid) return;
|
|
3441
3671
|
const values = this.getFormValues();
|
|
3442
3672
|
this.submitSubject$.next(values);
|
|
3443
|
-
this.onSubmit && this.onSubmit(values);
|
|
3444
3673
|
}
|
|
3445
3674
|
destroy() {
|
|
3446
3675
|
this.submitSubject$.unsubscribe();
|
|
3447
|
-
this.
|
|
3448
|
-
this.
|
|
3676
|
+
this.templateSubscription.unsubscribe();
|
|
3677
|
+
this.fieldEventSubject$.unsubscribe();
|
|
3449
3678
|
this.dataSubject$.unsubscribe();
|
|
3450
3679
|
this.fields.forEach(field => field.destroyField());
|
|
3451
3680
|
}
|
|
@@ -3461,15 +3690,22 @@ class FormCore {
|
|
|
3461
3690
|
*/
|
|
3462
3691
|
FormCore.checkIndexes = (struct, indexes = []) => {
|
|
3463
3692
|
if (!struct) return indexes;
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3693
|
+
const helper = (struct, indexes) => {
|
|
3694
|
+
for (let i = 0; i < struct.length; i++) {
|
|
3695
|
+
const structElement = struct[i];
|
|
3696
|
+
if (structElement.name === IVARPROPNAME) {
|
|
3697
|
+
throw new Error(`reserved ${IVARPROPNAME} name for field names`);
|
|
3698
|
+
}
|
|
3699
|
+
indexes.push(structElement.name);
|
|
3700
|
+
if (structElement.children) {
|
|
3701
|
+
helper(structElement.children, indexes);
|
|
3702
|
+
}
|
|
3472
3703
|
}
|
|
3704
|
+
};
|
|
3705
|
+
helper(struct, indexes);
|
|
3706
|
+
const duppedIndexes = indexes.filter((item, index) => indexes.indexOf(item) !== index);
|
|
3707
|
+
if (duppedIndexes.length > 0) {
|
|
3708
|
+
throw new Error(`duplicated indexes found on schema: ${JSON.stringify(duppedIndexes)}`);
|
|
3473
3709
|
}
|
|
3474
3710
|
return indexes;
|
|
3475
3711
|
};
|
|
@@ -3577,29 +3813,56 @@ class FormGroup {
|
|
|
3577
3813
|
* Prints the form group instance to the console.
|
|
3578
3814
|
*/
|
|
3579
3815
|
printFormGroupInstance() {
|
|
3580
|
-
console.
|
|
3816
|
+
console.table(this.forms);
|
|
3581
3817
|
}
|
|
3582
3818
|
/**
|
|
3583
3819
|
* Prototype submit function to multiple forms
|
|
3584
3820
|
* @param {string[]} indexes form indexes to be submitted
|
|
3585
3821
|
* @returns
|
|
3586
3822
|
*/
|
|
3587
|
-
submitMultipleFormsByIndex(indexes) {
|
|
3823
|
+
submitMultipleFormsByIndex(indexes, callback) {
|
|
3588
3824
|
let isValid = true;
|
|
3589
3825
|
let values = {};
|
|
3590
3826
|
let erroredFields = [];
|
|
3591
3827
|
indexes.forEach(index => {
|
|
3592
|
-
var _a;
|
|
3593
|
-
|
|
3828
|
+
var _a, _b;
|
|
3829
|
+
(_a = this.forms.get(index)) === null || _a === void 0 ? void 0 : _a.submit();
|
|
3830
|
+
const res = (_b = this.forms.get(index)) === null || _b === void 0 ? void 0 : _b.getFormValues();
|
|
3594
3831
|
isValid = isValid && ((res === null || res === void 0 ? void 0 : res.isValid) || false);
|
|
3595
3832
|
values = Object.assign(Object.assign({}, values), (res === null || res === void 0 ? void 0 : res.values) || {});
|
|
3596
3833
|
erroredFields = [...erroredFields, ...((res === null || res === void 0 ? void 0 : res.erroredFields) || [])];
|
|
3597
3834
|
});
|
|
3598
|
-
|
|
3835
|
+
isValid && callback && callback({
|
|
3599
3836
|
erroredFields,
|
|
3600
3837
|
isValid,
|
|
3601
3838
|
values
|
|
3602
|
-
};
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
onDataSubscription({
|
|
3842
|
+
ids,
|
|
3843
|
+
callback
|
|
3844
|
+
}) {
|
|
3845
|
+
const subs = ids.reduce((acc, formId) => {
|
|
3846
|
+
var _a;
|
|
3847
|
+
// @TODO add config on debounceTime on this events
|
|
3848
|
+
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(({
|
|
3849
|
+
key
|
|
3850
|
+
}) => {
|
|
3851
|
+
var _a;
|
|
3852
|
+
return {
|
|
3853
|
+
formField: key,
|
|
3854
|
+
values: (_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.getFormValues()
|
|
3855
|
+
};
|
|
3856
|
+
}));
|
|
3857
|
+
if (sub) {
|
|
3858
|
+
acc[formId] = sub;
|
|
3859
|
+
} else {
|
|
3860
|
+
console.warn(`failed to register form id ${formId}`);
|
|
3861
|
+
}
|
|
3862
|
+
return acc;
|
|
3863
|
+
}, {});
|
|
3864
|
+
const sub = combineLatest(subs).subscribe(callback);
|
|
3865
|
+
return sub;
|
|
3603
3866
|
}
|
|
3604
3867
|
}
|
|
3605
3868
|
|