- @for (item of selectionModel.items | filter: filterText:'name'; track item; let i = $index) {
+
+
@if (allowSelectNone || item.id) {
}
- }
-
+
+
}
@if (editing) {
@if ((selectionModel.items | filter: filterText:'name').length === 0 && createRef !== undefined) {
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts
index 2ecf95f2b..9dc5f019f 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts
@@ -1,3 +1,4 @@
+import { ScrollingModule } from '@angular/cdk/scrolling'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
@@ -64,7 +65,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
- imports: [NgxBootstrapIconsModule.pick(allIcons)],
+ imports: [NgxBootstrapIconsModule.pick(allIcons), ScrollingModule],
}).compileComponents()
hotkeyService = TestBed.inject(HotKeyService)
@@ -265,18 +266,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
expect(document.activeElement).toEqual(
component.listFilterTextInput.nativeElement
)
- expect(
- Array.from(
- (fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
- ).filter((b) => b.textContent.includes('Tag'))
- ).toHaveLength(2)
+ expect(component.buttonsViewport.getRenderedRange().end).toEqual(3) // all items shown
+
component.filterText = 'Tag2'
fixture.detectChanges()
- expect(
- Array.from(
- (fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
- ).filter((b) => b.textContent.includes('Tag'))
- ).toHaveLength(1)
+ expect(component.buttonsViewport.getRenderedRange().end).toEqual(1) // filtered
component.dropdown.close()
expect(component.filterText).toHaveLength(0)
}))
@@ -331,6 +325,8 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
.dispatchEvent(new MouseEvent('click')) // open
fixture.detectChanges()
tick(100)
+ component.buttonsViewport?.checkViewportSize()
+ fixture.detectChanges()
const filterInputEl: HTMLInputElement =
component.listFilterTextInput.nativeElement
expect(document.activeElement).toEqual(filterInputEl)
@@ -376,6 +372,8 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
.dispatchEvent(new MouseEvent('click')) // open
fixture.detectChanges()
tick(100)
+ component.buttonsViewport?.checkViewportSize()
+ fixture.detectChanges()
const filterInputEl: HTMLInputElement =
component.listFilterTextInput.nativeElement
expect(document.activeElement).toEqual(filterInputEl)
@@ -412,6 +410,8 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
.dispatchEvent(new MouseEvent('click')) // open
fixture.detectChanges()
tick(100)
+ component.buttonsViewport?.checkViewportSize()
+ fixture.detectChanges()
const filterInputEl: HTMLInputElement =
component.listFilterTextInput.nativeElement
expect(document.activeElement).toEqual(filterInputEl)
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
index ec5425630..f5b6ba89c 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
@@ -1,3 +1,7 @@
+import {
+ CdkVirtualScrollViewport,
+ ScrollingModule,
+} from '@angular/cdk/scrolling'
import { NgClass } from '@angular/common'
import {
Component,
@@ -627,18 +631,27 @@ export class FilterableDropdownSelectionModel {
NgxBootstrapIconsModule,
NgbDropdownModule,
NgClass,
+ ScrollingModule,
],
})
export class FilterableDropdownComponent
extends LoadingComponentWithPermissions
implements OnInit
{
+ public readonly FILTERABLE_BUTTON_HEIGHT_PX = 42
+
private filterPipe = inject(FilterPipe)
private hotkeyService = inject(HotKeyService)
@ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
@ViewChild('dropdown') dropdown: NgbDropdown
- @ViewChild('buttonItems') buttonItems: ElementRef
+ @ViewChild('buttonsViewport') buttonsViewport: CdkVirtualScrollViewport
+
+ private get renderedButtons(): Array