diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html index b50e0ecce8..d467fbe5f0 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.html +++ b/ui-ngx/src/app/shared/components/string-items-list.component.html @@ -30,7 +30,7 @@ close @@ -49,12 +48,11 @@ - - - - - {{ 'common.not-found' | translate }} - + @for (value of filteredValues | async; track value.value) { + + } @empty { + {{ 'common.not-found' | translate }} + } {{ hint }} diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.ts b/ui-ngx/src/app/shared/components/string-items-list.component.ts index a2fa28d0ff..a4a8ebd49b 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.ts +++ b/ui-ngx/src/app/shared/components/string-items-list.component.ts @@ -30,6 +30,7 @@ import { coerceArray, coerceBoolean } from '@shared/decorators/coercion'; import { Observable, of } from 'rxjs'; import { filter, mergeMap, share, tap } from 'rxjs/operators'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { isDefined } from '@core/utils'; export interface StringItemsOption { name: string; @@ -116,6 +117,9 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { @coerceArray() predefinedValues: StringItemsOption[]; + @Input() + fetchOptionsFn: (searchText?: string) => Observable>; + get itemsControl(): AbstractControl { return this.stringItemsForm.get('items'); } @@ -124,7 +128,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { return this.stringItemsForm.get('item'); } - onTouched = () => {}; + private onTouched = () => {}; private propagateChange: (value: any) => void = () => {}; private dirty = false; @@ -136,7 +140,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { } ngOnInit() { - if (this.predefinedValues) { + if (this.predefinedValues || isDefined(this.fetchOptionsFn)) { this.filteredValues = this.itemControl.valueChanges .pipe( tap((value) => { @@ -147,7 +151,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { } }), filter((value) => typeof value === 'string'), - mergeMap(name => this.fetchValues(name)), + mergeMap(name => this.fetchOptionsFn ? this.fetchOptionsFn(name) : this.fetchValues(name)), share() ); } @@ -199,19 +203,16 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { this.dirty = true; } - addItem(event: MatChipInputEvent): void { - const item = event.value?.trim() ?? ''; - if (item) { - if (this.predefinedValues) { - const findItems = this.predefinedValues - .filter(value => value.name.toLowerCase().includes(item.toLowerCase())); - if (findItems.length === 1) { - this.add(findItems[0]); - } - } else { - this.add({value: item, name: item}); - } + addOnBlur(event: FocusEvent) { + const target: HTMLElement = event.relatedTarget as HTMLElement; + if (target && target.tagName !== 'MAT-OPTION') { + this.addItem(this.stringItemInput.nativeElement.value ?? '') } + this.onTouched(); + } + + addOnEnd(event: MatChipInputEvent): void { + this.addItem(event.value ?? '') } removeItems(item: StringItemsOption) { @@ -239,6 +240,21 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { return values ? values.name : undefined; } + private addItem(value: string) { + const item = value.trim(); + if (item) { + if (this.predefinedValues) { + const findItems = this.predefinedValues + .filter(value => value.name.toLowerCase().includes(item.toLowerCase())); + if (findItems.length === 1) { + this.add(findItems[0]); + } + } else { + this.add({value: item, name: item}); + } + } + } + private add(item: StringItemsOption) { if (!this.modelValue || this.modelValue.indexOf(item.value) === -1) { if (!this.modelValue) {