@bolttech/form-engine-core 1.0.0-beta.2 → 1.0.0-beta.20

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 { Subject, Subscription, combineLatest, startWith, groupBy, mergeMap, debounceTime, filter, map, distinctUntilKeyChanged, distinctUntilChanged, skip } from 'rxjs';
1
+ import { Subject, Subscription, groupBy, mergeMap, debounceTime, filter, combineLatest, startWith, map, distinctUntilKeyChanged } from 'rxjs';
2
2
  import creditCardType from 'credit-card-type';
3
3
  import { isNumber as isNumber$1, isFunction, cloneDeep, isEqual, get, isNil, set } from 'lodash';
4
4
  import { getCurrencySymbol } from '@gaignoux/currency';
@@ -48,9 +48,9 @@ const DEFAULT_API_DEBOUNCE_TIME = 1000;
48
48
  const DEFAULT_STATE_REFRESH_TIME = 100;
49
49
  const TEMPLATE_REGEX_STRING_CONCATENATION_DETECTOR = /^\$\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}$/;
50
50
  const TEMPLATE_REGEX_DELIMITATOR = /\$\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*)\}/g;
51
- const TEMPLATE_REGEX_OPERATOR_SPLITTER = /\s*(\|\||&&|!)\s*/g;
52
- const TEMPLATE_REGEX_OPERATOR_MATCHER = /^\|\||&&|!$/;
53
- const TEMPLATE_AVALIABLE_SCOPES = ['fields', 'iVars'];
51
+ const TEMPLATE_REGEX_OPERATOR_SPLITTER = /\s*(\|\||&&|!+)\s*/g;
52
+ const TEMPLATE_REGEX_OPERATOR_MATCHER = /^\|\||&&|!+$/;
53
+ const TEMPLATE_AVALIABLE_SCOPES = ['fields', 'iVars', 'form'];
54
54
  const ALLOWED_RESET_PROPS_MUTATIONS = ['api', 'apiSchema', 'props', 'validations', 'visibilityConditions', 'resetValues'];
55
55
  const DEFAULT_LOG_VERBOSE = false;
56
56
 
@@ -72,11 +72,12 @@ function makeRequest(method, url, headers, body, queryParams) {
72
72
  return new Promise(function (resolve, reject) {
73
73
  const xhr = new XMLHttpRequest();
74
74
  if (queryParams) {
75
- const newUrl = new URL(url);
75
+ const [baseUrl, existingParamsString] = url.split('?');
76
+ const searchParams = new URLSearchParams(existingParamsString);
76
77
  Object.keys(queryParams).forEach(param => {
77
- newUrl.searchParams.append(param, queryParams[param]);
78
+ searchParams.append(param, queryParams[param]);
78
79
  });
79
- url = newUrl.toString();
80
+ url = `${baseUrl}?${searchParams.toString()}`;
80
81
  }
81
82
  xhr.open(method, url, true);
82
83
  if (headers) {
@@ -577,7 +578,7 @@ const formatters = {
577
578
  * console.log(maskedValue); // Output: 'ab***gh###'
578
579
  * ```
579
580
  */
580
- var generic = ((value, masks) => {
581
+ var generic = (value, masks) => {
581
582
  if (!(masks === null || masks === void 0 ? void 0 : masks.generic)) return value;
582
583
  let masked = value;
583
584
  masks.generic.forEach(item => {
@@ -596,7 +597,7 @@ var generic = ((value, masks) => {
596
597
  masked = masked.slice(0, from - 1) + maskedPortion + masked.slice(to);
597
598
  });
598
599
  return masked;
599
- });
600
+ };
600
601
 
601
602
  /**
602
603
  * Replaces all characters in a string with a specified replacement string or character.
@@ -881,7 +882,7 @@ const masks = {
881
882
  * console.log(isValid); // Output: true or false based on validation
882
883
  * ```
883
884
  */
884
- var length = ((value, validations) => {
885
+ var length = (value, validations) => {
885
886
  if (!validations.length || !value) return false;
886
887
  let targetValue = value;
887
888
  // We want length even if it is a numeric
@@ -897,7 +898,7 @@ var length = ((value, validations) => {
897
898
  greaterOrEqual: targetValue.length < validations.length.target
898
899
  };
899
900
  return condition[validations.length.rule];
900
- });
901
+ };
901
902
 
902
903
  /**
903
904
  * Validates a Spanish NIF (Número de Identificación Fiscal).
@@ -1135,7 +1136,7 @@ const CIF = value => {
1135
1136
  * console.log(isValid); // Output: true or false based on validation
1136
1137
  * ```
1137
1138
  */
1138
- var document = ((value, validations) => {
1139
+ var document = (value, validations) => {
1139
1140
  if (!value || !validations.document) return true;
1140
1141
  const validation = {
1141
1142
  NIF: (value, locale) => NIF(value, locale),
@@ -1144,7 +1145,7 @@ var document = ((value, validations) => {
1144
1145
  IBAN: value => IBAN(value)
1145
1146
  };
1146
1147
  return validation[validations.document.type](value, validations.document.locale);
1147
- });
1148
+ };
1148
1149
 
1149
1150
  /**
1150
1151
  * Validates if a value exceeds the maximum allowed value.
@@ -2262,10 +2263,10 @@ function run$1(value, handlers, validations) {
2262
2263
  * const isValid = validateValue(value, methods);
2263
2264
  * console.log(isValid); // Output: true
2264
2265
  */
2265
- var namedRule = ((value, methods, validations) => {
2266
+ var namedRule = (value, methods, validations) => {
2266
2267
  if (!methods) return false;
2267
2268
  return run$1(value, methods, validations).some(validation => validation);
2268
- });
2269
+ };
2269
2270
 
2270
2271
  /**
2271
2272
  * @internal
@@ -2485,6 +2486,7 @@ class FormField {
2485
2486
  * @param {() => TFormValues<unknown>} options.getFormValues, - form instance function that builds onData parameter payload from fields
2486
2487
  */
2487
2488
  constructor({
2489
+ formIndex,
2488
2490
  schemaComponent,
2489
2491
  config,
2490
2492
  path,
@@ -2495,14 +2497,17 @@ class FormField {
2495
2497
  templateSubject$,
2496
2498
  fieldEventSubject$,
2497
2499
  dataSubject$,
2498
- formValidNotification$,
2500
+ fieldValidNotification$,
2501
+ mountSubject$,
2499
2502
  mapper,
2500
2503
  getFormValues,
2501
- submitEvent
2504
+ submitEvent,
2505
+ visibility
2502
2506
  }) {
2503
- var _a, _b, _c, _d, _e, _f, _g;
2507
+ var _a, _b, _c, _d, _e, _f;
2504
2508
  this.valueSubscription$ = new Subscription();
2505
2509
  this.fieldStateSubscription$ = new Subscription();
2510
+ this.formIndex = formIndex;
2506
2511
  this.originalSchema = cloneDeep(schemaComponent);
2507
2512
  this.config = {
2508
2513
  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,
@@ -2515,7 +2520,6 @@ class FormField {
2515
2520
  this.path = path;
2516
2521
  this.children = children;
2517
2522
  this.validations = cloneDeep(schemaComponent.validations);
2518
- this.errorMessages = cloneDeep((_a = schemaComponent.validations) === null || _a === void 0 ? void 0 : _a.messages);
2519
2523
  this.visibilityConditions = cloneDeep(schemaComponent.visibilityConditions);
2520
2524
  this.resetValues = cloneDeep(schemaComponent.resetValues);
2521
2525
  this.resetPropertyValues = cloneDeep(schemaComponent.resetPropertyValues);
@@ -2523,7 +2527,7 @@ class FormField {
2523
2527
  this.formatters = cloneDeep(schemaComponent.formatters);
2524
2528
  this.masks = cloneDeep(schemaComponent.masks);
2525
2529
  if (mapper.valueChangeEvent) this.valueChangeEvent = mapper.valueChangeEvent;
2526
- if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) this.valuePropName = mapper.events.setValue;
2530
+ if ((_a = mapper.events) === null || _a === void 0 ? void 0 : _a.setValue) this.valuePropName = mapper.events.setValue;
2527
2531
  this.mapper = mapper;
2528
2532
  this.validateVisibility = validateVisibility;
2529
2533
  this.resetValue = resetValue;
@@ -2533,67 +2537,62 @@ class FormField {
2533
2537
  this.templateSubject$ = templateSubject$;
2534
2538
  this.fieldEventSubject$ = fieldEventSubject$;
2535
2539
  this.dataSubject$ = dataSubject$;
2536
- this.formValidNotification$ = formValidNotification$;
2537
- this._props = cloneDeep(schemaComponent.props || {});
2540
+ this.fieldValidNotification$ = fieldValidNotification$;
2541
+ this.mountSubject$ = mountSubject$;
2542
+ this._props = FormField.filterProps(cloneDeep(schemaComponent.props || {}));
2538
2543
  this._metadata = '';
2539
2544
  this.errorsString = '';
2540
2545
  this.errorsList = [];
2541
- this._visibility = true;
2546
+ this._visibility = typeof visibility === 'boolean' ? visibility : true;
2542
2547
  this._api = {
2543
2548
  default: {
2544
- response: ((_e = (_d = (_c = this.apiSchema) === null || _c === void 0 ? void 0 : _c.defaultConfig) === null || _d === void 0 ? void 0 : _d.config) === null || _e === void 0 ? void 0 : _e.fallbackValue) || '',
2549
+ response: ((_d = (_c = (_b = this.apiSchema) === null || _b === void 0 ? void 0 : _b.defaultConfig) === null || _c === void 0 ? void 0 : _c.config) === null || _d === void 0 ? void 0 : _d.fallbackValue) || '',
2545
2550
  status: null
2546
2551
  },
2547
- named: ((_f = this.apiSchema) === null || _f === void 0 ? void 0 : _f.configs) && Object.keys((_g = this.apiSchema) === null || _g === void 0 ? void 0 : _g.configs).reduce((acc, curr) => {
2552
+ named: ((_e = this.apiSchema) === null || _e === void 0 ? void 0 : _e.configs) && Object.keys((_f = this.apiSchema) === null || _f === void 0 ? void 0 : _f.configs).reduce((acc, curr) => {
2548
2553
  var _a, _b;
2549
2554
  acc[curr] = {
2550
2555
  response: ((_b = (_a = this.apiSchema) === null || _a === void 0 ? void 0 : _a.configs) === null || _b === void 0 ? void 0 : _b[curr].config.fallbackValue) || '',
2551
2556
  status: null
2552
2557
  };
2553
2558
  return acc;
2554
- }, {})
2559
+ }, {}),
2560
+ apiState: {
2561
+ loading: false
2562
+ }
2555
2563
  };
2556
2564
  this._errors = {};
2557
- this._mounted = true;
2558
- this._valid = false;
2565
+ this._mounted = false;
2566
+ this.valid = true;
2559
2567
  this.initializeObservers();
2560
2568
  }
2561
2569
  /**
2562
2570
  * method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
2571
+ * due to some visibility conditions unmounts the field from the adapter if they are children of it and avoid
2572
+ * emissions to unsubscribed fields
2563
2573
  */
2564
2574
  initializeObservers() {
2565
- var _a;
2566
2575
  if (!this.valueSubject$ || this.valueSubject$.closed) {
2567
- this.valueSubject$ = new SafeSubject(() => this._mounted);
2576
+ this.valueSubject$ = new SafeSubject(() => this.mounted);
2568
2577
  }
2569
2578
  if (!this.errorSubject$ || this.errorSubject$.closed) {
2570
- this.errorSubject$ = new SafeSubject(() => this._mounted);
2579
+ this.errorSubject$ = new SafeSubject(() => this.mounted);
2571
2580
  }
2572
2581
  if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
2573
- this.visibilitySubject$ = new SafeSubject(() => this._mounted);
2574
- }
2575
- if (!this.apiSubject$ || this.apiSubject$.closed) {
2576
- this.apiSubject$ = new SafeSubject(() => this._mounted);
2582
+ this.visibilitySubject$ = new SafeSubject(() => this.mounted);
2577
2583
  }
2578
2584
  if (!this.propsSubject$ || this.propsSubject$.closed) {
2579
- this.propsSubject$ = new SafeSubject(() => this._mounted);
2585
+ this.propsSubject$ = new SafeSubject(() => this.mounted);
2580
2586
  }
2581
2587
  if (!this.fieldStateSubscription$ || this.fieldStateSubscription$.closed) {
2582
2588
  this.fieldStateSubscription$ = new Subscription();
2583
2589
  }
2584
2590
  if (!this.apiEventQueueSubject$ || this.apiEventQueueSubject$.closed) {
2585
- this.apiEventQueueSubject$ = new SafeSubject(() => this._mounted);
2591
+ this.apiEventQueueSubject$ = new SafeSubject(() => this.mounted);
2586
2592
  }
2587
- this.fieldState$ = combineLatest({
2588
- visibility: this.visibilitySubject$.pipe(startWith(this._visibility)),
2589
- props: this.propsSubject$.pipe(startWith(this._props)),
2590
- errors: this.errorSubject$.pipe(startWith(Object.assign({}, ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && {
2591
- [this.mapper.events.setErrorMessage]: this.errorsString
2592
- })))
2593
- });
2594
2593
  !this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(groupBy(({
2595
2594
  event
2596
- }) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
2595
+ }) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiEventQueueSubject$ && !this.apiEventQueueSubject$.closed)).subscribe(payload => {
2597
2596
  this.apiRequest(payload);
2598
2597
  });
2599
2598
  }
@@ -2620,6 +2619,29 @@ class FormField {
2620
2619
  event: 'ON_PROPS'
2621
2620
  });
2622
2621
  }
2622
+ /**
2623
+ * Static function to remove templates form the component props that will be shown when
2624
+ * the field mounts and the template routine executes, to be used on the adapter
2625
+ *
2626
+ * @param {unknown} props - the properties from the adapter components.
2627
+ */
2628
+ static filterProps(props) {
2629
+ if (Array.isArray(props)) {
2630
+ return props.filter(el => typeof el === 'string' && el.includes('${') ? false : true).map(el => FormField.filterProps(el));
2631
+ }
2632
+ if (typeof props === 'object' && props !== null) {
2633
+ if (props instanceof Date) return props;
2634
+ return Object.keys(props).reduce((acc, curr) => {
2635
+ const propValue = props[curr];
2636
+ if (typeof propValue === 'string' && propValue.includes('${')) {
2637
+ return acc;
2638
+ }
2639
+ acc[curr] = FormField.filterProps(props[curr]);
2640
+ return acc;
2641
+ }, {});
2642
+ }
2643
+ return props;
2644
+ }
2623
2645
  /**
2624
2646
  * Retrieves the current state value of the form field.
2625
2647
  *
@@ -2711,12 +2733,9 @@ class FormField {
2711
2733
  * sets valid field state and notifies form instance via formValidNotification$
2712
2734
  */
2713
2735
  set valid(valid) {
2714
- if (this._valid !== valid) {
2715
- this._valid = valid;
2716
- this.formValidNotification$.next({
2717
- fieldTrigger: this.name
2718
- });
2719
- }
2736
+ if (typeof valid !== 'boolean' && this.valid === valid) return;
2737
+ this._valid = valid;
2738
+ this.triggerFieldValidNotification();
2720
2739
  }
2721
2740
  /**
2722
2741
  * Retrieves the validity status of the form field.
@@ -2726,6 +2745,18 @@ class FormField {
2726
2745
  get valid() {
2727
2746
  return this._valid;
2728
2747
  }
2748
+ /**
2749
+ * triggers field valid notification to handle the form instance valid notification
2750
+ *
2751
+ * Note: since form unmount can occur before field unmount, this subject might already be closed by form instance
2752
+ * quick workaround is to check if the subject is already closed before emitting
2753
+ * if form instance onValid or template form.valid doesn't work properly, might be due to this workaround
2754
+ */
2755
+ triggerFieldValidNotification() {
2756
+ !this.fieldValidNotification$.closed && this.fieldValidNotification$.next({
2757
+ fieldTrigger: this.name
2758
+ });
2759
+ }
2729
2760
  /**
2730
2761
  * Retrieves the error messages associated with the form field.
2731
2762
  *
@@ -2771,19 +2802,54 @@ class FormField {
2771
2802
  * @param {TApiResponse} response - The new API response data to be set.
2772
2803
  */
2773
2804
  set api(response) {
2774
- if (typeof response === 'undefined' || isEqual(response, this.api)) return;
2805
+ if (typeof response === 'undefined') return;
2775
2806
  this._api = response;
2776
- this.apiSubject$.next(this.api);
2777
2807
  this.templateSubject$.next({
2778
2808
  scope: 'fields',
2779
2809
  key: this.name,
2780
- event: 'ON_API'
2810
+ event: 'ON_API_RESPONSE'
2781
2811
  });
2782
2812
  // this.apiResponseSubject$.next({ key: this.name });
2783
2813
  this.emitEvents({
2784
2814
  event: 'ON_API_FIELD_RESPONSE'
2785
2815
  });
2786
2816
  }
2817
+ /**
2818
+ * notifies templates and event binded field configurations that a request starts it's processing
2819
+ */
2820
+ notifyApiRequest() {
2821
+ this._api.apiState.loading = true;
2822
+ this.templateSubject$.next({
2823
+ scope: 'fields',
2824
+ key: this.name,
2825
+ event: 'ON_API_REQUEST'
2826
+ });
2827
+ this.emitEvents({
2828
+ event: 'ON_API_FIELD_REQUEST'
2829
+ });
2830
+ }
2831
+ /** Retrieves the mounted status of the field.
2832
+ *
2833
+ * @returns {boolean} - the mounted status of the field.
2834
+ */
2835
+ get mounted() {
2836
+ return this._mounted;
2837
+ }
2838
+ /**
2839
+ * sets the mountedStatus and notifies the form that the field was mounted on the adapter
2840
+ * and it's ready to be handled by the form instance
2841
+ *
2842
+ * @param {boolean} mountedStatus - the mounted status to be set from the mountField function.
2843
+ */
2844
+ set mounted(mountedStatus) {
2845
+ if (typeof mountedStatus === 'undefined' || mountedStatus === this.mounted) return;
2846
+ this._mounted = mountedStatus;
2847
+ this.initializeObservers();
2848
+ this.mountSubject$.next({
2849
+ key: this.name,
2850
+ status: this.mounted
2851
+ });
2852
+ }
2787
2853
  /**
2788
2854
  * Mounts the form field by initializing necessary subjects and combining their streams.
2789
2855
  *
@@ -2796,10 +2862,15 @@ class FormField {
2796
2862
  valueSubscription,
2797
2863
  propsSubscription
2798
2864
  }) {
2799
- this.initializeObservers();
2865
+ this.mounted = true;
2800
2866
  this.subscribeValue(valueSubscription);
2801
2867
  this.subscribeState(propsSubscription);
2802
- this._mounted = true;
2868
+ this.valueSubject$.next(this.stateValue);
2869
+ this.propsSubject$.next(this.props);
2870
+ this.visibilitySubject$.next(this.visibility);
2871
+ this.setFieldValidity({
2872
+ event: 'ON_FIELD_MOUNT'
2873
+ });
2803
2874
  }
2804
2875
  /**
2805
2876
  * Sets the value of the form field and emits associated events.
@@ -2809,13 +2880,14 @@ class FormField {
2809
2880
  * @returns {void}
2810
2881
  */
2811
2882
  emitValue(prop) {
2812
- if (!this.visibility) return;
2883
+ if (!this.visibility || !this.mounted) return;
2813
2884
  this.value = prop.value;
2814
- this.emitEvents({
2815
- event: prop.event
2816
- });
2817
2885
  this.dataSubject$.next({
2818
- key: this.name,
2886
+ event: prop.event,
2887
+ fieldIndex: this.name,
2888
+ formIndex: this.formIndex
2889
+ });
2890
+ this.emitEvents({
2819
2891
  event: prop.event
2820
2892
  });
2821
2893
  }
@@ -2831,13 +2903,13 @@ class FormField {
2831
2903
  if (event === 'ON_FORM_SUBMIT') {
2832
2904
  return this.submitEvent();
2833
2905
  }
2834
- this.setFieldValidity({
2835
- event
2836
- });
2837
2906
  this.validateVisibility({
2838
2907
  event,
2839
2908
  key: this.name
2840
2909
  });
2910
+ this.setFieldValidity({
2911
+ event
2912
+ });
2841
2913
  this.resetValue({
2842
2914
  event,
2843
2915
  key: this.name
@@ -2874,16 +2946,17 @@ class FormField {
2874
2946
  const errors = {};
2875
2947
  const schemaValidations = (_a = this.validations) === null || _a === void 0 ? void 0 : _a.methods;
2876
2948
  schemaValidations && Object.keys(schemaValidations).forEach(validationKey => {
2949
+ var _a;
2877
2950
  const error = handleValidation(this.value, schemaValidations, validations, validationKey);
2878
2951
  // setting valid flag
2879
2952
  valid = !error && valid;
2880
2953
  // setting error messages
2881
- if (error && this.errorMessages) {
2882
- if (validationKey in this.errorMessages) {
2883
- const messages = this.errorMessages;
2954
+ if (error && ((_a = this.validations) === null || _a === void 0 ? void 0 : _a.messages)) {
2955
+ if (validationKey in this.validations.messages) {
2956
+ const messages = this.validations.messages;
2884
2957
  errors[validationKey] = messages[validationKey];
2885
- } else if ('default' in this.errorMessages) {
2886
- errors[validationKey] = this.errorMessages.default;
2958
+ } else if ('default' in this.validations.messages) {
2959
+ errors[validationKey] = this.validations.messages.default;
2887
2960
  }
2888
2961
  } else {
2889
2962
  delete errors[validationKey];
@@ -2953,6 +3026,7 @@ class FormField {
2953
3026
  }) {
2954
3027
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2955
3028
  return __awaiter(this, void 0, void 0, function* () {
3029
+ let requestMadeOnce = false;
2956
3030
  const configRequest = config => __awaiter(this, void 0, void 0, function* () {
2957
3031
  var _k;
2958
3032
  try {
@@ -2978,20 +3052,23 @@ class FormField {
2978
3052
  };
2979
3053
  }
2980
3054
  });
2981
- if (!((_b = (_a = this.apiSchema) === null || _a === void 0 ? void 0 : _a.defaultConfig) === null || _b === void 0 ? void 0 : _b.events.includes(event)) && !(((_c = this.apiSchema) === null || _c === void 0 ? void 0 : _c.configs) && Object.keys((_d = this.apiSchema) === null || _d === void 0 ? void 0 : _d.configs).some(key => {
3055
+ if (this.api.apiState.lastEvent === 'ON_API_FIELD_RESPONSE' && event === 'ON_API_FIELD_RESPONSE' || !((_b = (_a = this.apiSchema) === null || _a === void 0 ? void 0 : _a.defaultConfig) === null || _b === void 0 ? void 0 : _b.events.includes(event)) && !(((_c = this.apiSchema) === null || _c === void 0 ? void 0 : _c.configs) && Object.keys((_d = this.apiSchema) === null || _d === void 0 ? void 0 : _d.configs).some(key => {
2982
3056
  var _a, _b;
2983
3057
  return (_b = (_a = this.apiSchema) === null || _a === void 0 ? void 0 : _a.configs) === null || _b === void 0 ? void 0 : _b[key].events.includes(event);
2984
3058
  }))) return;
2985
3059
  const responses = {
2986
3060
  default: Object.assign({}, this.api.default),
2987
- named: Object.assign({}, this.api.named)
3061
+ named: Object.assign({}, this.api.named),
3062
+ apiState: Object.assign({}, this.api.apiState)
2988
3063
  };
2989
3064
  const config = (_e = this.apiSchema.defaultConfig) === null || _e === void 0 ? void 0 : _e.config;
2990
3065
  if (config && ((_g = (_f = this.apiSchema) === null || _f === void 0 ? void 0 : _f.defaultConfig) === null || _g === void 0 ? void 0 : _g.events.includes(event)) && this.checkApiRequestValidations(config)) {
3066
+ !requestMadeOnce && this.notifyApiRequest();
2991
3067
  const {
2992
3068
  response,
2993
3069
  status
2994
3070
  } = yield configRequest(config);
3071
+ requestMadeOnce = true;
2995
3072
  responses.default = {
2996
3073
  response,
2997
3074
  status
@@ -3009,10 +3086,12 @@ class FormField {
3009
3086
  var _l, _m, _o, _p;
3010
3087
  const config = (_m = (_l = this.apiSchema) === null || _l === void 0 ? void 0 : _l.configs) === null || _m === void 0 ? void 0 : _m[configKey].config;
3011
3088
  if (config && ((_p = (_o = this.apiSchema) === null || _o === void 0 ? void 0 : _o.configs) === null || _p === void 0 ? void 0 : _p[configKey].events.includes(event)) && this.checkApiRequestValidations(config)) {
3089
+ !requestMadeOnce && this.notifyApiRequest();
3012
3090
  const {
3013
3091
  response,
3014
3092
  status
3015
3093
  } = yield configRequest(config);
3094
+ requestMadeOnce = true;
3016
3095
  return {
3017
3096
  name: configKey,
3018
3097
  result: {
@@ -3028,7 +3107,11 @@ class FormField {
3028
3107
  });
3029
3108
  }
3030
3109
  }
3031
- this.api = responses;
3110
+ if (requestMadeOnce) {
3111
+ responses.apiState.lastEvent = event;
3112
+ responses.apiState.loading = false;
3113
+ this.api = responses;
3114
+ }
3032
3115
  });
3033
3116
  }
3034
3117
  /**
@@ -3037,16 +3120,23 @@ class FormField {
3037
3120
  * @returns {void}
3038
3121
  */
3039
3122
  destroyField() {
3040
- this._mounted = false;
3123
+ this.mounted = false;
3041
3124
  this.valueSubscription$.unsubscribe();
3042
3125
  this.visibilitySubject$.unsubscribe();
3043
3126
  this.fieldStateSubscription$.unsubscribe();
3044
3127
  this.propsSubject$.unsubscribe();
3045
3128
  this.errorSubject$.unsubscribe();
3046
- this.apiSubject$.unsubscribe();
3047
3129
  this.apiEventQueueSubject$.unsubscribe();
3048
- !this.formValidNotification$.closed && this.formValidNotification$.next({
3049
- fieldTrigger: this.name
3130
+ this.triggerFieldValidNotification();
3131
+ this.dataSubject$.next({
3132
+ event: 'ON_FIELD_UNMOUNT',
3133
+ fieldIndex: this.name,
3134
+ formIndex: this.formIndex
3135
+ });
3136
+ !this.fieldEventSubject$.closed && this.fieldEventSubject$.next({
3137
+ event: 'ON_FIELD_UNMOUNT',
3138
+ fieldName: this.name,
3139
+ fieldInstance: this
3050
3140
  });
3051
3141
  }
3052
3142
  /**
@@ -3056,7 +3146,14 @@ class FormField {
3056
3146
  * @returns {void}
3057
3147
  */
3058
3148
  subscribeState(callback) {
3059
- this.fieldStateSubscription$ = this.fieldState$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS)).subscribe({
3149
+ var _a;
3150
+ this.fieldStateSubscription$ = combineLatest({
3151
+ visibility: this.visibilitySubject$.pipe(startWith(this.visibility)),
3152
+ props: this.propsSubject$.pipe(startWith(this.props)),
3153
+ errors: this.errorSubject$.pipe(startWith(Object.assign({}, ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && {
3154
+ [this.mapper.events.setErrorMessage]: this.errorsString
3155
+ })))
3156
+ }).pipe(debounceTime(this.config.defaultStateRefreshTimeMS)).subscribe({
3060
3157
  next: callback
3061
3158
  });
3062
3159
  }
@@ -3096,61 +3193,100 @@ class FormCore {
3096
3193
  this.queuedFieldResetValuesEvents = new Map();
3097
3194
  this.queuedFieldResetPropertyEvents = new Map();
3098
3195
  this.queuedInitialValues = new Map();
3196
+ this._valid = false;
3197
+ this.index = entry.index;
3099
3198
  this.schema = entry.schema;
3100
3199
  this.fields = new Map();
3101
3200
  this.action = entry.action || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.action);
3102
3201
  this.method = entry.method || ((_b = entry.schema) === null || _b === void 0 ? void 0 : _b.method);
3103
- this._iVars = entry.iVars || ((_c = entry.schema) === null || _c === void 0 ? void 0 : _c.iVars) || {};
3104
3202
  this.config = {
3105
- defaultAPIdebounceTimeMS: Number((_d = entry.config) === null || _d === void 0 ? void 0 : _d.defaultAPIdebounceTimeMS) ? Number((_e = entry.config) === null || _e === void 0 ? void 0 : _e.defaultAPIdebounceTimeMS) : DEFAULT_API_DEBOUNCE_TIME,
3106
- defaultStateRefreshTimeMS: Number((_f = entry.config) === null || _f === void 0 ? void 0 : _f.defaultStateRefreshTimeMS) ? Number((_g = entry.config) === null || _g === void 0 ? void 0 : _g.defaultStateRefreshTimeMS) : DEFAULT_STATE_REFRESH_TIME,
3107
- defaultLogVerbose: ((_h = entry.config) === null || _h === void 0 ? void 0 : _h.defaultLogVerbose) ? entry.config.defaultLogVerbose : DEFAULT_LOG_VERBOSE
3203
+ defaultAPIdebounceTimeMS: Number((_c = entry.config) === null || _c === void 0 ? void 0 : _c.defaultAPIdebounceTimeMS) ? Number((_d = entry.config) === null || _d === void 0 ? void 0 : _d.defaultAPIdebounceTimeMS) : DEFAULT_API_DEBOUNCE_TIME,
3204
+ defaultStateRefreshTimeMS: Number((_e = entry.config) === null || _e === void 0 ? void 0 : _e.defaultStateRefreshTimeMS) ? Number((_f = entry.config) === null || _f === void 0 ? void 0 : _f.defaultStateRefreshTimeMS) : DEFAULT_STATE_REFRESH_TIME,
3205
+ defaultLogVerbose: ((_g = entry.config) === null || _g === void 0 ? void 0 : _g.defaultLogVerbose) ? entry.config.defaultLogVerbose : DEFAULT_LOG_VERBOSE
3108
3206
  };
3109
- (_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
3207
+ (_h = entry.mappers) === null || _h === void 0 ? void 0 : _h.map(mapper => {
3110
3208
  this.mappers.set(mapper.componentName, mapper);
3111
3209
  });
3210
+ if ((!entry.submitSubject$ || !entry.dataSubject$ || !entry.formValidSubject$) && this.config.defaultLogVerbose) console.warn(`some formGroup events are not properly instanciated, any onData, onValid, onSubmit events managed by formGroup won't trigger on form: ${this.index}`);
3112
3211
  this.schema && FormCore.checkIndexes(this.schema.components);
3113
3212
  this.templateSubject$ = new Subject();
3114
- this.submitSubject$ = new Subject();
3115
3213
  this.fieldEventSubject$ = new Subject();
3116
- this.dataSubject$ = new Subject();
3117
3214
  this.mountSubject$ = new Subject();
3118
- this.formValidNotification$ = new Subject();
3215
+ this.fieldValidNotification$ = new Subject();
3216
+ this.submitSubject$ = entry.submitSubject$ ? entry.submitSubject$ : new Subject();
3217
+ this.dataSubject$ = entry.dataSubject$ ? entry.dataSubject$ : new Subject();
3218
+ this.formValidSubject$ = entry.formValidSubject$ ? entry.formValidSubject$ : new Subject();
3119
3219
  this.subscribedTemplates = [];
3120
- this.schema && this.serializeStructure(this.schema.components);
3121
- this.schema && this.subscribeTemplates();
3122
3220
  this.templateSubscription$ = this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
3123
- this.templateSubject$.next({
3124
- scope: 'iVars',
3125
- event: 'ON_IVARS'
3221
+ this.fieldValidNotification$.subscribe(() => {
3222
+ this.validateForm();
3126
3223
  });
3127
- /*
3128
- only emits event ON_FIELD_MOUNT if does not have initialValue, if has initialValue, initialValues class property setter will
3129
- emit the value along with ON_FIELD_MOUNT event
3130
- */
3131
- const initialValues = entry.initialValues || ((_k = entry.schema) === null || _k === void 0 ? void 0 : _k.initialValues);
3132
- this.fields.forEach((field, key) => {
3133
- var _a;
3134
- if (!initialValues || initialValues && !Object.keys(initialValues).includes(key)) {
3135
- const propValue = (_a = field === null || field === void 0 ? void 0 : field.props) === null || _a === void 0 ? void 0 : _a[(field === null || field === void 0 ? void 0 : field.valuePropName) || ''];
3136
- !(field === null || field === void 0 ? void 0 : field.value) && (field === null || field === void 0 ? void 0 : field.emitValue({
3137
- value: typeof propValue === 'string' && !propValue.includes('${') ? propValue : '',
3138
- event: 'ON_FIELD_MOUNT'
3139
- }));
3224
+ this.mountSubject$.subscribe(this.mountActions.bind(this));
3225
+ this.initialValues = entry.initialValues || ((_j = entry.schema) === null || _j === void 0 ? void 0 : _j.initialValues);
3226
+ this.iVars = entry.iVars || ((_k = entry.schema) === null || _k === void 0 ? void 0 : _k.iVars) || {};
3227
+ }
3228
+ /**
3229
+ * mock function to simulate form mount onto the adapter
3230
+ */
3231
+ generateFields() {
3232
+ this.schema && this.serializeStructure(this.schema.components);
3233
+ this.fields.forEach(field => {
3234
+ field.mountField({
3235
+ valueSubscription: () => null,
3236
+ propsSubscription: () => null
3237
+ });
3238
+ });
3239
+ }
3240
+ /**
3241
+ * callback function passed to field instance to notify field adapter mount status
3242
+ * once the field has all field instance properties set, this function will handle all
3243
+ * field routines
3244
+ *
3245
+ * @param { string } entry.key field unique identifier
3246
+ * @param { boolean } entry.status mount status notified from field
3247
+ */
3248
+ mountActions({
3249
+ key,
3250
+ status
3251
+ }) {
3252
+ if (status) {
3253
+ const field = this.fields.get(key);
3254
+ if (!field) {
3255
+ /*
3256
+ @TODO check a better way to handle nested fields unmounted by visiblity conditions from a parent
3257
+ since they are dependent on adapter field recycling runtimes
3258
+ */
3259
+ this.config.defaultLogVerbose && console.warn(`field ${key} was mounted but since it's parent has some visibility condition the field was already removed`);
3260
+ return;
3140
3261
  }
3262
+ this.subscribeTemplates();
3141
3263
  this.refreshTemplates({
3142
3264
  scope: 'fields',
3143
- key,
3144
3265
  event: 'ON_FIELDS'
3145
3266
  });
3146
- });
3147
- this.setInitialValues(initialValues);
3267
+ this.templateSubject$.next({
3268
+ scope: 'iVars',
3269
+ event: 'ON_IVARS'
3270
+ });
3271
+ if (!this.queuedInitialValues.has(key)) field.valuePropName && !field.value ? field.emitValue({
3272
+ event: 'ON_FIELD_MOUNT',
3273
+ value: ''
3274
+ }) : field.emitEvents({
3275
+ event: 'ON_FIELD_MOUNT'
3276
+ });
3277
+ this.checkFieldEventQueues(key);
3278
+ }
3148
3279
  }
3149
- setInitialValues(payload) {
3280
+ /**
3281
+ * initialValues setter to handle field values set externally from the adapter
3282
+ *
3283
+ * @param { Record<string, unknown> | undefined } payload initialValues to set onto fields
3284
+ */
3285
+ set initialValues(payload) {
3150
3286
  if (payload) {
3151
3287
  Object.keys(payload).forEach(key => {
3152
3288
  const field = this.fields.get(key);
3153
- if (!field || !(field === null || field === void 0 ? void 0 : field.visibility)) {
3289
+ if (!field || !(field === null || field === void 0 ? void 0 : field.visibility) || !(field === null || field === void 0 ? void 0 : field.mounted)) {
3154
3290
  this.queuedInitialValues.set(key, payload === null || payload === void 0 ? void 0 : payload[key]);
3155
3291
  } else {
3156
3292
  field.emitValue({
@@ -3182,15 +3318,30 @@ class FormCore {
3182
3318
  });
3183
3319
  }
3184
3320
  /**
3185
- * Checks if the form is valid by validating all form fields.
3321
+ * Validates all form fields and sets the form valid flag
3186
3322
  *
3187
- * @returns {boolean} True if the form is valid; otherwise, false.
3188
3323
  */
3189
- get isValid() {
3324
+ validateForm() {
3325
+ if (this.fields.size === 0) return this.valid = false;
3190
3326
  for (const [, field] of this.fields) {
3191
- if (!field.valid) return false;
3327
+ if (!field.valid) return this.valid = false;
3192
3328
  }
3193
- return true;
3329
+ return this.valid = true;
3330
+ }
3331
+ get valid() {
3332
+ return this._valid;
3333
+ }
3334
+ set valid(valid) {
3335
+ if (this._valid === valid) return;
3336
+ this._valid = valid;
3337
+ this.templateSubject$.next({
3338
+ event: 'ON_FORM',
3339
+ scope: 'form'
3340
+ });
3341
+ this.formValidSubject$.next({
3342
+ formIndex: this.index,
3343
+ valid: this.valid
3344
+ });
3194
3345
  }
3195
3346
  /**
3196
3347
  * Subscribes to templates for dynamic updates.
@@ -3243,6 +3394,11 @@ class FormCore {
3243
3394
  const value = get(this.iVars, [key, ...(property ? [property] : []), ...path]);
3244
3395
  return value;
3245
3396
  }
3397
+ case 'form':
3398
+ {
3399
+ const value = get(this, [key, ...(property ? [property] : []), ...path]);
3400
+ return value;
3401
+ }
3246
3402
  case 'fields':
3247
3403
  {
3248
3404
  const field = this.fields.get(key);
@@ -3293,10 +3449,8 @@ class FormCore {
3293
3449
  }
3294
3450
  const fieldProp = field[property];
3295
3451
  let propState;
3296
- if (Array.isArray(fieldProp)) {
3297
- propState = [...fieldProp];
3298
- } else if (typeof fieldProp === 'object' && !isNil(fieldProp)) {
3299
- propState = Object.assign({}, fieldProp);
3452
+ if (Array.isArray(fieldProp) || typeof fieldProp === 'object' && !isNil(fieldProp)) {
3453
+ propState = cloneDeep(fieldProp);
3300
3454
  } else {
3301
3455
  this.config.defaultLogVerbose && console.warn(`invalid template property, skipping evaluation of ${field.name} with ${fieldProp}`);
3302
3456
  return;
@@ -3466,6 +3620,8 @@ class FormCore {
3466
3620
  * @param {string} field field to check
3467
3621
  */
3468
3622
  checkFieldEventQueues(field) {
3623
+ var _a;
3624
+ if (!((_a = this.fields.get(field)) === null || _a === void 0 ? void 0 : _a.mounted)) return;
3469
3625
  if (this.queuedFieldVisibilityEvents.has(field)) {
3470
3626
  this.setFieldVisibility(Object.assign({
3471
3627
  field: field
@@ -3473,10 +3629,11 @@ class FormCore {
3473
3629
  this.queuedFieldVisibilityEvents.delete(field);
3474
3630
  }
3475
3631
  if (this.queuedInitialValues.has(field)) {
3476
- this.setInitialValues({
3477
- [field]: this.queuedInitialValues.get(field)
3478
- });
3632
+ const value = this.queuedInitialValues.get(field);
3479
3633
  this.queuedInitialValues.delete(field);
3634
+ this.initialValues = {
3635
+ [field]: value
3636
+ };
3480
3637
  }
3481
3638
  if (this.queuedFieldResetValuesEvents.has(field)) {
3482
3639
  this.setResetFieldValue(Object.assign({
@@ -3506,14 +3663,16 @@ class FormCore {
3506
3663
  showOnlyIfTrue
3507
3664
  }) {
3508
3665
  const fieldInstance = this.fields.get(field);
3509
- if (!fieldInstance) {
3666
+ if (!fieldInstance || !fieldInstance.mounted) {
3510
3667
  this.queuedFieldVisibilityEvents.set(field, {
3511
3668
  hasError,
3512
3669
  showOnlyIfTrue
3513
3670
  });
3514
3671
  } else {
3672
+ const currentVisibility = fieldInstance.visibility;
3515
3673
  const visibility = showOnlyIfTrue ? hasError : !hasError;
3516
3674
  fieldInstance.visibility = visibility;
3675
+ if (currentVisibility === visibility) return;
3517
3676
  /**
3518
3677
  * I was sure I would not require to gambiarra, but..
3519
3678
  * in order to ignore an hidden value on a form submit
@@ -3524,11 +3683,17 @@ class FormCore {
3524
3683
  * and trigger the validation when it's visible
3525
3684
  */
3526
3685
  if (fieldInstance.visibility) {
3527
- fieldInstance === null || fieldInstance === void 0 ? void 0 : fieldInstance.emitValue({
3528
- value: this.queuedInitialValues.get(field) || '',
3529
- event: 'ON_FIELD_MOUNT'
3530
- });
3531
- this.queuedInitialValues.delete(field);
3686
+ if (this.queuedInitialValues.has(field)) {
3687
+ fieldInstance === null || fieldInstance === void 0 ? void 0 : fieldInstance.emitValue({
3688
+ value: this.queuedInitialValues.get(field) || '',
3689
+ event: 'ON_FIELD_MOUNT'
3690
+ });
3691
+ this.queuedInitialValues.delete(field);
3692
+ } else {
3693
+ fieldInstance.emitEvents({
3694
+ event: 'ON_FIELD_MOUNT'
3695
+ });
3696
+ }
3532
3697
  } else {
3533
3698
  fieldInstance.value = '';
3534
3699
  fieldInstance.valid = true;
@@ -3585,12 +3750,12 @@ class FormCore {
3585
3750
  key,
3586
3751
  value
3587
3752
  }) {
3588
- if (!this.fields.has(key)) {
3753
+ const field = this.fields.get(key);
3754
+ if (!field || !(field === null || field === void 0 ? void 0 : field.mounted)) {
3589
3755
  this.queuedFieldResetValuesEvents.set(key, {
3590
3756
  value
3591
3757
  });
3592
3758
  } else {
3593
- const field = this.fields.get(key);
3594
3759
  field.emitValue({
3595
3760
  value: value,
3596
3761
  event: 'ON_FIELD_CLEARED'
@@ -3656,7 +3821,8 @@ class FormCore {
3656
3821
  path,
3657
3822
  value
3658
3823
  }) {
3659
- if (!this.fields.has(key)) {
3824
+ const field = this.fields.get(key);
3825
+ if (!field) {
3660
3826
  this.queuedFieldResetPropertyEvents.set(key, {
3661
3827
  property,
3662
3828
  path,
@@ -3720,13 +3886,20 @@ class FormCore {
3720
3886
  fieldSchema,
3721
3887
  mapperElement
3722
3888
  }) {
3723
- var _a, _b;
3889
+ var _a, _b, _c, _d;
3724
3890
  if (this.fields.has(fieldSchema.name)) {
3725
3891
  throw new Error(`field name ${fieldSchema.name} already defined`);
3726
3892
  }
3727
3893
  const mapper = mapperElement || ((_a = this.mappers) === null || _a === void 0 ? void 0 : _a.get(fieldSchema.component));
3728
3894
  if (!mapper) throw new Error(`mapper not found for ${fieldSchema.component}, add it to the mappers configuration`);
3895
+ if ((_b = mapper.events) === null || _b === void 0 ? void 0 : _b.setValue) {
3896
+ const initialValue = (_c = fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.props) === null || _c === void 0 ? void 0 : _c[(_d = mapper === null || mapper === void 0 ? void 0 : mapper.events) === null || _d === void 0 ? void 0 : _d.setValue];
3897
+ if (!(typeof initialValue === 'undefined') && !this.queuedInitialValues.has(fieldSchema.name) && !(typeof initialValue === 'string' && initialValue.includes('${'))) {
3898
+ this.queuedInitialValues.set(fieldSchema.name, cloneDeep(initialValue));
3899
+ }
3900
+ }
3729
3901
  this.fields.set(fieldSchema.name, new FormField({
3902
+ formIndex: this.index,
3730
3903
  schemaComponent: fieldSchema,
3731
3904
  mapper,
3732
3905
  children: fieldSchema.children ? fieldSchema.children.map(el => el.name) : [],
@@ -3737,25 +3910,18 @@ class FormCore {
3737
3910
  templateSubject$: this.templateSubject$,
3738
3911
  fieldEventSubject$: this.fieldEventSubject$,
3739
3912
  dataSubject$: this.dataSubject$,
3740
- formValidNotification$: this.formValidNotification$,
3913
+ fieldValidNotification$: this.fieldValidNotification$,
3914
+ mountSubject$: this.mountSubject$,
3741
3915
  config: this.config,
3742
- submitEvent: this.submit.bind(this)
3916
+ submitEvent: this.submit.bind(this),
3917
+ visibility: fieldSchema.visibility
3743
3918
  }));
3744
- this.subscribeTemplates();
3745
- this.refreshTemplates({
3746
- scope: 'fields',
3747
- event: 'ON_FIELDS'
3748
- });
3749
- if (!this.queuedInitialValues.has(fieldSchema.name)) {
3750
- const field = this.fields.get(fieldSchema.name);
3751
- const propValue = (_b = field === null || field === void 0 ? void 0 : field.props) === null || _b === void 0 ? void 0 : _b[(field === null || field === void 0 ? void 0 : field.valuePropName) || ''];
3752
- !(field === null || field === void 0 ? void 0 : field.value) && (field === null || field === void 0 ? void 0 : field.emitValue({
3753
- value: typeof propValue === 'string' && !propValue.includes('${') ? propValue : '',
3754
- event: 'ON_FIELD_MOUNT'
3755
- }));
3756
- }
3757
- this.checkFieldEventQueues(fieldSchema.name);
3758
3919
  }
3920
+ /**
3921
+ * function to be called from the adapter to remove a field when a field is removed from it
3922
+ *
3923
+ * @param {{ key: string }} entry.key
3924
+ */
3759
3925
  removeField({
3760
3926
  key
3761
3927
  }) {
@@ -3789,6 +3955,7 @@ class FormCore {
3789
3955
  }
3790
3956
  if (!mapper) throw new Error(`mapper not found for ${structElement.component}, add it to the mappers configuration`);
3791
3957
  this.fields.set(structElement.name, new FormField({
3958
+ formIndex: this.index,
3792
3959
  schemaComponent: structElement,
3793
3960
  mapper,
3794
3961
  path,
@@ -3799,10 +3966,12 @@ class FormCore {
3799
3966
  templateSubject$: this.templateSubject$,
3800
3967
  fieldEventSubject$: this.fieldEventSubject$,
3801
3968
  dataSubject$: this.dataSubject$,
3802
- formValidNotification$: this.formValidNotification$,
3969
+ fieldValidNotification$: this.fieldValidNotification$,
3970
+ mountSubject$: this.mountSubject$,
3803
3971
  config: this.config,
3804
3972
  getFormValues: this.getFormValues.bind(this),
3805
- submitEvent: this.submit.bind(this)
3973
+ submitEvent: this.submit.bind(this),
3974
+ visibility: structElement.visibility
3806
3975
  }));
3807
3976
  } else {
3808
3977
  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) || [];
@@ -3867,13 +4036,7 @@ class FormCore {
3867
4036
  * Prints the current values of all form fields.
3868
4037
  */
3869
4038
  printValues() {
3870
- const values = {};
3871
- this.fields.forEach((val, key) => {
3872
- if (val.value) {
3873
- set(values, val.nameToSubmit || key, val.value);
3874
- }
3875
- });
3876
- console.table(values);
4039
+ console.table(this.getFormValues().values);
3877
4040
  }
3878
4041
  /**
3879
4042
  * Gets the current values of all form fields.
@@ -3882,10 +4045,12 @@ class FormCore {
3882
4045
  */
3883
4046
  getFormValues() {
3884
4047
  const values = {};
4048
+ const metadata = {};
3885
4049
  const erroredFields = [];
3886
4050
  this.fields.forEach((val, key) => {
3887
- if (val.value) {
4051
+ if (typeof val.value === 'string' && val.value.length > 0) {
3888
4052
  set(values, val.nameToSubmit || key, val.value);
4053
+ metadata[key] = val.metadata;
3889
4054
  }
3890
4055
  if (!val.valid) {
3891
4056
  erroredFields.push(key);
@@ -3893,10 +4058,17 @@ class FormCore {
3893
4058
  });
3894
4059
  return {
3895
4060
  values,
4061
+ metadata,
3896
4062
  erroredFields,
3897
- isValid: this.isValid
4063
+ isValid: this.valid
3898
4064
  };
3899
4065
  }
4066
+ /**
4067
+ * function to be called to events sent from the adapter
4068
+ *
4069
+ * @param {{callback: (payload: TFieldEvent) => void}} entry.callback callback function from the adapter
4070
+ * @returns
4071
+ */
3900
4072
  subscribeFieldEvent({
3901
4073
  callback
3902
4074
  }) {
@@ -3905,6 +4077,12 @@ class FormCore {
3905
4077
  });
3906
4078
  return sub;
3907
4079
  }
4080
+ /**
4081
+ * to be called from the adapter when the form mounts
4082
+ *
4083
+ * @param {(payload: TFormValues<T>) => void} callback
4084
+ * @returns Subscription
4085
+ */
3908
4086
  subscribeOnMount(callback) {
3909
4087
  const sub = this.mountSubject$.pipe(map(() => this.getFormValues())).subscribe({
3910
4088
  next: callback
@@ -3916,49 +4094,49 @@ class FormCore {
3916
4094
  * @param {(payload: { field: string; data: TFormValues }) => void} callback callback function to call onData
3917
4095
  */
3918
4096
  subscribeData(callback) {
3919
- const sub = this.dataSubject$.pipe(groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), map(({
3920
- key
4097
+ const sub = this.dataSubject$.pipe(filter(({
4098
+ formIndex
4099
+ }) => this.index === formIndex), groupBy(payload => payload.event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), map(({
4100
+ fieldIndex
3921
4101
  }) => ({
3922
- field: key,
4102
+ field: fieldIndex,
3923
4103
  data: this.getFormValues()
3924
4104
  }))).subscribe({
3925
4105
  next: callback
3926
4106
  });
3927
4107
  return sub;
3928
4108
  }
4109
+ /**
4110
+ * method to register a callback function to be called when the form is valid
4111
+ *
4112
+ * @param {(payload: TFormValues<T>) => void} callback callback function to call when the submit action occurs
4113
+ */
3929
4114
  subscribeOnSubmit(callback) {
3930
- const sub = this.submitSubject$.pipe(map(() => this.getFormValues())).subscribe({
4115
+ const sub = this.submitSubject$.pipe(filter(({
4116
+ formIndex
4117
+ }) => formIndex === this.index), map(({
4118
+ values
4119
+ }) => values)).subscribe({
3931
4120
  next: callback
3932
4121
  });
3933
4122
  return sub;
3934
4123
  }
3935
4124
  /**
4125
+ * method to check whenever the validity status of the form changes, only emits on status change
3936
4126
  *
3937
4127
  * @param {(payload: TFormValidationPayload) => void} callback callback function to call onValid
3938
4128
  */
3939
4129
  subscribeFormValidation(callback) {
3940
- const sub = this.formValidNotification$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS), map(({
3941
- fieldTrigger
3942
- }) => ({
3943
- fieldTrigger,
3944
- valid: this.isValid
4130
+ const sub = this.formValidSubject$.pipe(filter(({
4131
+ formIndex
4132
+ }) => this.index === formIndex), debounceTime(this.config.defaultStateRefreshTimeMS), map(() => ({
4133
+ formIndex: this.index,
4134
+ valid: this.valid
3945
4135
  })), distinctUntilKeyChanged('valid')).subscribe({
3946
4136
  next: callback
3947
4137
  });
3948
4138
  return sub;
3949
4139
  }
3950
- /**
3951
- * Submits the form by triggering form field events and invoking the onSubmit callback.
3952
- */
3953
- mounted() {
3954
- this.fields.forEach(field => {
3955
- field.emitEvents({
3956
- event: 'ON_FORM_MOUNT'
3957
- });
3958
- });
3959
- const values = this.getFormValues();
3960
- this.mountSubject$.next(values);
3961
- }
3962
4140
  /**
3963
4141
  * Submits the form by triggering form field events and invoking the onSubmit callback.
3964
4142
  */
@@ -3968,16 +4146,20 @@ class FormCore {
3968
4146
  event: 'ON_FIELD_VALIDATION'
3969
4147
  });
3970
4148
  });
3971
- if (!this.isValid) return;
4149
+ if (!this.valid) return;
3972
4150
  const values = this.getFormValues();
3973
- this.submitSubject$.next(values);
4151
+ this.submitSubject$.next({
4152
+ formIndex: this.index,
4153
+ values
4154
+ });
3974
4155
  }
4156
+ /**
4157
+ * recycles all the Suscriptions, to be called from the adapter when the form leaves the page
4158
+ */
3975
4159
  destroy() {
3976
- this.submitSubject$.unsubscribe();
3977
4160
  this.templateSubscription$.unsubscribe();
3978
4161
  this.fieldEventSubject$.unsubscribe();
3979
- this.dataSubject$.unsubscribe();
3980
- this.formValidNotification$.unsubscribe();
4162
+ this.fieldValidNotification$.unsubscribe();
3981
4163
  this.fields.forEach(field => field.destroyField());
3982
4164
  }
3983
4165
  }
@@ -4020,6 +4202,9 @@ class FormGroup {
4020
4202
  var _a, _b, _c, _d, _e;
4021
4203
  this.destroy = () => {
4022
4204
  this.forms.forEach(form => form.destroy());
4205
+ this.dataSubject$.unsubscribe();
4206
+ this.formValidSubject$.unsubscribe();
4207
+ this.submitSubject$.unsubscribe();
4023
4208
  };
4024
4209
  this.forms = new Map();
4025
4210
  this.config = {
@@ -4027,6 +4212,9 @@ class FormGroup {
4027
4212
  defaultStateRefreshTimeMS: Number((_c = entry === null || entry === void 0 ? void 0 : entry.config) === null || _c === void 0 ? void 0 : _c.defaultStateRefreshTimeMS) ? Number((_d = entry === null || entry === void 0 ? void 0 : entry.config) === null || _d === void 0 ? void 0 : _d.defaultStateRefreshTimeMS) : DEFAULT_STATE_REFRESH_TIME,
4028
4213
  defaultLogVerbose: ((_e = entry === null || entry === void 0 ? void 0 : entry.config) === null || _e === void 0 ? void 0 : _e.defaultLogVerbose) ? entry.config.defaultLogVerbose : DEFAULT_LOG_VERBOSE
4029
4214
  };
4215
+ this.dataSubject$ = new Subject();
4216
+ this.formValidSubject$ = new Subject();
4217
+ this.submitSubject$ = new Subject();
4030
4218
  }
4031
4219
  /**
4032
4220
  * Creates an empty form with given index
@@ -4038,14 +4226,13 @@ class FormGroup {
4038
4226
  index,
4039
4227
  mappers
4040
4228
  }) {
4041
- const formInstance = new FormCore({
4042
- index,
4043
- mappers,
4044
- config: this.config
4045
- });
4046
4229
  this.addForm({
4047
4230
  key: index,
4048
- formInstance
4231
+ params: {
4232
+ index,
4233
+ mappers,
4234
+ config: this.config
4235
+ }
4049
4236
  });
4050
4237
  }
4051
4238
  /**
@@ -4057,11 +4244,16 @@ class FormGroup {
4057
4244
  */
4058
4245
  addForm({
4059
4246
  key,
4060
- formInstance
4247
+ params
4061
4248
  }) {
4062
4249
  this.checkIndexes({
4063
- key
4250
+ key: key
4064
4251
  });
4252
+ const formInstance = new FormCore(Object.assign(Object.assign({}, params), {
4253
+ dataSubject$: this.dataSubject$,
4254
+ formValidSubject$: this.formValidSubject$,
4255
+ submitSubject$: this.submitSubject$
4256
+ }));
4065
4257
  if (!formInstance.config) {
4066
4258
  formInstance.config = this.config;
4067
4259
  }
@@ -4103,9 +4295,15 @@ class FormGroup {
4103
4295
  formIndex,
4104
4296
  fieldIndex
4105
4297
  }) {
4106
- var _a, _b, _c;
4107
- (_b = (_a = this.forms.get(formIndex)) === null || _a === void 0 ? void 0 : _a.fields.get(fieldIndex)) === null || _b === void 0 ? void 0 : _b.destroyField();
4108
- (_c = this.forms.get(formIndex)) === null || _c === void 0 ? void 0 : _c.fields.delete(fieldIndex);
4298
+ var _a;
4299
+ const form = this.forms.get(formIndex);
4300
+ (_a = form === null || form === void 0 ? void 0 : form.fields.get(fieldIndex)) === null || _a === void 0 ? void 0 : _a.destroyField();
4301
+ form === null || form === void 0 ? void 0 : form.fields.delete(fieldIndex);
4302
+ if ((form === null || form === void 0 ? void 0 : form.fields.size) === 0) {
4303
+ this.removeForm({
4304
+ key: formIndex
4305
+ });
4306
+ }
4109
4307
  }
4110
4308
  /**
4111
4309
  * Checks if the specified key already exists in the form group.
@@ -4136,6 +4334,7 @@ class FormGroup {
4136
4334
  submitMultipleFormsByIndex(indexes, callback) {
4137
4335
  let isValid = true;
4138
4336
  let values = {};
4337
+ let metadata = {};
4139
4338
  let erroredFields = [];
4140
4339
  indexes.forEach(index => {
4141
4340
  var _a, _b;
@@ -4143,64 +4342,63 @@ class FormGroup {
4143
4342
  const res = (_b = this.forms.get(index)) === null || _b === void 0 ? void 0 : _b.getFormValues();
4144
4343
  isValid = isValid && ((res === null || res === void 0 ? void 0 : res.isValid) || false);
4145
4344
  values = Object.assign(Object.assign({}, values), (res === null || res === void 0 ? void 0 : res.values) || {});
4345
+ metadata = Object.assign(Object.assign({}, metadata), (res === null || res === void 0 ? void 0 : res.metadata) || {});
4146
4346
  erroredFields = [...erroredFields, ...((res === null || res === void 0 ? void 0 : res.erroredFields) || [])];
4147
4347
  });
4148
4348
  isValid && callback && callback({
4149
4349
  erroredFields,
4150
4350
  isValid,
4151
- values
4351
+ values,
4352
+ metadata
4152
4353
  });
4153
4354
  }
4154
4355
  onDataSubscription({
4155
4356
  ids,
4156
4357
  callback
4157
4358
  }) {
4158
- const subs = ids.reduce((acc, formId) => {
4159
- var _a, _b;
4160
- 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(this.config.defaultStateRefreshTimeMS))), map(({
4161
- key
4162
- }) => {
4163
- var _a;
4164
- return {
4165
- formField: key,
4166
- values: (_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.getFormValues()
4359
+ const sub = this.dataSubject$.pipe(filter(({
4360
+ formIndex
4361
+ }) => ids.includes(formIndex)), groupBy(({
4362
+ event,
4363
+ formIndex
4364
+ }) => `${event}.${formIndex}`), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), map(({
4365
+ fieldIndex,
4366
+ formIndex
4367
+ }) => ids.reduce((acc, curr) => {
4368
+ const formInstance = this.forms.get(curr);
4369
+ if (formInstance) {
4370
+ acc[curr] = {
4371
+ formId: formIndex,
4372
+ formField: fieldIndex,
4373
+ values: formInstance.getFormValues()
4167
4374
  };
4168
- }), startWith({
4169
- formField: null,
4170
- values: (_b = this.forms.get(formId)) === null || _b === void 0 ? void 0 : _b.getFormValues()
4171
- }));
4172
- if (sub) {
4173
- acc[formId] = sub;
4174
- } else {
4175
- this.config.defaultLogVerbose && console.warn(`failed to register form id ${formId}`);
4176
4375
  }
4177
4376
  return acc;
4178
- }, {});
4179
- const sub = combineLatest(subs).subscribe(callback);
4377
+ }, {}))).subscribe(callback);
4180
4378
  return sub;
4181
4379
  }
4182
4380
  onValidSubscription({
4183
4381
  ids,
4184
4382
  callback
4185
4383
  }) {
4186
- const subs = ids.reduce((acc, formId) => {
4187
- var _a;
4188
- const sub = (_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.formValidNotification$.pipe(groupBy(payload => `${formId}.${payload.fieldTrigger}`), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), startWith({
4189
- fieldTrigger: null
4190
- }), map(() => {
4384
+ const sub = this.formValidSubject$.pipe(filter(({
4385
+ formIndex
4386
+ }) => ids.includes(formIndex)), groupBy(({
4387
+ formIndex
4388
+ }) => formIndex), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS))), startWith({
4389
+ fieldTrigger: null
4390
+ }), map(() => ({
4391
+ groupValid: ids.every(id => {
4191
4392
  var _a;
4192
- return ((_a = this.forms.get(formId)) === null || _a === void 0 ? void 0 : _a.isValid) === false ? false : true;
4193
- }), distinctUntilChanged());
4194
- if (sub) {
4195
- acc[formId] = sub;
4196
- } else {
4197
- this.config.defaultLogVerbose && console.warn(`failed to register validation subscription form id ${formId}`);
4198
- }
4199
- return acc;
4200
- }, {});
4201
- const sub = combineLatest(subs).pipe(map(forms => ({
4202
- groupValid: Object.keys(forms).every(formId => forms[formId]),
4203
- forms
4393
+ return (_a = this.forms.get(id)) === null || _a === void 0 ? void 0 : _a.valid;
4394
+ }),
4395
+ forms: ids.reduce((acc, curr) => {
4396
+ const formInstance = this.forms.get(curr);
4397
+ if (formInstance) {
4398
+ acc[curr] = formInstance.valid;
4399
+ }
4400
+ return acc;
4401
+ }, {})
4204
4402
  }))).subscribe(callback);
4205
4403
  return sub;
4206
4404
  }
@@ -4208,17 +4406,15 @@ class FormGroup {
4208
4406
  ids,
4209
4407
  callback
4210
4408
  }) {
4211
- const subs = ids.reduce((acc, formId) => {
4212
- const form = this.forms.get(formId);
4213
- const sub = form === null || form === void 0 ? void 0 : form.submitSubject$.pipe(map(() => form === null || form === void 0 ? void 0 : form.getFormValues()), startWith(undefined));
4214
- if (sub) {
4215
- acc[formId] = sub;
4216
- } else {
4217
- this.config.defaultLogVerbose && console.warn(`failed to register form id ${formId}`);
4409
+ const sub = this.submitSubject$.pipe(filter(({
4410
+ formIndex
4411
+ }) => ids.includes(formIndex)), map(() => ids.reduce((acc, curr) => {
4412
+ const formInstance = this.forms.get(curr);
4413
+ if (formInstance) {
4414
+ acc[curr] = formInstance.getFormValues();
4218
4415
  }
4219
4416
  return acc;
4220
- }, {});
4221
- const sub = combineLatest(subs).pipe(skip(1)).subscribe(callback);
4417
+ }, {}))).subscribe(callback);
4222
4418
  return sub;
4223
4419
  }
4224
4420
  }