mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-2002] added ARROW support for search autocomplete results (#2732)
* [ADF-2002] added arrow up and down feature * [ADF-2002] fixed search bar animation and added arrow support * [ADF-2002] added some test for arrow manage
This commit is contained in:
@@ -10,13 +10,15 @@
|
||||
</a>
|
||||
<mat-form-field class="adf-input-form-field-divider">
|
||||
<input matInput
|
||||
#inputSearch
|
||||
[type]="inputType"
|
||||
[autocomplete]="getAutoComplete()"
|
||||
id="adf-control-input"
|
||||
[(ngModel)]="searchTerm"
|
||||
(focus)="activateToolbar()"
|
||||
(focus)="activateToolbar($event)"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.escape)="toggleSearchBar()"
|
||||
(keyup.arrowdown)="selectFirstResult()"
|
||||
(ngModelChange)="inputChange($event)"
|
||||
[searchAutocomplete]="auto"
|
||||
(keyup.enter)="searchSubmit($event)">
|
||||
@@ -36,6 +38,8 @@
|
||||
[tabindex]="0"
|
||||
(focus)="onFocus($event)"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.arrowdown)="onRowArrowDown($event)"
|
||||
(keyup.arrowup)="onRowArrowUp($event)"
|
||||
class="adf-search-autocomplete-item"
|
||||
(click)="elementClicked(item)"
|
||||
(keyup.enter)="elementClicked(item)">
|
||||
|
@@ -55,6 +55,7 @@
|
||||
}
|
||||
|
||||
&-search-autocomplete-item {
|
||||
|
||||
&:hover {
|
||||
background-color: mat-color($background, 'hover');
|
||||
opacity: 1;
|
||||
|
@@ -69,6 +69,13 @@ describe('SearchControlComponent', () => {
|
||||
TestBed.resetTestingModule();
|
||||
}));
|
||||
|
||||
function typeWordIntoSearchInput(word: string): void {
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = word;
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
describe('when input values are inserted', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
@@ -83,18 +90,12 @@ describe('SearchControlComponent', () => {
|
||||
expect(value).toBe('customSearchTerm');
|
||||
});
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'customSearchTerm';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('customSearchTerm');
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should update FAYT search when user inputs a valid term', async(() => {
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'customSearchTerm';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('customSearchTerm');
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
|
||||
|
||||
@@ -108,10 +109,7 @@ describe('SearchControlComponent', () => {
|
||||
}));
|
||||
|
||||
it('should NOT update FAYT term when user inputs an empty string as search term ', async(() => {
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = '';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('');
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
|
||||
|
||||
@@ -126,10 +124,7 @@ describe('SearchControlComponent', () => {
|
||||
component.searchChange.subscribe(value => {
|
||||
expect(value).toBe('cu');
|
||||
});
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'cu';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('cu');
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
});
|
||||
@@ -188,9 +183,7 @@ describe('SearchControlComponent', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
let enterKeyEvent: any = new Event('keyup');
|
||||
enterKeyEvent.keyCode = '13';
|
||||
inputDebugElement.nativeElement.dispatchEvent(enterKeyEvent);
|
||||
@@ -209,10 +202,7 @@ describe('SearchControlComponent', () => {
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -227,10 +217,7 @@ describe('SearchControlComponent', () => {
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(noResult));
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'NO RES';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('NO RES');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -245,9 +232,7 @@ describe('SearchControlComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'NO RES';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('NO RES');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -268,9 +253,7 @@ describe('SearchControlComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -290,9 +273,7 @@ describe('SearchControlComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -315,9 +296,7 @@ describe('SearchControlComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -356,15 +335,75 @@ describe('SearchControlComponent', () => {
|
||||
component.liveSearchEnabled = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#autocomplete-search-result-list')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should select the first item on autocomplete list when ARROW DOWN is pressed on input', async(() => {
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
typeWordIntoSearchInput('TEST');
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
|
||||
|
||||
inputDebugElement.triggerEventHandler('keyup.arrowdown', {});
|
||||
fixture.detectChanges();
|
||||
expect(document.activeElement.id).toBe('result_option_0');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should select the second item on autocomplete list when ARROW DOWN is pressed on list', async(() => {
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
|
||||
|
||||
inputDebugElement.triggerEventHandler('keyup.arrowdown', {});
|
||||
fixture.detectChanges();
|
||||
expect(document.activeElement.id).toBe('result_option_0');
|
||||
|
||||
let firstElement = debugElement.query(By.css('#result_option_0'));
|
||||
firstElement.triggerEventHandler('keyup.arrowdown', { target : firstElement.nativeElement});
|
||||
fixture.detectChanges();
|
||||
expect(document.activeElement.id).toBe('result_option_1');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should focus the input search when ARROW UP is pressed on the first list item', async(() => {
|
||||
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
|
||||
|
||||
inputDebugElement.triggerEventHandler('keyup.arrowdown', {});
|
||||
fixture.detectChanges();
|
||||
expect(document.activeElement.id).toBe('result_option_0');
|
||||
|
||||
let firstElement = debugElement.query(By.css('#result_option_0'));
|
||||
firstElement.triggerEventHandler('keyup.arrowup', { target : firstElement.nativeElement});
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(document.activeElement.id).toBe('adf-control-input');
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('search button', () => {
|
||||
@@ -434,10 +473,7 @@ describe('SearchControlComponent', () => {
|
||||
expect(item.entry.id).toBe('123');
|
||||
});
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -456,10 +492,7 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
@@ -476,10 +509,7 @@ describe('SearchControlComponent', () => {
|
||||
expect(component.searchTerm).toBe('TEST');
|
||||
});
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
typeWordIntoSearchInput('TEST');
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
|
@@ -17,11 +17,13 @@
|
||||
|
||||
import { AuthenticationService, ThumbnailService } from '@alfresco/adf-core';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output,
|
||||
QueryList, ViewEncapsulation, ViewChild, ViewChildren, ElementRef } from '@angular/core';
|
||||
import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { MatListItem } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-control',
|
||||
@@ -73,6 +75,15 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
optionClicked: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@ViewChild(SearchComponent)
|
||||
searchAutocomplete: SearchComponent;
|
||||
|
||||
@ViewChild('inputSearch')
|
||||
inputSearch: ElementRef;
|
||||
|
||||
@ViewChildren(MatListItem)
|
||||
private listResultElement: QueryList<MatListItem>;
|
||||
|
||||
searchTerm: string = '';
|
||||
subscriptAnimationState: string;
|
||||
|
||||
@@ -88,6 +99,10 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (this.subscriptAnimationState === 'inactive') {
|
||||
this.searchTerm = '';
|
||||
this.searchAutocomplete.resetResults();
|
||||
if ( document.activeElement.id === this.inputSearch.nativeElement.id) {
|
||||
this.inputSearch.nativeElement.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -165,15 +180,39 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
activateToolbar($event) {
|
||||
activateToolbar() {
|
||||
if (!this.isSearchBarActive()) {
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
selectFirstResult() {
|
||||
if ( this.listResultElement && this.listResultElement.length > 0) {
|
||||
let firstElement: MatListItem = <MatListItem> this.listResultElement.first;
|
||||
firstElement._getHostElement().focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowDown($event: KeyboardEvent): void {
|
||||
let nextElement: any = this.getNextElementSibling(<Element> $event.target);
|
||||
if (nextElement) {
|
||||
nextElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowUp($event: KeyboardEvent): void {
|
||||
let previousElement: any = this.getPreviousElementSibling(<Element> $event.target);
|
||||
if (previousElement) {
|
||||
previousElement.focus();
|
||||
}else {
|
||||
this.inputSearch.nativeElement.focus();
|
||||
this.focusSubject.next(new FocusEvent('focus'));
|
||||
}
|
||||
}
|
||||
|
||||
private setupFocusEventHandlers() {
|
||||
let focusEvents: Observable<FocusEvent> = this.focusSubject.asObservable()
|
||||
.distinctUntilChanged().debounceTime(50);
|
||||
.debounceTime(50);
|
||||
focusEvents.filter(($event: any) => {
|
||||
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout');
|
||||
}).subscribe(() => {
|
||||
@@ -181,4 +220,12 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private getNextElementSibling(node: Element): Element {
|
||||
return node.nextElementSibling;
|
||||
}
|
||||
|
||||
private getPreviousElementSibling(node: Element): Element {
|
||||
return node.previousElementSibling;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DOWN_ARROW, ENTER, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes';
|
||||
import { ENTER, ESCAPE } from '@angular/cdk/keycodes';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Directive,
|
||||
@@ -36,10 +36,6 @@ import { Subject } from 'rxjs/Subject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { SearchComponent } from './search.component';
|
||||
|
||||
export const AUTOCOMPLETE_OPTION_HEIGHT = 48;
|
||||
|
||||
export const AUTOCOMPLETE_PANEL_HEIGHT = 256;
|
||||
|
||||
export const SEARCH_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SearchTriggerDirective),
|
||||
@@ -150,14 +146,8 @@ export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
|
||||
} else if (keyCode === ENTER) {
|
||||
this.escapeEventStream.next();
|
||||
event.preventDefault();
|
||||
}else {
|
||||
let isArrowKey = keyCode === UP_ARROW || keyCode === DOWN_ARROW;
|
||||
if ( isArrowKey ) {
|
||||
if ( !this.panelOpen ) {
|
||||
this.openPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleInput(event: KeyboardEvent): void {
|
||||
@@ -168,6 +158,7 @@ export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
|
||||
this.searchPanel.keyPressedStream.next(inputValue);
|
||||
this.openPanel();
|
||||
} else {
|
||||
this.searchPanel.resetResults();
|
||||
this.closePanel();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user