-<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
+<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown" (keydown)="listKeyDown($event)">
<button class="btn btn-sm" id="dropdown_{{name}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
<svg class="toolbaricon" fill="currentColor">
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
<input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
</div>
</div>
- <div *ngIf="selectionModel.items" class="items">
- <ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText">
- <app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" [disabled]="disabled"></app-toggleable-dropdown-button>
+ <div *ngIf="selectionModel.items" class="items" #buttonItems>
+ <ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText; let i = index">
+ <app-toggleable-dropdown-button
+ *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i)" [disabled]="disabled">
+ </app-toggleable-dropdown-button>
</ng-container>
</div>
<button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
export class FilterableDropdownComponent {
@ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
@ViewChild('dropdown') dropdown: NgbDropdown
+ @ViewChild('buttonItems') buttonItems: ElementRef
filterText: string
return this.title ? this.title.replace(/\s/g, '_').toLowerCase() : null
}
- getUpdatedDocumentCount(id: number) {
- if (this.documentCounts) {
- return this.documentCounts.find((c) => c.id === id)?.document_count
- }
- }
-
modelIsDirty: boolean = false
+ private keyboardIndex: number
+
constructor(private filterPipe: FilterPipe) {
this.selectionModelChange.subscribe((updatedModel) => {
this.modelIsDirty = updatedModel.isDirty()
let filtered = this.filterPipe.transform(this.items, this.filterText)
if (filtered.length == 1) {
this.selectionModel.toggle(filtered[0].id)
- if (this.editing) {
- this.applyClicked()
- } else {
- this.dropdown.close()
- }
+ setTimeout(() => {
+ if (this.editing) {
+ this.applyClicked()
+ } else {
+ this.dropdown.close()
+ }
+ }, 200)
}
}
this.selectionModel.reset(true)
this.selectionModelChange.emit(this.selectionModel)
}
+
+ getUpdatedDocumentCount(id: number) {
+ if (this.documentCounts) {
+ return this.documentCounts.find((c) => c.id === id)?.document_count
+ }
+ }
+
+ listKeyDown(event: KeyboardEvent) {
+ switch (event.key) {
+ case 'ArrowDown':
+ if (event.target instanceof HTMLInputElement) {
+ if (
+ !this.filterText ||
+ event.target.selectionStart === this.filterText.length
+ ) {
+ this.keyboardIndex = -1
+ this.focusNextButtonItem()
+ event.preventDefault()
+ }
+ } else if (event.target instanceof HTMLButtonElement) {
+ this.focusNextButtonItem()
+ event.preventDefault()
+ }
+ break
+ case 'ArrowUp':
+ if (event.target instanceof HTMLButtonElement) {
+ if (this.keyboardIndex === 0) {
+ this.listFilterTextInput.nativeElement.focus()
+ } else {
+ this.focusPreviousButtonItem()
+ }
+ event.preventDefault()
+ }
+ break
+ case 'Tab':
+ // just track the index in case user uses arrows
+ if (event.target instanceof HTMLInputElement) {
+ this.keyboardIndex = 0
+ } else if (event.target instanceof HTMLButtonElement) {
+ if (event.shiftKey) {
+ if (this.keyboardIndex > 0) {
+ this.focusPreviousButtonItem(false)
+ }
+ } else {
+ this.focusNextButtonItem(false)
+ }
+ }
+ default:
+ break
+ }
+ }
+
+ focusNextButtonItem(setFocus: boolean = true) {
+ this.keyboardIndex = Math.min(this.items.length - 1, this.keyboardIndex + 1)
+ if (setFocus) this.setButtonItemFocus()
+ }
+
+ focusPreviousButtonItem(setFocus: boolean = true) {
+ this.keyboardIndex = Math.max(0, this.keyboardIndex - 1)
+ if (setFocus) this.setButtonItemFocus()
+ }
+
+ setButtonItemFocus() {
+ this.buttonItems.nativeElement.children[
+ this.keyboardIndex
+ ]?.children[0].focus()
+ }
+
+ setButtonItemIndex(index: number) {
+ // just track the index in case user uses arrows
+ this.keyboardIndex = index
+ }
}