Fix keyup vs down bug

This commit is contained in:
shamoon 2024-04-03 01:26:29 -07:00
parent 500699d86f
commit da08a8744f
3 changed files with 121 additions and 113 deletions

View File

@ -4,7 +4,7 @@
<i-bs width="1em" height="1em" name="search"></i-bs>
<input #searchInput class="form-control form-control-sm" type="text" name="query"
autocomplete="off" placeholder="Search" aria-label="Search" i18n-placeholder
[(ngModel)]="query" (ngModelChange)="this.queryDebounce.next($event)" (keyup)="searchInputKeyDown($event)" ngbDropdownAnchor>
[(ngModel)]="query" (ngModelChange)="this.queryDebounce.next($event)" (keydown)="searchInputKeyDown($event)" ngbDropdownAnchor>
<div class="position-absolute top-50 end-0 translate-middle">
<span class="badge d-none d-lg-inline text-muted position-absolute top-50 start-100 translate-middle ms-n4 fw-normal">⌘K</span>
@if (loading) {
@ -59,7 +59,7 @@
</div>
</ng-template>
<div ngbDropdownMenu class="w-100 mh-75 overflow-y-scroll shadow-lg">
<div ngbDropdownMenu class="w-100 mh-75 overflow-y-scroll shadow-lg" (keydown)="dropdownKeyDown($event)">
@if (searchResults?.total === 0) {
<h6 class="dropdown-header" i18n="@@searchResults.noResults">No results</h6>
} @else {

View File

@ -169,7 +169,7 @@ describe('GlobalSearchComponent', () => {
component.primaryButtons.get(1).nativeElement,
'focus'
)
component.handleKeyboardEvent(
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(1)
@ -179,12 +179,12 @@ describe('GlobalSearchComponent', () => {
component.secondaryButtons.get(1).nativeElement,
'focus'
)
component.handleKeyboardEvent(
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowRight' })
)
expect(secondaryItemFocusSpy).toHaveBeenCalled()
component.handleKeyboardEvent(
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowLeft' })
)
expect(firstItemFocusSpy).toHaveBeenCalled()
@ -193,9 +193,7 @@ describe('GlobalSearchComponent', () => {
component.primaryButtons.get(0).nativeElement,
'focus'
)
component.handleKeyboardEvent(
new KeyboardEvent('keydown', { key: 'ArrowUp' })
)
component.dropdownKeyDown(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
expect(component['currentItemIndex']).toBe(0)
expect(zeroItemSpy).toHaveBeenCalled()
@ -203,22 +201,42 @@ describe('GlobalSearchComponent', () => {
component.searchInput.nativeElement,
'focus'
)
component.handleKeyboardEvent(
new KeyboardEvent('keydown', { key: 'ArrowUp' })
)
component.dropdownKeyDown(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
expect(component['currentItemIndex']).toBe(-1)
expect(inputFocusSpy).toHaveBeenCalled()
component.handleKeyboardEvent(
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
component['currentItemIndex'] = searchResults.total - 1
component['setCurrentItem']()
component.handleKeyboardEvent(
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(-1)
// Search input
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowUp' })
)
expect(component['currentItemIndex']).toBe(searchResults.total - 1)
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(0)
expect(zeroItemSpy).toHaveBeenCalled()
component.searchResults = { total: 1 } as any
const primaryActionSpy = jest.spyOn(component, 'primaryAction')
component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(primaryActionSpy).toHaveBeenCalled()
const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset')
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'Escape' })
)
expect(resetSpy).toHaveBeenCalled()
})
it('should search on query debounce', fakeAsync(() => {
@ -380,27 +398,6 @@ describe('GlobalSearchComponent', () => {
expect(focusSpy).toHaveBeenCalled()
})
it('should handle search input keyboard nav', () => {
component.searchResults = searchResults as any
component.resultsDropdown.open()
fixture.detectChanges()
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(0)
component.searchResults = { total: 1 } as any
const primaryActionSpy = jest.spyOn(component, 'primaryAction')
component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(primaryActionSpy).toHaveBeenCalled()
const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset')
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'Escape' })
)
expect(resetSpy).toHaveBeenCalled()
})
it('should reset on dropdown close', () => {
const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset')
component.onDropdownOpenChange(false)

View File

@ -65,37 +65,6 @@ export class GlobalSearchComponent {
if (event.key === 'k' && (event.ctrlKey || event.metaKey)) {
this.searchInput.nativeElement.focus()
}
if (
this.searchResults &&
this.resultsDropdown.isOpen() &&
document.activeElement !== this.searchInput.nativeElement
) {
if (event.key === 'ArrowDown') {
if (this.currentItemIndex < this.searchResults.total - 1) {
event.preventDefault()
this.currentItemIndex++
this.setCurrentItem()
} else {
event.preventDefault()
this.currentItemIndex = 0
this.setCurrentItem()
}
} else if (event.key === 'ArrowUp') {
if (this.currentItemIndex > 0) {
event.preventDefault()
this.currentItemIndex--
this.setCurrentItem()
} else {
this.searchInput.nativeElement.focus()
this.currentItemIndex = -1
}
} else if (event.key === 'ArrowRight') {
this.secondaryButtons.get(this.domIndex)?.nativeElement.focus()
} else if (event.key === 'ArrowLeft') {
this.primaryButtons.get(this.domIndex).nativeElement.focus()
}
}
}
constructor(
@ -277,6 +246,14 @@ export class GlobalSearchComponent {
event.preventDefault()
this.currentItemIndex = 0
this.setCurrentItem()
} else if (
event.key === 'ArrowUp' &&
this.searchResults?.total &&
this.resultsDropdown.isOpen()
) {
event.preventDefault()
this.currentItemIndex = this.searchResults.total - 1
this.setCurrentItem()
} else if (
event.key === 'Enter' &&
this.searchResults?.total === 1 &&
@ -288,6 +265,40 @@ export class GlobalSearchComponent {
}
}
dropdownKeyDown(event: KeyboardEvent) {
if (
this.searchResults?.total &&
this.resultsDropdown.isOpen() &&
document.activeElement !== this.searchInput.nativeElement
) {
if (event.key === 'ArrowDown') {
event.preventDefault()
if (this.currentItemIndex < this.searchResults.total - 1) {
this.currentItemIndex++
this.setCurrentItem()
} else {
this.searchInput.nativeElement.focus()
this.currentItemIndex = -1
}
} else if (event.key === 'ArrowUp') {
event.preventDefault()
if (this.currentItemIndex > 0) {
this.currentItemIndex--
this.setCurrentItem()
} else {
this.searchInput.nativeElement.focus()
this.currentItemIndex = -1
}
} else if (event.key === 'ArrowRight') {
event.preventDefault()
this.secondaryButtons.get(this.domIndex)?.nativeElement.focus()
} else if (event.key === 'ArrowLeft') {
event.preventDefault()
this.primaryButtons.get(this.domIndex).nativeElement.focus()
}
}
}
public onDropdownOpenChange(open: boolean) {
if (!open) {
this.reset()