@bolttech/form-engine-core 1.0.0-beta.1 → 1.0.0-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,6 +1,6 @@
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, distinctUntilChanged, skip } from 'rxjs';
2
2
  import creditCardType from 'credit-card-type';
3
- import { isNumber as isNumber$1, isFunction, cloneDeep, isEqual, get, isNil, set } from 'lodash';
3
+ import { isNumber as isNumber$1, isFunction, cloneDeep, merge, isEqual, get, isNil, set } from 'lodash';
4
4
  import { getCurrencySymbol } from '@gaignoux/currency';
5
5
 
6
6
  var TMutationEnum;
@@ -2496,9 +2496,11 @@ class FormField {
2496
2496
  fieldEventSubject$,
2497
2497
  dataSubject$,
2498
2498
  formValidNotification$,
2499
+ mountSubject$,
2499
2500
  mapper,
2500
2501
  getFormValues,
2501
- submitEvent
2502
+ submitEvent,
2503
+ visibility
2502
2504
  }) {
2503
2505
  var _a, _b, _c, _d, _e, _f, _g;
2504
2506
  this.valueSubscription$ = new Subscription();
@@ -2534,11 +2536,12 @@ class FormField {
2534
2536
  this.fieldEventSubject$ = fieldEventSubject$;
2535
2537
  this.dataSubject$ = dataSubject$;
2536
2538
  this.formValidNotification$ = formValidNotification$;
2537
- this._props = cloneDeep(schemaComponent.props || {});
2539
+ this.mountSubject$ = mountSubject$;
2540
+ this._props = FormField.filterProps(cloneDeep(schemaComponent.props || {}));
2538
2541
  this._metadata = '';
2539
2542
  this.errorsString = '';
2540
2543
  this.errorsList = [];
2541
- this._visibility = true;
2544
+ this._visibility = typeof visibility === 'boolean' ? visibility : true;
2542
2545
  this._api = {
2543
2546
  default: {
2544
2547
  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) || '',
@@ -2554,43 +2557,37 @@ class FormField {
2554
2557
  }, {})
2555
2558
  };
2556
2559
  this._errors = {};
2557
- this._mounted = true;
2558
- this._valid = false;
2560
+ this._mounted = false;
2561
+ this._valid = true;
2559
2562
  this.initializeObservers();
2560
2563
  }
2561
2564
  /**
2562
2565
  * method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
2566
+ * due to some visibility conditions unmounts the field from the adapter if they are children of it and avoid
2567
+ * emissions to unsubscribed fields
2563
2568
  */
2564
2569
  initializeObservers() {
2565
- var _a;
2566
2570
  if (!this.valueSubject$ || this.valueSubject$.closed) {
2567
- this.valueSubject$ = new SafeSubject(() => this._mounted);
2571
+ this.valueSubject$ = new SafeSubject(() => this.mounted);
2568
2572
  }
2569
2573
  if (!this.errorSubject$ || this.errorSubject$.closed) {
2570
- this.errorSubject$ = new SafeSubject(() => this._mounted);
2574
+ this.errorSubject$ = new SafeSubject(() => this.mounted);
2571
2575
  }
2572
2576
  if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
2573
- this.visibilitySubject$ = new SafeSubject(() => this._mounted);
2577
+ this.visibilitySubject$ = new SafeSubject(() => this.mounted);
2574
2578
  }
2575
2579
  if (!this.apiSubject$ || this.apiSubject$.closed) {
2576
- this.apiSubject$ = new SafeSubject(() => this._mounted);
2580
+ this.apiSubject$ = new SafeSubject(() => this.mounted);
2577
2581
  }
2578
2582
  if (!this.propsSubject$ || this.propsSubject$.closed) {
2579
- this.propsSubject$ = new SafeSubject(() => this._mounted);
2583
+ this.propsSubject$ = new SafeSubject(() => this.mounted);
2580
2584
  }
2581
2585
  if (!this.fieldStateSubscription$ || this.fieldStateSubscription$.closed) {
2582
2586
  this.fieldStateSubscription$ = new Subscription();
2583
2587
  }
2584
2588
  if (!this.apiEventQueueSubject$ || this.apiEventQueueSubject$.closed) {
2585
- this.apiEventQueueSubject$ = new SafeSubject(() => this._mounted);
2589
+ this.apiEventQueueSubject$ = new SafeSubject(() => this.mounted);
2586
2590
  }
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
2591
  !this.apiEventQueueSubject$.observed && this.apiEventQueueSubject$.pipe(groupBy(({
2595
2592
  event
2596
2593
  }) => event), mergeMap(group$ => group$.pipe(debounceTime(this.config.defaultAPIdebounceTimeMS))), filter(() => this.apiSubject$ && !this.apiSubject$.closed)).subscribe(payload => {
@@ -2611,8 +2608,9 @@ class FormField {
2611
2608
  * @param {Record<string, unknown>} props - The new properties to be set.
2612
2609
  */
2613
2610
  set props(props) {
2614
- if (typeof props === 'undefined' || isEqual(props, this.props)) return;
2615
- this._props = props;
2611
+ const diffProps = merge(cloneDeep(this.props), props);
2612
+ if (typeof diffProps === 'undefined' || isEqual(diffProps, this.props)) return;
2613
+ this._props = diffProps;
2616
2614
  this.propsSubject$.next(this.props);
2617
2615
  this.templateSubject$.next({
2618
2616
  scope: 'fields',
@@ -2620,6 +2618,28 @@ class FormField {
2620
2618
  event: 'ON_PROPS'
2621
2619
  });
2622
2620
  }
2621
+ /**
2622
+ * Static function to remove templates form the component props that will be shown when
2623
+ * the field mounts and the template routine executes, to be used on the adapter
2624
+ *
2625
+ * @param {unknown} props - the properties from the adapter components.
2626
+ */
2627
+ static filterProps(props) {
2628
+ if (Array.isArray(props)) {
2629
+ return props.filter(el => typeof el === 'string' && el.includes('${') ? false : true).map(el => FormField.filterProps(el));
2630
+ }
2631
+ if (typeof props === 'object' && props !== null) {
2632
+ return Object.keys(props).reduce((acc, curr) => {
2633
+ const propValue = props[curr];
2634
+ if (typeof propValue === 'string' && propValue.includes('${')) {
2635
+ return acc;
2636
+ }
2637
+ acc[curr] = FormField.filterProps(props[curr]);
2638
+ return acc;
2639
+ }, {});
2640
+ }
2641
+ return props;
2642
+ }
2623
2643
  /**
2624
2644
  * Retrieves the current state value of the form field.
2625
2645
  *
@@ -2784,6 +2804,29 @@ class FormField {
2784
2804
  event: 'ON_API_FIELD_RESPONSE'
2785
2805
  });
2786
2806
  }
2807
+ /**
2808
+ * Retrieves the mounted status of the field.
2809
+ *
2810
+ * @returns {boolean} - the mounted status of the field.
2811
+ */
2812
+ get mounted() {
2813
+ return this._mounted;
2814
+ }
2815
+ /**
2816
+ * sets the mountedStatus and notifies the form that the field was mounted on the adapter
2817
+ * and it's ready to be handled by the form instance
2818
+ *
2819
+ * @param {boolean} mountedStatus - the mounted status to be set from the mountField function.
2820
+ */
2821
+ set mounted(mountedStatus) {
2822
+ if (typeof mountedStatus === 'undefined' || mountedStatus === this.mounted) return;
2823
+ this._mounted = mountedStatus;
2824
+ this.initializeObservers();
2825
+ this.mountSubject$.next({
2826
+ key: this.name,
2827
+ status: this.mounted
2828
+ });
2829
+ }
2787
2830
  /**
2788
2831
  * Mounts the form field by initializing necessary subjects and combining their streams.
2789
2832
  *
@@ -2796,10 +2839,15 @@ class FormField {
2796
2839
  valueSubscription,
2797
2840
  propsSubscription
2798
2841
  }) {
2799
- this.initializeObservers();
2842
+ this.mounted = true;
2800
2843
  this.subscribeValue(valueSubscription);
2801
2844
  this.subscribeState(propsSubscription);
2802
- this._mounted = true;
2845
+ this.valueSubject$.next(this.stateValue);
2846
+ this.propsSubject$.next(this.props);
2847
+ this.visibilitySubject$.next(this.visibility);
2848
+ this.setFieldValidity({
2849
+ event: 'ON_FIELD_MOUNT'
2850
+ });
2803
2851
  }
2804
2852
  /**
2805
2853
  * Sets the value of the form field and emits associated events.
@@ -2809,7 +2857,7 @@ class FormField {
2809
2857
  * @returns {void}
2810
2858
  */
2811
2859
  emitValue(prop) {
2812
- if (!this.visibility) return;
2860
+ if (!this.visibility || !this.mounted) return;
2813
2861
  this.value = prop.value;
2814
2862
  this.emitEvents({
2815
2863
  event: prop.event
@@ -2831,13 +2879,13 @@ class FormField {
2831
2879
  if (event === 'ON_FORM_SUBMIT') {
2832
2880
  return this.submitEvent();
2833
2881
  }
2834
- this.setFieldValidity({
2835
- event
2836
- });
2837
2882
  this.validateVisibility({
2838
2883
  event,
2839
2884
  key: this.name
2840
2885
  });
2886
+ this.setFieldValidity({
2887
+ event
2888
+ });
2841
2889
  this.resetValue({
2842
2890
  event,
2843
2891
  key: this.name
@@ -3037,7 +3085,7 @@ class FormField {
3037
3085
  * @returns {void}
3038
3086
  */
3039
3087
  destroyField() {
3040
- this._mounted = false;
3088
+ this.mounted = false;
3041
3089
  this.valueSubscription$.unsubscribe();
3042
3090
  this.visibilitySubject$.unsubscribe();
3043
3091
  this.fieldStateSubscription$.unsubscribe();
@@ -3056,7 +3104,14 @@ class FormField {
3056
3104
  * @returns {void}
3057
3105
  */
3058
3106
  subscribeState(callback) {
3059
- this.fieldStateSubscription$ = this.fieldState$.pipe(debounceTime(this.config.defaultStateRefreshTimeMS)).subscribe({
3107
+ var _a;
3108
+ this.fieldStateSubscription$ = combineLatest({
3109
+ visibility: this.visibilitySubject$.pipe(startWith(this.visibility)),
3110
+ props: this.propsSubject$.pipe(startWith(this.props)),
3111
+ errors: this.errorSubject$.pipe(startWith(Object.assign({}, ((_a = this.mapper.events) === null || _a === void 0 ? void 0 : _a.setErrorMessage) && {
3112
+ [this.mapper.events.setErrorMessage]: this.errorsString
3113
+ })))
3114
+ }).pipe(debounceTime(this.config.defaultStateRefreshTimeMS)).subscribe({
3060
3115
  next: callback
3061
3116
  });
3062
3117
  }
@@ -3100,13 +3155,12 @@ class FormCore {
3100
3155
  this.fields = new Map();
3101
3156
  this.action = entry.action || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.action);
3102
3157
  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
3158
  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
3159
+ 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,
3160
+ 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,
3161
+ defaultLogVerbose: ((_g = entry.config) === null || _g === void 0 ? void 0 : _g.defaultLogVerbose) ? entry.config.defaultLogVerbose : DEFAULT_LOG_VERBOSE
3108
3162
  };
3109
- (_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
3163
+ (_h = entry.mappers) === null || _h === void 0 ? void 0 : _h.map(mapper => {
3110
3164
  this.mappers.set(mapper.componentName, mapper);
3111
3165
  });
3112
3166
  this.schema && FormCore.checkIndexes(this.schema.components);
@@ -3117,40 +3171,77 @@ class FormCore {
3117
3171
  this.mountSubject$ = new Subject();
3118
3172
  this.formValidNotification$ = new Subject();
3119
3173
  this.subscribedTemplates = [];
3120
- this.schema && this.serializeStructure(this.schema.components);
3121
- this.schema && this.subscribeTemplates();
3122
3174
  this.templateSubscription$ = this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
3123
- this.templateSubject$.next({
3124
- scope: 'iVars',
3125
- event: 'ON_IVARS'
3175
+ this.mountSubject$.subscribe(this.mountActions.bind(this));
3176
+ this.initialValues = entry.initialValues || ((_j = entry.schema) === null || _j === void 0 ? void 0 : _j.initialValues);
3177
+ this.iVars = entry.iVars || ((_k = entry.schema) === null || _k === void 0 ? void 0 : _k.iVars) || {};
3178
+ }
3179
+ /**
3180
+ * mock function to simulate form mount onto the adapter
3181
+ */
3182
+ generateFields() {
3183
+ this.schema && this.serializeStructure(this.schema.components);
3184
+ this.fields.forEach(field => {
3185
+ field.mountField({
3186
+ valueSubscription: () => null,
3187
+ propsSubscription: () => null
3188
+ });
3126
3189
  });
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
- }));
3190
+ }
3191
+ /**
3192
+ * callback function passed to field instance to notify field adapter mount status
3193
+ * once the field has all field instance properties set, this function will handle all
3194
+ * field routines
3195
+ *
3196
+ * @param { string } entry.key field unique identifier
3197
+ * @param { boolean } entry.status mount status notified from field
3198
+ */
3199
+ mountActions({
3200
+ key,
3201
+ status
3202
+ }) {
3203
+ var _a;
3204
+ if (status) {
3205
+ const field = this.fields.get(key);
3206
+ if (!field) {
3207
+ /*
3208
+ @TODO check a better way to handle nested fields unmounted by visiblity conditions from a parent
3209
+ since they are dependent on adapter field recycling runtimes
3210
+ */
3211
+ this.config.defaultLogVerbose && console.log(`field ${key} was mounted but since it's parent has some visibility condition the field was already removed`);
3212
+ return;
3140
3213
  }
3214
+ this.subscribeTemplates();
3141
3215
  this.refreshTemplates({
3142
3216
  scope: 'fields',
3143
- key,
3144
3217
  event: 'ON_FIELDS'
3145
3218
  });
3146
- });
3147
- this.setInitialValues(initialValues);
3219
+ this.templateSubject$.next({
3220
+ scope: 'iVars',
3221
+ event: 'ON_IVARS'
3222
+ });
3223
+ if (!this.queuedInitialValues.has(key)) {
3224
+ 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) || ''];
3225
+ !(field === null || field === void 0 ? void 0 : field.value) ? field.emitValue({
3226
+ value: typeof propValue === 'string' && !propValue.includes('${') ? propValue : '',
3227
+ event: 'ON_FIELD_MOUNT'
3228
+ }) : field.emitEvents({
3229
+ event: 'ON_FIELD_MOUNT'
3230
+ });
3231
+ }
3232
+ this.checkFieldEventQueues(key);
3233
+ }
3148
3234
  }
3149
- setInitialValues(payload) {
3235
+ /**
3236
+ * initialValues setter to handle field values set externally from the adapter
3237
+ *
3238
+ * @param { Record<string, unknown> | undefined } payload initialValues to set onto fields
3239
+ */
3240
+ set initialValues(payload) {
3150
3241
  if (payload) {
3151
3242
  Object.keys(payload).forEach(key => {
3152
3243
  const field = this.fields.get(key);
3153
- if (!field || !(field === null || field === void 0 ? void 0 : field.visibility)) {
3244
+ if (!field || !(field === null || field === void 0 ? void 0 : field.visibility) || !(field === null || field === void 0 ? void 0 : field.mounted)) {
3154
3245
  this.queuedInitialValues.set(key, payload === null || payload === void 0 ? void 0 : payload[key]);
3155
3246
  } else {
3156
3247
  field.emitValue({
@@ -3187,6 +3278,7 @@ class FormCore {
3187
3278
  * @returns {boolean} True if the form is valid; otherwise, false.
3188
3279
  */
3189
3280
  get isValid() {
3281
+ if (this.fields.size === 0) return false;
3190
3282
  for (const [, field] of this.fields) {
3191
3283
  if (!field.valid) return false;
3192
3284
  }
@@ -3293,10 +3385,8 @@ class FormCore {
3293
3385
  }
3294
3386
  const fieldProp = field[property];
3295
3387
  let propState;
3296
- if (Array.isArray(fieldProp)) {
3297
- propState = [...fieldProp];
3298
- } else if (typeof fieldProp === 'object' && !isNil(fieldProp)) {
3299
- propState = Object.assign({}, fieldProp);
3388
+ if (Array.isArray(fieldProp) || typeof fieldProp === 'object' && !isNil(fieldProp)) {
3389
+ propState = cloneDeep(fieldProp);
3300
3390
  } else {
3301
3391
  this.config.defaultLogVerbose && console.warn(`invalid template property, skipping evaluation of ${field.name} with ${fieldProp}`);
3302
3392
  return;
@@ -3466,6 +3556,8 @@ class FormCore {
3466
3556
  * @param {string} field field to check
3467
3557
  */
3468
3558
  checkFieldEventQueues(field) {
3559
+ var _a;
3560
+ if (!((_a = this.fields.get(field)) === null || _a === void 0 ? void 0 : _a.mounted)) return;
3469
3561
  if (this.queuedFieldVisibilityEvents.has(field)) {
3470
3562
  this.setFieldVisibility(Object.assign({
3471
3563
  field: field
@@ -3473,10 +3565,11 @@ class FormCore {
3473
3565
  this.queuedFieldVisibilityEvents.delete(field);
3474
3566
  }
3475
3567
  if (this.queuedInitialValues.has(field)) {
3476
- this.setInitialValues({
3477
- [field]: this.queuedInitialValues.get(field)
3478
- });
3568
+ const value = this.queuedInitialValues.get(field);
3479
3569
  this.queuedInitialValues.delete(field);
3570
+ this.initialValues = {
3571
+ [field]: value
3572
+ };
3480
3573
  }
3481
3574
  if (this.queuedFieldResetValuesEvents.has(field)) {
3482
3575
  this.setResetFieldValue(Object.assign({
@@ -3506,14 +3599,16 @@ class FormCore {
3506
3599
  showOnlyIfTrue
3507
3600
  }) {
3508
3601
  const fieldInstance = this.fields.get(field);
3509
- if (!fieldInstance) {
3602
+ if (!fieldInstance || !fieldInstance.mounted) {
3510
3603
  this.queuedFieldVisibilityEvents.set(field, {
3511
3604
  hasError,
3512
3605
  showOnlyIfTrue
3513
3606
  });
3514
3607
  } else {
3608
+ const currentVisibility = fieldInstance.visibility;
3515
3609
  const visibility = showOnlyIfTrue ? hasError : !hasError;
3516
3610
  fieldInstance.visibility = visibility;
3611
+ if (currentVisibility === visibility) return;
3517
3612
  /**
3518
3613
  * I was sure I would not require to gambiarra, but..
3519
3614
  * in order to ignore an hidden value on a form submit
@@ -3585,12 +3680,12 @@ class FormCore {
3585
3680
  key,
3586
3681
  value
3587
3682
  }) {
3588
- if (!this.fields.has(key)) {
3683
+ const field = this.fields.get(key);
3684
+ if (!field || !(field === null || field === void 0 ? void 0 : field.mounted)) {
3589
3685
  this.queuedFieldResetValuesEvents.set(key, {
3590
3686
  value
3591
3687
  });
3592
3688
  } else {
3593
- const field = this.fields.get(key);
3594
3689
  field.emitValue({
3595
3690
  value: value,
3596
3691
  event: 'ON_FIELD_CLEARED'
@@ -3656,7 +3751,8 @@ class FormCore {
3656
3751
  path,
3657
3752
  value
3658
3753
  }) {
3659
- if (!this.fields.has(key)) {
3754
+ const field = this.fields.get(key);
3755
+ if (!field || field.mounted) {
3660
3756
  this.queuedFieldResetPropertyEvents.set(key, {
3661
3757
  property,
3662
3758
  path,
@@ -3720,7 +3816,7 @@ class FormCore {
3720
3816
  fieldSchema,
3721
3817
  mapperElement
3722
3818
  }) {
3723
- var _a, _b;
3819
+ var _a;
3724
3820
  if (this.fields.has(fieldSchema.name)) {
3725
3821
  throw new Error(`field name ${fieldSchema.name} already defined`);
3726
3822
  }
@@ -3738,24 +3834,17 @@ class FormCore {
3738
3834
  fieldEventSubject$: this.fieldEventSubject$,
3739
3835
  dataSubject$: this.dataSubject$,
3740
3836
  formValidNotification$: this.formValidNotification$,
3837
+ mountSubject$: this.mountSubject$,
3741
3838
  config: this.config,
3742
- submitEvent: this.submit.bind(this)
3839
+ submitEvent: this.submit.bind(this),
3840
+ visibility: fieldSchema.visibility
3743
3841
  }));
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
3842
  }
3843
+ /**
3844
+ * function to be called from the adapter to remove a field when a field is removed from it
3845
+ *
3846
+ * @param {{ key: string }} entry.key
3847
+ */
3759
3848
  removeField({
3760
3849
  key
3761
3850
  }) {
@@ -3800,9 +3889,11 @@ class FormCore {
3800
3889
  fieldEventSubject$: this.fieldEventSubject$,
3801
3890
  dataSubject$: this.dataSubject$,
3802
3891
  formValidNotification$: this.formValidNotification$,
3892
+ mountSubject$: this.mountSubject$,
3803
3893
  config: this.config,
3804
3894
  getFormValues: this.getFormValues.bind(this),
3805
- submitEvent: this.submit.bind(this)
3895
+ submitEvent: this.submit.bind(this),
3896
+ visibility: structElement.visibility
3806
3897
  }));
3807
3898
  } else {
3808
3899
  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) || [];
@@ -3882,10 +3973,12 @@ class FormCore {
3882
3973
  */
3883
3974
  getFormValues() {
3884
3975
  const values = {};
3976
+ const metadata = {};
3885
3977
  const erroredFields = [];
3886
3978
  this.fields.forEach((val, key) => {
3887
3979
  if (val.value) {
3888
3980
  set(values, val.nameToSubmit || key, val.value);
3981
+ metadata[key] = val.metadata;
3889
3982
  }
3890
3983
  if (!val.valid) {
3891
3984
  erroredFields.push(key);
@@ -3893,10 +3986,17 @@ class FormCore {
3893
3986
  });
3894
3987
  return {
3895
3988
  values,
3989
+ metadata,
3896
3990
  erroredFields,
3897
3991
  isValid: this.isValid
3898
3992
  };
3899
3993
  }
3994
+ /**
3995
+ * function to be called to events sent from the adapter
3996
+ *
3997
+ * @param {{callback: (payload: TFieldEvent) => void}} entry.callback callback function from the adapter
3998
+ * @returns
3999
+ */
3900
4000
  subscribeFieldEvent({
3901
4001
  callback
3902
4002
  }) {
@@ -3905,6 +4005,12 @@ class FormCore {
3905
4005
  });
3906
4006
  return sub;
3907
4007
  }
4008
+ /**
4009
+ * to be called from the adapter when the form mounts
4010
+ *
4011
+ * @param {(payload: TFormValues<T>) => void} callback
4012
+ * @returns Subscription
4013
+ */
3908
4014
  subscribeOnMount(callback) {
3909
4015
  const sub = this.mountSubject$.pipe(map(() => this.getFormValues())).subscribe({
3910
4016
  next: callback
@@ -3926,13 +4032,19 @@ class FormCore {
3926
4032
  });
3927
4033
  return sub;
3928
4034
  }
4035
+ /**
4036
+ * method to register a callback function to be called when the form is valid
4037
+ *
4038
+ * @param {(payload: TFormValues<T>) => void} callback callback function to call when the submit action occurs
4039
+ */
3929
4040
  subscribeOnSubmit(callback) {
3930
- const sub = this.submitSubject$.pipe(map(() => this.getFormValues())).subscribe({
4041
+ const sub = this.submitSubject$.subscribe({
3931
4042
  next: callback
3932
4043
  });
3933
4044
  return sub;
3934
4045
  }
3935
4046
  /**
4047
+ * method to check whenever the validity status of the form changes, only emits on status change
3936
4048
  *
3937
4049
  * @param {(payload: TFormValidationPayload) => void} callback callback function to call onValid
3938
4050
  */
@@ -3947,18 +4059,6 @@ class FormCore {
3947
4059
  });
3948
4060
  return sub;
3949
4061
  }
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
4062
  /**
3963
4063
  * Submits the form by triggering form field events and invoking the onSubmit callback.
3964
4064
  */
@@ -3972,6 +4072,9 @@ class FormCore {
3972
4072
  const values = this.getFormValues();
3973
4073
  this.submitSubject$.next(values);
3974
4074
  }
4075
+ /**
4076
+ * recycles all the Suscriptions, to be called from the adapter when the form leaves the page
4077
+ */
3975
4078
  destroy() {
3976
4079
  this.submitSubject$.unsubscribe();
3977
4080
  this.templateSubscription$.unsubscribe();
@@ -4103,9 +4206,15 @@ class FormGroup {
4103
4206
  formIndex,
4104
4207
  fieldIndex
4105
4208
  }) {
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);
4209
+ var _a;
4210
+ const form = this.forms.get(formIndex);
4211
+ (_a = form === null || form === void 0 ? void 0 : form.fields.get(fieldIndex)) === null || _a === void 0 ? void 0 : _a.destroyField();
4212
+ form === null || form === void 0 ? void 0 : form.fields.delete(fieldIndex);
4213
+ if ((form === null || form === void 0 ? void 0 : form.fields.size) === 0) {
4214
+ this.removeForm({
4215
+ key: formIndex
4216
+ });
4217
+ }
4109
4218
  }
4110
4219
  /**
4111
4220
  * Checks if the specified key already exists in the form group.
@@ -4136,6 +4245,7 @@ class FormGroup {
4136
4245
  submitMultipleFormsByIndex(indexes, callback) {
4137
4246
  let isValid = true;
4138
4247
  let values = {};
4248
+ let metadata = {};
4139
4249
  let erroredFields = [];
4140
4250
  indexes.forEach(index => {
4141
4251
  var _a, _b;
@@ -4143,12 +4253,14 @@ class FormGroup {
4143
4253
  const res = (_b = this.forms.get(index)) === null || _b === void 0 ? void 0 : _b.getFormValues();
4144
4254
  isValid = isValid && ((res === null || res === void 0 ? void 0 : res.isValid) || false);
4145
4255
  values = Object.assign(Object.assign({}, values), (res === null || res === void 0 ? void 0 : res.values) || {});
4256
+ metadata = Object.assign(Object.assign({}, metadata), (res === null || res === void 0 ? void 0 : res.metadata) || {});
4146
4257
  erroredFields = [...erroredFields, ...((res === null || res === void 0 ? void 0 : res.erroredFields) || [])];
4147
4258
  });
4148
4259
  isValid && callback && callback({
4149
4260
  erroredFields,
4150
4261
  isValid,
4151
- values
4262
+ values,
4263
+ metadata
4152
4264
  });
4153
4265
  }
4154
4266
  onDataSubscription({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolttech/form-engine-core",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.11",
4
4
  "module": "./index.esm.js",
5
5
  "type": "module",
6
6
  "main": "./index.esm.js",
@@ -62,6 +62,7 @@ interface IComponentSchema {
62
62
  formatters?: TFormatters;
63
63
  masks?: TMasks;
64
64
  children?: IComponentSchema[];
65
+ visibility?: boolean;
65
66
  }
66
67
  interface IComponentSchemaAsFormField<T> extends IComponentSchema {
67
68
  mapper?: TMapper<T>;
@@ -1,4 +1,4 @@
1
- import { Observable, Subject, Subscription } from 'rxjs';
1
+ import { Subject, Subscription } from 'rxjs';
2
2
  import { TApiConfig, TApiEvent, TApiResponse, TErrorMessages, TFormatters, TMasks, TResetPathMethods, TResetValueMethods, TSchemaFormConfig, TValidations, TVisibility } from '../types/schema';
3
3
  import { IComponentSchema, IComponentSchemaAsFormField } from '../interfaces/schema';
4
4
  import { IState } from '../interfaces/state';
@@ -49,7 +49,6 @@ declare class FormField {
49
49
  apiEventQueueSubject$: SafeSubject<{
50
50
  event: TEvents;
51
51
  }>;
52
- fieldState$: Observable<IState>;
53
52
  fieldStateSubscription$: Subscription;
54
53
  templateSubject$: Subject<TTemplateEvent>;
55
54
  dataSubject$: Subject<{
@@ -57,6 +56,10 @@ declare class FormField {
57
56
  event: TEvents;
58
57
  }>;
59
58
  formValidNotification$: Subject<Pick<TFormValidationPayload, 'fieldTrigger'>>;
59
+ mountSubject$: Subject<{
60
+ key: string;
61
+ status: boolean;
62
+ }>;
60
63
  validateVisibility: (payload: {
61
64
  event: TEvents;
62
65
  key: string;
@@ -90,7 +93,7 @@ declare class FormField {
90
93
  * @param {TMapper<unknown>} options.mapper, - component generic mapper containing render parameters for adapters
91
94
  * @param {() => TFormValues<unknown>} options.getFormValues, - form instance function that builds onData parameter payload from fields
92
95
  */
93
- constructor({ schemaComponent, config, path, children, validateVisibility, resetValue, resetProperty, templateSubject$, fieldEventSubject$, dataSubject$, formValidNotification$, mapper, getFormValues, submitEvent, }: {
96
+ constructor({ schemaComponent, config, path, children, validateVisibility, resetValue, resetProperty, templateSubject$, fieldEventSubject$, dataSubject$, formValidNotification$, mountSubject$, mapper, getFormValues, submitEvent, visibility, }: {
94
97
  schemaComponent: IComponentSchema;
95
98
  config?: TSchemaFormConfig;
96
99
  path?: string;
@@ -115,11 +118,18 @@ declare class FormField {
115
118
  event: TEvents;
116
119
  }>;
117
120
  formValidNotification$: Subject<Pick<TFormValidationPayload, 'fieldTrigger'>>;
121
+ mountSubject$: Subject<{
122
+ key: string;
123
+ status: boolean;
124
+ }>;
118
125
  mapper: TMapper<unknown>;
119
126
  getFormValues: () => TFormValues<unknown>;
127
+ visibility?: boolean;
120
128
  });
121
129
  /**
122
130
  * method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
131
+ * due to some visibility conditions unmounts the field from the adapter if they are children of it and avoid
132
+ * emissions to unsubscribed fields
123
133
  */
124
134
  initializeObservers(): void;
125
135
  /**
@@ -134,6 +144,13 @@ declare class FormField {
134
144
  * @param {Record<string, unknown>} props - The new properties to be set.
135
145
  */
136
146
  set props(props: Record<string, unknown>);
147
+ /**
148
+ * Static function to remove templates form the component props that will be shown when
149
+ * the field mounts and the template routine executes, to be used on the adapter
150
+ *
151
+ * @param {unknown} props - the properties from the adapter components.
152
+ */
153
+ static filterProps(props: unknown): unknown;
137
154
  /**
138
155
  * Retrieves the current state value of the form field.
139
156
  *
@@ -199,6 +216,19 @@ declare class FormField {
199
216
  * @param {TApiResponse} response - The new API response data to be set.
200
217
  */
201
218
  set api(response: TApiResponse);
219
+ /**
220
+ * Retrieves the mounted status of the field.
221
+ *
222
+ * @returns {boolean} - the mounted status of the field.
223
+ */
224
+ get mounted(): boolean;
225
+ /**
226
+ * sets the mountedStatus and notifies the form that the field was mounted on the adapter
227
+ * and it's ready to be handled by the form instance
228
+ *
229
+ * @param {boolean} mountedStatus - the mounted status to be set from the mountField function.
230
+ */
231
+ set mounted(mountedStatus: boolean);
202
232
  /**
203
233
  * Mounts the form field by initializing necessary subjects and combining their streams.
204
234
  *
@@ -17,7 +17,10 @@ declare class FormCore {
17
17
  templateSubject$: Subject<TTemplateEvent>;
18
18
  templateSubscription$: Subscription;
19
19
  submitSubject$: Subject<TFormValues<any>>;
20
- mountSubject$: Subject<TFormValues<any>>;
20
+ mountSubject$: Subject<{
21
+ key: string;
22
+ status: boolean;
23
+ }>;
21
24
  fieldEventSubject$: Subject<TFieldEvent>;
22
25
  dataSubject$: Subject<{
23
26
  key: string;
@@ -54,7 +57,28 @@ declare class FormCore {
54
57
  * @param {((payload: {field: string;data: TFormValues;}) => void) | undefined} [entry.onData] - A callback function to handle data emission.
55
58
  */
56
59
  constructor(entry: TFormEntry & Omit<IFormSchema, 'components'>);
57
- setInitialValues(payload: Record<string, unknown> | undefined): void;
60
+ /**
61
+ * mock function to simulate form mount onto the adapter
62
+ */
63
+ generateFields(): void;
64
+ /**
65
+ * callback function passed to field instance to notify field adapter mount status
66
+ * once the field has all field instance properties set, this function will handle all
67
+ * field routines
68
+ *
69
+ * @param { string } entry.key field unique identifier
70
+ * @param { boolean } entry.status mount status notified from field
71
+ */
72
+ mountActions({ key, status }: {
73
+ key: string;
74
+ status: boolean;
75
+ }): void;
76
+ /**
77
+ * initialValues setter to handle field values set externally from the adapter
78
+ *
79
+ * @param { Record<string, unknown> | undefined } payload initialValues to set onto fields
80
+ */
81
+ set initialValues(payload: Record<string, unknown> | undefined);
58
82
  /**
59
83
  * Retrieves the internal variables (iVars) of the form.
60
84
  *
@@ -230,6 +254,11 @@ declare class FormCore {
230
254
  fieldSchema: IComponentSchema;
231
255
  mapperElement?: TMapper<unknown>;
232
256
  }): void;
257
+ /**
258
+ * function to be called from the adapter to remove a field when a field is removed from it
259
+ *
260
+ * @param {{ key: string }} entry.key
261
+ */
233
262
  removeField({ key }: {
234
263
  key: string;
235
264
  }): void;
@@ -266,9 +295,21 @@ declare class FormCore {
266
295
  * @returns {TFormValues} The current form values.
267
296
  */
268
297
  getFormValues<T>(): TFormValues<T>;
298
+ /**
299
+ * function to be called to events sent from the adapter
300
+ *
301
+ * @param {{callback: (payload: TFieldEvent) => void}} entry.callback callback function from the adapter
302
+ * @returns
303
+ */
269
304
  subscribeFieldEvent({ callback, }: {
270
305
  callback: (payload: TFieldEvent) => void;
271
306
  }): Subscription;
307
+ /**
308
+ * to be called from the adapter when the form mounts
309
+ *
310
+ * @param {(payload: TFormValues<T>) => void} callback
311
+ * @returns Subscription
312
+ */
272
313
  subscribeOnMount<T>(callback: (payload: TFormValues<T>) => void): Subscription;
273
314
  /**
274
315
  *
@@ -278,8 +319,14 @@ declare class FormCore {
278
319
  field: string;
279
320
  data: TFormValues<T>;
280
321
  }) => void): Subscription;
322
+ /**
323
+ * method to register a callback function to be called when the form is valid
324
+ *
325
+ * @param {(payload: TFormValues<T>) => void} callback callback function to call when the submit action occurs
326
+ */
281
327
  subscribeOnSubmit<T>(callback: (payload: TFormValues<T>) => void): Subscription;
282
328
  /**
329
+ * method to check whenever the validity status of the form changes, only emits on status change
283
330
  *
284
331
  * @param {(payload: TFormValidationPayload) => void} callback callback function to call onValid
285
332
  */
@@ -287,11 +334,10 @@ declare class FormCore {
287
334
  /**
288
335
  * Submits the form by triggering form field events and invoking the onSubmit callback.
289
336
  */
290
- mounted<T>(): void;
337
+ submit<T>(): void;
291
338
  /**
292
- * Submits the form by triggering form field events and invoking the onSubmit callback.
339
+ * recycles all the Suscriptions, to be called from the adapter when the form leaves the page
293
340
  */
294
- submit<T>(): void;
295
341
  destroy(): void;
296
342
  }
297
343
  type TFormCore = FormCore;
@@ -19,6 +19,7 @@ import { TMapper } from './mapper';
19
19
  */
20
20
  type TFormValues<T> = {
21
21
  values: T;
22
+ metadata: unknown;
22
23
  erroredFields: string[];
23
24
  isValid: boolean;
24
25
  };