From 0f79e2345fe9d96d8c20eef845dc3e0e4f87d422 Mon Sep 17 00:00:00 2001 From: sumeyye Date: Tue, 30 Dec 2025 14:06:36 +0300 Subject: [PATCH] fix: lookup search problems --- .../src/lib/lookup-search.component.html | 80 +++--- .../src/lib/lookup-search.component.scss | 9 +- .../lookup/src/lib/lookup-search.component.ts | 232 +++++++++--------- 3 files changed, 172 insertions(+), 149 deletions(-) diff --git a/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html b/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html index e795025aec..5e09e264e8 100644 --- a/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html +++ b/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html @@ -1,45 +1,61 @@
@if (label()) { - + }
- + @if (displayValue() && !disabled()) { - + }
@if (showDropdown() && !disabled()) { -
- @if (isLoading()) { -
- - {{ 'AbpUi::Loading' | abpLocalization }} -
- } @else if (searchResults().length > 0) { - @for (item of searchResults(); track item.key) { - + } + } @else if (displayValue()) { + @if (noResultsTemplate()) { + + } @else { +
+ {{ 'AbpUi::NoDataAvailableInDatatable' | abpLocalization }} +
+ } } - - } - } @else if (displayValue()) { - @if (noResultsTemplate()) { - - } @else { -
- {{ 'AbpUi::NoRecordsFound' | abpLocalization }}
- } - } -
} -
\ No newline at end of file + diff --git a/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss b/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss index 6fd1e2dc26..4b9a9ab12e 100644 --- a/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss +++ b/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss @@ -1,5 +1,8 @@ .abp-lookup-dropdown { - z-index: 1050; - max-height: 200px; - overflow-y: auto; + z-index: 1060; + max-height: 200px; + overflow-y: auto; + top: 100%; + margin-top: 0.25rem; + background-color: var(--lpx-content-bg); } diff --git a/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts b/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts index 9633b15ab8..b78bfa19ce 100644 --- a/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts +++ b/npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts @@ -1,15 +1,15 @@ import { - Component, - input, - output, - model, - signal, - OnInit, - ChangeDetectionStrategy, - TemplateRef, - contentChild, - DestroyRef, - inject, + Component, + input, + output, + model, + signal, + OnInit, + ChangeDetectionStrategy, + TemplateRef, + contentChild, + DestroyRef, + inject, } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CommonModule } from '@angular/common'; @@ -18,119 +18,123 @@ import { LocalizationPipe } from '@abp/ng.core'; import { Subject, Observable, debounceTime, distinctUntilChanged, of, finalize } from 'rxjs'; export interface LookupItem { - key: string; - displayName: string; - [key: string]: unknown; + key: string; + displayName: string; + [key: string]: unknown; } export type LookupSearchFn = (filter: string) => Observable; @Component({ - selector: 'abp-lookup-search', - templateUrl: './lookup-search.component.html', - styleUrl: './lookup-search.component.scss', - imports: [CommonModule, FormsModule, LocalizationPipe], - changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'abp-lookup-search', + templateUrl: './lookup-search.component.html', + styleUrl: './lookup-search.component.scss', + imports: [CommonModule, FormsModule, LocalizationPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class LookupSearchComponent implements OnInit { - private readonly destroyRef = inject(DestroyRef); - - readonly label = input(); - readonly placeholder = input(''); - readonly debounceTime = input(300); - readonly minSearchLength = input(0); - readonly displayKey = input('displayName' as keyof T); - readonly valueKey = input('key' as keyof T); - readonly disabled = input(false); - - readonly searchFn = input>(() => of([])); - - readonly selectedValue = model(''); - readonly displayValue = model(''); - - readonly itemSelected = output(); - readonly searchChanged = output(); - - readonly itemTemplate = contentChild>('itemTemplate'); - readonly noResultsTemplate = contentChild>('noResultsTemplate'); - - readonly searchResults = signal([]); - readonly showDropdown = signal(false); - readonly isLoading = signal(false); - - private readonly searchSubject = new Subject(); - - ngOnInit() { - this.searchSubject.pipe( - debounceTime(this.debounceTime()), - distinctUntilChanged(), - takeUntilDestroyed(this.destroyRef) - ).subscribe(filter => { - this.performSearch(filter); - }); - } - - onSearchInput(filter: string) { - this.displayValue.set(filter); - this.showDropdown.set(true); - this.searchChanged.emit(filter); - - if (filter.length >= this.minSearchLength()) { - this.searchSubject.next(filter); - } else { - this.searchResults.set([]); - } - } - - onSearchFocus() { - this.showDropdown.set(true); - const currentFilter = this.displayValue() || ''; - if (currentFilter.length >= this.minSearchLength()) { - this.performSearch(currentFilter); - } - } - - onSearchBlur(event: FocusEvent) { - const relatedTarget = event.relatedTarget as HTMLElement; - if (!relatedTarget?.closest('.abp-lookup-dropdown')) { - this.showDropdown.set(false); - } - } - - selectItem(item: T) { - const displayKeyValue = String(item[this.displayKey()] ?? ''); - const valueKeyValue = String(item[this.valueKey()] ?? ''); - - this.displayValue.set(displayKeyValue); - this.selectedValue.set(valueKeyValue); - this.searchResults.set([]); - this.showDropdown.set(false); - this.itemSelected.emit(item); - } - - clearSelection() { - this.displayValue.set(''); - this.selectedValue.set(''); - this.searchResults.set([]); + private readonly destroyRef = inject(DestroyRef); + + readonly label = input(); + readonly placeholder = input(''); + readonly debounceTime = input(300); + readonly minSearchLength = input(0); + readonly displayKey = input('displayName' as keyof T); + readonly valueKey = input('key' as keyof T); + readonly disabled = input(false); + + readonly searchFn = input>(() => of([])); + + readonly selectedValue = model(''); + readonly displayValue = model(''); + + readonly itemSelected = output(); + readonly searchChanged = output(); + + readonly itemTemplate = contentChild>('itemTemplate'); + readonly noResultsTemplate = contentChild>('noResultsTemplate'); + + readonly searchResults = signal([]); + readonly showDropdown = signal(true); + readonly isLoading = signal(false); + + private readonly searchSubject = new Subject(); + + ngOnInit() { + this.searchSubject + .pipe( + debounceTime(this.debounceTime()), + distinctUntilChanged(), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(filter => { + this.performSearch(filter); + }); + } + + onSearchInput(filter: string) { + this.displayValue.set(filter); + this.showDropdown.set(true); + this.searchChanged.emit(filter); + + if (filter.length >= this.minSearchLength()) { + this.searchSubject.next(filter); + } else { + this.searchResults.set([]); } + } - private performSearch(filter: string) { - this.isLoading.set(true); - - this.searchFn()(filter).pipe( - takeUntilDestroyed(this.destroyRef), - finalize(() => this.isLoading.set(false)) - ).subscribe({ - next: results => { - this.searchResults.set(results); - }, - error: () => { - this.searchResults.set([]); - } - }); + onSearchFocus() { + this.showDropdown.set(true); + const currentFilter = this.displayValue() || ''; + if (currentFilter.length >= this.minSearchLength()) { + this.performSearch(currentFilter); } + } - getDisplayValue(item: T): string { - return String(item[this.displayKey()] ?? item[this.valueKey()] ?? ''); + onSearchBlur(event: FocusEvent) { + const relatedTarget = event.relatedTarget as HTMLElement; + if (!relatedTarget?.closest('.abp-lookup-dropdown')) { + this.showDropdown.set(false); } + } + + selectItem(item: T) { + const displayKeyValue = String(item[this.displayKey()] ?? ''); + const valueKeyValue = String(item[this.valueKey()] ?? ''); + + this.displayValue.set(displayKeyValue); + this.selectedValue.set(valueKeyValue); + this.searchResults.set([]); + this.showDropdown.set(false); + this.itemSelected.emit(item); + } + + clearSelection() { + this.displayValue.set(''); + this.selectedValue.set(''); + this.searchResults.set([]); + } + + private performSearch(filter: string) { + this.isLoading.set(true); + + this.searchFn()(filter) + .pipe( + takeUntilDestroyed(this.destroyRef), + finalize(() => this.isLoading.set(false)), + ) + .subscribe({ + next: results => { + this.searchResults.set(results); + }, + error: () => { + this.searchResults.set([]); + }, + }); + } + + getDisplayValue(item: T): string { + return String(item[this.displayKey()] ?? item[this.valueKey()] ?? ''); + } }