@bolttech/form-engine-core 0.0.3-beta.8 → 1.0.0-beta.10

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
1
  import { Subject, Subscription, combineLatest, startWith, groupBy, mergeMap, debounceTime, filter, 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,35 +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
2570
  var _a;
2566
2571
  if (!this.valueSubject$ || this.valueSubject$.closed) {
2567
- this.valueSubject$ = new SafeSubject(() => this._mounted);
2572
+ this.valueSubject$ = new SafeSubject(() => this.mounted);
2568
2573
  }
2569
2574
  if (!this.errorSubject$ || this.errorSubject$.closed) {
2570
- this.errorSubject$ = new SafeSubject(() => this._mounted);
2575
+ this.errorSubject$ = new SafeSubject(() => this.mounted);
2571
2576
  }
2572
2577
  if (!this.visibilitySubject$ || this.visibilitySubject$.closed) {
2573
- this.visibilitySubject$ = new SafeSubject(() => this._mounted);
2578
+ this.visibilitySubject$ = new SafeSubject(() => this.mounted);
2574
2579
  }
2575
2580
  if (!this.apiSubject$ || this.apiSubject$.closed) {
2576
- this.apiSubject$ = new SafeSubject(() => this._mounted);
2581
+ this.apiSubject$ = new SafeSubject(() => this.mounted);
2577
2582
  }
2578
2583
  if (!this.propsSubject$ || this.propsSubject$.closed) {
2579
- this.propsSubject$ = new SafeSubject(() => this._mounted);
2584
+ this.propsSubject$ = new SafeSubject(() => this.mounted);
2580
2585
  }
2581
2586
  if (!this.fieldStateSubscription$ || this.fieldStateSubscription$.closed) {
2582
2587
  this.fieldStateSubscription$ = new Subscription();
2583
2588
  }
2584
2589
  if (!this.apiEventQueueSubject$ || this.apiEventQueueSubject$.closed) {
2585
- this.apiEventQueueSubject$ = new SafeSubject(() => this._mounted);
2590
+ this.apiEventQueueSubject$ = new SafeSubject(() => this.mounted);
2586
2591
  }
2587
2592
  this.fieldState$ = combineLatest({
2588
2593
  visibility: this.visibilitySubject$.pipe(startWith(this._visibility)),
@@ -2611,8 +2616,9 @@ class FormField {
2611
2616
  * @param {Record<string, unknown>} props - The new properties to be set.
2612
2617
  */
2613
2618
  set props(props) {
2614
- if (typeof props === 'undefined' || isEqual(props, this.props)) return;
2615
- this._props = props;
2619
+ const diffProps = merge(cloneDeep(this.props), props);
2620
+ if (typeof diffProps === 'undefined' || isEqual(diffProps, this.props)) return;
2621
+ this._props = diffProps;
2616
2622
  this.propsSubject$.next(this.props);
2617
2623
  this.templateSubject$.next({
2618
2624
  scope: 'fields',
@@ -2620,6 +2626,28 @@ class FormField {
2620
2626
  event: 'ON_PROPS'
2621
2627
  });
2622
2628
  }
2629
+ /**
2630
+ * Static function to remove templates form the component props that will be shown when
2631
+ * the field mounts and the template routine executes, to be used on the adapter
2632
+ *
2633
+ * @param {unknown} props - the properties from the adapter components.
2634
+ */
2635
+ static filterProps(props) {
2636
+ if (Array.isArray(props)) {
2637
+ return props.filter(el => typeof el === 'string' && el.includes('${') ? false : true).map(el => FormField.filterProps(el));
2638
+ }
2639
+ if (typeof props === 'object' && props !== null) {
2640
+ return Object.keys(props).reduce((acc, curr) => {
2641
+ const propValue = props[curr];
2642
+ if (typeof propValue === 'string' && propValue.includes('${')) {
2643
+ return acc;
2644
+ }
2645
+ acc[curr] = FormField.filterProps(props[curr]);
2646
+ return acc;
2647
+ }, {});
2648
+ }
2649
+ return props;
2650
+ }
2623
2651
  /**
2624
2652
  * Retrieves the current state value of the form field.
2625
2653
  *
@@ -2784,6 +2812,29 @@ class FormField {
2784
2812
  event: 'ON_API_FIELD_RESPONSE'
2785
2813
  });
2786
2814
  }
2815
+ /**
2816
+ * Retrieves the mounted status of the field.
2817
+ *
2818
+ * @returns {boolean} - the mounted status of the field.
2819
+ */
2820
+ get mounted() {
2821
+ return this._mounted;
2822
+ }
2823
+ /**
2824
+ * sets the mountedStatus and notifies the form that the field was mounted on the adapter
2825
+ * and it's ready to be handled by the form instance
2826
+ *
2827
+ * @param {boolean} mountedStatus - the mounted status to be set from the mountField function.
2828
+ */
2829
+ set mounted(mountedStatus) {
2830
+ if (typeof mountedStatus === 'undefined' || mountedStatus === this.mounted) return;
2831
+ this._mounted = mountedStatus;
2832
+ this.initializeObservers();
2833
+ this.mountSubject$.next({
2834
+ key: this.name,
2835
+ status: this.mounted
2836
+ });
2837
+ }
2787
2838
  /**
2788
2839
  * Mounts the form field by initializing necessary subjects and combining their streams.
2789
2840
  *
@@ -2796,10 +2847,15 @@ class FormField {
2796
2847
  valueSubscription,
2797
2848
  propsSubscription
2798
2849
  }) {
2799
- this.initializeObservers();
2850
+ this.mounted = true;
2800
2851
  this.subscribeValue(valueSubscription);
2801
2852
  this.subscribeState(propsSubscription);
2802
- this._mounted = true;
2853
+ this.valueSubject$.next(this.stateValue);
2854
+ this.propsSubject$.next(this.props);
2855
+ this.visibilitySubject$.next(this.visibility);
2856
+ this.setFieldValidity({
2857
+ event: 'ON_FIELD_MOUNT'
2858
+ });
2803
2859
  }
2804
2860
  /**
2805
2861
  * Sets the value of the form field and emits associated events.
@@ -2809,7 +2865,7 @@ class FormField {
2809
2865
  * @returns {void}
2810
2866
  */
2811
2867
  emitValue(prop) {
2812
- if (!this.visibility) return;
2868
+ if (!this.visibility || !this.mounted) return;
2813
2869
  this.value = prop.value;
2814
2870
  this.emitEvents({
2815
2871
  event: prop.event
@@ -2831,13 +2887,13 @@ class FormField {
2831
2887
  if (event === 'ON_FORM_SUBMIT') {
2832
2888
  return this.submitEvent();
2833
2889
  }
2834
- this.setFieldValidity({
2835
- event
2836
- });
2837
2890
  this.validateVisibility({
2838
2891
  event,
2839
2892
  key: this.name
2840
2893
  });
2894
+ this.setFieldValidity({
2895
+ event
2896
+ });
2841
2897
  this.resetValue({
2842
2898
  event,
2843
2899
  key: this.name
@@ -3037,7 +3093,7 @@ class FormField {
3037
3093
  * @returns {void}
3038
3094
  */
3039
3095
  destroyField() {
3040
- this._mounted = false;
3096
+ this.mounted = false;
3041
3097
  this.valueSubscription$.unsubscribe();
3042
3098
  this.visibilitySubject$.unsubscribe();
3043
3099
  this.fieldStateSubscription$.unsubscribe();
@@ -3100,13 +3156,12 @@ class FormCore {
3100
3156
  this.fields = new Map();
3101
3157
  this.action = entry.action || ((_a = entry.schema) === null || _a === void 0 ? void 0 : _a.action);
3102
3158
  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
3159
  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
3160
+ 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,
3161
+ 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,
3162
+ defaultLogVerbose: ((_g = entry.config) === null || _g === void 0 ? void 0 : _g.defaultLogVerbose) ? entry.config.defaultLogVerbose : DEFAULT_LOG_VERBOSE
3108
3163
  };
3109
- (_j = entry.mappers) === null || _j === void 0 ? void 0 : _j.map(mapper => {
3164
+ (_h = entry.mappers) === null || _h === void 0 ? void 0 : _h.map(mapper => {
3110
3165
  this.mappers.set(mapper.componentName, mapper);
3111
3166
  });
3112
3167
  this.schema && FormCore.checkIndexes(this.schema.components);
@@ -3117,40 +3172,77 @@ class FormCore {
3117
3172
  this.mountSubject$ = new Subject();
3118
3173
  this.formValidNotification$ = new Subject();
3119
3174
  this.subscribedTemplates = [];
3120
- this.schema && this.serializeStructure(this.schema.components);
3121
- this.schema && this.subscribeTemplates();
3122
3175
  this.templateSubscription$ = this.templateSubject$.subscribe(this.refreshTemplates.bind(this));
3123
- this.templateSubject$.next({
3124
- scope: 'iVars',
3125
- event: 'ON_IVARS'
3176
+ this.mountSubject$.subscribe(this.mountActions.bind(this));
3177
+ this.initialValues = entry.initialValues || ((_j = entry.schema) === null || _j === void 0 ? void 0 : _j.initialValues);
3178
+ this.iVars = entry.iVars || ((_k = entry.schema) === null || _k === void 0 ? void 0 : _k.iVars) || {};
3179
+ }
3180
+ /**
3181
+ * mock function to simulate form mount onto the adapter
3182
+ */
3183
+ generateFields() {
3184
+ this.schema && this.serializeStructure(this.schema.components);
3185
+ this.fields.forEach(field => {
3186
+ field.mountField({
3187
+ valueSubscription: () => null,
3188
+ propsSubscription: () => null
3189
+ });
3126
3190
  });
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
- }));
3191
+ }
3192
+ /**
3193
+ * callback function passed to field instance to notify field adapter mount status
3194
+ * once the field has all field instance properties set, this function will handle all
3195
+ * field routines
3196
+ *
3197
+ * @param { string } entry.key field unique identifier
3198
+ * @param { boolean } entry.status mount status notified from field
3199
+ */
3200
+ mountActions({
3201
+ key,
3202
+ status
3203
+ }) {
3204
+ var _a;
3205
+ if (status) {
3206
+ const field = this.fields.get(key);
3207
+ if (!field) {
3208
+ /*
3209
+ @TODO check a better way to handle nested fields unmounted by visiblity conditions from a parent
3210
+ since they are dependent on adapter field recycling runtimes
3211
+ */
3212
+ this.config.defaultLogVerbose && console.log(`field ${key} was mounted but since it's parent has some visibility condition the field was already removed`);
3213
+ return;
3140
3214
  }
3215
+ this.subscribeTemplates();
3141
3216
  this.refreshTemplates({
3142
3217
  scope: 'fields',
3143
- key,
3144
3218
  event: 'ON_FIELDS'
3145
3219
  });
3146
- });
3147
- this.setInitialValues(initialValues);
3220
+ this.templateSubject$.next({
3221
+ scope: 'iVars',
3222
+ event: 'ON_IVARS'
3223
+ });
3224
+ if (!this.queuedInitialValues.has(key)) {
3225
+ 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) || ''];
3226
+ !(field === null || field === void 0 ? void 0 : field.value) ? field.emitValue({
3227
+ value: typeof propValue === 'string' && !propValue.includes('${') ? propValue : '',
3228
+ event: 'ON_FIELD_MOUNT'
3229
+ }) : field.emitEvents({
3230
+ event: 'ON_FIELD_MOUNT'
3231
+ });
3232
+ }
3233
+ this.checkFieldEventQueues(key);
3234
+ }
3148
3235
  }
3149
- setInitialValues(payload) {
3236
+ /**
3237
+ * initialValues setter to handle field values set externally from the adapter
3238
+ *
3239
+ * @param { Record<string, unknown> | undefined } payload initialValues to set onto fields
3240
+ */
3241
+ set initialValues(payload) {
3150
3242
  if (payload) {
3151
3243
  Object.keys(payload).forEach(key => {
3152
3244
  const field = this.fields.get(key);
3153
- if (!field || !(field === null || field === void 0 ? void 0 : field.visibility)) {
3245
+ if (!field || !(field === null || field === void 0 ? void 0 : field.visibility) || !(field === null || field === void 0 ? void 0 : field.mounted)) {
3154
3246
  this.queuedInitialValues.set(key, payload === null || payload === void 0 ? void 0 : payload[key]);
3155
3247
  } else {
3156
3248
  field.emitValue({
@@ -3187,6 +3279,7 @@ class FormCore {
3187
3279
  * @returns {boolean} True if the form is valid; otherwise, false.
3188
3280
  */
3189
3281
  get isValid() {
3282
+ if (this.fields.size === 0) return false;
3190
3283
  for (const [, field] of this.fields) {
3191
3284
  if (!field.valid) return false;
3192
3285
  }
@@ -3464,6 +3557,8 @@ class FormCore {
3464
3557
  * @param {string} field field to check
3465
3558
  */
3466
3559
  checkFieldEventQueues(field) {
3560
+ var _a;
3561
+ if (!((_a = this.fields.get(field)) === null || _a === void 0 ? void 0 : _a.mounted)) return;
3467
3562
  if (this.queuedFieldVisibilityEvents.has(field)) {
3468
3563
  this.setFieldVisibility(Object.assign({
3469
3564
  field: field
@@ -3471,10 +3566,11 @@ class FormCore {
3471
3566
  this.queuedFieldVisibilityEvents.delete(field);
3472
3567
  }
3473
3568
  if (this.queuedInitialValues.has(field)) {
3474
- this.setInitialValues({
3475
- [field]: this.queuedInitialValues.get(field)
3476
- });
3569
+ const value = this.queuedInitialValues.get(field);
3477
3570
  this.queuedInitialValues.delete(field);
3571
+ this.initialValues = {
3572
+ [field]: value
3573
+ };
3478
3574
  }
3479
3575
  if (this.queuedFieldResetValuesEvents.has(field)) {
3480
3576
  this.setResetFieldValue(Object.assign({
@@ -3504,14 +3600,16 @@ class FormCore {
3504
3600
  showOnlyIfTrue
3505
3601
  }) {
3506
3602
  const fieldInstance = this.fields.get(field);
3507
- if (!fieldInstance) {
3603
+ if (!fieldInstance || !fieldInstance.mounted) {
3508
3604
  this.queuedFieldVisibilityEvents.set(field, {
3509
3605
  hasError,
3510
3606
  showOnlyIfTrue
3511
3607
  });
3512
3608
  } else {
3609
+ const currentVisibility = fieldInstance.visibility;
3513
3610
  const visibility = showOnlyIfTrue ? hasError : !hasError;
3514
3611
  fieldInstance.visibility = visibility;
3612
+ if (currentVisibility === visibility) return;
3515
3613
  /**
3516
3614
  * I was sure I would not require to gambiarra, but..
3517
3615
  * in order to ignore an hidden value on a form submit
@@ -3583,12 +3681,12 @@ class FormCore {
3583
3681
  key,
3584
3682
  value
3585
3683
  }) {
3586
- if (!this.fields.has(key)) {
3684
+ const field = this.fields.get(key);
3685
+ if (!field || !(field === null || field === void 0 ? void 0 : field.mounted)) {
3587
3686
  this.queuedFieldResetValuesEvents.set(key, {
3588
3687
  value
3589
3688
  });
3590
3689
  } else {
3591
- const field = this.fields.get(key);
3592
3690
  field.emitValue({
3593
3691
  value: value,
3594
3692
  event: 'ON_FIELD_CLEARED'
@@ -3654,7 +3752,8 @@ class FormCore {
3654
3752
  path,
3655
3753
  value
3656
3754
  }) {
3657
- if (!this.fields.has(key)) {
3755
+ const field = this.fields.get(key);
3756
+ if (!field || field.mounted) {
3658
3757
  this.queuedFieldResetPropertyEvents.set(key, {
3659
3758
  property,
3660
3759
  path,
@@ -3718,7 +3817,7 @@ class FormCore {
3718
3817
  fieldSchema,
3719
3818
  mapperElement
3720
3819
  }) {
3721
- var _a, _b;
3820
+ var _a;
3722
3821
  if (this.fields.has(fieldSchema.name)) {
3723
3822
  throw new Error(`field name ${fieldSchema.name} already defined`);
3724
3823
  }
@@ -3736,24 +3835,17 @@ class FormCore {
3736
3835
  fieldEventSubject$: this.fieldEventSubject$,
3737
3836
  dataSubject$: this.dataSubject$,
3738
3837
  formValidNotification$: this.formValidNotification$,
3838
+ mountSubject$: this.mountSubject$,
3739
3839
  config: this.config,
3740
- submitEvent: this.submit.bind(this)
3840
+ submitEvent: this.submit.bind(this),
3841
+ visibility: fieldSchema.visibility
3741
3842
  }));
3742
- this.subscribeTemplates();
3743
- this.refreshTemplates({
3744
- scope: 'fields',
3745
- event: 'ON_FIELDS'
3746
- });
3747
- if (!this.queuedInitialValues.has(fieldSchema.name)) {
3748
- const field = this.fields.get(fieldSchema.name);
3749
- 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) || ''];
3750
- !(field === null || field === void 0 ? void 0 : field.value) && (field === null || field === void 0 ? void 0 : field.emitValue({
3751
- value: typeof propValue === 'string' && !propValue.includes('${') ? propValue : '',
3752
- event: 'ON_FIELD_MOUNT'
3753
- }));
3754
- }
3755
- this.checkFieldEventQueues(fieldSchema.name);
3756
3843
  }
3844
+ /**
3845
+ * function to be called from the adapter to remove a field when a field is removed from it
3846
+ *
3847
+ * @param {{ key: string }} entry.key
3848
+ */
3757
3849
  removeField({
3758
3850
  key
3759
3851
  }) {
@@ -3798,9 +3890,11 @@ class FormCore {
3798
3890
  fieldEventSubject$: this.fieldEventSubject$,
3799
3891
  dataSubject$: this.dataSubject$,
3800
3892
  formValidNotification$: this.formValidNotification$,
3893
+ mountSubject$: this.mountSubject$,
3801
3894
  config: this.config,
3802
3895
  getFormValues: this.getFormValues.bind(this),
3803
- submitEvent: this.submit.bind(this)
3896
+ submitEvent: this.submit.bind(this),
3897
+ visibility: structElement.visibility
3804
3898
  }));
3805
3899
  } else {
3806
3900
  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) || [];
@@ -3898,6 +3992,12 @@ class FormCore {
3898
3992
  isValid: this.isValid
3899
3993
  };
3900
3994
  }
3995
+ /**
3996
+ * function to be called to events sent from the adapter
3997
+ *
3998
+ * @param {{callback: (payload: TFieldEvent) => void}} entry.callback callback function from the adapter
3999
+ * @returns
4000
+ */
3901
4001
  subscribeFieldEvent({
3902
4002
  callback
3903
4003
  }) {
@@ -3906,6 +4006,12 @@ class FormCore {
3906
4006
  });
3907
4007
  return sub;
3908
4008
  }
4009
+ /**
4010
+ * to be called from the adapter when the form mounts
4011
+ *
4012
+ * @param {(payload: TFormValues<T>) => void} callback
4013
+ * @returns Subscription
4014
+ */
3909
4015
  subscribeOnMount(callback) {
3910
4016
  const sub = this.mountSubject$.pipe(map(() => this.getFormValues())).subscribe({
3911
4017
  next: callback
@@ -3927,13 +4033,19 @@ class FormCore {
3927
4033
  });
3928
4034
  return sub;
3929
4035
  }
4036
+ /**
4037
+ * method to register a callback function to be called when the form is valid
4038
+ *
4039
+ * @param {(payload: TFormValues<T>) => void} callback callback function to call when the submit action occurs
4040
+ */
3930
4041
  subscribeOnSubmit(callback) {
3931
- const sub = this.submitSubject$.pipe(map(() => this.getFormValues())).subscribe({
4042
+ const sub = this.submitSubject$.subscribe({
3932
4043
  next: callback
3933
4044
  });
3934
4045
  return sub;
3935
4046
  }
3936
4047
  /**
4048
+ * method to check whenever the validity status of the form changes, only emits on status change
3937
4049
  *
3938
4050
  * @param {(payload: TFormValidationPayload) => void} callback callback function to call onValid
3939
4051
  */
@@ -3948,18 +4060,6 @@ class FormCore {
3948
4060
  });
3949
4061
  return sub;
3950
4062
  }
3951
- /**
3952
- * Submits the form by triggering form field events and invoking the onSubmit callback.
3953
- */
3954
- mounted() {
3955
- this.fields.forEach(field => {
3956
- field.emitEvents({
3957
- event: 'ON_FORM_MOUNT'
3958
- });
3959
- });
3960
- const values = this.getFormValues();
3961
- this.mountSubject$.next(values);
3962
- }
3963
4063
  /**
3964
4064
  * Submits the form by triggering form field events and invoking the onSubmit callback.
3965
4065
  */
@@ -3973,6 +4073,9 @@ class FormCore {
3973
4073
  const values = this.getFormValues();
3974
4074
  this.submitSubject$.next(values);
3975
4075
  }
4076
+ /**
4077
+ * recycles all the Suscriptions, to be called from the adapter when the form leaves the page
4078
+ */
3976
4079
  destroy() {
3977
4080
  this.submitSubject$.unsubscribe();
3978
4081
  this.templateSubscription$.unsubscribe();
@@ -4104,9 +4207,15 @@ class FormGroup {
4104
4207
  formIndex,
4105
4208
  fieldIndex
4106
4209
  }) {
4107
- var _a, _b, _c;
4108
- (_b = (_a = this.forms.get(formIndex)) === null || _a === void 0 ? void 0 : _a.fields.get(fieldIndex)) === null || _b === void 0 ? void 0 : _b.destroyField();
4109
- (_c = this.forms.get(formIndex)) === null || _c === void 0 ? void 0 : _c.fields.delete(fieldIndex);
4210
+ var _a;
4211
+ const form = this.forms.get(formIndex);
4212
+ (_a = form === null || form === void 0 ? void 0 : form.fields.get(fieldIndex)) === null || _a === void 0 ? void 0 : _a.destroyField();
4213
+ form === null || form === void 0 ? void 0 : form.fields.delete(fieldIndex);
4214
+ if ((form === null || form === void 0 ? void 0 : form.fields.size) === 0) {
4215
+ this.removeForm({
4216
+ key: formIndex
4217
+ });
4218
+ }
4110
4219
  }
4111
4220
  /**
4112
4221
  * Checks if the specified key already exists in the form group.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolttech/form-engine-core",
3
- "version": "0.0.3-beta.8",
3
+ "version": "1.0.0-beta.10",
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>;
@@ -57,6 +57,10 @@ declare class FormField {
57
57
  event: TEvents;
58
58
  }>;
59
59
  formValidNotification$: Subject<Pick<TFormValidationPayload, 'fieldTrigger'>>;
60
+ mountSubject$: Subject<{
61
+ key: string;
62
+ status: boolean;
63
+ }>;
60
64
  validateVisibility: (payload: {
61
65
  event: TEvents;
62
66
  key: string;
@@ -90,7 +94,7 @@ declare class FormField {
90
94
  * @param {TMapper<unknown>} options.mapper, - component generic mapper containing render parameters for adapters
91
95
  * @param {() => TFormValues<unknown>} options.getFormValues, - form instance function that builds onData parameter payload from fields
92
96
  */
93
- constructor({ schemaComponent, config, path, children, validateVisibility, resetValue, resetProperty, templateSubject$, fieldEventSubject$, dataSubject$, formValidNotification$, mapper, getFormValues, submitEvent, }: {
97
+ constructor({ schemaComponent, config, path, children, validateVisibility, resetValue, resetProperty, templateSubject$, fieldEventSubject$, dataSubject$, formValidNotification$, mountSubject$, mapper, getFormValues, submitEvent, visibility, }: {
94
98
  schemaComponent: IComponentSchema;
95
99
  config?: TSchemaFormConfig;
96
100
  path?: string;
@@ -115,11 +119,18 @@ declare class FormField {
115
119
  event: TEvents;
116
120
  }>;
117
121
  formValidNotification$: Subject<Pick<TFormValidationPayload, 'fieldTrigger'>>;
122
+ mountSubject$: Subject<{
123
+ key: string;
124
+ status: boolean;
125
+ }>;
118
126
  mapper: TMapper<unknown>;
119
127
  getFormValues: () => TFormValues<unknown>;
128
+ visibility?: boolean;
120
129
  });
121
130
  /**
122
131
  * method to initialize all recycled Subjects and initialize Observers on field instance creation or rerender
132
+ * due to some visibility conditions unmounts the field from the adapter if they are children of it and avoid
133
+ * emissions to unsubscribed fields
123
134
  */
124
135
  initializeObservers(): void;
125
136
  /**
@@ -134,6 +145,13 @@ declare class FormField {
134
145
  * @param {Record<string, unknown>} props - The new properties to be set.
135
146
  */
136
147
  set props(props: Record<string, unknown>);
148
+ /**
149
+ * Static function to remove templates form the component props that will be shown when
150
+ * the field mounts and the template routine executes, to be used on the adapter
151
+ *
152
+ * @param {unknown} props - the properties from the adapter components.
153
+ */
154
+ static filterProps(props: unknown): unknown;
137
155
  /**
138
156
  * Retrieves the current state value of the form field.
139
157
  *
@@ -199,6 +217,19 @@ declare class FormField {
199
217
  * @param {TApiResponse} response - The new API response data to be set.
200
218
  */
201
219
  set api(response: TApiResponse);
220
+ /**
221
+ * Retrieves the mounted status of the field.
222
+ *
223
+ * @returns {boolean} - the mounted status of the field.
224
+ */
225
+ get mounted(): boolean;
226
+ /**
227
+ * sets the mountedStatus and notifies the form that the field was mounted on the adapter
228
+ * and it's ready to be handled by the form instance
229
+ *
230
+ * @param {boolean} mountedStatus - the mounted status to be set from the mountField function.
231
+ */
232
+ set mounted(mountedStatus: boolean);
202
233
  /**
203
234
  * Mounts the form field by initializing necessary subjects and combining their streams.
204
235
  *
@@ -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;