Browse Source

UI: Fix string-items-list autocomplete selection and blur handling

Replace addOnBlur with matChipInputAddOnBlur directive and dedicated onOptionSelected handler for proper autocomplete behavior. Prevent mousedown default on options to avoid premature blur, and conditionally hide "not found" when user values are allowed.
pull/15273/head
Vladyslav_Prykhodko 7 months ago
parent
commit
66d263f81b
  1. 11
      ui-ngx/src/app/shared/components/string-items-list.component.html
  2. 45
      ui-ngx/src/app/shared/components/string-items-list.component.ts

11
ui-ngx/src/app/shared/components/string-items-list.component.html

@ -31,7 +31,6 @@
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon> <mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip-row> </mat-chip-row>
<input matInput type="text" <input matInput type="text"
(blur)="addOnBlur($event)"
placeholder="{{ placeholder }}" placeholder="{{ placeholder }}"
style="max-width: 300px;min-width: 250px" style="max-width: 300px;min-width: 250px"
#stringItemInput #stringItemInput
@ -40,19 +39,23 @@
#origin="matAutocompleteOrigin" #origin="matAutocompleteOrigin"
[matChipInputFor]="itemsChipList" [matChipInputFor]="itemsChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
matAutocompleteOrigin matAutocompleteOrigin
(matChipInputTokenEnd)="addOnEnd($event)" (matChipInputTokenEnd)="addOnEnd($event)"
[matAutocompleteConnectedTo]="origin" [matAutocompleteConnectedTo]="origin"
[matAutocomplete]="stringItemAutocomplete" [matAutocomplete]="stringItemAutocomplete"
[matAutocompleteDisabled]="!predefinedValues?.length"> [matAutocompleteDisabled]="!predefinedValues?.length && !fetchOptionsFn">
</mat-chip-grid> </mat-chip-grid>
<mat-autocomplete #stringItemAutocomplete="matAutocomplete" <mat-autocomplete #stringItemAutocomplete="matAutocomplete"
[displayWith]="displayValueFn" [displayWith]="displayValueFn"
(optionSelected)="onOptionSelected($event)"
class="tb-autocomplete"> class="tb-autocomplete">
@for (value of filteredValues | async; track value.value) { @for (value of filteredValues | async; track value.value) {
<mat-option [value]="value"><span [innerHTML]="value.name | highlight:searchText"></span></mat-option> <mat-option [value]="value" (mousedown)="$event.preventDefault()"><span [innerHTML]="value.name | highlight:searchText:true"></span></mat-option>
} @empty { } @empty {
<mat-option [value]="null">{{ 'common.not-found' | translate }}</mat-option> @if (!allowUserValue) {
<mat-option [value]="null" (mousedown)="$event.preventDefault()">{{ 'common.not-found' | translate }}</mat-option>
}
} }
</mat-autocomplete> </mat-autocomplete>
<mat-hint [hidden]="!hint"> <mat-hint [hidden]="!hint">

45
ui-ngx/src/app/shared/components/string-items-list.component.ts

@ -29,7 +29,7 @@ import { FloatLabelType, MatFormFieldAppearance, SubscriptSizing } from '@angula
import { coerceArray, coerceBoolean } from '@shared/decorators/coercion'; import { coerceArray, coerceBoolean } from '@shared/decorators/coercion';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { filter, mergeMap, share, tap } from 'rxjs/operators'; import { filter, mergeMap, share, tap } from 'rxjs/operators';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { isDefined, isUndefined } from '@core/utils'; import { isDefined, isUndefined } from '@core/utils';
export interface StringItemsOption { export interface StringItemsOption {
@ -37,18 +37,18 @@ export interface StringItemsOption {
value: any; value: any;
} }
@Component({ @Component({
selector: 'tb-string-items-list', selector: 'tb-string-items-list',
templateUrl: './string-items-list.component.html', templateUrl: './string-items-list.component.html',
styleUrls: [], styleUrls: [],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => StringItemsListComponent), useExisting: forwardRef(() => StringItemsListComponent),
multi: true multi: true
} }
], ],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
standalone: false standalone: false
}) })
export class StringItemsListComponent implements ControlValueAccessor, OnInit { export class StringItemsListComponent implements ControlValueAccessor, OnInit {
@ -149,9 +149,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
this.filteredValues = this.itemControl.valueChanges this.filteredValues = this.itemControl.valueChanges
.pipe( .pipe(
tap((value) => { tap((value) => {
if (value && typeof value !== 'string') { if (value === null) {
this.add(value);
} else if (value === null) {
this.clear(); this.clear();
} }
}), }),
@ -209,16 +207,17 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
this.dirty = true; this.dirty = true;
} }
addOnBlur(event: FocusEvent) { onOptionSelected(event: MatAutocompleteSelectedEvent): void {
const target: HTMLElement = event.relatedTarget as HTMLElement; if (event.option.value) {
if (target && target.tagName !== 'MAT-OPTION') { this.add(event.option.value);
this.addItem(this.stringItemInput.nativeElement.value ?? '') } else {
this.clear();
} }
this.onTouched();
} }
addOnEnd(event: MatChipInputEvent): void { addOnEnd(event: MatChipInputEvent): void {
this.addItem(event.value ?? '') this.addItem(event.value ?? '');
this.onTouched();
} }
removeItems(item: StringItemsOption) { removeItems(item: StringItemsOption) {
@ -295,6 +294,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
this.stringItemInput.nativeElement.value = value; this.stringItemInput.nativeElement.value = value;
this.itemControl.patchValue(value, {emitEvent: true}); this.itemControl.patchValue(value, {emitEvent: true});
setTimeout(() => { setTimeout(() => {
this.stringItemInput.nativeElement.value = value;
this.itemControl.patchValue(null, {emitEvent: false});
this.stringItemInput.nativeElement.blur(); this.stringItemInput.nativeElement.blur();
this.stringItemInput.nativeElement.focus(); this.stringItemInput.nativeElement.focus();
}, 0); }, 0);

Loading…
Cancel
Save