@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/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Subscription, Subject, combineLatest, startWith, filter, groupBy, mergeMap, debounceTime, map } from 'rxjs';
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, isNil, isEqual, get, set } from 'lodash';
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 = /\${(.*?)}/g;
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(extractedValues.map(el => el.split('.')[0]))),
107
- originPropertyKeys: Array.from(new Set(extractedValues.map(el => el.split('.')[1])))
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
- * Validates if a date value falls between two specified dates.
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} value - The date value to be validated in string format.
1934
- * @param {TValidationMethods} validations - The validation methods object containing the betweenDates validation rules.
1935
- * @returns {boolean} - Returns `true` if the date value fails the betweenDates validation, otherwise `false`.
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
- * ```typescript
1939
- * const validations = {
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
- * const result1 = betweenDates('2023-06-01', validations);
1947
- * console.log(result1); // false (date is within the range)
1946
+ * @example
1947
+ * // Returns false
1948
+ * invalidStringDate("1997-07-25", "YYYYMMDD");
1948
1949
  *
1949
- * const result2 = betweenDates('2024-01-01', validations);
1950
- * console.log(result2); // true (date is outside the range)
1951
- * ```
1950
+ * @example
1951
+ * // Returns true, as the format does not match
1952
+ * invalidStringDate("1997/25/07", "MMDDYYYY");
1952
1953
  */
1953
- const betweenDates = (value, validations) => {
1954
- var _a;
1955
- let fail = false;
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
- return fail;
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
- let originDate = new Date(dateRearrangeMapper[(_e = validations.date) === null || _e === void 0 ? void 0 : _e.origin.format](originValue).toString());
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 ((_g = (_f = validations.date) === null || _f === void 0 ? void 0 : _f.target) === null || _g === void 0 ? void 0 : _g.format) {
2060
- target = new Date(dateRearrangeMapper[(_j = (_h = validations.date) === null || _h === void 0 ? void 0 : _h.target) === null || _j === void 0 ? void 0 : _j.format](validations.date.target.value).toString());
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 (((_k = validations.date.target) === null || _k === void 0 ? void 0 : _k.value) && target) {
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[(_l = validations.date) === null || _l === void 0 ? void 0 : _l.operator];
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
- const run = (value, handlers) => {
2278
+ function run(value, handlers) {
2178
2279
  const runner = [];
2179
2280
  Object.keys(handlers).forEach(rule => {
2180
- runner.push(validations$1[rule](value, {
2181
- [rule]: handlers[rule]
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} validations - The validation methods object containing the multipleValidations rule set.
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, validations) => {
2216
- if (!validations.multipleValidations) return false;
2217
- const runner = run(value, validations.multipleValidations.validations);
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[validations.multipleValidations.rule]();
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
- apiResponseSubject$,
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) : 1000,
2293
- defaultStateRefreshTimeMS: Number(config === null || config === void 0 ? void 0 : config.defaultStateRefreshTimeMS) ? Number(config === null || config === void 0 ? void 0 : config.defaultStateRefreshTimeMS) : 100
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.errorMessages;
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 ((_a = mapper.events) === null || _a === void 0 ? void 0 : _a.setValue) this.valuePropName = mapper.events.setValue;
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.apiResponseSubject$ = apiResponseSubject$;
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 Subject();
2499
+ this.valueSubject$ = new SafeSubject(() => this._mounted);
2345
2500
  }
2346
2501
  if (!this.errorSubject$ || this.errorSubject$.closed) {
2347
- this.errorSubject$ = new Subject();
2502
+ this.errorSubject$ = new SafeSubject(() => this._mounted);
2348
2503
  }
2349
2504
  if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
2350
- this.visibilitySubject$ = new Subject();
2505
+ this.visibilitySubject$ = new SafeSubject(() => this._mounted);
2351
2506
  }
2352
2507
  if (!this.apiSubject$ || this.apiSubject$.closed) {
2353
- this.apiSubject$ = new Subject();
2508
+ this.apiSubject$ = new SafeSubject(() => this._mounted);
2354
2509
  }
2355
2510
  if (!this.propsSubject$ || this.propsSubject$.closed) {
2356
- this.propsSubject$ = new Subject();
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 Subject();
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
- apiResponse: this.apiSubject$.pipe(startWith(this._api)),
2368
- props: this.propsSubject$.pipe(startWith(this._props))
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(this.debounceDistinct(({
2526
+ !this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(groupBy(({
2371
2527
  event
2372
- }) => event, (_a = this.config) === null || _a === void 0 ? void 0 : _a.defaultAPIdebounceTimeMS), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
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 = this.maskValue(this.formatValue(val['_value']));
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 = this.maskValue(this.formatValue(val));
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._errorsString = Object.values(this.errors).join(', ');
2534
- this.errorSubject$.next(Object.values(this.errors));
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 = Object.assign({}, this.errors);
2648
- const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.config;
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
- var _a, _b;
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 (((_a = this.validations) === null || _a === void 0 ? void 0 : _a.events.includes(event)) || event === 'ON_FORM_SUBMIT') {
2656
- if (error && ((_b = this.errorMessages) === null || _b === void 0 ? void 0 : _b[validationKey])) {
2657
- errors[validationKey] = this.errorMessages[validationKey];
2658
- } else {
2659
- delete errors[validationKey];
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.errors = errors;
2665
- // remove later
2666
- if (this.errorMessagePropName) this.props = Object.assign(Object.assign({}, this.props), {
2667
- [this.errorMessagePropName]: this.errorsString
2668
- });
2669
- }
2670
- /**
2671
- * WIP expensive function to get updated field validity on each event
2672
- */
2673
- updateValidityFlag() {
2674
- var _a;
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 = validations[validationKey](this.value, preConditions);
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.onSubmit = entry.onSubmit;
2869
- this.config = entry.config;
2870
- this.mappers = entry.mappers;
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.apiResponseSubject$ = new Subject();
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
- component,
2943
- props,
2944
- name,
2945
- validations,
2946
- visibilityConditions,
2947
- resetValues,
2948
- errorMessages,
2949
- apiSchema,
2950
- metadata
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
- errorMessages,
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
- if (!this.fields.has(key)) return console.warn(`failed to get value from ${key}`);
3001
- return path.length > 0 ? get(this.fields.get(key)[property], path) : this.fields.get(key)[property];
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 = /\${(.*?)}/g;
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 = /\s*(\|\||&&|!)\s*/g;
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 = /\${(.*?)}/g;
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 = validations[validationKey](field.value, structElement.validations);
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 = validations[validationKey](field.value, structElement.validations);
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
- const mapper = this.mappers.find(mapEl => mapEl.componentName === structElement.component);
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: (_a = this.initialValues) === null || _a === void 0 ? void 0 : _a[structElement.name],
3464
+ initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[structElement.name],
3306
3465
  templateSubject$: this.templateSubject$,
3307
- apiResponseSubject$: this.apiResponseSubject$,
3466
+ fieldEventSubject$: this.fieldEventSubject$,
3308
3467
  dataSubject$: this.dataSubject$,
3309
3468
  config: this.config
3310
3469
  }));
3311
3470
  } else {
3312
- currField.children = ((_b = structElement === null || structElement === void 0 ? void 0 : structElement.children) === null || _b === void 0 ? void 0 : _b.map(el => el.name)) || (currField === null || currField === void 0 ? void 0 : 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
- return this.serializeStructure(structElement.children, `${path ? `${path}.` : ``}${structElement.name}`);
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[key] = val.value;
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[key] = val.value;
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.apiResponseSubject$.unsubscribe();
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
- for (let i = 0; i < struct.length; i++) {
3435
- const structElement = struct[i];
3436
- if (structElement.name === IVARPROPNAME) {
3437
- throw new Error(`reserved ${IVARPROPNAME} name for field names`);
3438
- }
3439
- indexes.push(structElement.name);
3440
- if (structElement.children) {
3441
- return FormCore.checkIndexes(structElement.children, indexes);
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
- const res = (_a = this.forms.get(index)) === null || _a === void 0 ? void 0 : _a.getFormValues();
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
- return {
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