Better hover & keyboard interaction
This commit is contained in:
parent
7a73ce8ad3
commit
7737b24d7d
@ -9,11 +9,15 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ng-template #resultItemTemplate let-item="item" let-nameProp="nameProp" let-type="type" let-icon="icon">
|
<ng-template #resultItemTemplate let-item="item" let-nameProp="nameProp" let-type="type" let-icon="icon">
|
||||||
<div #resultItem ngbDropdownItem class="py-2 d-flex align-items-center focus-ring border-0 cursor-pointer" tabindex="-1" (click)="primaryAction(type, item)">
|
<div #resultItem ngbDropdownItem class="py-2 d-flex align-items-center focus-ring border-0 cursor-pointer" tabindex="-1"
|
||||||
|
(click)="primaryAction(type, item)"
|
||||||
|
(mouseenter)="onItemHover($event)">
|
||||||
<i-bs width="1.2em" height="1.2em" name="{{icon}}" class="me-2 text-muted"></i-bs>
|
<i-bs width="1.2em" height="1.2em" name="{{icon}}" class="me-2 text-muted"></i-bs>
|
||||||
<span class="text-truncate">{{item[nameProp]}}</span>
|
<span class="text-truncate">{{item[nameProp]}}</span>
|
||||||
<div class="btn-group ms-auto">
|
<div class="btn-group ms-auto">
|
||||||
<button #primaryButton type="button" class="btn btn-sm btn-outline-primary d-flex" (click)="primaryAction(type, item); $event.stopPropagation()">
|
<button #primaryButton type="button" class="btn btn-sm btn-outline-primary d-flex"
|
||||||
|
(click)="primaryAction(type, item); $event.stopPropagation()"
|
||||||
|
(mouseenter)="onButtonHover($event)">
|
||||||
@if (type === 'document' || type === 'workflow' || type === 'customField' || type === 'group' || type === 'user') {
|
@if (type === 'document' || type === 'workflow' || type === 'customField' || type === 'group' || type === 'user') {
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs>
|
<i-bs width="1em" height="1em" name="pencil"></i-bs>
|
||||||
<span class=""> <ng-container i18n>Edit</ng-container></span>
|
<span class=""> <ng-container i18n>Edit</ng-container></span>
|
||||||
@ -23,7 +27,9 @@
|
|||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
@if (type !== 'workflow' && type !== 'customField' && type !== 'group' && type !== 'user') {
|
@if (type !== 'workflow' && type !== 'customField' && type !== 'group' && type !== 'user') {
|
||||||
<button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex" (click)="secondaryAction(type, item); $event.stopPropagation()">
|
<button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex"
|
||||||
|
(click)="secondaryAction(type, item); $event.stopPropagation()"
|
||||||
|
(mouseenter)="onButtonHover($event)">
|
||||||
@if (type === 'document') {
|
@if (type === 'document') {
|
||||||
<i-bs width="1em" height="1em" name="download"></i-bs>
|
<i-bs width="1em" height="1em" name="download"></i-bs>
|
||||||
<span class=""> <ng-container i18n>Download</ng-container></span>
|
<span class=""> <ng-container i18n>Download</ng-container></span>
|
||||||
|
@ -33,6 +33,7 @@ import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-ac
|
|||||||
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||||
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||||
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
|
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
|
||||||
|
import { ElementRef } from '@angular/core'
|
||||||
|
|
||||||
const searchResults = {
|
const searchResults = {
|
||||||
total: 11,
|
total: 11,
|
||||||
@ -144,7 +145,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle keyboard events', () => {
|
it('should handle keyboard nav', () => {
|
||||||
const focusSpy = jest.spyOn(component.searchInput.nativeElement, 'focus')
|
const focusSpy = jest.spyOn(component.searchInput.nativeElement, 'focus')
|
||||||
component.handleKeyboardEvent(
|
component.handleKeyboardEvent(
|
||||||
new KeyboardEvent('keydown', { key: 'k', ctrlKey: true })
|
new KeyboardEvent('keydown', { key: 'k', ctrlKey: true })
|
||||||
@ -205,7 +206,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
expect(inputFocusSpy).toHaveBeenCalled()
|
expect(inputFocusSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should search', fakeAsync(() => {
|
it('should search on query debounce', fakeAsync(() => {
|
||||||
const query = 'test'
|
const query = 'test'
|
||||||
const searchSpy = jest.spyOn(searchService, 'globalSearch')
|
const searchSpy = jest.spyOn(searchService, 'globalSearch')
|
||||||
searchSpy.mockReturnValue(of({} as any))
|
searchSpy.mockReturnValue(of({} as any))
|
||||||
@ -216,7 +217,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
expect(dropdownOpenSpy).toHaveBeenCalled()
|
expect(dropdownOpenSpy).toHaveBeenCalled()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should perform primary action', () => {
|
it('should support primary action', () => {
|
||||||
const object = { id: 1 }
|
const object = { id: 1 }
|
||||||
const routerSpy = jest.spyOn(router, 'navigate')
|
const routerSpy = jest.spyOn(router, 'navigate')
|
||||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||||
@ -276,7 +277,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should perform secondary action', () => {
|
it('should support secondary action', () => {
|
||||||
const doc = searchResults.documents[0]
|
const doc = searchResults.documents[0]
|
||||||
const openSpy = jest.spyOn(window, 'open')
|
const openSpy = jest.spyOn(window, 'open')
|
||||||
component.secondaryAction('document', doc)
|
component.secondaryAction('document', doc)
|
||||||
@ -305,7 +306,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reset', () => {
|
it('should support reset', () => {
|
||||||
const debounce = jest.spyOn(component.queryDebounce, 'next')
|
const debounce = jest.spyOn(component.queryDebounce, 'next')
|
||||||
const closeSpy = jest.spyOn(component.resultsDropdown, 'close')
|
const closeSpy = jest.spyOn(component.resultsDropdown, 'close')
|
||||||
component['reset'](true)
|
component['reset'](true)
|
||||||
@ -315,7 +316,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
expect(closeSpy).toHaveBeenCalled()
|
expect(closeSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should set current item', () => {
|
it('should support focus current item', () => {
|
||||||
component.searchResults = searchResults as any
|
component.searchResults = searchResults as any
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
const focusSpy = jest.spyOn(
|
const focusSpy = jest.spyOn(
|
||||||
@ -327,7 +328,7 @@ describe('GlobalSearchComponent', () => {
|
|||||||
expect(focusSpy).toHaveBeenCalled()
|
expect(focusSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle search input keydown', () => {
|
it('should handle search input keyboard nav', () => {
|
||||||
component.searchResults = searchResults as any
|
component.searchResults = searchResults as any
|
||||||
component.resultsDropdown.open()
|
component.resultsDropdown.open()
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
@ -353,4 +354,24 @@ describe('GlobalSearchComponent', () => {
|
|||||||
component.onDropdownOpenChange(false)
|
component.onDropdownOpenChange(false)
|
||||||
expect(resetSpy).toHaveBeenCalled()
|
expect(resetSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should focus button on dropdown item hover', () => {
|
||||||
|
component.searchResults = searchResults as any
|
||||||
|
fixture.detectChanges()
|
||||||
|
const item: ElementRef = component.resultItems.first
|
||||||
|
const focusSpy = jest.spyOn(
|
||||||
|
component.primaryButtons.first.nativeElement,
|
||||||
|
'focus'
|
||||||
|
)
|
||||||
|
component.onItemHover({ currentTarget: item.nativeElement } as any)
|
||||||
|
expect(component['currentItemIndex']).toBe(0)
|
||||||
|
expect(focusSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should focus on button hover', () => {
|
||||||
|
const event = { currentTarget: { focus: jest.fn() } }
|
||||||
|
const focusSpy = jest.spyOn(event.currentTarget, 'focus')
|
||||||
|
component.onButtonHover(event as any)
|
||||||
|
expect(focusSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -47,6 +47,7 @@ export class GlobalSearchComponent {
|
|||||||
|
|
||||||
@ViewChild('searchInput') searchInput: ElementRef
|
@ViewChild('searchInput') searchInput: ElementRef
|
||||||
@ViewChild('resultsDropdown') resultsDropdown: NgbDropdown
|
@ViewChild('resultsDropdown') resultsDropdown: NgbDropdown
|
||||||
|
@ViewChildren('resultItem') resultItems: QueryList<ElementRef>
|
||||||
@ViewChildren('primaryButton') primaryButtons: QueryList<ElementRef>
|
@ViewChildren('primaryButton') primaryButtons: QueryList<ElementRef>
|
||||||
@ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef>
|
@ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef>
|
||||||
|
|
||||||
@ -214,6 +215,18 @@ export class GlobalSearchComponent {
|
|||||||
item.nativeElement.focus()
|
item.nativeElement.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onItemHover(event: MouseEvent) {
|
||||||
|
const item: ElementRef = this.resultItems
|
||||||
|
.toArray()
|
||||||
|
.find((item) => item.nativeElement === event.currentTarget)
|
||||||
|
this.currentItemIndex = this.resultItems.toArray().indexOf(item)
|
||||||
|
this.setCurrentItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
onButtonHover(event: MouseEvent) {
|
||||||
|
;(event.currentTarget as HTMLElement).focus()
|
||||||
|
}
|
||||||
|
|
||||||
public searchInputKeyDown(event: KeyboardEvent) {
|
public searchInputKeyDown(event: KeyboardEvent) {
|
||||||
if (
|
if (
|
||||||
event.key === 'ArrowDown' &&
|
event.key === 'ArrowDown' &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user