@fuentis/phoenix-ui 0.0.9-alpha.527 → 0.0.9-alpha.528
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/fesm2022/fuentis-phoenix-ui.mjs +86 -104
- package/fesm2022/fuentis-phoenix-ui.mjs.map +1 -1
- package/index.d.ts +47 -67
- package/package.json +1 -1
|
@@ -3783,33 +3783,45 @@ function getFieldType(control) {
|
|
|
3783
3783
|
}
|
|
3784
3784
|
}
|
|
3785
3785
|
|
|
3786
|
+
/**
|
|
3787
|
+
* MetaFormService is a small UI-state bus used by MetaForm/GroupsForm components.
|
|
3788
|
+
*
|
|
3789
|
+
* - formReadOnlyState: global read-only mode (Edit toggles it).
|
|
3790
|
+
* - formBuildCompleted: emits TRUE when MetaForm finished rebuilding controls & validators.
|
|
3791
|
+
*
|
|
3792
|
+
* Angular dev-mode NOTE:
|
|
3793
|
+
* Emitting "build completed = true" synchronously during the same change detection cycle
|
|
3794
|
+
* can cause NG0100 ExpressionChangedAfterItHasBeenCheckedError.
|
|
3795
|
+
* Therefore we emit TRUE in a macrotask (setTimeout 0).
|
|
3796
|
+
*/
|
|
3786
3797
|
class MetaFormService {
|
|
3787
3798
|
router = inject(Router);
|
|
3788
3799
|
formEvent = new Subject();
|
|
3789
3800
|
formReadOnlySubject = new BehaviorSubject(false);
|
|
3790
3801
|
formTabContent = new BehaviorSubject('FORM');
|
|
3791
3802
|
formDirtyStatus = new BehaviorSubject(false);
|
|
3803
|
+
/**
|
|
3804
|
+
* Build completion flag:
|
|
3805
|
+
* - false = rebuilding (controls/validators may change)
|
|
3806
|
+
* - true = stable and ready
|
|
3807
|
+
*/
|
|
3792
3808
|
formBuildCompletedSubject = new BehaviorSubject(false);
|
|
3809
|
+
/** Prevent stale timers from flipping the flag after a new rebuild already started. */
|
|
3810
|
+
buildDoneTimer = null;
|
|
3811
|
+
// Public streams
|
|
3793
3812
|
currentFormState = this.formEvent.asObservable();
|
|
3794
3813
|
formReadOnlyState = this.formReadOnlySubject.asObservable();
|
|
3795
3814
|
currentFormTabContent = this.formTabContent.asObservable();
|
|
3796
3815
|
currentFormDirtyStatus = this.formDirtyStatus.asObservable();
|
|
3797
|
-
/**
|
|
3798
|
-
* Notify when form is completed
|
|
3799
|
-
* @description Notifies you about when form bild is completed in meta-form component class. Usefull when you need to attach some listeners on form controls or any other action related to form when all controls are avaiable
|
|
3800
|
-
* @returns {boolean}
|
|
3801
|
-
*/
|
|
3802
3816
|
formBuildCompleted = this.formBuildCompletedSubject.asObservable();
|
|
3803
|
-
constructor() { }
|
|
3804
3817
|
setFormEvent(event) {
|
|
3805
3818
|
this.formEvent.next(event);
|
|
3806
3819
|
}
|
|
3807
3820
|
setFormReadOnlyState(state) {
|
|
3808
3821
|
this.formReadOnlySubject.next(state);
|
|
3822
|
+
// Optional query param sync (disabled by default):
|
|
3809
3823
|
// this.router.navigate([], {
|
|
3810
|
-
// queryParams: {
|
|
3811
|
-
// edit: state ? null : !state,
|
|
3812
|
-
// },
|
|
3824
|
+
// queryParams: { edit: state ? null : !state },
|
|
3813
3825
|
// queryParamsHandling: 'merge',
|
|
3814
3826
|
// });
|
|
3815
3827
|
}
|
|
@@ -3820,25 +3832,46 @@ class MetaFormService {
|
|
|
3820
3832
|
this.formDirtyStatus.next(status);
|
|
3821
3833
|
}
|
|
3822
3834
|
/**
|
|
3823
|
-
*
|
|
3824
|
-
*
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3835
|
+
* Call at the BEGINNING of each rebuild.
|
|
3836
|
+
* Safe to emit synchronously.
|
|
3837
|
+
*/
|
|
3838
|
+
startFormBuild() {
|
|
3839
|
+
if (this.buildDoneTimer != null) {
|
|
3840
|
+
clearTimeout(this.buildDoneTimer);
|
|
3841
|
+
this.buildDoneTimer = null;
|
|
3842
|
+
}
|
|
3843
|
+
this.formBuildCompletedSubject.next(false);
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* Call at the END of each rebuild.
|
|
3847
|
+
* Emit in a macrotask to avoid NG0100.
|
|
3848
|
+
*/
|
|
3849
|
+
finishFormBuild() {
|
|
3850
|
+
if (this.buildDoneTimer != null) {
|
|
3851
|
+
clearTimeout(this.buildDoneTimer);
|
|
3852
|
+
this.buildDoneTimer = null;
|
|
3853
|
+
}
|
|
3854
|
+
this.buildDoneTimer = setTimeout(() => {
|
|
3855
|
+
this.formBuildCompletedSubject.next(true);
|
|
3856
|
+
this.buildDoneTimer = null;
|
|
3857
|
+
}, 0);
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Backward compatibility for old callers.
|
|
3829
3861
|
*/
|
|
3830
3862
|
setFormBuildCompletition(isCompleted) {
|
|
3831
|
-
|
|
3863
|
+
if (!isCompleted)
|
|
3864
|
+
this.startFormBuild();
|
|
3865
|
+
else
|
|
3866
|
+
this.finishFormBuild();
|
|
3832
3867
|
}
|
|
3833
3868
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3834
3869
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormService, providedIn: 'root' });
|
|
3835
3870
|
}
|
|
3836
3871
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MetaFormService, decorators: [{
|
|
3837
3872
|
type: Injectable,
|
|
3838
|
-
args: [{
|
|
3839
|
-
|
|
3840
|
-
}]
|
|
3841
|
-
}], ctorParameters: () => [] });
|
|
3873
|
+
args: [{ providedIn: 'root' }]
|
|
3874
|
+
}] });
|
|
3842
3875
|
|
|
3843
3876
|
class MetaFormAbstract {
|
|
3844
3877
|
fb;
|
|
@@ -3848,8 +3881,10 @@ class MetaFormAbstract {
|
|
|
3848
3881
|
metaFormValues;
|
|
3849
3882
|
metaFormControls;
|
|
3850
3883
|
/**
|
|
3851
|
-
* Hook for wiring dependent fields (category -> color, entity -> assignees etc).
|
|
3852
|
-
*
|
|
3884
|
+
* Hook for wiring dependent fields (category -> color, entity -> assignees etc.).
|
|
3885
|
+
* Rebound safely when metadata changes, without duplicating subscriptions.
|
|
3886
|
+
*
|
|
3887
|
+
* Optionally return a cleanup function.
|
|
3853
3888
|
*/
|
|
3854
3889
|
setupDependencies;
|
|
3855
3890
|
loading = false;
|
|
@@ -3860,26 +3895,9 @@ class MetaFormAbstract {
|
|
|
3860
3895
|
onFormSubmit = new EventEmitter();
|
|
3861
3896
|
onFormCancel = new EventEmitter();
|
|
3862
3897
|
formActive$;
|
|
3863
|
-
/**
|
|
3864
|
-
* IMPORTANT:
|
|
3865
|
-
* Do not subscribe in the constructor, because @Input() metaForm arrives later.
|
|
3866
|
-
* We wire subscriptions in ngOnChanges when the real FormGroup instance is available.
|
|
3867
|
-
*/
|
|
3868
3898
|
formSub;
|
|
3869
|
-
/**
|
|
3870
|
-
* Dependency setup may create subscriptions. We allow cleanup by supporting
|
|
3871
|
-
* optional cleanup functions returned by setupDependencies().
|
|
3872
|
-
*/
|
|
3873
3899
|
dependencyCleanup = [];
|
|
3874
|
-
/**
|
|
3875
|
-
* Metadata signature used to detect when controls changed.
|
|
3876
|
-
* If the signature changes, dependencies are cleaned up and re-bound.
|
|
3877
|
-
*/
|
|
3878
3900
|
lastMetaSignature = '';
|
|
3879
|
-
/**
|
|
3880
|
-
* Subscriptions used to remove server-side validation errors when the user edits the field.
|
|
3881
|
-
* This prevents "sticky" errors blocking save after the value is corrected.
|
|
3882
|
-
*/
|
|
3883
3901
|
clearServerErrorSubs = new Map();
|
|
3884
3902
|
constructor(fb, metaService, translateService) {
|
|
3885
3903
|
this.fb = fb;
|
|
@@ -3888,29 +3906,22 @@ class MetaFormAbstract {
|
|
|
3888
3906
|
this.formActive$ = this.metaService.formReadOnlyState;
|
|
3889
3907
|
}
|
|
3890
3908
|
ngOnChanges(changes) {
|
|
3891
|
-
/**
|
|
3892
|
-
* Re-wire form subscription whenever the input form instance changes.
|
|
3893
|
-
* This is critical because the component does NOT own the FormGroup;
|
|
3894
|
-
* it is provided by the parent.
|
|
3895
|
-
*/
|
|
3896
3909
|
if (changes['metaForm']) {
|
|
3897
3910
|
this.rewireFormSubscription();
|
|
3898
3911
|
}
|
|
3899
|
-
/**
|
|
3900
|
-
* If metadata, initial values, or disabled flag changes, rebuild/sync the form.
|
|
3901
|
-
* Safe to call multiple times.
|
|
3902
|
-
*/
|
|
3903
3912
|
if (changes['metaFormControls'] ||
|
|
3904
3913
|
changes['metaFormValues'] ||
|
|
3905
|
-
changes['disableForm']
|
|
3906
|
-
|
|
3907
|
-
|
|
3914
|
+
changes['disableForm'] ||
|
|
3915
|
+
changes['setupDependencies']) {
|
|
3916
|
+
if (!this.metaForm) {
|
|
3917
|
+
throw new Error('[MetaFormAbstract] metaForm input is required. Parent must pass [metaForm].');
|
|
3918
|
+
}
|
|
3908
3919
|
this.syncForm();
|
|
3909
3920
|
}
|
|
3910
3921
|
}
|
|
3911
3922
|
/**
|
|
3912
3923
|
* Backwards compatible API:
|
|
3913
|
-
* Older code may call createForm(controls).
|
|
3924
|
+
* Older code may call createForm(controls).
|
|
3914
3925
|
*/
|
|
3915
3926
|
createForm(controls) {
|
|
3916
3927
|
if (controls)
|
|
@@ -3921,59 +3932,48 @@ class MetaFormAbstract {
|
|
|
3921
3932
|
this.syncForm();
|
|
3922
3933
|
}
|
|
3923
3934
|
/**
|
|
3924
|
-
*
|
|
3925
|
-
*
|
|
3926
|
-
*
|
|
3927
|
-
*
|
|
3928
|
-
*
|
|
3929
|
-
*
|
|
3930
|
-
*
|
|
3935
|
+
* Rebuild flow (stable + fast):
|
|
3936
|
+
* 1) startFormBuild() (sync)
|
|
3937
|
+
* 2) ensure controls & validators (no emits)
|
|
3938
|
+
* 3) patch values (emitEvent:false)
|
|
3939
|
+
* 4) enable/disable (emitEvent:false)
|
|
3940
|
+
* 5) bind dependencies (safe rebind)
|
|
3941
|
+
* 6) updateValueAndValidity (emitEvent:false)
|
|
3942
|
+
* 7) finishFormBuild() (macrotask -> avoids NG0100)
|
|
3931
3943
|
*/
|
|
3932
|
-
// MetaFormAbstract (samo bitne izmene – kompletno je kompatibilno sa tvojim kodom)
|
|
3933
3944
|
syncForm() {
|
|
3934
3945
|
const flatControls = this.flattenControls(this.metaFormControls);
|
|
3935
3946
|
const values = this.metaFormValues ?? null;
|
|
3936
|
-
|
|
3947
|
+
this.metaService.startFormBuild();
|
|
3937
3948
|
this.ensureControls(flatControls);
|
|
3938
|
-
// 2) Patch initial values WITHOUT firing valueChanges
|
|
3939
3949
|
if (values) {
|
|
3940
3950
|
this.metaForm.patchValue(values, { emitEvent: false });
|
|
3941
3951
|
}
|
|
3942
|
-
// 3) Enable/disable entire form without emitting
|
|
3943
3952
|
if (this.disableForm)
|
|
3944
3953
|
this.metaForm.disable({ emitEvent: false });
|
|
3945
3954
|
else
|
|
3946
3955
|
this.metaForm.enable({ emitEvent: false });
|
|
3947
|
-
// 4) Bind dependencies (safe rebind)
|
|
3948
3956
|
this.bindDependenciesIfNeeded(flatControls);
|
|
3949
|
-
//
|
|
3957
|
+
// Recompute validity WITHOUT emitting events during build.
|
|
3950
3958
|
this.metaForm.updateValueAndValidity({ emitEvent: false });
|
|
3951
|
-
//
|
|
3952
|
-
this.metaService.
|
|
3959
|
+
// Emit "build completed" AFTER CD cycle.
|
|
3960
|
+
this.metaService.finishFormBuild();
|
|
3953
3961
|
}
|
|
3954
|
-
/**
|
|
3955
|
-
* Supports both:
|
|
3956
|
-
* - Flat controls: [ {configuration:{key}}, ...]
|
|
3957
|
-
* - Grouped controls: [ { ctrl: [ ... ] }, ... ]
|
|
3958
|
-
*/
|
|
3959
3962
|
flattenControls(input) {
|
|
3960
3963
|
if (!Array.isArray(input))
|
|
3961
3964
|
return [];
|
|
3962
3965
|
const isFlat = input.every((x) => !!x?.configuration?.key);
|
|
3963
3966
|
if (isFlat)
|
|
3964
3967
|
return input;
|
|
3965
|
-
|
|
3966
|
-
|
|
3968
|
+
return input
|
|
3969
|
+
.flatMap((x) => (Array.isArray(x?.ctrl) ? x.ctrl : x))
|
|
3970
|
+
.filter((x) => !!x?.configuration?.key);
|
|
3967
3971
|
}
|
|
3968
3972
|
metaSignature(controls) {
|
|
3969
3973
|
return (controls ?? [])
|
|
3970
3974
|
.map((c) => `${c?.configuration?.key ?? ''}:${c?.configuration?.type ?? ''}`)
|
|
3971
3975
|
.join('|');
|
|
3972
3976
|
}
|
|
3973
|
-
/**
|
|
3974
|
-
* Ensures dependencies are bound exactly once per metadata signature.
|
|
3975
|
-
* If metadata changes (new controls, different keys), we clean up and re-bind.
|
|
3976
|
-
*/
|
|
3977
3977
|
bindDependenciesIfNeeded(flatControls) {
|
|
3978
3978
|
if (!this.setupDependencies)
|
|
3979
3979
|
return;
|
|
@@ -3989,7 +3989,6 @@ class MetaFormAbstract {
|
|
|
3989
3989
|
findMeta: (key) => flatControls.find((c) => c?.configuration?.key === key) ?? null,
|
|
3990
3990
|
};
|
|
3991
3991
|
const maybeCleanup = this.setupDependencies(ctx);
|
|
3992
|
-
// Allow setupDependencies to return a cleanup function (optional)
|
|
3993
3992
|
if (typeof maybeCleanup === 'function') {
|
|
3994
3993
|
this.dependencyCleanup.push(maybeCleanup);
|
|
3995
3994
|
}
|
|
@@ -4003,31 +4002,22 @@ class MetaFormAbstract {
|
|
|
4003
4002
|
catch { }
|
|
4004
4003
|
});
|
|
4005
4004
|
this.dependencyCleanup = [];
|
|
4006
|
-
this.lastMetaSignature = '';
|
|
4007
4005
|
}
|
|
4008
|
-
/**
|
|
4009
|
-
* Creates missing controls, applies validators, and removes stale controls.
|
|
4010
|
-
* Also attaches "clear server error on edit" subscriptions per control.
|
|
4011
|
-
*/
|
|
4012
4006
|
ensureControls(controls) {
|
|
4013
4007
|
(controls ?? []).forEach((control) => {
|
|
4014
4008
|
const key = control?.configuration?.key;
|
|
4015
4009
|
if (!key)
|
|
4016
4010
|
return;
|
|
4017
|
-
// Create control once
|
|
4018
4011
|
if (!this.metaForm.contains(key)) {
|
|
4019
4012
|
this.metaForm.addControl(key, this.fb.control(getFieldType(control)));
|
|
4020
4013
|
}
|
|
4021
|
-
// Apply sync validators
|
|
4022
4014
|
this.applyValidators(control);
|
|
4023
|
-
// Clear server-side errors when user edits this field
|
|
4024
4015
|
this.bindClearServerError(key);
|
|
4025
|
-
// Disable if metadata says so
|
|
4026
4016
|
if (control?.disable) {
|
|
4027
4017
|
this.metaForm.get(key)?.disable({ emitEvent: false });
|
|
4028
4018
|
}
|
|
4029
4019
|
});
|
|
4030
|
-
//
|
|
4020
|
+
// remove stale controls when metadata changes
|
|
4031
4021
|
const allowed = new Set((controls ?? [])
|
|
4032
4022
|
.map((c) => c?.configuration?.key)
|
|
4033
4023
|
.filter(Boolean));
|
|
@@ -4040,10 +4030,8 @@ class MetaFormAbstract {
|
|
|
4040
4030
|
});
|
|
4041
4031
|
}
|
|
4042
4032
|
/**
|
|
4043
|
-
* Removes
|
|
4044
|
-
*
|
|
4045
|
-
*
|
|
4046
|
-
* We DO NOT touch other errors (required, pattern, etc.).
|
|
4033
|
+
* Removes ONLY server-origin uniqueness errors on user edit.
|
|
4034
|
+
* Keeps required/pattern/email/etc.
|
|
4047
4035
|
*/
|
|
4048
4036
|
bindClearServerError(key) {
|
|
4049
4037
|
if (this.clearServerErrorSubs.has(key))
|
|
@@ -4059,17 +4047,14 @@ class MetaFormAbstract {
|
|
|
4059
4047
|
if (!hasServerUnique)
|
|
4060
4048
|
return;
|
|
4061
4049
|
const { uniqueEntry, unique, ...rest } = errors;
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4050
|
+
ctrl.setErrors(Object.keys(rest).length ? rest : null, {
|
|
4051
|
+
emitEvent: false,
|
|
4052
|
+
});
|
|
4053
|
+
// Here it's a user interaction, so emitting is safe and desired:
|
|
4065
4054
|
this.metaForm.updateValueAndValidity({ emitEvent: true });
|
|
4066
4055
|
});
|
|
4067
4056
|
this.clearServerErrorSubs.set(key, sub);
|
|
4068
4057
|
}
|
|
4069
|
-
/**
|
|
4070
|
-
* Applies synchronous validators based on metadata.
|
|
4071
|
-
* Uses emitEvent:false to avoid unnecessary loops.
|
|
4072
|
-
*/
|
|
4073
4058
|
applyValidators(control) {
|
|
4074
4059
|
const key = control?.configuration?.key;
|
|
4075
4060
|
if (!key)
|
|
@@ -4105,12 +4090,9 @@ class MetaFormAbstract {
|
|
|
4105
4090
|
ctrl.setValidators(validators);
|
|
4106
4091
|
ctrl.updateValueAndValidity({ emitEvent: false });
|
|
4107
4092
|
}
|
|
4108
|
-
/**
|
|
4109
|
-
* Subscribes MetaFormService to the correct FormGroup instance.
|
|
4110
|
-
* Must run after @Input metaForm is available.
|
|
4111
|
-
*/
|
|
4112
4093
|
rewireFormSubscription() {
|
|
4113
4094
|
this.formSub?.unsubscribe();
|
|
4095
|
+
// metaForm is required, but keep fallback safe
|
|
4114
4096
|
if (!this.metaForm) {
|
|
4115
4097
|
this.metaForm = this.fb.group({});
|
|
4116
4098
|
}
|