@bolttech/form-engine-core 0.0.1-beta.2 → 0.0.1-beta.21
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 +519 -264
- 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 +25 -56
- package/src/managers/form.d.ts +35 -33
- package/src/managers/formGroup.d.ts +31 -2
- package/src/types/event.d.ts +7 -1
- package/src/types/form.d.ts +6 -11
- package/src/types/schema.d.ts +80 -39
- 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) {
|
|
@@ -1928,41 +1929,44 @@ const conditions = (value, validations) => {
|
|
|
1928
1929
|
};
|
|
1929
1930
|
|
|
1930
1931
|
/**
|
|
1931
|
-
*
|
|
1932
|
+
* @internal
|
|
1933
|
+
* Validates if a date string matches a specific date format.
|
|
1934
|
+
* The function accepts strings with '/' or '-' separators and removes them before validating the format.
|
|
1932
1935
|
*
|
|
1933
|
-
* @param {string}
|
|
1934
|
-
* @param {
|
|
1935
|
-
*
|
|
1936
|
+
* @param {string} string - The date string to be validated. It can contain '/' or '-' separators.
|
|
1937
|
+
* @param {string} format - The expected date format. It can be one of the following:
|
|
1938
|
+
* 'DDMMYYYY', 'YYYYMMDD', 'YYYYDDMM', 'MMDDYYYY',
|
|
1939
|
+
* 'DMYYYY', 'YYYYMD', 'YYYYDM', 'MDYYYY'.
|
|
1940
|
+
* @returns {boolean} - Returns `false` if the date string matches the specified format, otherwise `true`.
|
|
1936
1941
|
*
|
|
1937
1942
|
* @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
|
-
* };
|
|
1943
|
+
* // Returns false
|
|
1944
|
+
* invalidStringDate("25/07/1997", "DDMMYYYY");
|
|
1945
1945
|
*
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
1946
|
+
* @example
|
|
1947
|
+
* // Returns false
|
|
1948
|
+
* invalidStringDate("1997-07-25", "YYYYMMDD");
|
|
1948
1949
|
*
|
|
1949
|
-
*
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
1950
|
+
* @example
|
|
1951
|
+
* // Returns true, as the format does not match
|
|
1952
|
+
* invalidStringDate("1997/25/07", "MMDDYYYY");
|
|
1952
1953
|
*/
|
|
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
|
-
}
|
|
1954
|
+
const invalidStringDate = (value, format) => {
|
|
1955
|
+
if (!value.includes('/') && !value.includes('-') || !format) {
|
|
1956
|
+
return true;
|
|
1964
1957
|
}
|
|
1965
|
-
|
|
1958
|
+
const valueParts = value.replace(/[-/]/g, '');
|
|
1959
|
+
const dateMapper = {
|
|
1960
|
+
DDMMYYYY: /^(\d{2})(\d{2})(\d{4})$/,
|
|
1961
|
+
YYYYMMDD: /^(\d{4})(\d{2})(\d{2})$/,
|
|
1962
|
+
YYYYDDMM: /^(\d{4})(\d{2})(\d{2})$/,
|
|
1963
|
+
MMDDYYYY: /^(\d{2})(\d{2})(\d{4})$/,
|
|
1964
|
+
DMYYYY: /^(\d{1,2})(\d{1,2})(\d{4})$/,
|
|
1965
|
+
YYYYMD: /^(\d{4})(\d{1,2})(\d{1,2})$/,
|
|
1966
|
+
YYYYDM: /^(\d{4})(\d{1,2})(\d{1,2})$/,
|
|
1967
|
+
MDYYYY: /^(\d{1,2})(\d{1,2})(\d{4})$/
|
|
1968
|
+
};
|
|
1969
|
+
return !dateMapper[format].test(valueParts);
|
|
1966
1970
|
};
|
|
1967
1971
|
/**
|
|
1968
1972
|
* @internal
|
|
@@ -2015,6 +2019,44 @@ const dateRearrangeMapper = {
|
|
|
2015
2019
|
MMDDYYYY: value => value,
|
|
2016
2020
|
timestamp: value => new Date(value).toString()
|
|
2017
2021
|
};
|
|
2022
|
+
/**
|
|
2023
|
+
* @function betweenDates
|
|
2024
|
+
* Validates if a date value falls between two specified dates.
|
|
2025
|
+
*
|
|
2026
|
+
* @param {string} value - The date value to be validated in string format.
|
|
2027
|
+
* @param {TValidationMethods} validations - The validation methods object containing the betweenDates validation rules.
|
|
2028
|
+
* @returns {boolean} - Returns `true` if the date value fails the betweenDates validation, otherwise `false`.
|
|
2029
|
+
*
|
|
2030
|
+
* @example
|
|
2031
|
+
* ```typescript
|
|
2032
|
+
* const validations = {
|
|
2033
|
+
* betweenDates: [
|
|
2034
|
+
* { origin: { value: '2023-01-01', format: 'yyyy-MM-dd' }, operator: '>=' },
|
|
2035
|
+
* { origin: { value: '2023-12-31', format: 'yyyy-MM-dd' }, operator: '<=' }
|
|
2036
|
+
* ]
|
|
2037
|
+
* };
|
|
2038
|
+
*
|
|
2039
|
+
* const result1 = betweenDates('2023-06-01', validations);
|
|
2040
|
+
* console.log(result1); // false (date is within the range)
|
|
2041
|
+
*
|
|
2042
|
+
* const result2 = betweenDates('2024-01-01', validations);
|
|
2043
|
+
* console.log(result2); // true (date is outside the range)
|
|
2044
|
+
* ```
|
|
2045
|
+
*/
|
|
2046
|
+
const betweenDates = (value, validations) => {
|
|
2047
|
+
var _a;
|
|
2048
|
+
let fail = false;
|
|
2049
|
+
if (((_a = validations.betweenDates) === null || _a === void 0 ? void 0 : _a.length) != 2) return false;
|
|
2050
|
+
for (const validation of validations.betweenDates) {
|
|
2051
|
+
if (date(value, {
|
|
2052
|
+
date: validation
|
|
2053
|
+
})) {
|
|
2054
|
+
fail = true;
|
|
2055
|
+
break;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return fail;
|
|
2059
|
+
};
|
|
2018
2060
|
/**
|
|
2019
2061
|
* @function date
|
|
2020
2062
|
* Validates a date value based on various date conditions and intervals.
|
|
@@ -2048,22 +2090,24 @@ const dateRearrangeMapper = {
|
|
|
2048
2090
|
* ```
|
|
2049
2091
|
*/
|
|
2050
2092
|
const date = (value, validations) => {
|
|
2051
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
2093
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2052
2094
|
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
2095
|
return false;
|
|
2054
2096
|
}
|
|
2055
2097
|
const originValue = validations.date.origin.value || value;
|
|
2056
|
-
|
|
2098
|
+
const mappedValue = dateRearrangeMapper[(_e = validations.date) === null || _e === void 0 ? void 0 : _e.origin.format](originValue).toString();
|
|
2099
|
+
if (invalidStringDate(mappedValue, (_f = validations.date) === null || _f === void 0 ? void 0 : _f.origin.format)) return false;
|
|
2100
|
+
let originDate = new Date(mappedValue);
|
|
2057
2101
|
let targetDate = new Date();
|
|
2058
2102
|
let target = new Date();
|
|
2059
|
-
if ((
|
|
2060
|
-
target = new Date(dateRearrangeMapper[(
|
|
2103
|
+
if ((_h = (_g = validations.date) === null || _g === void 0 ? void 0 : _g.target) === null || _h === void 0 ? void 0 : _h.format) {
|
|
2104
|
+
target = new Date(dateRearrangeMapper[(_k = (_j = validations.date) === null || _j === void 0 ? void 0 : _j.target) === null || _k === void 0 ? void 0 : _k.format](validations.date.target.value).toString());
|
|
2061
2105
|
targetDate = target;
|
|
2062
2106
|
}
|
|
2063
2107
|
if (validations.date.origin.intervals) {
|
|
2064
2108
|
targetDate = getIntervalsDate(originDate, validations.date.origin.intervals);
|
|
2065
2109
|
const date = new Date();
|
|
2066
|
-
if (((
|
|
2110
|
+
if (((_l = validations.date.target) === null || _l === void 0 ? void 0 : _l.value) && target) {
|
|
2067
2111
|
date.setDate(target.getDate());
|
|
2068
2112
|
date.setMonth(target.getMonth());
|
|
2069
2113
|
}
|
|
@@ -2082,7 +2126,7 @@ const date = (value, validations) => {
|
|
|
2082
2126
|
'===': originTimestamp === targetTimestamp,
|
|
2083
2127
|
'!==': originTimestamp !== targetTimestamp
|
|
2084
2128
|
};
|
|
2085
|
-
return operationsMapper[(
|
|
2129
|
+
return operationsMapper[(_m = validations.date) === null || _m === void 0 ? void 0 : _m.operator];
|
|
2086
2130
|
};
|
|
2087
2131
|
/**
|
|
2088
2132
|
* @function validDate
|
|
@@ -2111,11 +2155,69 @@ const validDate = (value, validations) => {
|
|
|
2111
2155
|
const month = parseInt(dateParts[0], 10) - 1; // Month is zero-based
|
|
2112
2156
|
const day = parseInt(dateParts[1], 10);
|
|
2113
2157
|
const date = new Date(year, month, day);
|
|
2158
|
+
/* This line is here because we need to guarantee that the field will not be valid while typing,
|
|
2159
|
+
* that is, imagine the scenario where the user wants to type 1995, when typing 19 or 199,
|
|
2160
|
+
* the function returns valid, as there is a year 19 or 199.
|
|
2161
|
+
*/
|
|
2162
|
+
console.log('ValidDate: ', date.getFullYear());
|
|
2163
|
+
if (date.getFullYear() < 1900) {
|
|
2164
|
+
return true;
|
|
2165
|
+
}
|
|
2114
2166
|
// Check if the date is valid
|
|
2115
2167
|
const isValidDate = date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
|
|
2116
2168
|
return !isValidDate;
|
|
2117
2169
|
};
|
|
2118
2170
|
|
|
2171
|
+
/**
|
|
2172
|
+
* @internal
|
|
2173
|
+
* Runs a set of validation handlers against a given value.
|
|
2174
|
+
*
|
|
2175
|
+
* @param {unknown} value - The value to be validated.
|
|
2176
|
+
* @param {TValidationMethods} handlers - An object containing validation methods to be applied.
|
|
2177
|
+
* @param {TValidationHandler} validations - An object containing every validation methods to be executed.
|
|
2178
|
+
* @returns {boolean[]} - An array of boolean results for each validation method.
|
|
2179
|
+
*
|
|
2180
|
+
* @example
|
|
2181
|
+
* const handlers = {
|
|
2182
|
+
* max: { max: 10 },
|
|
2183
|
+
* required: true,
|
|
2184
|
+
* email: true
|
|
2185
|
+
* };
|
|
2186
|
+
* const results = run('test@example.com', handlers);
|
|
2187
|
+
* console.log(results); // [false, false, true] (value fails 'max', passes 'required', passes 'email')
|
|
2188
|
+
*/
|
|
2189
|
+
function run$1(value, handlers, validations) {
|
|
2190
|
+
const runner = [];
|
|
2191
|
+
Object.keys(handlers).forEach(rule => {
|
|
2192
|
+
runner.push(validations[rule](value, {
|
|
2193
|
+
[rule]: handlers[rule]
|
|
2194
|
+
}));
|
|
2195
|
+
});
|
|
2196
|
+
return runner;
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Validates a given value based on specified validation methods inside a custom named validation.
|
|
2200
|
+
*
|
|
2201
|
+
* @param {unknown} value - The value to be validated.
|
|
2202
|
+
* @param {TValidationMethods} methods - The validation methods to be applied.
|
|
2203
|
+
* @param {TValidationHandler} validations - An object containing every validation methods to be executed.
|
|
2204
|
+
* @returns {boolean} - Returns true if any of the validation methods pass, otherwise false.
|
|
2205
|
+
*
|
|
2206
|
+
* @example
|
|
2207
|
+
* const value = 'example@example.com';
|
|
2208
|
+
* const methods = {
|
|
2209
|
+
* required: true,
|
|
2210
|
+
* email: true
|
|
2211
|
+
* };
|
|
2212
|
+
*
|
|
2213
|
+
* const isValid = validateValue(value, methods);
|
|
2214
|
+
* console.log(isValid); // Output: true
|
|
2215
|
+
*/
|
|
2216
|
+
var namedRule = ((value, methods, validations) => {
|
|
2217
|
+
if (!methods) return false;
|
|
2218
|
+
return run$1(value, methods, validations).some(validation => validation);
|
|
2219
|
+
});
|
|
2220
|
+
|
|
2119
2221
|
/**
|
|
2120
2222
|
* @internal
|
|
2121
2223
|
* An object mapping validation keys to their respective validation functions.
|
|
@@ -2150,7 +2252,6 @@ const validations$1 = {
|
|
|
2150
2252
|
notEmpty,
|
|
2151
2253
|
bool,
|
|
2152
2254
|
exists,
|
|
2153
|
-
greaterThan: () => true,
|
|
2154
2255
|
isNumber,
|
|
2155
2256
|
conditions,
|
|
2156
2257
|
validDate,
|
|
@@ -2174,20 +2275,26 @@ const validations$1 = {
|
|
|
2174
2275
|
* const results = run('test@example.com', handlers);
|
|
2175
2276
|
* console.log(results); // [false, false, true] (value fails 'max', passes 'required', passes 'email')
|
|
2176
2277
|
*/
|
|
2177
|
-
|
|
2278
|
+
function run(value, handlers) {
|
|
2178
2279
|
const runner = [];
|
|
2179
2280
|
Object.keys(handlers).forEach(rule => {
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2281
|
+
let handler;
|
|
2282
|
+
if (isFunction(validations$1[rule])) {
|
|
2283
|
+
handler = validations$1[rule](value, {
|
|
2284
|
+
[rule]: handlers[rule]
|
|
2285
|
+
});
|
|
2286
|
+
} else {
|
|
2287
|
+
handler = namedRule(value, handlers[rule], validations$1);
|
|
2288
|
+
}
|
|
2289
|
+
runner.push(handler);
|
|
2183
2290
|
});
|
|
2184
2291
|
return runner;
|
|
2185
|
-
}
|
|
2292
|
+
}
|
|
2186
2293
|
/**
|
|
2187
2294
|
* Validates that a value meets multiple validation rules.
|
|
2188
2295
|
*
|
|
2189
2296
|
* @param {number | string | boolean} value - The value to be validated.
|
|
2190
|
-
* @param {TValidationMethods}
|
|
2297
|
+
* @param {TValidationMethods} methods - The validation methods object containing the multipleValidations rule set.
|
|
2191
2298
|
* @returns {boolean} - Returns `true` if the value meets the specified multiple validation rules, otherwise `false`.
|
|
2192
2299
|
*
|
|
2193
2300
|
* @example
|
|
@@ -2212,15 +2319,15 @@ const run = (value, handlers) => {
|
|
|
2212
2319
|
* console.log(result2); // false
|
|
2213
2320
|
* ```
|
|
2214
2321
|
*/
|
|
2215
|
-
const multipleValidations = (value,
|
|
2216
|
-
if (!
|
|
2217
|
-
const runner = run(value,
|
|
2322
|
+
const multipleValidations = (value, methods) => {
|
|
2323
|
+
if (!methods.multipleValidations) return false;
|
|
2324
|
+
const runner = run(value, methods.multipleValidations.validations);
|
|
2218
2325
|
const rulesMapper = {
|
|
2219
2326
|
AND: () => runner.every(validation => validation),
|
|
2220
2327
|
OR: () => runner.some(validation => validation),
|
|
2221
2328
|
NOT: () => !runner.every(validation => validation)
|
|
2222
2329
|
};
|
|
2223
|
-
return rulesMapper[
|
|
2330
|
+
return rulesMapper[methods.multipleValidations.rule]();
|
|
2224
2331
|
};
|
|
2225
2332
|
|
|
2226
2333
|
const validations = {
|
|
@@ -2247,7 +2354,6 @@ const validations = {
|
|
|
2247
2354
|
notEmpty,
|
|
2248
2355
|
bool,
|
|
2249
2356
|
exists,
|
|
2250
|
-
greaterThan: () => true,
|
|
2251
2357
|
isNumber,
|
|
2252
2358
|
conditions,
|
|
2253
2359
|
multipleValidations,
|
|
@@ -2256,6 +2362,53 @@ const validations = {
|
|
|
2256
2362
|
validDate
|
|
2257
2363
|
};
|
|
2258
2364
|
|
|
2365
|
+
/**
|
|
2366
|
+
* Custom RXJS Subject to gracefully handle errors on unsubscribed Subjects
|
|
2367
|
+
* that were unmounted due to adapter external handling such as visibility
|
|
2368
|
+
*/
|
|
2369
|
+
class SafeSubject extends Subject {
|
|
2370
|
+
constructor(isMounted) {
|
|
2371
|
+
super();
|
|
2372
|
+
this.isMounted = isMounted;
|
|
2373
|
+
}
|
|
2374
|
+
next(value) {
|
|
2375
|
+
if (this.isMounted()) {
|
|
2376
|
+
super.next(value);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/**
|
|
2382
|
+
* @internal
|
|
2383
|
+
* Handles the validation of a given value based on specified validation methods and rules.
|
|
2384
|
+
*
|
|
2385
|
+
* @param {string | number | boolean | unknown} value - The value to be validated.
|
|
2386
|
+
* @param {TSchemaValidation} validations - The schema validations to be applied.
|
|
2387
|
+
* @param {TValidationHandler} methods - The validation handler methods.
|
|
2388
|
+
* @param {keyof TValidationMethods} key - The specific key of the validation method to be used.
|
|
2389
|
+
* @returns {boolean} - Returns true if the value passes the validation, otherwise false.
|
|
2390
|
+
*
|
|
2391
|
+
* @example
|
|
2392
|
+
* const value = 'example@example.com';
|
|
2393
|
+
* const validations = {
|
|
2394
|
+
* required: true,
|
|
2395
|
+
* customName: { email: true }
|
|
2396
|
+
* };
|
|
2397
|
+
* const methods = {
|
|
2398
|
+
* email: (value) => /\S+@\S+\.\S+/.test(value)
|
|
2399
|
+
* };
|
|
2400
|
+
* const key = 'required';
|
|
2401
|
+
*
|
|
2402
|
+
* const isValid = handleValidation(value, validations, methods, key);
|
|
2403
|
+
* console.log(isValid); // Output: true
|
|
2404
|
+
*/
|
|
2405
|
+
function handleValidation(value, validations, methods, key) {
|
|
2406
|
+
if (isFunction(methods[key])) {
|
|
2407
|
+
return methods[key](value, validations);
|
|
2408
|
+
}
|
|
2409
|
+
return namedRule(value, validations[key], methods);
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2259
2412
|
/**
|
|
2260
2413
|
* Represents a form field with observables for managing form state, validations, and API requests.
|
|
2261
2414
|
*/
|
|
@@ -2282,40 +2435,41 @@ class FormField {
|
|
|
2282
2435
|
resetValue,
|
|
2283
2436
|
initialValue,
|
|
2284
2437
|
templateSubject$,
|
|
2285
|
-
|
|
2438
|
+
fieldEventSubject$,
|
|
2286
2439
|
dataSubject$,
|
|
2287
2440
|
mapper
|
|
2288
2441
|
}) {
|
|
2289
2442
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
2290
2443
|
this.fieldStateSubscription$ = new Subscription();
|
|
2444
|
+
this.originalSchema = schemaComponent;
|
|
2291
2445
|
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) :
|
|
2446
|
+
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,
|
|
2447
|
+
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
2448
|
};
|
|
2295
2449
|
this.name = schemaComponent.name;
|
|
2450
|
+
this.nameToSubmit = schemaComponent.nameToSubmit;
|
|
2296
2451
|
this.component = schemaComponent.component;
|
|
2297
2452
|
this.path = path;
|
|
2298
2453
|
this.children = children;
|
|
2299
2454
|
this.validations = schemaComponent.validations;
|
|
2300
|
-
this.errorMessages = schemaComponent.
|
|
2455
|
+
this.errorMessages = (_a = schemaComponent.validations) === null || _a === void 0 ? void 0 : _a.messages;
|
|
2301
2456
|
this.visibilityConditions = schemaComponent.visibilityConditions;
|
|
2302
2457
|
this.resetValues = schemaComponent.resetValues;
|
|
2303
2458
|
this.apiSchema = schemaComponent.api;
|
|
2304
2459
|
this.formatters = schemaComponent.formatters;
|
|
2305
2460
|
this.masks = schemaComponent.masks;
|
|
2306
2461
|
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;
|
|
2462
|
+
if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) this.valuePropName = mapper.events.setValue;
|
|
2309
2463
|
this.mapper = mapper;
|
|
2310
2464
|
this.validateVisibility = validateVisibility;
|
|
2311
2465
|
this.resetValue = resetValue;
|
|
2312
2466
|
this.templateSubject$ = templateSubject$;
|
|
2313
|
-
this.
|
|
2467
|
+
this.fieldEventSubject$ = fieldEventSubject$;
|
|
2314
2468
|
this.dataSubject$ = dataSubject$;
|
|
2315
2469
|
this._props = schemaComponent.props || {};
|
|
2316
|
-
this._value = '';
|
|
2317
|
-
this._stateValue = '';
|
|
2318
2470
|
this._metadata = '';
|
|
2471
|
+
this.errorsString = '';
|
|
2472
|
+
this.errorsList = [];
|
|
2319
2473
|
this.initialValue = initialValue;
|
|
2320
2474
|
this._visibility = true;
|
|
2321
2475
|
this._api = {
|
|
@@ -2331,9 +2485,10 @@ class FormField {
|
|
|
2331
2485
|
}, {})
|
|
2332
2486
|
};
|
|
2333
2487
|
this._errors = {};
|
|
2334
|
-
this._errorsString = '';
|
|
2335
2488
|
this._valid = false;
|
|
2489
|
+
this._mounted = true;
|
|
2336
2490
|
this.initializeObservers();
|
|
2491
|
+
this.value = this.initialValue || '';
|
|
2337
2492
|
}
|
|
2338
2493
|
/**
|
|
2339
2494
|
* method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
|
|
@@ -2341,53 +2496,38 @@ class FormField {
|
|
|
2341
2496
|
initializeObservers() {
|
|
2342
2497
|
var _a;
|
|
2343
2498
|
if (!this.valueSubject$ || this.valueSubject$.closed) {
|
|
2344
|
-
this.valueSubject$ = new
|
|
2499
|
+
this.valueSubject$ = new SafeSubject(() => this._mounted);
|
|
2345
2500
|
}
|
|
2346
2501
|
if (!this.errorSubject$ || this.errorSubject$.closed) {
|
|
2347
|
-
this.errorSubject$ = new
|
|
2502
|
+
this.errorSubject$ = new SafeSubject(() => this._mounted);
|
|
2348
2503
|
}
|
|
2349
2504
|
if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
|
|
2350
|
-
this.visibilitySubject$ = new
|
|
2505
|
+
this.visibilitySubject$ = new SafeSubject(() => this._mounted);
|
|
2351
2506
|
}
|
|
2352
2507
|
if (!this.apiSubject$ || this.apiSubject$.closed) {
|
|
2353
|
-
this.apiSubject$ = new
|
|
2508
|
+
this.apiSubject$ = new SafeSubject(() => this._mounted);
|
|
2354
2509
|
}
|
|
2355
2510
|
if (!this.propsSubject$ || this.propsSubject$.closed) {
|
|
2356
|
-
this.propsSubject$ = new
|
|
2511
|
+
this.propsSubject$ = new SafeSubject(() => this._mounted);
|
|
2357
2512
|
}
|
|
2358
2513
|
if (!this.fieldStateSubscription$ || this.fieldStateSubscription$.closed) {
|
|
2359
2514
|
this.fieldStateSubscription$ = new Subscription();
|
|
2360
2515
|
}
|
|
2361
2516
|
if (!this.apiEventQueueSubject$ || this.apiEventQueueSubject$.closed) {
|
|
2362
|
-
this.apiEventQueueSubject$ = new
|
|
2517
|
+
this.apiEventQueueSubject$ = new SafeSubject(() => this._mounted);
|
|
2363
2518
|
}
|
|
2364
2519
|
this.fieldState$ = combineLatest({
|
|
2365
|
-
errors: this.errorSubject$.pipe(startWith([])),
|
|
2366
2520
|
visibility: this.visibilitySubject$.pipe(startWith(this._visibility)),
|
|
2367
|
-
|
|
2368
|
-
|
|
2521
|
+
props: this.propsSubject$.pipe(startWith(this._props)),
|
|
2522
|
+
errors: this.errorSubject$.pipe(startWith(Object.assign({}, ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && {
|
|
2523
|
+
[this.mapper.events.setErrorMessage]: this.errorsString
|
|
2524
|
+
})))
|
|
2369
2525
|
});
|
|
2370
|
-
!this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(
|
|
2526
|
+
!this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(groupBy(({
|
|
2371
2527
|
event
|
|
2372
|
-
}) => event, (
|
|
2528
|
+
}) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
|
|
2373
2529
|
this.apiRequest(payload);
|
|
2374
2530
|
});
|
|
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
2531
|
}
|
|
2392
2532
|
/**
|
|
2393
2533
|
* Retrieves the properties associated with the form field.
|
|
@@ -2414,7 +2554,7 @@ class FormField {
|
|
|
2414
2554
|
/**
|
|
2415
2555
|
* Retrieves the current state value of the form field.
|
|
2416
2556
|
*
|
|
2417
|
-
* @returns {unknown} - The current state value of the form field.
|
|
2557
|
+
* @returns {Record<string,unknown>} - The current state value of the form field.
|
|
2418
2558
|
*/
|
|
2419
2559
|
get stateValue() {
|
|
2420
2560
|
return this._stateValue;
|
|
@@ -2422,14 +2562,6 @@ class FormField {
|
|
|
2422
2562
|
get metadata() {
|
|
2423
2563
|
return this._metadata;
|
|
2424
2564
|
}
|
|
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
2565
|
/**
|
|
2434
2566
|
* Retrieves the current value of the form field.
|
|
2435
2567
|
*
|
|
@@ -2444,6 +2576,7 @@ class FormField {
|
|
|
2444
2576
|
* @param {unknown} value - The new value to be set.
|
|
2445
2577
|
*/
|
|
2446
2578
|
set value(value) {
|
|
2579
|
+
var _a, _b, _c;
|
|
2447
2580
|
/*
|
|
2448
2581
|
too much unstable, if the valueChangeEvent parses the template event
|
|
2449
2582
|
value, might occur unexpected results
|
|
@@ -2463,22 +2596,19 @@ class FormField {
|
|
|
2463
2596
|
if (typeof val === 'undefined' || val === null) return;
|
|
2464
2597
|
if (typeof val === 'object' && '_value' in val && '_metadata' in val) {
|
|
2465
2598
|
this._value = this.formatValue(val['_value']);
|
|
2466
|
-
this._stateValue =
|
|
2599
|
+
this._stateValue = ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setValue) ? {
|
|
2600
|
+
[this.mapper.events.setValue]: this.maskValue(this.formatValue(val['_value']))
|
|
2601
|
+
} : {};
|
|
2467
2602
|
this._metadata = val._metadata;
|
|
2468
2603
|
} else {
|
|
2469
2604
|
this._value = this.formatValue(val);
|
|
2470
|
-
this._stateValue =
|
|
2605
|
+
this._stateValue = ((_b = this.mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) ? {
|
|
2606
|
+
[(_c = this.mapper.events) === null || _c === void 0 ? void 0 : _c.setValue]: this.maskValue(this.formatValue(val))
|
|
2607
|
+
} : {};
|
|
2608
|
+
this.maskValue(this.formatValue(val));
|
|
2471
2609
|
this._metadata = val;
|
|
2472
2610
|
}
|
|
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);
|
|
2611
|
+
this.stateValue && this.valueSubject$.next(this.stateValue);
|
|
2482
2612
|
this.templateSubject$.next({
|
|
2483
2613
|
key: this.name,
|
|
2484
2614
|
event: 'ON_VALUE'
|
|
@@ -2500,6 +2630,23 @@ class FormField {
|
|
|
2500
2630
|
set visibility(visible) {
|
|
2501
2631
|
if (typeof visible === 'undefined' || visible === this.visibility) return;
|
|
2502
2632
|
this._visibility = visible;
|
|
2633
|
+
/**
|
|
2634
|
+
* I was sure I would not require to gambiarra, but..
|
|
2635
|
+
* in order to ignore an hidden value on a form submit
|
|
2636
|
+
* or revalidate it when it comes back to visibility
|
|
2637
|
+
* I needed to...
|
|
2638
|
+
* I don't recommend setting private properties like this
|
|
2639
|
+
* this will force the field to be valid when it's hidden
|
|
2640
|
+
* and trigger the validation when it's visible
|
|
2641
|
+
*/
|
|
2642
|
+
if (!this.visibility) {
|
|
2643
|
+
this.value = '';
|
|
2644
|
+
this._valid = true;
|
|
2645
|
+
} else {
|
|
2646
|
+
this.setFieldValidity({
|
|
2647
|
+
event: 'ON_FIELD_MOUNT'
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2503
2650
|
this.visibilitySubject$.next(this.visibility);
|
|
2504
2651
|
this.templateSubject$.next({
|
|
2505
2652
|
key: this.name,
|
|
@@ -2528,10 +2675,17 @@ class FormField {
|
|
|
2528
2675
|
* @param {TErrorMessages} errors - The new error messages to be set.
|
|
2529
2676
|
*/
|
|
2530
2677
|
set errors(errors) {
|
|
2678
|
+
var _a;
|
|
2531
2679
|
if (typeof errors === 'undefined' || isEqual(errors, this.errors)) return;
|
|
2532
2680
|
this._errors = errors;
|
|
2533
|
-
this.
|
|
2534
|
-
this.
|
|
2681
|
+
this.errorsList = Object.values(this.errors);
|
|
2682
|
+
this.errorsString = this.errorsList.join(', ');
|
|
2683
|
+
/**
|
|
2684
|
+
* if any error receives a list of errors, set a prop for it, currently only supporting a single string
|
|
2685
|
+
*/
|
|
2686
|
+
((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && this.errorSubject$.next({
|
|
2687
|
+
[this.mapper.events.setErrorMessage]: this.errorsString
|
|
2688
|
+
});
|
|
2535
2689
|
this.templateSubject$.next({
|
|
2536
2690
|
key: this.name,
|
|
2537
2691
|
event: 'ON_PROPS'
|
|
@@ -2567,11 +2721,8 @@ class FormField {
|
|
|
2567
2721
|
* Mounts the form field by initializing necessary subjects and combining their streams.
|
|
2568
2722
|
*
|
|
2569
2723
|
* @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
2724
|
* @param {(value: unknown) => unknown} prop.valueSubscription - Adapter value change function
|
|
2573
2725
|
* @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
2726
|
* @returns {void}
|
|
2576
2727
|
*/
|
|
2577
2728
|
mountField({
|
|
@@ -2581,6 +2732,7 @@ class FormField {
|
|
|
2581
2732
|
this.initializeObservers();
|
|
2582
2733
|
this.subscribeValue(valueSubscription);
|
|
2583
2734
|
this.subscribeState(propsSubscription);
|
|
2735
|
+
this._mounted = true;
|
|
2584
2736
|
}
|
|
2585
2737
|
/**
|
|
2586
2738
|
* Sets the value of the form field and emits associated events.
|
|
@@ -2622,6 +2774,11 @@ class FormField {
|
|
|
2622
2774
|
this.apiEventQueueSubject$.next({
|
|
2623
2775
|
event
|
|
2624
2776
|
});
|
|
2777
|
+
this.fieldEventSubject$.next({
|
|
2778
|
+
event,
|
|
2779
|
+
fieldName: this.name,
|
|
2780
|
+
fieldInstance: this
|
|
2781
|
+
});
|
|
2625
2782
|
}
|
|
2626
2783
|
/**
|
|
2627
2784
|
* Sets the validity state of the field based on the provided validation rules and triggers error message updates.
|
|
@@ -2632,53 +2789,43 @@ class FormField {
|
|
|
2632
2789
|
setFieldValidity({
|
|
2633
2790
|
event
|
|
2634
2791
|
}) {
|
|
2635
|
-
var _a;
|
|
2636
|
-
if (!this.validations) {
|
|
2792
|
+
var _a, _b, _c, _d;
|
|
2793
|
+
if (!this.validations || !this.visibility) {
|
|
2794
|
+
this.errors = {};
|
|
2637
2795
|
this._valid = true;
|
|
2638
2796
|
return;
|
|
2639
2797
|
}
|
|
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
2798
|
let valid = true;
|
|
2647
|
-
const errors =
|
|
2648
|
-
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.
|
|
2799
|
+
const errors = {};
|
|
2800
|
+
const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.methods;
|
|
2649
2801
|
schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
|
|
2650
|
-
|
|
2651
|
-
const error = validations[validationKey](this.value, schemaValidations);
|
|
2802
|
+
const error = handleValidation(this.value, schemaValidations, validations, validationKey);
|
|
2652
2803
|
// setting valid flag
|
|
2653
2804
|
valid = !error && valid;
|
|
2654
2805
|
// setting error messages
|
|
2655
|
-
if (
|
|
2656
|
-
if (
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2806
|
+
if (error && this.errorMessages) {
|
|
2807
|
+
if (validationKey in this.errorMessages) {
|
|
2808
|
+
const messages = this.errorMessages;
|
|
2809
|
+
errors[validationKey] = messages[validationKey];
|
|
2810
|
+
} else if ('default' in this.errorMessages) {
|
|
2811
|
+
errors[validationKey] = this.errorMessages.default;
|
|
2660
2812
|
}
|
|
2813
|
+
} else {
|
|
2814
|
+
delete errors[validationKey];
|
|
2661
2815
|
}
|
|
2662
2816
|
});
|
|
2663
2817
|
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;
|
|
2818
|
+
if ((_c = (_b = this.validations) === null || _b === void 0 ? void 0 : _b.eventMessages) === null || _c === void 0 ? void 0 : _c[event]) {
|
|
2819
|
+
const eventMessages = {};
|
|
2820
|
+
(_d = this.validations.eventMessages[event]) === null || _d === void 0 ? void 0 : _d.forEach(method => {
|
|
2821
|
+
if (method in errors) {
|
|
2822
|
+
eventMessages[method] = errors[method];
|
|
2823
|
+
}
|
|
2824
|
+
});
|
|
2825
|
+
this.errors = eventMessages;
|
|
2826
|
+
} else if (event === 'ON_FORM_SUBMIT') {
|
|
2827
|
+
this.errors = errors;
|
|
2828
|
+
}
|
|
2682
2829
|
}
|
|
2683
2830
|
/**
|
|
2684
2831
|
* Formats the field value using the specified formatters, if available.
|
|
@@ -2712,7 +2859,7 @@ class FormField {
|
|
|
2712
2859
|
let valid = true;
|
|
2713
2860
|
const preConditions = config.preConditions;
|
|
2714
2861
|
preConditions && Object.keys(preConditions).forEach(validationKey => {
|
|
2715
|
-
const error =
|
|
2862
|
+
const error = handleValidation(this.value, preConditions, validations, validationKey);
|
|
2716
2863
|
valid = valid && !error;
|
|
2717
2864
|
});
|
|
2718
2865
|
if (config.blockRequestWhenInvalid) {
|
|
@@ -2808,6 +2955,7 @@ class FormField {
|
|
|
2808
2955
|
* @returns {void}
|
|
2809
2956
|
*/
|
|
2810
2957
|
destroyField() {
|
|
2958
|
+
this._mounted = false;
|
|
2811
2959
|
this.valueSubject$.unsubscribe();
|
|
2812
2960
|
this.visibilitySubject$.unsubscribe();
|
|
2813
2961
|
this.fieldStateSubscription$.unsubscribe();
|
|
@@ -2854,26 +3002,29 @@ class FormCore {
|
|
|
2854
3002
|
* @param {string} [entry.action] - The action attribute of the form.
|
|
2855
3003
|
* @param {string} [entry.method] - The method attribute of the form.
|
|
2856
3004
|
* @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
3005
|
* @param {((payload: {field: string;data: TFormValues;}) => void) | undefined} [entry.onData] - A callback function to handle data emission.
|
|
2859
3006
|
*/
|
|
2860
3007
|
constructor(entry) {
|
|
2861
|
-
var _a, _b, _c, _d;
|
|
3008
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
3009
|
+
this.mappers = new Map();
|
|
2862
3010
|
this.schema = entry.schema;
|
|
2863
3011
|
this.fields = new Map();
|
|
2864
3012
|
this.initialValues = entry.initialValues || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.initialValues);
|
|
2865
3013
|
this.action = entry.action || ((_b = entry.schema) === null || _b === void 0 ? void 0 : _b.action);
|
|
2866
3014
|
this.method = entry.method || ((_c = entry.schema) === null || _c === void 0 ? void 0 : _c.method);
|
|
2867
3015
|
this._iVars = entry.iVars || ((_d = entry.schema) === null || _d === void 0 ? void 0 : _d.iVars) || {};
|
|
2868
|
-
this.
|
|
2869
|
-
|
|
2870
|
-
|
|
3016
|
+
this.config = {
|
|
3017
|
+
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,
|
|
3018
|
+
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
|
|
3019
|
+
};
|
|
3020
|
+
(_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
|
|
3021
|
+
this.mappers.set(mapper.componentName, mapper);
|
|
3022
|
+
});
|
|
2871
3023
|
this.schema && FormCore.checkIndexes(this.schema.components);
|
|
2872
3024
|
this.templateSubject$ = new Subject();
|
|
2873
3025
|
this.submitSubject$ = new Subject();
|
|
2874
|
-
this.
|
|
3026
|
+
this.fieldEventSubject$ = new Subject();
|
|
2875
3027
|
this.dataSubject$ = new Subject();
|
|
2876
|
-
this.dataCallbackSubscription$ = new Subscription();
|
|
2877
3028
|
this.subscribedTemplates = [];
|
|
2878
3029
|
this.schema && this.serializeStructure(this.schema.components);
|
|
2879
3030
|
this.schema && this.subscribeTemplates();
|
|
@@ -2882,8 +3033,6 @@ class FormCore {
|
|
|
2882
3033
|
key: IVARPROPNAME,
|
|
2883
3034
|
event: 'ON_IVARS'
|
|
2884
3035
|
});
|
|
2885
|
-
this.apiResponseSubject$.subscribe(this.refreshApi.bind(this));
|
|
2886
|
-
entry.onData && this.subscribeData(entry.onData);
|
|
2887
3036
|
/*
|
|
2888
3037
|
mount events needs to occur on form level, only when all the fields are instantiated
|
|
2889
3038
|
is it possible to apply all the side effects that occur globally, same effect occur
|
|
@@ -2934,20 +3083,17 @@ class FormCore {
|
|
|
2934
3083
|
* Subscribes to templates for dynamic updates.
|
|
2935
3084
|
*/
|
|
2936
3085
|
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
|
-
*/
|
|
3086
|
+
this.subscribedTemplates = [];
|
|
2941
3087
|
this.fields.forEach(({
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
3088
|
+
originalSchema: {
|
|
3089
|
+
component,
|
|
3090
|
+
props,
|
|
3091
|
+
name,
|
|
3092
|
+
validations,
|
|
3093
|
+
visibilityConditions,
|
|
3094
|
+
resetValues,
|
|
3095
|
+
api
|
|
3096
|
+
}
|
|
2951
3097
|
}, key) => {
|
|
2952
3098
|
const template = {
|
|
2953
3099
|
component,
|
|
@@ -2956,28 +3102,10 @@ class FormCore {
|
|
|
2956
3102
|
validations,
|
|
2957
3103
|
visibilityConditions,
|
|
2958
3104
|
resetValues,
|
|
2959
|
-
|
|
2960
|
-
apiSchema,
|
|
2961
|
-
metadata
|
|
3105
|
+
apiSchema: api
|
|
2962
3106
|
};
|
|
2963
3107
|
traverseObject(template, key).forEach(element => this.subscribedTemplates.push(element));
|
|
2964
3108
|
});
|
|
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
3109
|
}
|
|
2982
3110
|
/**
|
|
2983
3111
|
* Gets the value of a property from a field.
|
|
@@ -2997,8 +3125,12 @@ class FormCore {
|
|
|
2997
3125
|
const value = get(this.iVars, [property, ...path]);
|
|
2998
3126
|
return value;
|
|
2999
3127
|
}
|
|
3000
|
-
|
|
3001
|
-
return
|
|
3128
|
+
const field = this.fields.get(key);
|
|
3129
|
+
if (!field) return console.warn(`failed to get value from ${key}`);
|
|
3130
|
+
if (property === 'props' && path[0] === field.valuePropName) {
|
|
3131
|
+
return field.stateValue;
|
|
3132
|
+
}
|
|
3133
|
+
return path.length > 0 ? get(field[property], path) : field[property];
|
|
3002
3134
|
}
|
|
3003
3135
|
/**
|
|
3004
3136
|
* Sets the value of a property in a field.
|
|
@@ -3031,7 +3163,6 @@ class FormCore {
|
|
|
3031
3163
|
now using key !== originKey, check if any recursion error occurs
|
|
3032
3164
|
**/
|
|
3033
3165
|
if (property === 'props' && path[0] === field.valuePropName && key !== originKey) {
|
|
3034
|
-
// field.value = value;
|
|
3035
3166
|
field.emitValue({
|
|
3036
3167
|
event: 'ON_FIELD_CHANGE',
|
|
3037
3168
|
value
|
|
@@ -3062,18 +3193,17 @@ class FormCore {
|
|
|
3062
3193
|
* @returns {string[]} An array of extracted parameters.
|
|
3063
3194
|
*/
|
|
3064
3195
|
extractParams(expression) {
|
|
3065
|
-
const regex =
|
|
3196
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
3066
3197
|
const extractedValues = [];
|
|
3067
3198
|
let match;
|
|
3068
3199
|
while (!isNil(match = regex.exec(expression))) {
|
|
3069
3200
|
extractedValues.push(match[1]);
|
|
3070
3201
|
}
|
|
3071
|
-
const operatorRegex =
|
|
3202
|
+
const operatorRegex = TEMPLATE_REGEX_OPERATOR_SPLITTER;
|
|
3072
3203
|
const splittedString = extractedValues.map(el => el.split(operatorRegex));
|
|
3073
3204
|
const result = splittedString.map(splittedStringVal => {
|
|
3074
|
-
// console.log(splittedStringVal)
|
|
3075
3205
|
return splittedStringVal.filter(Boolean).reduce((acc, curr) => {
|
|
3076
|
-
if (curr.match(
|
|
3206
|
+
if (curr.match(TEMPLATE_REGEX_OPERATOR_MATCHER)) {
|
|
3077
3207
|
return `${acc}${curr}`;
|
|
3078
3208
|
}
|
|
3079
3209
|
let value;
|
|
@@ -3104,6 +3234,11 @@ class FormCore {
|
|
|
3104
3234
|
case 'object':
|
|
3105
3235
|
if (currValue === null) {
|
|
3106
3236
|
value = null;
|
|
3237
|
+
break;
|
|
3238
|
+
}
|
|
3239
|
+
if (currValue instanceof Date) {
|
|
3240
|
+
value = `new Date(\`${currValue}\`)`;
|
|
3241
|
+
break;
|
|
3107
3242
|
}
|
|
3108
3243
|
value = JSON.stringify(currValue);
|
|
3109
3244
|
break;
|
|
@@ -3115,7 +3250,6 @@ class FormCore {
|
|
|
3115
3250
|
});
|
|
3116
3251
|
return result.map(el => {
|
|
3117
3252
|
try {
|
|
3118
|
-
// console.log(el);
|
|
3119
3253
|
return new Function(`return ${el}`)();
|
|
3120
3254
|
} catch (e) {
|
|
3121
3255
|
console.log(e);
|
|
@@ -3131,8 +3265,8 @@ class FormCore {
|
|
|
3131
3265
|
* @returns {string} The expression string with the replacements made.
|
|
3132
3266
|
*/
|
|
3133
3267
|
replaceExpression(expression, values) {
|
|
3134
|
-
const regex =
|
|
3135
|
-
return expression.replace(regex, () => values.shift() || '');
|
|
3268
|
+
const regex = TEMPLATE_REGEX_DELIMITATOR;
|
|
3269
|
+
return expression.replace(regex, () => String(values.shift()) || '');
|
|
3136
3270
|
}
|
|
3137
3271
|
/**
|
|
3138
3272
|
* Checks if an expression string contains string concatenation within a marked expression.
|
|
@@ -3188,34 +3322,6 @@ class FormCore {
|
|
|
3188
3322
|
}
|
|
3189
3323
|
});
|
|
3190
3324
|
}
|
|
3191
|
-
/**
|
|
3192
|
-
* Refreshes api observed fields.
|
|
3193
|
-
*
|
|
3194
|
-
* @param {object} options - Options for refreshing api.
|
|
3195
|
-
* @param {string} options.key - The key of the field triggering the update.
|
|
3196
|
-
*/
|
|
3197
|
-
refreshApi({
|
|
3198
|
-
key
|
|
3199
|
-
}) {
|
|
3200
|
-
/*
|
|
3201
|
-
global api notifications needs to have field dependency array
|
|
3202
|
-
in order to be reliable, disabled for now
|
|
3203
|
-
*/
|
|
3204
|
-
return key;
|
|
3205
|
-
// const emmittedFields: string[] = [];
|
|
3206
|
-
// this.subscribedTemplates.forEach((template) => {
|
|
3207
|
-
// if (
|
|
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
|
-
// });
|
|
3218
|
-
}
|
|
3219
3325
|
/**
|
|
3220
3326
|
* Validates visibility conditions for a given event and updates field visibility accordingly.
|
|
3221
3327
|
*
|
|
@@ -3233,13 +3339,13 @@ class FormCore {
|
|
|
3233
3339
|
structVisibility.forEach(structElement => {
|
|
3234
3340
|
if (!structElement.events.includes(event)) return;
|
|
3235
3341
|
Object.keys(structElement.validations).forEach(validationKey => {
|
|
3236
|
-
const error =
|
|
3342
|
+
const error = handleValidation(field.value, structElement.validations, validations, validationKey);
|
|
3237
3343
|
if (Array.isArray(structElement.fields)) {
|
|
3238
3344
|
structElement.fields.forEach(fieldKey => {
|
|
3239
|
-
if (!this.fields.has(fieldKey)) console.warn(`failed to update visibility onto field ${fieldKey}`);else this.fields.get(fieldKey).visibility = error;
|
|
3345
|
+
if (!this.fields.has(fieldKey)) console.warn(`failed to update visibility onto field ${fieldKey}`);else this.fields.get(fieldKey).visibility = structElement.showOnlyIfTrue ? error : !error;
|
|
3240
3346
|
});
|
|
3241
3347
|
} else if (structElement.fields) {
|
|
3242
|
-
if (!this.fields.has(structElement.fields)) console.warn(`failed to update visibility onto field ${structElement.fields}`);else this.fields.get(structElement.fields).visibility = error;
|
|
3348
|
+
if (!this.fields.has(structElement.fields)) console.warn(`failed to update visibility onto field ${structElement.fields}`);else this.fields.get(structElement.fields).visibility = structElement.showOnlyIfTrue ? error : !error;
|
|
3243
3349
|
}
|
|
3244
3350
|
});
|
|
3245
3351
|
});
|
|
@@ -3261,7 +3367,7 @@ class FormCore {
|
|
|
3261
3367
|
structResetValue.forEach(structElement => {
|
|
3262
3368
|
if (!structElement.events.includes(event)) return;
|
|
3263
3369
|
Object.keys(structElement.validations).forEach(validationKey => {
|
|
3264
|
-
const error =
|
|
3370
|
+
const error = handleValidation(field.value, structElement.validations, validations, validationKey);
|
|
3265
3371
|
if (!error) {
|
|
3266
3372
|
if (Array.isArray(structElement.fields)) {
|
|
3267
3373
|
structElement.fields.forEach((fieldKey, index) => {
|
|
@@ -3281,6 +3387,54 @@ class FormCore {
|
|
|
3281
3387
|
});
|
|
3282
3388
|
});
|
|
3283
3389
|
}
|
|
3390
|
+
/**
|
|
3391
|
+
* Adds a field onto the form instance regardless there is a schema or not
|
|
3392
|
+
*
|
|
3393
|
+
* @param fieldSchema
|
|
3394
|
+
*/
|
|
3395
|
+
addField({
|
|
3396
|
+
fieldSchema,
|
|
3397
|
+
mapperElement
|
|
3398
|
+
}) {
|
|
3399
|
+
var _a, _b, _c;
|
|
3400
|
+
if (this.fields.has(fieldSchema.name)) {
|
|
3401
|
+
throw new Error(`field name ${fieldSchema.name} already defined`);
|
|
3402
|
+
}
|
|
3403
|
+
const mapper = mapperElement || ((_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(fieldSchema.component));
|
|
3404
|
+
if (!mapper) throw new Error(`mapper not found for ${fieldSchema.component}, add it to the mappers configuration`);
|
|
3405
|
+
this.fields.set(fieldSchema.name, new FormField({
|
|
3406
|
+
schemaComponent: fieldSchema,
|
|
3407
|
+
mapper,
|
|
3408
|
+
children: fieldSchema.children ? fieldSchema.children.map(el => el.name) : [],
|
|
3409
|
+
validateVisibility: this.validateVisibility.bind(this),
|
|
3410
|
+
resetValue: this.resetValue.bind(this),
|
|
3411
|
+
initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[fieldSchema.name],
|
|
3412
|
+
templateSubject$: this.templateSubject$,
|
|
3413
|
+
fieldEventSubject$: this.fieldEventSubject$,
|
|
3414
|
+
dataSubject$: this.dataSubject$,
|
|
3415
|
+
config: this.config
|
|
3416
|
+
}));
|
|
3417
|
+
this.subscribeTemplates();
|
|
3418
|
+
this.refreshTemplates({
|
|
3419
|
+
event: 'ON_FIELDS',
|
|
3420
|
+
key: fieldSchema.name
|
|
3421
|
+
});
|
|
3422
|
+
(_c = this.fields.get(fieldSchema.name)) === null || _c === void 0 ? void 0 : _c.emitEvents({
|
|
3423
|
+
event: 'ON_FIELD_MOUNT'
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
removeField({
|
|
3427
|
+
key
|
|
3428
|
+
}) {
|
|
3429
|
+
var _a;
|
|
3430
|
+
(_a = this.fields.get(key)) === null || _a === void 0 ? void 0 : _a.destroyField();
|
|
3431
|
+
this.fields.delete(key);
|
|
3432
|
+
this.subscribeTemplates();
|
|
3433
|
+
this.templateSubject$.next({
|
|
3434
|
+
key,
|
|
3435
|
+
event: 'ON_FIELDS'
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3284
3438
|
/**
|
|
3285
3439
|
* Serializes the schema structure to create form fields.
|
|
3286
3440
|
*
|
|
@@ -3290,10 +3444,15 @@ class FormCore {
|
|
|
3290
3444
|
serializeStructure(struct, path) {
|
|
3291
3445
|
if (!struct) return;
|
|
3292
3446
|
struct.forEach(structElement => {
|
|
3293
|
-
var _a, _b;
|
|
3447
|
+
var _a, _b, _c;
|
|
3294
3448
|
const currField = this.fields.get(structElement.name);
|
|
3295
3449
|
if (!currField) {
|
|
3296
|
-
|
|
3450
|
+
let mapper;
|
|
3451
|
+
if (structElement === null || structElement === void 0 ? void 0 : structElement.mapper) {
|
|
3452
|
+
mapper = structElement === null || structElement === void 0 ? void 0 : structElement.mapper;
|
|
3453
|
+
} else {
|
|
3454
|
+
mapper = (_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(structElement.component);
|
|
3455
|
+
}
|
|
3297
3456
|
if (!mapper) throw new Error(`mapper not found for ${structElement.component}, add it to the mappers configuration`);
|
|
3298
3457
|
this.fields.set(structElement.name, new FormField({
|
|
3299
3458
|
schemaComponent: structElement,
|
|
@@ -3302,19 +3461,20 @@ class FormCore {
|
|
|
3302
3461
|
children: structElement.children ? structElement.children.map(el => el.name) : [],
|
|
3303
3462
|
validateVisibility: this.validateVisibility.bind(this),
|
|
3304
3463
|
resetValue: this.resetValue.bind(this),
|
|
3305
|
-
initialValue: (
|
|
3464
|
+
initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[structElement.name],
|
|
3306
3465
|
templateSubject$: this.templateSubject$,
|
|
3307
|
-
|
|
3466
|
+
fieldEventSubject$: this.fieldEventSubject$,
|
|
3308
3467
|
dataSubject$: this.dataSubject$,
|
|
3309
3468
|
config: this.config
|
|
3310
3469
|
}));
|
|
3311
3470
|
} else {
|
|
3312
|
-
currField.children = ((
|
|
3471
|
+
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) || [];
|
|
3313
3472
|
currField.path = path;
|
|
3473
|
+
currField.originalSchema = structElement;
|
|
3314
3474
|
currField.templateSubject$ = this.templateSubject$;
|
|
3315
3475
|
}
|
|
3316
3476
|
if (structElement.children) {
|
|
3317
|
-
|
|
3477
|
+
this.serializeStructure(structElement.children, `${path ? `${path}.` : ``}${structElement.name}`);
|
|
3318
3478
|
}
|
|
3319
3479
|
});
|
|
3320
3480
|
}
|
|
@@ -3371,7 +3531,7 @@ class FormCore {
|
|
|
3371
3531
|
const values = {};
|
|
3372
3532
|
this.fields.forEach((val, key) => {
|
|
3373
3533
|
if (val.value) {
|
|
3374
|
-
values
|
|
3534
|
+
set(values, val.nameToSubmit || key, val.value);
|
|
3375
3535
|
}
|
|
3376
3536
|
});
|
|
3377
3537
|
console.log(values);
|
|
@@ -3386,7 +3546,7 @@ class FormCore {
|
|
|
3386
3546
|
const erroredFields = [];
|
|
3387
3547
|
this.fields.forEach((val, key) => {
|
|
3388
3548
|
if (val.value) {
|
|
3389
|
-
values
|
|
3549
|
+
set(values, val.nameToSubmit || key, val.value);
|
|
3390
3550
|
}
|
|
3391
3551
|
if (!val.valid) {
|
|
3392
3552
|
erroredFields.push(key);
|
|
@@ -3398,6 +3558,35 @@ class FormCore {
|
|
|
3398
3558
|
isValid: this.isValid
|
|
3399
3559
|
};
|
|
3400
3560
|
}
|
|
3561
|
+
subscribeFieldEvent({
|
|
3562
|
+
callback
|
|
3563
|
+
}) {
|
|
3564
|
+
const sub = this.fieldEventSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS)))).subscribe({
|
|
3565
|
+
next: callback
|
|
3566
|
+
});
|
|
3567
|
+
return sub;
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
*
|
|
3571
|
+
* @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call on data
|
|
3572
|
+
*/
|
|
3573
|
+
subscribeData(callback) {
|
|
3574
|
+
const sub = this.dataSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), map(({
|
|
3575
|
+
key
|
|
3576
|
+
}) => ({
|
|
3577
|
+
field: key,
|
|
3578
|
+
data: this.getFormValues()
|
|
3579
|
+
}))).subscribe({
|
|
3580
|
+
next: callback
|
|
3581
|
+
});
|
|
3582
|
+
return sub;
|
|
3583
|
+
}
|
|
3584
|
+
subscribeOnSubmit(callback) {
|
|
3585
|
+
const sub = this.submitSubject$.pipe(map(() => this.getFormValues())).subscribe({
|
|
3586
|
+
next: callback
|
|
3587
|
+
});
|
|
3588
|
+
return sub;
|
|
3589
|
+
}
|
|
3401
3590
|
/**
|
|
3402
3591
|
* Submits the form by triggering form field events and invoking the onSubmit callback.
|
|
3403
3592
|
*/
|
|
@@ -3410,12 +3599,11 @@ class FormCore {
|
|
|
3410
3599
|
if (!this.isValid) return;
|
|
3411
3600
|
const values = this.getFormValues();
|
|
3412
3601
|
this.submitSubject$.next(values);
|
|
3413
|
-
this.onSubmit && this.onSubmit(values);
|
|
3414
3602
|
}
|
|
3415
3603
|
destroy() {
|
|
3416
3604
|
this.submitSubject$.unsubscribe();
|
|
3417
3605
|
this.templateSubject$.unsubscribe();
|
|
3418
|
-
this.
|
|
3606
|
+
this.fieldEventSubject$.unsubscribe();
|
|
3419
3607
|
this.dataSubject$.unsubscribe();
|
|
3420
3608
|
this.fields.forEach(field => field.destroyField());
|
|
3421
3609
|
}
|
|
@@ -3431,15 +3619,22 @@ class FormCore {
|
|
|
3431
3619
|
*/
|
|
3432
3620
|
FormCore.checkIndexes = (struct, indexes = []) => {
|
|
3433
3621
|
if (!struct) return indexes;
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3622
|
+
const helper = (struct, indexes) => {
|
|
3623
|
+
for (let i = 0; i < struct.length; i++) {
|
|
3624
|
+
const structElement = struct[i];
|
|
3625
|
+
if (structElement.name === IVARPROPNAME) {
|
|
3626
|
+
throw new Error(`reserved ${IVARPROPNAME} name for field names`);
|
|
3627
|
+
}
|
|
3628
|
+
indexes.push(structElement.name);
|
|
3629
|
+
if (structElement.children) {
|
|
3630
|
+
helper(structElement.children, indexes);
|
|
3631
|
+
}
|
|
3442
3632
|
}
|
|
3633
|
+
};
|
|
3634
|
+
helper(struct, indexes);
|
|
3635
|
+
const duppedIndexes = indexes.filter((item, index) => indexes.indexOf(item) !== index);
|
|
3636
|
+
if (duppedIndexes.length > 0) {
|
|
3637
|
+
throw new Error(`duplicated indexes found on schema: ${JSON.stringify(duppedIndexes)}`);
|
|
3443
3638
|
}
|
|
3444
3639
|
return indexes;
|
|
3445
3640
|
};
|
|
@@ -3454,6 +3649,25 @@ class FormGroup {
|
|
|
3454
3649
|
constructor() {
|
|
3455
3650
|
this.forms = new Map();
|
|
3456
3651
|
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Creates an empty form with given index
|
|
3654
|
+
*
|
|
3655
|
+
* @param {string} options.index
|
|
3656
|
+
* @param {TMapper<unknown>} options.mappers
|
|
3657
|
+
*/
|
|
3658
|
+
createFormWithIndex({
|
|
3659
|
+
index,
|
|
3660
|
+
mappers
|
|
3661
|
+
}) {
|
|
3662
|
+
const formInstance = new FormCore({
|
|
3663
|
+
index,
|
|
3664
|
+
mappers
|
|
3665
|
+
});
|
|
3666
|
+
this.addForm({
|
|
3667
|
+
key: index,
|
|
3668
|
+
formInstance
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3457
3671
|
/**
|
|
3458
3672
|
* Adds a form instance to the form group.
|
|
3459
3673
|
*
|
|
@@ -3496,6 +3710,20 @@ class FormGroup {
|
|
|
3496
3710
|
(_a = this.forms.get(key)) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
3497
3711
|
this.forms.delete(key);
|
|
3498
3712
|
}
|
|
3713
|
+
/**
|
|
3714
|
+
* removes a field given a form and field index
|
|
3715
|
+
*
|
|
3716
|
+
* @param {string} options.formIndex
|
|
3717
|
+
* @param {string} options.fieldIndex
|
|
3718
|
+
*/
|
|
3719
|
+
removeField({
|
|
3720
|
+
formIndex,
|
|
3721
|
+
fieldIndex
|
|
3722
|
+
}) {
|
|
3723
|
+
var _a, _b, _c;
|
|
3724
|
+
(_b = (_a = this.forms.get(formIndex)) === null || _a === void 0 ? void 0 : _a.fields.get(fieldIndex)) === null || _b === void 0 ? void 0 : _b.destroyField();
|
|
3725
|
+
(_c = this.forms.get(formIndex)) === null || _c === void 0 ? void 0 : _c.fields.delete(fieldIndex);
|
|
3726
|
+
}
|
|
3499
3727
|
/**
|
|
3500
3728
|
* Checks if the specified key already exists in the form group.
|
|
3501
3729
|
*
|
|
@@ -3521,22 +3749,49 @@ class FormGroup {
|
|
|
3521
3749
|
* @param {string[]} indexes form indexes to be submitted
|
|
3522
3750
|
* @returns
|
|
3523
3751
|
*/
|
|
3524
|
-
submitMultipleFormsByIndex(indexes) {
|
|
3752
|
+
submitMultipleFormsByIndex(indexes, callback) {
|
|
3525
3753
|
let isValid = true;
|
|
3526
3754
|
let values = {};
|
|
3527
3755
|
let erroredFields = [];
|
|
3528
3756
|
indexes.forEach(index => {
|
|
3529
|
-
var _a;
|
|
3530
|
-
|
|
3757
|
+
var _a, _b;
|
|
3758
|
+
(_a = this.forms.get(index)) === null || _a === void 0 ? void 0 : _a.submit();
|
|
3759
|
+
const res = (_b = this.forms.get(index)) === null || _b === void 0 ? void 0 : _b.getFormValues();
|
|
3531
3760
|
isValid = isValid && ((res === null || res === void 0 ? void 0 : res.isValid) || false);
|
|
3532
3761
|
values = Object.assign(Object.assign({}, values), (res === null || res === void 0 ? void 0 : res.values) || {});
|
|
3533
3762
|
erroredFields = [...erroredFields, ...((res === null || res === void 0 ? void 0 : res.erroredFields) || [])];
|
|
3534
3763
|
});
|
|
3535
|
-
|
|
3764
|
+
isValid && callback && callback({
|
|
3536
3765
|
erroredFields,
|
|
3537
3766
|
isValid,
|
|
3538
3767
|
values
|
|
3539
|
-
};
|
|
3768
|
+
});
|
|
3769
|
+
}
|
|
3770
|
+
onDataSubscription({
|
|
3771
|
+
ids,
|
|
3772
|
+
callback
|
|
3773
|
+
}) {
|
|
3774
|
+
const subs = ids.reduce((acc, formId) => {
|
|
3775
|
+
var _a;
|
|
3776
|
+
// @TODO add config on debounceTime on this events
|
|
3777
|
+
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(({
|
|
3778
|
+
key
|
|
3779
|
+
}) => {
|
|
3780
|
+
var _a;
|
|
3781
|
+
return {
|
|
3782
|
+
formField: key,
|
|
3783
|
+
values: (_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.getFormValues()
|
|
3784
|
+
};
|
|
3785
|
+
}));
|
|
3786
|
+
if (sub) {
|
|
3787
|
+
acc[formId] = sub;
|
|
3788
|
+
} else {
|
|
3789
|
+
console.warn(`failed to register form id ${formId}`);
|
|
3790
|
+
}
|
|
3791
|
+
return acc;
|
|
3792
|
+
}, {});
|
|
3793
|
+
const sub = combineLatest(subs).subscribe(callback);
|
|
3794
|
+
return sub;
|
|
3540
3795
|
}
|
|
3541
3796
|
}
|
|
3542
3797
|
|