@yuuvis/client-framework 3.2.2 → 3.3.0

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.
@@ -48,40 +48,45 @@ class CatalogComponent extends AbstractMatFormField {
48
48
  super(...arguments);
49
49
  this.#system = inject(SystemService);
50
50
  this.#dRef = inject(DestroyRef);
51
- this.translate = inject(TranslateService);
51
+ this.#translate = inject(TranslateService);
52
52
  this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
53
53
  this.multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
54
54
  this._options = signal([], ...(ngDevMode ? [{ debugName: "_options" }] : /* istanbul ignore next */ []));
55
55
  this.options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
56
+ /**
57
+ * Additional semantics for the form element.
58
+ */
59
+ this.classifications = input([], ...(ngDevMode ? [{ debugName: "classifications" }] : /* istanbul ignore next */ []));
60
+ /**
61
+ * Possibles values are `EDIT` (default),`SEARCH`,`CREATE`. In search situation validation
62
+ * of the form element will be turned off, so you are able to enter search terms that do not
63
+ * meet the elements validators.
64
+ */
65
+ this.situation = input(...(ngDevMode ? [undefined, { debugName: "situation" }] : /* istanbul ignore next */ []));
66
+ this.ngControl = injectNgControl(this);
67
+ this.formCtrl = new FormControl(undefined);
56
68
  this.#optionsEffect = effect(() => {
57
69
  untracked(() => {
58
70
  this.#setOptions(this.options() || [], false);
59
71
  });
60
72
  }, ...(ngDevMode ? [{ debugName: "#optionsEffect" }] : /* istanbul ignore next */ []));
61
- /**
62
- * Additional semantics for the form element.
63
- */
64
- this.classifications = input([], ...(ngDevMode ? [{ debugName: "classifications" }] : /* istanbul ignore next */ []));
65
73
  this.#classificationEffect = effect(() => {
66
74
  const cls = this.classifications();
67
75
  untracked(() => {
68
- const ce = this.#system.getClassifications(cls).get(Classification.STRING_CATALOG);
69
- if (ce && ce.options) {
70
- this.#setOptions(ce.options, false);
76
+ const cle = this.#system
77
+ .getClassifications(cls)
78
+ .get(Classification.STRING_CATALOG);
79
+ if (cle?.options) {
80
+ this.#setOptions(cle.options, false);
71
81
  }
72
82
  });
73
83
  }, ...(ngDevMode ? [{ debugName: "#classificationEffect" }] : /* istanbul ignore next */ []));
74
- /**
75
- * Possibles values are `EDIT` (default),`SEARCH`,`CREATE`. In search situation validation of the form element will be turned off, so you are able to enter search terms that do not meet the elements validators.
76
- */
77
- this.situation = input(...(ngDevMode ? [undefined, { debugName: "situation" }] : /* istanbul ignore next */ []));
78
- this.ngControl = injectNgControl(this);
79
- this.fc = new FormControl(undefined);
80
84
  // eslint-disable-next-line @typescript-eslint/no-empty-function
81
85
  this.propagateChange = (_) => { };
82
86
  }
83
87
  #system;
84
88
  #dRef;
89
+ #translate;
85
90
  #optionsEffect;
86
91
  #classificationEffect;
87
92
  #onValueChange(val) {
@@ -90,57 +95,60 @@ class CatalogComponent extends AbstractMatFormField {
90
95
  }
91
96
  #setOptions(options, translate) {
92
97
  if (translate) {
93
- this._options.set(options.map((o) => ({
94
- label: this.translate.instant(o),
95
- value: o
98
+ this._options.set(options.map((opt) => ({
99
+ label: this.#translate.instant(opt),
100
+ value: opt
96
101
  })));
97
102
  }
98
103
  else {
99
- this._options.set(options.map((o) => ({
100
- label: o,
101
- value: o
104
+ this._options.set(options.map((opt) => ({
105
+ label: opt,
106
+ value: opt
102
107
  })));
103
108
  }
104
109
  }
105
110
  writeValue(value) {
106
111
  this.value = value;
107
- this.fc.setValue(value, { emitEvent: false });
112
+ this.formCtrl.setValue(value, { emitEvent: false });
108
113
  }
114
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
109
115
  registerOnChange(fn) {
110
116
  this.propagateChange = fn;
111
117
  }
112
118
  // eslint-disable-next-line @typescript-eslint/no-empty-function
113
- registerOnTouched(fn) { }
119
+ registerOnTouched() { }
114
120
  setDisabledState(isDisabled) {
115
121
  if (isDisabled) {
116
- this.fc.disable();
122
+ this.formCtrl.disable();
117
123
  }
118
124
  else {
119
- this.fc.enable();
125
+ this.formCtrl.enable();
120
126
  }
121
127
  this.disabled = isDisabled;
122
128
  }
123
129
  ngOnInit() {
124
130
  if (this.required)
125
- this.fc.setValidators(Validators.required);
126
- this.fc.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
127
- this.errorState = FormUtils.getErrorState(this.fc);
131
+ this.formCtrl.setValidators(Validators.required);
132
+ this.formCtrl.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe(() => {
133
+ this.errorState = FormUtils.getErrorState(this.formCtrl);
128
134
  if (this.ngControl?.control) {
129
- this.ngControl.control.setErrors(this.fc.errors);
135
+ this.ngControl.control.setErrors(this.formCtrl.errors);
130
136
  }
131
137
  });
132
- this.fc.updateValueAndValidity();
133
- this.fc.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => this.#onValueChange(v));
138
+ this.formCtrl.updateValueAndValidity();
139
+ this.formCtrl.valueChanges
140
+ .pipe(takeUntilDestroyed(this.#dRef))
141
+ .subscribe((value) => this.#onValueChange(value));
134
142
  }
135
143
  ngOnDestroy() {
136
144
  super.onNgOnDestroy();
137
145
  }
138
146
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CatalogComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
139
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: CatalogComponent, isStandalone: true, selector: "yuv-catalog", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: CatalogComponent }], usesInheritance: true, ngImport: i0, template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"fc\">\n @for (o of _options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] }); }
147
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: CatalogComponent, isStandalone: true, selector: "yuv-catalog", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: CatalogComponent }], usesInheritance: true, ngImport: i0, template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"formCtrl\">\n @for (o of _options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] }); }
140
148
  }
141
149
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CatalogComponent, decorators: [{
142
150
  type: Component,
143
- args: [{ selector: 'yuv-catalog', imports: [MatSelectModule, ReactiveFormsModule], providers: [{ provide: MatFormFieldControl, useExisting: CatalogComponent }], template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"fc\">\n @for (o of _options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n" }]
151
+ args: [{ selector: 'yuv-catalog', imports: [MatSelectModule, ReactiveFormsModule], providers: [{ provide: MatFormFieldControl, useExisting: CatalogComponent }], template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"formCtrl\">\n @for (o of _options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n" }]
144
152
  }], propDecorators: { readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], classifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "classifications", required: false }] }], situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }] } });
145
153
 
146
154
  class EditTableDataComponent {
@@ -527,25 +535,6 @@ class DatetimeComponent extends AbstractMatFormField {
527
535
  }
528
536
  #dRef;
529
537
  #localeEffect;
530
- // constructor() {
531
- // super();
532
- // this.fc.valueChanges.pipe(takeUntilDestroyed()).subscribe((v) => this.onValueChange(v));
533
- // this.fc.statusChanges.pipe(takeUntilDestroyed()).subscribe((v) => {
534
- // this.errorState = FormUtils.getErrorState(this.fc);
535
- // if (this.ngControl?.control) {
536
- // this.ngControl.control.setErrors(this.fc.errors);
537
- // }
538
- // // this.errorState = v === 'INVALID';
539
- // // this.ngControl?.control?.setErrors(this.errorState ? { datecontrol: true } : null);
540
- // });
541
- // }
542
- #setLabels() {
543
- this.labels = {
544
- calendarApply: this.translate.instant('yuv.form.element.datetime.calendar.select'),
545
- calendarCancel: this.translate.instant('yuv.form.element.datetime.calendar.cancel'),
546
- shortcut: { today: this.translate.instant('yuv.form.element.datetime.calendar.today') }
547
- };
548
- }
549
538
  openCalendar() {
550
539
  this.pickerCmp().openCalendar();
551
540
  }
@@ -563,14 +552,6 @@ class DatetimeComponent extends AbstractMatFormField {
563
552
  // this.fc.setValue(this.value, { emitEvent: false });
564
553
  this.fc.patchValue(this.value, { emitEvent: false, onlySelf: true });
565
554
  }
566
- #propagate(value) {
567
- let propagateValue = value;
568
- if (propagateValue && !this.withTime()) {
569
- // returns a string in the format 'yyyy-MM-dd' like expected for resolution date
570
- propagateValue = `${propagateValue.getFullYear()}-${(propagateValue.getMonth() + 1).toString().padStart(2, '0')}-${propagateValue.getDate().toString().padStart(2, '0')}`;
571
- }
572
- this.propagateChange(propagateValue);
573
- }
574
555
  registerOnChange(fn) {
575
556
  this.propagateChange = fn;
576
557
  }
@@ -591,15 +572,32 @@ class DatetimeComponent extends AbstractMatFormField {
591
572
  ngOnInit() {
592
573
  this.focusHandled.set(true);
593
574
  this.#setLabels();
594
- this.translate.onLangChange.subscribe((e) => this._locale.set(e.lang));
595
- this.fc.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => this.onValueChange(v));
596
- this.fc.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
575
+ this.translate.onLangChange.subscribe((evt) => this._locale.set(evt.lang));
576
+ this.fc.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((value) => this.onValueChange(value));
577
+ this.fc.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe(() => {
597
578
  this.errorState = FormUtils.getErrorState(this.fc);
598
579
  if (this.ngControl?.control) {
599
580
  this.ngControl.control.setErrors(this.fc.errors);
600
581
  }
601
582
  });
602
583
  }
584
+ #setLabels() {
585
+ this.labels = {
586
+ calendarApply: this.translate.instant('yuv.form.element.datetime.calendar.select'),
587
+ calendarCancel: this.translate.instant('yuv.form.element.datetime.calendar.cancel'),
588
+ shortcut: { today: this.translate.instant('yuv.form.element.datetime.calendar.today') }
589
+ };
590
+ }
591
+ #propagate(value) {
592
+ let propagateValue = value;
593
+ if (propagateValue && !this.withTime()) {
594
+ // returns a string in the format 'yyyy-MM-dd' like expected for resolution date
595
+ const month = (propagateValue.getMonth() + 1).toString().padStart(2, '0');
596
+ const day = propagateValue.getDate().toString().padStart(2, '0');
597
+ propagateValue = `${propagateValue.getFullYear()}-${month}-${day}`;
598
+ }
599
+ this.propagateChange(propagateValue);
600
+ }
603
601
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DatetimeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
604
602
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: DatetimeComponent, isStandalone: true, selector: "yuv-datetime", inputs: { locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, onlyFutureDates: { classPropertyName: "onlyFutureDates", publicName: "onlyFutureDates", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, calendar: { classPropertyName: "calendar", publicName: "calendar", isSignal: true, isRequired: false, transformFunction: null }, withTime: { classPropertyName: "withTime", publicName: "withTime", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: DatetimeComponent }], viewQueries: [{ propertyName: "pickerCmp", first: true, predicate: DatepickerComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<yuv-datepicker\n [calendar]=\"calendar()\"\n [disabled]=\"readonly()\"\n [withTime]=\"withTime()\"\n [locale]=\"_locale()\"\n [labels]=\"labels!\"\n [onlyFutureDates]=\"onlyFutureDates()\"\n [formControl]=\"fc\"\n></yuv-datepicker>\n", styles: [":host{display:flex;overflow-x:auto;--ymt-scrollbar-outer-size: 0px;--ymt-scrollbar-inner-size: 0px}:host yuv-datepicker{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: YuvDatepickerModule }, { kind: "component", type: i2$1.DatepickerComponent, selector: "yuv-datepicker", inputs: ["calendar", "readonly", "hour12", "locale", "labels", "withTime", "onlyFutureDates", "minDate", "maxDate", "disabled"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatDatepickerModule }] }); }
605
603
  }
@@ -777,6 +775,8 @@ class DynamicCatalogComponent extends AbstractMatFormField {
777
775
  #dRef;
778
776
  #translate;
779
777
  #currentLang;
778
+ #ctrlValue;
779
+ #optionIsActiveValidator;
780
780
  #optionsResource;
781
781
  constructor() {
782
782
  super();
@@ -787,14 +787,62 @@ class DynamicCatalogComponent extends AbstractMatFormField {
787
787
  this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
788
788
  this.multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
789
789
  this.#currentLang = signal(this.#translate.getCurrentLang(), ...(ngDevMode ? [{ debugName: "#currentLang" }] : /* istanbul ignore next */ []));
790
+ this.#ctrlValue = signal(undefined, ...(ngDevMode ? [{ debugName: "#ctrlValue" }] : /* istanbul ignore next */ []));
791
+ this.#optionIsActiveValidator = (control) => {
792
+ const val = control.value;
793
+ if (val === null || val === undefined || val === '' || (Array.isArray(val) && val.length === 0))
794
+ return null;
795
+ const opts = this.options();
796
+ const values = Array.isArray(val) ? val : [val];
797
+ const hasInvalid = values.some((v) => {
798
+ const opt = opts.find((o) => o.value === v);
799
+ return !opt || opt.disabled || opt.invalid;
800
+ });
801
+ return hasInvalid ? { invalidValue: true } : null;
802
+ };
790
803
  this.#optionsResource = rxResource({
791
- params: computed(() => ({ catalog: this.catalog(), locale: this.#currentLang() })),
792
- stream: ({ params }) => this.#catalogService.getEntries(params.catalog, { locale: params.locale }).pipe(map((res) => res.entries.map((entry) => ({
804
+ defaultValue: [],
805
+ params: computed(() => ({
806
+ catalog: this.catalog(),
807
+ situation: this.situation(),
808
+ locale: this.#currentLang()
809
+ })),
810
+ stream: ({ params }) => this.#catalogService
811
+ .getEntries(params.catalog, {
812
+ locale: params.locale,
813
+ includeInvalidEntries: true //params.situation === 'SEARCH' // include all entries in search situation
814
+ })
815
+ .pipe(map((res) => res.entries.map((entry) => ({
793
816
  value: entry.name,
817
+ disabled: entry._state !== 'active', // disable entries that are not active
794
818
  label: this.#resolveLabel(entry, params.locale)
795
- }))))
819
+ }))), map((res) => {
820
+ const current = this.value;
821
+ const currentValues = Array.isArray(current) ? current : current ? [current] : [];
822
+ // pre-set values that are not part of the entries (e.g. deleted entry)
823
+ // get appended to the options list and marked as invalid.
824
+ for (const v of currentValues) {
825
+ if (!res.find((opt) => opt.value === v)) {
826
+ res.push({ value: v, disabled: true, invalid: true, label: v });
827
+ }
828
+ }
829
+ return res;
830
+ }))
796
831
  });
797
832
  this.options = this.#optionsResource.value;
833
+ this.selectedDisplay = computed(() => {
834
+ const val = this.#ctrlValue();
835
+ if (val === null || val === undefined || val === '')
836
+ return [];
837
+ const opts = this.options();
838
+ const values = Array.isArray(val) ? val : [val];
839
+ return values.map((v) => {
840
+ const opt = opts.find((o) => o.value === v);
841
+ if (!opt)
842
+ return { value: v, label: v, invalid: true };
843
+ return { value: v, label: opt.label, invalid: !!opt.invalid || !!opt.disabled };
844
+ });
845
+ }, ...(ngDevMode ? [{ debugName: "selectedDisplay" }] : /* istanbul ignore next */ []));
798
846
  /**
799
847
  * Possibles values are `EDIT` (default),`SEARCH`,`CREATE`. In search situation
800
848
  * validation of the form element will be turned off, so you are able to enter
@@ -802,13 +850,19 @@ class DynamicCatalogComponent extends AbstractMatFormField {
802
850
  */
803
851
  this.situation = input(...(ngDevMode ? [undefined, { debugName: "situation" }] : /* istanbul ignore next */ []));
804
852
  this.ngControl = injectNgControl(this);
805
- this.ctrl = new FormControl(undefined);
853
+ this.ctrl = new FormControl(undefined, this.#optionIsActiveValidator);
806
854
  // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/explicit-module-boundary-types
807
855
  this.propagateChange = (fnc) => { };
808
856
  this.#translate.onLangChange.pipe(takeUntilDestroyed()).subscribe((evt) => this.#currentLang.set(evt.lang));
857
+ // re-run validation once options have resolved (writeValue may run before the catalog response arrives)
858
+ effect(() => {
859
+ this.options();
860
+ this.ctrl.updateValueAndValidity({ emitEvent: false });
861
+ });
809
862
  }
810
863
  writeValue(value) {
811
864
  this.value = value;
865
+ this.#ctrlValue.set(value);
812
866
  this.ctrl.setValue(value, { emitEvent: false });
813
867
  }
814
868
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
@@ -828,7 +882,7 @@ class DynamicCatalogComponent extends AbstractMatFormField {
828
882
  }
829
883
  ngOnInit() {
830
884
  if (this.required)
831
- this.ctrl.setValidators(Validators.required);
885
+ this.ctrl.setValidators([this.#optionIsActiveValidator, Validators.required]);
832
886
  this.ctrl.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe(() => {
833
887
  this.errorState = FormUtils.getErrorState(this.ctrl);
834
888
  if (this.ngControl?.control) {
@@ -844,6 +898,7 @@ class DynamicCatalogComponent extends AbstractMatFormField {
844
898
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
845
899
  #onValueChange(val) {
846
900
  this.value = val;
901
+ this.#ctrlValue.set(val);
847
902
  this.propagateChange(this.value);
848
903
  }
849
904
  #resolveLabel(entry, locale) {
@@ -851,11 +906,11 @@ class DynamicCatalogComponent extends AbstractMatFormField {
851
906
  return locs.find((loc) => loc.locale === locale)?.label || locs[0]?.label || entry.name;
852
907
  }
853
908
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DynamicCatalogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
854
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DynamicCatalogComponent, isStandalone: true, selector: "yuv-dynamic-catalog", inputs: { catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: DynamicCatalogComponent }], usesInheritance: true, ngImport: i0, template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"ctrl\">\n @for (o of options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] }); }
909
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DynamicCatalogComponent, isStandalone: true, selector: "yuv-dynamic-catalog", inputs: { catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: DynamicCatalogComponent }], usesInheritance: true, ngImport: i0, template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"ctrl\">\n <mat-select-trigger>\n @if (multiple()) {\n @for (s of selectedDisplay(); track s.value; let last = $last) {\n <span [class.invalid]=\"s.invalid\">{{ s.label }}</span>@if (!last) {<span>, </span>}\n }\n } @else {\n @if (selectedDisplay()[0]; as s) {\n <span [class.invalid]=\"s.invalid\">{{ s.label }}</span>\n }\n }\n </mat-select-trigger>\n @for (o of options(); track o.value) {\n <mat-option [disabled]=\"o.disabled\" [class.invalid]=\"o.invalid\" [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n", styles: ["mat-option.invalid,.invalid{color:var(--ymt-danger)}\n"], dependencies: [{ kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i1.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] }); }
855
910
  }
856
911
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DynamicCatalogComponent, decorators: [{
857
912
  type: Component,
858
- args: [{ selector: 'yuv-dynamic-catalog', imports: [MatSelectModule, ReactiveFormsModule], providers: [{ provide: MatFormFieldControl, useExisting: DynamicCatalogComponent }], template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"ctrl\">\n @for (o of options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n" }]
913
+ args: [{ selector: 'yuv-dynamic-catalog', imports: [MatSelectModule, ReactiveFormsModule], providers: [{ provide: MatFormFieldControl, useExisting: DynamicCatalogComponent }], template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"ctrl\">\n <mat-select-trigger>\n @if (multiple()) {\n @for (s of selectedDisplay(); track s.value; let last = $last) {\n <span [class.invalid]=\"s.invalid\">{{ s.label }}</span>@if (!last) {<span>, </span>}\n }\n } @else {\n @if (selectedDisplay()[0]; as s) {\n <span [class.invalid]=\"s.invalid\">{{ s.label }}</span>\n }\n }\n </mat-select-trigger>\n @for (o of options(); track o.value) {\n <mat-option [disabled]=\"o.disabled\" [class.invalid]=\"o.invalid\" [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n", styles: ["mat-option.invalid,.invalid{color:var(--ymt-danger)}\n"] }]
859
914
  }], ctorParameters: () => [], propDecorators: { catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: true }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }] } });
860
915
 
861
916
  /**