Fix (beta): dont include hidden buttons in virtual scroll container height (#13194)

This commit is contained in:
shamoon
2026-07-22 08:42:31 -07:00
committed by GitHub
parent d113826cf8
commit e106618c39
3 changed files with 50 additions and 22 deletions
@@ -34,30 +34,28 @@
</div>
@if (selectionModel.items) {
<cdk-virtual-scroll-viewport class="items" [itemSize]="FILTERABLE_BUTTON_HEIGHT_PX" #buttonsViewport [style.height.px]="scrollViewportHeight">
<div *cdkVirtualFor="let item of selectionModel.items | filter: filterText:'name'; trackBy: trackByItem; let i = index">
@if (allowSelectNone || item.id) {
<pngx-toggleable-dropdown-button
[item]="item"
[hideCount]="hideCount(item)"
[opacifyCount]="!editing"
[state]="selectionModel.get(item.id)"
[count]="getUpdatedDocumentCount(item.id)"
(toggled)="selectionModel.toggle(item.id)"
(exclude)="excludeClicked(item.id)"
[disabled]="disabled">
</pngx-toggleable-dropdown-button>
}
<div *cdkVirtualFor="let item of filteredItems; trackBy: trackByItem; let i = index">
<pngx-toggleable-dropdown-button
[item]="item"
[hideCount]="hideCount(item)"
[opacifyCount]="!editing"
[state]="selectionModel.get(item.id)"
[count]="getUpdatedDocumentCount(item.id)"
(toggled)="selectionModel.toggle(item.id)"
(exclude)="excludeClicked(item.id)"
[disabled]="disabled">
</pngx-toggleable-dropdown-button>
</div>
</cdk-virtual-scroll-viewport>
}
@if (editing) {
@if ((selectionModel.items | filter: filterText:'name').length === 0 && createRef !== undefined) {
@if (filteredItems.length === 0 && createRef !== undefined) {
<button class="list-group-item list-group-item-action bg-light" (click)="createClicked()" [disabled]="disabled">
<small class="ms-2"><ng-container i18n>Create</ng-container> "{{filterText}}"</small>
<i-bs width="1.5em" height="1em" name="plus"></i-bs>
</button>
}
@if ((selectionModel.items | filter: filterText:'name').length > 0) {
@if (filteredItems.length > 0) {
<button class="list-group-item list-group-item-action bg-light d-flex align-items-center" (click)="applyClicked()" [disabled]="!modelIsDirty() || disabled">
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty()}" i18n>Apply</small>
<i-bs width="1.5em" height="1em" name="arrow-right"></i-bs>
@@ -265,7 +265,9 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
expect(document.activeElement).toEqual(
component.listFilterTextInput.nativeElement
)
expect(component.buttonsViewport.getRenderedRange().end).toEqual(3) // all items shown
expect(component.buttonsViewport.getRenderedRange().end).toEqual(
items.length
) // all selectable items shown
component.filterText = 'Tag2'
fixture.detectChanges()
@@ -278,6 +280,29 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
expect(component.filterText).toHaveLength(0)
})
it('should omit disallowed null items from the virtual scroll viewport', async () => {
component.selectionModel.items = items
component.icon = 'tag-fill'
fixture.nativeElement
.querySelector('button')
.dispatchEvent(new MouseEvent('click')) // open
fixture.detectChanges()
await wait(100)
fixture.detectChanges()
component.buttonsViewport?.checkViewportSize()
fixture.detectChanges()
expect(component.filteredItems).toEqual(items)
expect(component.scrollViewportHeight).toEqual(
items.length * component.FILTERABLE_BUTTON_HEIGHT_PX
)
expect(
component.buttonsViewport.elementRef.nativeElement.querySelectorAll(
'.cdk-virtual-scroll-content-wrapper > div'
)
).toHaveLength(items.length)
})
it('should toggle & close on enter inside filter field if 1 item remains', async () => {
component.selectionModel.items = items
component.icon = 'tag-fill'
@@ -801,12 +801,17 @@ export class FilterableDropdownComponent
private keyboardIndex: number
public get filteredItems(): MatchingModel[] {
return this.filterPipe
.transform(this.items, this.filterText, 'name')
.filter((item) => this.allowSelectNone || Boolean(item.id))
}
public get scrollViewportHeight(): number {
const filteredLength = this.filterPipe.transform(
this.items,
this.filterText
).length
return Math.min(filteredLength * this.FILTERABLE_BUTTON_HEIGHT_PX, 400)
return Math.min(
this.filteredItems.length * this.FILTERABLE_BUTTON_HEIGHT_PX,
400
)
}
constructor() {
@@ -878,7 +883,7 @@ export class FilterableDropdownComponent
}
listFilterEnter(): void {
let filtered = this.filterPipe.transform(this.items, this.filterText)
const filtered = this.filteredItems
if (filtered.length == 1) {
this.selectionModel.toggle(filtered[0].id)
setTimeout(() => {