@bolttech/form-engine-core 0.0.1-beta.1 → 0.0.1-beta.11

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,4 +1,4 @@
1
- import { Subscription, Subject, combineLatest, startWith, groupBy, mergeMap, debounceTime, map } from 'rxjs';
1
+ import { Subscription, Subject, combineLatest, startWith, groupBy, mergeMap, debounceTime, filter, map } from 'rxjs';
2
2
  import creditCardType from 'credit-card-type';
3
3
  import { isNumber as isNumber$1, isNil, isEqual, get, set } from 'lodash';
4
4
  import { getCurrencySymbol } from '@gaignoux/currency';
@@ -681,16 +681,9 @@ const currency = (value, masks) => {
681
681
  if (integerPart === '') {
682
682
  integerPart = '0';
683
683
  }
684
- console.log('Before', {
685
- convertedValue
686
- });
687
684
  if (align === 'right' && String(value).endsWith(' ')) {
688
685
  convertedValue = convertedValue.slice(0, -1);
689
686
  }
690
- console.log('After', {
691
- value,
692
- convertedValue
693
- });
694
687
  let newRawValue = integerPart;
695
688
  let decimalPart = convertedValue.slice(convertedValue.length - precision);
696
689
  if (precision > 0) {
@@ -2256,6 +2249,9 @@ const validations = {
2256
2249
  validDate
2257
2250
  };
2258
2251
 
2252
+ const DEFAULT_API_DEBOUNCE_TIME = 1000;
2253
+ const DEFAULT_STATE_REFRESH_TIME = 100;
2254
+
2259
2255
  /**
2260
2256
  * Represents a form field with observables for managing form state, validations, and API requests.
2261
2257
  */
@@ -2265,6 +2261,7 @@ class FormField {
2265
2261
  *
2266
2262
  * @param {object} options - Configuration options for the form field.
2267
2263
  * @param {IComponentSchema} options.schemaComponent - The schema definition for the form field.
2264
+ * @param {TSchemaFormConfig} options.config - The schema default configuration for debounced actions.
2268
2265
  * @param {string} [options.path] - The path within the form field (used internally during recursion).
2269
2266
  * @param {string[]} options.children - An array of children fields names.
2270
2267
  * @param {Function} options.validateVisibility - A function to validate the visibility of the field.
@@ -2274,19 +2271,26 @@ class FormField {
2274
2271
  */
2275
2272
  constructor({
2276
2273
  schemaComponent,
2274
+ config,
2277
2275
  path,
2278
2276
  children,
2279
2277
  validateVisibility,
2280
2278
  resetValue,
2281
2279
  initialValue,
2282
2280
  templateSubject$,
2283
- apiResponseSubject$,
2281
+ fieldEventSubject$,
2284
2282
  dataSubject$,
2285
2283
  mapper
2286
2284
  }) {
2287
2285
  var _a, _b, _c, _d, _e, _f, _g;
2288
2286
  this.fieldStateSubscription$ = new Subscription();
2287
+ this.originalSchema = schemaComponent;
2288
+ this.config = {
2289
+ 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,
2290
+ 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
2291
+ };
2289
2292
  this.name = schemaComponent.name;
2293
+ this.nameToSubmit = schemaComponent.nameToSubmit;
2290
2294
  this.component = schemaComponent.component;
2291
2295
  this.path = path;
2292
2296
  this.children = children;
@@ -2299,17 +2303,20 @@ class FormField {
2299
2303
  this.masks = schemaComponent.masks;
2300
2304
  if (mapper.valueChangeEvent) this.valueChangeEvent = mapper.valueChangeEvent;
2301
2305
  if ((_a = mapper.events) === null || _a === void 0 ? void 0 : _a.setValue) this.valuePropName = mapper.events.setValue;
2302
- if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setErrorMessage) this.errorMessagePropName = mapper.events.setErrorMessage;
2303
2306
  this.mapper = mapper;
2304
2307
  this.validateVisibility = validateVisibility;
2305
2308
  this.resetValue = resetValue;
2306
2309
  this.templateSubject$ = templateSubject$;
2307
- this.apiResponseSubject$ = apiResponseSubject$;
2310
+ this.fieldEventSubject$ = fieldEventSubject$;
2308
2311
  this.dataSubject$ = dataSubject$;
2309
2312
  this._props = schemaComponent.props || {};
2310
2313
  this._value = '';
2311
- this._stateValue = '';
2314
+ this._stateValue = ((_b = this.mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) ? {
2315
+ [this.mapper.events.setValue]: ''
2316
+ } : {};
2312
2317
  this._metadata = '';
2318
+ this.errorsString = '';
2319
+ this.errorsList = [];
2313
2320
  this.initialValue = initialValue;
2314
2321
  this._visibility = true;
2315
2322
  this._api = {
@@ -2324,7 +2331,7 @@ class FormField {
2324
2331
  return acc;
2325
2332
  }, {})
2326
2333
  };
2327
- this._errorsString = '';
2334
+ this._errors = {};
2328
2335
  this._valid = false;
2329
2336
  this.initializeObservers();
2330
2337
  }
@@ -2332,6 +2339,7 @@ class FormField {
2332
2339
  * method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
2333
2340
  */
2334
2341
  initializeObservers() {
2342
+ var _a;
2335
2343
  if (!this.valueSubject$ || this.valueSubject$.closed) {
2336
2344
  this.valueSubject$ = new Subject();
2337
2345
  }
@@ -2354,33 +2362,21 @@ class FormField {
2354
2362
  this.apiEventQueueSubject$ = new Subject();
2355
2363
  }
2356
2364
  this.fieldState$ = combineLatest({
2357
- errors: this.errorSubject$.pipe(startWith([])),
2358
2365
  visibility: this.visibilitySubject$.pipe(startWith(this._visibility)),
2359
- apiResponse: this.apiSubject$.pipe(startWith(this._api)),
2360
- props: this.propsSubject$.pipe(startWith(this._props))
2366
+ props: this.propsSubject$.pipe(startWith(this._props)),
2367
+ errors: this.errorSubject$.pipe(startWith(Object.assign({}, ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && {
2368
+ [this.mapper.events.setErrorMessage]: this.errorsString
2369
+ })))
2361
2370
  });
2362
- !this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(this.debounceDistinct(({
2371
+ !this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(groupBy(({
2363
2372
  event
2364
- }) => event, 1000)).subscribe(payload => {
2373
+ }) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
2365
2374
  this.apiRequest(payload);
2366
2375
  });
2367
2376
  if (!isNil(this.initialValue)) {
2368
2377
  this.value = this.initialValue;
2369
2378
  }
2370
2379
  }
2371
- /**
2372
- * Observable function to emit api events debounced and distinct for each event type,
2373
- * avoiding previous events being cancelled by new events if they occur inside the debounce time interval
2374
- *
2375
- * @param {(event: { event: TEvents }) => TEvents} keyExtractor function that will pass the event key to the groupBy operator
2376
- * @param {number} debounceTimeMs time to wait for each individual event emmited
2377
- * @returns
2378
- */
2379
- debounceDistinct(keyExtractor, debounceTimeMs) {
2380
- return source$ => source$.pipe(groupBy(keyExtractor), mergeMap(group$ => group$.pipe(debounceTime(debounceTimeMs), map(() => ({
2381
- event: group$.key
2382
- })))));
2383
- }
2384
2380
  /**
2385
2381
  * Retrieves the properties associated with the form field.
2386
2382
  *
@@ -2406,7 +2402,7 @@ class FormField {
2406
2402
  /**
2407
2403
  * Retrieves the current state value of the form field.
2408
2404
  *
2409
- * @returns {unknown} - The current state value of the form field.
2405
+ * @returns {Record<string,unknown>} - The current state value of the form field.
2410
2406
  */
2411
2407
  get stateValue() {
2412
2408
  return this._stateValue;
@@ -2414,14 +2410,6 @@ class FormField {
2414
2410
  get metadata() {
2415
2411
  return this._metadata;
2416
2412
  }
2417
- /**
2418
- * Retrieves the concatenated string of errors associated with the form field.
2419
- *
2420
- * @returns {string} - The concatenated string of errors.
2421
- */
2422
- get errorsString() {
2423
- return this._errorsString;
2424
- }
2425
2413
  /**
2426
2414
  * Retrieves the current value of the form field.
2427
2415
  *
@@ -2436,6 +2424,7 @@ class FormField {
2436
2424
  * @param {unknown} value - The new value to be set.
2437
2425
  */
2438
2426
  set value(value) {
2427
+ var _a, _b, _c;
2439
2428
  /*
2440
2429
  too much unstable, if the valueChangeEvent parses the template event
2441
2430
  value, might occur unexpected results
@@ -2455,22 +2444,19 @@ class FormField {
2455
2444
  if (typeof val === 'undefined' || val === null) return;
2456
2445
  if (typeof val === 'object' && '_value' in val && '_metadata' in val) {
2457
2446
  this._value = this.formatValue(val['_value']);
2458
- this._stateValue = this.maskValue(this.formatValue(val['_value']));
2447
+ this._stateValue = ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setValue) ? {
2448
+ [this.mapper.events.setValue]: this.maskValue(this.formatValue(val['_value']))
2449
+ } : {};
2459
2450
  this._metadata = val._metadata;
2460
2451
  } else {
2461
2452
  this._value = this.formatValue(val);
2462
- this._stateValue = this.maskValue(this.formatValue(val));
2453
+ this._stateValue = ((_b = this.mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) ? {
2454
+ [(_c = this.mapper.events) === null || _c === void 0 ? void 0 : _c.setValue]: this.maskValue(this.formatValue(val))
2455
+ } : {};
2456
+ this.maskValue(this.formatValue(val));
2463
2457
  this._metadata = val;
2464
2458
  }
2465
- /*
2466
- update prop value attribute to sync with templating
2467
- currently doesn't need prop Subject emission since it's synced with value
2468
- to avoid excessive prop subject emissions on each keystroke
2469
- */
2470
- if (this.valuePropName) this._props = Object.assign(Object.assign({}, this.props), {
2471
- [this.valuePropName]: this.value
2472
- });
2473
- this.valueSubject$.next(this._stateValue);
2459
+ this.stateValue && this.valueSubject$.next(this.stateValue);
2474
2460
  this.templateSubject$.next({
2475
2461
  key: this.name,
2476
2462
  event: 'ON_VALUE'
@@ -2520,10 +2506,17 @@ class FormField {
2520
2506
  * @param {TErrorMessages} errors - The new error messages to be set.
2521
2507
  */
2522
2508
  set errors(errors) {
2509
+ var _a;
2523
2510
  if (typeof errors === 'undefined' || isEqual(errors, this.errors)) return;
2524
2511
  this._errors = errors;
2525
- this._errorsString = Object.values(this.errors).join(', ');
2526
- this.errorSubject$.next(Object.values(this.errors));
2512
+ this.errorsList = Object.values(this.errors);
2513
+ this.errorsString = this.errorsList.join(', ');
2514
+ /**
2515
+ * if any error receieves a list of errors, set a prop for it, currently only supporting a single string
2516
+ */
2517
+ ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && this.errorSubject$.next({
2518
+ [this.mapper.events.setErrorMessage]: this.errorsString
2519
+ });
2527
2520
  this.templateSubject$.next({
2528
2521
  key: this.name,
2529
2522
  event: 'ON_PROPS'
@@ -2559,11 +2552,8 @@ class FormField {
2559
2552
  * Mounts the form field by initializing necessary subjects and combining their streams.
2560
2553
  *
2561
2554
  * @param {object} mountOpts - Adapter mount options.
2562
- * @param {string} prop.valuePropName - Adapter value property name.
2563
- * @param {(event: unknown) => unknown} prop.valueChangeEvent - Adapter change event handler function
2564
2555
  * @param {(value: unknown) => unknown} prop.valueSubscription - Adapter value change function
2565
2556
  * @param {(payload: Partial<IState>) => unknown} prop.propsSubscription - Adapter prop change function
2566
- * @param {string} prop.errorMessagePropName - error message property name to set errors onto component
2567
2557
  * @returns {void}
2568
2558
  */
2569
2559
  mountField({
@@ -2614,6 +2604,11 @@ class FormField {
2614
2604
  this.apiEventQueueSubject$.next({
2615
2605
  event
2616
2606
  });
2607
+ this.fieldEventSubject$.next({
2608
+ event,
2609
+ fieldName: this.name,
2610
+ fieldInstance: this
2611
+ });
2617
2612
  }
2618
2613
  /**
2619
2614
  * Sets the validity state of the field based on the provided validation rules and triggers error message updates.
@@ -2639,14 +2634,19 @@ class FormField {
2639
2634
  const errors = Object.assign({}, this.errors);
2640
2635
  const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.config;
2641
2636
  schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
2642
- var _a, _b;
2637
+ var _a;
2643
2638
  const error = validations[validationKey](this.value, schemaValidations);
2644
2639
  // setting valid flag
2645
2640
  valid = !error && valid;
2646
2641
  // setting error messages
2647
2642
  if (((_a = this.validations) === null || _a === void 0 ? void 0 : _a.events.includes(event)) || event === 'ON_FORM_SUBMIT') {
2648
- if (error && ((_b = this.errorMessages) === null || _b === void 0 ? void 0 : _b[validationKey])) {
2649
- errors[validationKey] = this.errorMessages[validationKey];
2643
+ if (error && this.errorMessages) {
2644
+ if (validationKey in this.errorMessages) {
2645
+ const messages = this.errorMessages;
2646
+ errors[validationKey] = messages[validationKey];
2647
+ } else if ('default' in this.errorMessages) {
2648
+ errors[validationKey] = this.errorMessages.default;
2649
+ }
2650
2650
  } else {
2651
2651
  delete errors[validationKey];
2652
2652
  }
@@ -2654,10 +2654,19 @@ class FormField {
2654
2654
  });
2655
2655
  this._valid = valid;
2656
2656
  this.errors = errors;
2657
- // remove later
2658
- if (this.errorMessagePropName) this.props = Object.assign(Object.assign({}, this.props), {
2659
- [this.errorMessagePropName]: this.errorsString
2657
+ }
2658
+ /**
2659
+ * WIP expensive function to get updated field validity on each event
2660
+ */
2661
+ updateValidityFlag() {
2662
+ var _a;
2663
+ let valid = true;
2664
+ const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.config;
2665
+ schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
2666
+ const error = validations[validationKey](this.value, schemaValidations);
2667
+ valid = !error && valid;
2660
2668
  });
2669
+ this._valid = valid;
2661
2670
  }
2662
2671
  /**
2663
2672
  * Formats the field value using the specified formatters, if available.
@@ -2802,7 +2811,7 @@ class FormField {
2802
2811
  * @returns {void}
2803
2812
  */
2804
2813
  subscribeState(callback) {
2805
- this.fieldStateSubscription$ = this.fieldState$.pipe(debounceTime(100)).subscribe({
2814
+ this.fieldStateSubscription$ = this.fieldState$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS)).subscribe({
2806
2815
  next: callback
2807
2816
  });
2808
2817
  }
@@ -2837,7 +2846,8 @@ class FormCore {
2837
2846
  * @param {((payload: {field: string;data: TFormValues;}) => void) | undefined} [entry.onData] - A callback function to handle data emission.
2838
2847
  */
2839
2848
  constructor(entry) {
2840
- var _a, _b, _c, _d;
2849
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2850
+ this.mappers = new Map();
2841
2851
  this.schema = entry.schema;
2842
2852
  this.fields = new Map();
2843
2853
  this.initialValues = entry.initialValues || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.initialValues);
@@ -2845,11 +2855,17 @@ class FormCore {
2845
2855
  this.method = entry.method || ((_c = entry.schema) === null || _c === void 0 ? void 0 : _c.method);
2846
2856
  this._iVars = entry.iVars || ((_d = entry.schema) === null || _d === void 0 ? void 0 : _d.iVars) || {};
2847
2857
  this.onSubmit = entry.onSubmit;
2848
- this.mappers = entry.mappers;
2858
+ this.config = {
2859
+ 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,
2860
+ 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
2861
+ };
2862
+ (_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
2863
+ this.mappers.set(mapper.componentName, mapper);
2864
+ });
2849
2865
  this.schema && FormCore.checkIndexes(this.schema.components);
2850
2866
  this.templateSubject$ = new Subject();
2851
2867
  this.submitSubject$ = new Subject();
2852
- this.apiResponseSubject$ = new Subject();
2868
+ this.fieldEventSubject$ = new Subject();
2853
2869
  this.dataSubject$ = new Subject();
2854
2870
  this.dataCallbackSubscription$ = new Subscription();
2855
2871
  this.subscribedTemplates = [];
@@ -2860,7 +2876,6 @@ class FormCore {
2860
2876
  key: IVARPROPNAME,
2861
2877
  event: 'ON_IVARS'
2862
2878
  });
2863
- this.apiResponseSubject$.subscribe(this.refreshApi.bind(this));
2864
2879
  entry.onData && this.subscribeData(entry.onData);
2865
2880
  /*
2866
2881
  mount events needs to occur on form level, only when all the fields are instantiated
@@ -2912,20 +2927,18 @@ class FormCore {
2912
2927
  * Subscribes to templates for dynamic updates.
2913
2928
  */
2914
2929
  subscribeTemplates() {
2915
- /*
2916
- @TODO fix removal of templates of removed fields, they are kept
2917
- tried: this.subscribedTemplates = [] and only stores the last one..
2918
- */
2930
+ this.subscribedTemplates = [];
2919
2931
  this.fields.forEach(({
2920
- component,
2921
- props,
2922
- name,
2923
- validations,
2924
- visibilityConditions,
2925
- resetValues,
2926
- errorMessages,
2927
- apiSchema,
2928
- metadata
2932
+ originalSchema: {
2933
+ component,
2934
+ props,
2935
+ name,
2936
+ validations,
2937
+ visibilityConditions,
2938
+ resetValues,
2939
+ errorMessages,
2940
+ api
2941
+ }
2929
2942
  }, key) => {
2930
2943
  const template = {
2931
2944
  component,
@@ -2935,19 +2948,17 @@ class FormCore {
2935
2948
  visibilityConditions,
2936
2949
  resetValues,
2937
2950
  errorMessages,
2938
- apiSchema,
2939
- metadata
2951
+ apiSchema: api
2940
2952
  };
2941
2953
  traverseObject(template, key).forEach(element => this.subscribedTemplates.push(element));
2942
2954
  });
2943
- // console.log(subscribedProps);
2944
2955
  }
2945
2956
  /**
2946
2957
  *
2947
2958
  * @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call on data
2948
2959
  */
2949
2960
  subscribeData(callback) {
2950
- this.dataCallbackSubscription$ = this.dataSubject$.pipe(debounceTime(100), map(({
2961
+ this.dataCallbackSubscription$ = this.dataSubject$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS), map(({
2951
2962
  key
2952
2963
  }) => ({
2953
2964
  field: key,
@@ -2974,8 +2985,12 @@ class FormCore {
2974
2985
  const value = get(this.iVars, [property, ...path]);
2975
2986
  return value;
2976
2987
  }
2977
- if (!this.fields.has(key)) return console.warn(`failed to get value from ${key}`);
2978
- return path.length > 0 ? get(this.fields.get(key)[property], path) : this.fields.get(key)[property];
2988
+ const field = this.fields.get(key);
2989
+ if (!field) return console.warn(`failed to get value from ${key}`);
2990
+ if (property === 'props' && path[0] === field.valuePropName) {
2991
+ return field.stateValue;
2992
+ }
2993
+ return path.length > 0 ? get(field[property], path) : field[property];
2979
2994
  }
2980
2995
  /**
2981
2996
  * Sets the value of a property in a field.
@@ -3008,7 +3023,6 @@ class FormCore {
3008
3023
  now using key !== originKey, check if any recursion error occurs
3009
3024
  **/
3010
3025
  if (property === 'props' && path[0] === field.valuePropName && key !== originKey) {
3011
- // field.value = value;
3012
3026
  field.emitValue({
3013
3027
  event: 'ON_FIELD_CHANGE',
3014
3028
  value
@@ -3048,7 +3062,6 @@ class FormCore {
3048
3062
  const operatorRegex = /\s*(\|\||&&|!)\s*/g;
3049
3063
  const splittedString = extractedValues.map(el => el.split(operatorRegex));
3050
3064
  const result = splittedString.map(splittedStringVal => {
3051
- // console.log(splittedStringVal)
3052
3065
  return splittedStringVal.filter(Boolean).reduce((acc, curr) => {
3053
3066
  if (curr.match(/^\|\||&&|!$/)) {
3054
3067
  return `${acc}${curr}`;
@@ -3081,6 +3094,11 @@ class FormCore {
3081
3094
  case 'object':
3082
3095
  if (currValue === null) {
3083
3096
  value = null;
3097
+ break;
3098
+ }
3099
+ if (currValue instanceof Date) {
3100
+ value = `new Date(\`${currValue}\`)`;
3101
+ break;
3084
3102
  }
3085
3103
  value = JSON.stringify(currValue);
3086
3104
  break;
@@ -3092,7 +3110,6 @@ class FormCore {
3092
3110
  });
3093
3111
  return result.map(el => {
3094
3112
  try {
3095
- // console.log(el);
3096
3113
  return new Function(`return ${el}`)();
3097
3114
  } catch (e) {
3098
3115
  console.log(e);
@@ -3109,7 +3126,7 @@ class FormCore {
3109
3126
  */
3110
3127
  replaceExpression(expression, values) {
3111
3128
  const regex = /\${(.*?)}/g;
3112
- return expression.replace(regex, () => values.shift() || '');
3129
+ return expression.replace(regex, () => String(values.shift()) || '');
3113
3130
  }
3114
3131
  /**
3115
3132
  * Checks if an expression string contains string concatenation within a marked expression.
@@ -3165,34 +3182,6 @@ class FormCore {
3165
3182
  }
3166
3183
  });
3167
3184
  }
3168
- /**
3169
- * Refreshes api observed fields.
3170
- *
3171
- * @param {object} options - Options for refreshing api.
3172
- * @param {string} options.key - The key of the field triggering the update.
3173
- */
3174
- refreshApi({
3175
- key
3176
- }) {
3177
- /*
3178
- global api notifications needs to have field dependency array
3179
- in order to be reliable, disabled for now
3180
- */
3181
- return key;
3182
- // const emmittedFields: string[] = [];
3183
- // this.subscribedTemplates.forEach((template) => {
3184
- // if (
3185
- // template.originFieldKeys.includes(key) &&
3186
- // template.originPropertyKeys.includes('api') &&
3187
- // !emmittedFields.includes(template.destinationKey)
3188
- // ) {
3189
- // emmittedFields.push(template.destinationKey);
3190
- // this.fields
3191
- // .get(template.destinationKey)
3192
- // ?.emitEvents({ event: 'ON_API_RESPONSE' });
3193
- // }
3194
- // });
3195
- }
3196
3185
  /**
3197
3186
  * Validates visibility conditions for a given event and updates field visibility accordingly.
3198
3187
  *
@@ -3258,6 +3247,54 @@ class FormCore {
3258
3247
  });
3259
3248
  });
3260
3249
  }
3250
+ /**
3251
+ * Adds a field onto the form instance regardless there is a schema or not
3252
+ *
3253
+ * @param fieldSchema
3254
+ */
3255
+ addField({
3256
+ fieldSchema,
3257
+ mapperElement
3258
+ }) {
3259
+ var _a, _b, _c;
3260
+ if (this.fields.has(fieldSchema.name)) {
3261
+ throw new Error(`field name ${fieldSchema.name} already defined`);
3262
+ }
3263
+ const mapper = mapperElement || ((_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(fieldSchema.component));
3264
+ if (!mapper) throw new Error(`mapper not found for ${fieldSchema.component}, add it to the mappers configuration`);
3265
+ this.fields.set(fieldSchema.name, new FormField({
3266
+ schemaComponent: fieldSchema,
3267
+ mapper,
3268
+ children: fieldSchema.children ? fieldSchema.children.map(el => el.name) : [],
3269
+ validateVisibility: this.validateVisibility.bind(this),
3270
+ resetValue: this.resetValue.bind(this),
3271
+ initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[fieldSchema.name],
3272
+ templateSubject$: this.templateSubject$,
3273
+ fieldEventSubject$: this.fieldEventSubject$,
3274
+ dataSubject$: this.dataSubject$,
3275
+ config: this.config
3276
+ }));
3277
+ this.subscribeTemplates();
3278
+ this.refreshTemplates({
3279
+ event: 'ON_FIELDS',
3280
+ key: fieldSchema.name
3281
+ });
3282
+ (_c = this.fields.get(fieldSchema.name)) === null || _c === void 0 ? void 0 : _c.emitEvents({
3283
+ event: 'ON_FIELD_MOUNT'
3284
+ });
3285
+ }
3286
+ removeField({
3287
+ key
3288
+ }) {
3289
+ var _a;
3290
+ (_a = this.fields.get(key)) === null || _a === void 0 ? void 0 : _a.destroyField();
3291
+ this.fields.delete(key);
3292
+ this.subscribeTemplates();
3293
+ this.templateSubject$.next({
3294
+ key,
3295
+ event: 'ON_FIELDS'
3296
+ });
3297
+ }
3261
3298
  /**
3262
3299
  * Serializes the schema structure to create form fields.
3263
3300
  *
@@ -3267,10 +3304,15 @@ class FormCore {
3267
3304
  serializeStructure(struct, path) {
3268
3305
  if (!struct) return;
3269
3306
  struct.forEach(structElement => {
3270
- var _a, _b;
3307
+ var _a, _b, _c;
3271
3308
  const currField = this.fields.get(structElement.name);
3272
3309
  if (!currField) {
3273
- const mapper = this.mappers.find(mapEl => mapEl.componentName === structElement.component);
3310
+ let mapper;
3311
+ if (structElement === null || structElement === void 0 ? void 0 : structElement.mapper) {
3312
+ mapper = structElement === null || structElement === void 0 ? void 0 : structElement.mapper;
3313
+ } else {
3314
+ mapper = (_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(structElement.component);
3315
+ }
3274
3316
  if (!mapper) throw new Error(`mapper not found for ${structElement.component}, add it to the mappers configuration`);
3275
3317
  this.fields.set(structElement.name, new FormField({
3276
3318
  schemaComponent: structElement,
@@ -3279,18 +3321,20 @@ class FormCore {
3279
3321
  children: structElement.children ? structElement.children.map(el => el.name) : [],
3280
3322
  validateVisibility: this.validateVisibility.bind(this),
3281
3323
  resetValue: this.resetValue.bind(this),
3282
- initialValue: (_a = this.initialValues) === null || _a === void 0 ? void 0 : _a[structElement.name],
3324
+ initialValue: (_b = this.initialValues) === null || _b === void 0 ? void 0 : _b[structElement.name],
3283
3325
  templateSubject$: this.templateSubject$,
3284
- apiResponseSubject$: this.apiResponseSubject$,
3285
- dataSubject$: this.dataSubject$
3326
+ fieldEventSubject$: this.fieldEventSubject$,
3327
+ dataSubject$: this.dataSubject$,
3328
+ config: this.config
3286
3329
  }));
3287
3330
  } else {
3288
- 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) || [];
3331
+ 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) || [];
3289
3332
  currField.path = path;
3333
+ currField.originalSchema = structElement;
3290
3334
  currField.templateSubject$ = this.templateSubject$;
3291
3335
  }
3292
3336
  if (structElement.children) {
3293
- return this.serializeStructure(structElement.children, `${path ? `${path}.` : ``}${structElement.name}`);
3337
+ this.serializeStructure(structElement.children, `${path ? `${path}.` : ``}${structElement.name}`);
3294
3338
  }
3295
3339
  });
3296
3340
  }
@@ -3347,10 +3391,9 @@ class FormCore {
3347
3391
  const values = {};
3348
3392
  this.fields.forEach((val, key) => {
3349
3393
  if (val.value) {
3350
- values[key] = val.value;
3394
+ set(values, val.nameToSubmit || key, val.value);
3351
3395
  }
3352
3396
  });
3353
- console.log(values);
3354
3397
  }
3355
3398
  /**
3356
3399
  * Gets the current values of all form fields.
@@ -3362,7 +3405,7 @@ class FormCore {
3362
3405
  const erroredFields = [];
3363
3406
  this.fields.forEach((val, key) => {
3364
3407
  if (val.value) {
3365
- values[key] = val.value;
3408
+ set(values, val.nameToSubmit || key, val.value);
3366
3409
  }
3367
3410
  if (!val.valid) {
3368
3411
  erroredFields.push(key);
@@ -3374,6 +3417,14 @@ class FormCore {
3374
3417
  isValid: this.isValid
3375
3418
  };
3376
3419
  }
3420
+ subscribeFieldEvent({
3421
+ callback
3422
+ }) {
3423
+ const sub = this.fieldEventSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS)))).subscribe({
3424
+ next: callback
3425
+ });
3426
+ return sub;
3427
+ }
3377
3428
  /**
3378
3429
  * Submits the form by triggering form field events and invoking the onSubmit callback.
3379
3430
  */
@@ -3391,8 +3442,9 @@ class FormCore {
3391
3442
  destroy() {
3392
3443
  this.submitSubject$.unsubscribe();
3393
3444
  this.templateSubject$.unsubscribe();
3394
- this.apiResponseSubject$.unsubscribe();
3445
+ this.fieldEventSubject$.unsubscribe();
3395
3446
  this.dataSubject$.unsubscribe();
3447
+ this.fields.forEach(field => field.destroyField());
3396
3448
  }
3397
3449
  }
3398
3450
  /**
@@ -3406,15 +3458,22 @@ class FormCore {
3406
3458
  */
3407
3459
  FormCore.checkIndexes = (struct, indexes = []) => {
3408
3460
  if (!struct) return indexes;
3409
- for (let i = 0; i < struct.length; i++) {
3410
- const structElement = struct[i];
3411
- if (structElement.name === IVARPROPNAME) {
3412
- throw new Error(`reserved ${IVARPROPNAME} name for field names`);
3413
- }
3414
- indexes.push(structElement.name);
3415
- if (structElement.children) {
3416
- return FormCore.checkIndexes(structElement.children, indexes);
3461
+ const helper = (struct, indexes) => {
3462
+ for (let i = 0; i < struct.length; i++) {
3463
+ const structElement = struct[i];
3464
+ if (structElement.name === IVARPROPNAME) {
3465
+ throw new Error(`reserved ${IVARPROPNAME} name for field names`);
3466
+ }
3467
+ indexes.push(structElement.name);
3468
+ if (structElement.children) {
3469
+ helper(structElement.children, indexes);
3470
+ }
3417
3471
  }
3472
+ };
3473
+ helper(struct, indexes);
3474
+ const duppedIndexes = indexes.filter((item, index) => indexes.indexOf(item) !== index);
3475
+ if (duppedIndexes.length > 0) {
3476
+ throw new Error(`duplicated indexes found on schema: ${JSON.stringify(duppedIndexes)}`);
3418
3477
  }
3419
3478
  return indexes;
3420
3479
  };
@@ -3429,6 +3488,25 @@ class FormGroup {
3429
3488
  constructor() {
3430
3489
  this.forms = new Map();
3431
3490
  }
3491
+ /**
3492
+ * Creates an empty form with given index
3493
+ *
3494
+ * @param {string} options.index
3495
+ * @param {TMapper<unknown>} options.mappers
3496
+ */
3497
+ createFormWithIndex({
3498
+ index,
3499
+ mappers
3500
+ }) {
3501
+ const formInstance = new FormCore({
3502
+ index,
3503
+ mappers
3504
+ });
3505
+ this.addForm({
3506
+ key: index,
3507
+ formInstance
3508
+ });
3509
+ }
3432
3510
  /**
3433
3511
  * Adds a form instance to the form group.
3434
3512
  *
@@ -3471,6 +3549,20 @@ class FormGroup {
3471
3549
  (_a = this.forms.get(key)) === null || _a === void 0 ? void 0 : _a.destroy();
3472
3550
  this.forms.delete(key);
3473
3551
  }
3552
+ /**
3553
+ * removes a field given a form and field index
3554
+ *
3555
+ * @param {string} options.formIndex
3556
+ * @param {string} options.fieldIndex
3557
+ */
3558
+ removeField({
3559
+ formIndex,
3560
+ fieldIndex
3561
+ }) {
3562
+ var _a, _b, _c;
3563
+ (_b = (_a = this.forms.get(formIndex)) === null || _a === void 0 ? void 0 : _a.fields.get(fieldIndex)) === null || _b === void 0 ? void 0 : _b.destroyField();
3564
+ (_c = this.forms.get(formIndex)) === null || _c === void 0 ? void 0 : _c.fields.delete(fieldIndex);
3565
+ }
3474
3566
  /**
3475
3567
  * Checks if the specified key already exists in the form group.
3476
3568
  *