alfresco-ng2-components/lib/core/search-text/search-text-input.component.ts
Denys Vuika e94b2f99bd
[ADF-5406] SCSS and HTML template path fixes (#7063)
* remove useless mixin

* fix scss paths

* style fixes

* fix styles

* style fixes

* style fixes

* style fixes

* style fixes

* insights fixes

* css and template path fixes

* bug fixes
2021-06-01 09:21:00 +01:00

283 lines
9.1 KiB
TypeScript

/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ViewEncapsulation, Component, Input, OnDestroy, ViewChild, ElementRef, Output, EventEmitter, OnInit } from '@angular/core';
import { Subject, Observable, Subscription } from 'rxjs';
import { debounceTime, takeUntil, filter } from 'rxjs/operators';
import { Direction } from '@angular/cdk/bidi';
import { searchAnimation } from './animations';
import { UserPreferencesService } from '../services/user-preferences.service';
import { SearchTextStateEnum, SearchAnimationState, SearchAnimationDirection } from '../models/search-text-input.model';
@Component({
selector: 'adf-search-text-input',
templateUrl: './search-text-input.component.html',
animations: [searchAnimation],
encapsulation: ViewEncapsulation.None,
host: {
'class': 'adf-search-text-input'
}
})
export class SearchTextInputComponent implements OnInit, OnDestroy {
/** Toggles auto-completion of the search input field. */
@Input()
autocomplete: boolean = false;
/** Toggles whether to use an expanding search control. If false
* then a regular input is used.
*/
@Input()
expandable: boolean = true;
/** Type of the input field to render, e.g. "search" or "text" (default). */
@Input()
inputType: string = 'text';
/** Toggles "find-as-you-type" suggestions for possible matches. */
@Input()
liveSearchEnabled: boolean = true;
/** Trigger autocomplete results on input change. */
@Input()
searchAutocomplete: any = false;
/** Search term preselected */
@Input()
searchTerm: string = '';
/** Debounce time in milliseconds. */
@Input()
debounceTime: number = 0;
/** Listener for results-list events (focus, blur and focusout). */
@Input()
focusListener: Observable<FocusEvent>;
/** Collapse search bar on submit. */
@Input()
collapseOnSubmit: boolean = true;
/** Default state expanded or Collapsed. */
@Input()
defaultState: SearchTextStateEnum = SearchTextStateEnum.collapsed;
/** Emitted when the search term is changed. The search term is provided
* in the 'value' property of the returned object. If the term is less
* than three characters in length then it is truncated to an empty
* string.
*/
@Output()
searchChange: EventEmitter<string> = new EventEmitter();
/** Emitted when the search is submitted by pressing the ENTER key.
* The search term is provided as the value of the event.
*/
@Output()
submit: EventEmitter<any> = new EventEmitter();
/** Emitted when the result list is selected */
@Output()
selectResult: EventEmitter<any> = new EventEmitter();
/** Emitted when the result list is reset */
@Output()
reset: EventEmitter<boolean> = new EventEmitter();
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;
subscriptAnimationState: any;
animationStates: SearchAnimationDirection = {
ltr : {
active: { value: 'active', params: { 'margin-left': 13 } },
inactive: { value: 'inactive', params: { 'transform': 'translateX(82%)' } }
},
rtl: {
active: { value: 'active', params: { 'margin-right': 13 } },
inactive: { value: 'inactive', params: { 'transform': 'translateX(-82%)' } }
}
};
private dir = 'ltr';
private onDestroy$ = new Subject<boolean>();
private toggleSearch = new Subject<any>();
private focusSubscription: Subscription;
private valueChange = new Subject<string>();
constructor (
private userPreferencesService: UserPreferencesService
) {
this.toggleSearch
.pipe(
debounceTime(200),
takeUntil(this.onDestroy$)
)
.subscribe(() => {
if (this.expandable) {
this.subscriptAnimationState = this.toggleAnimation();
if (this.subscriptAnimationState.value === 'inactive') {
this.searchTerm = '';
this.reset.emit(true);
if ( document.activeElement.id === this.searchInput.nativeElement.id) {
this.searchInput.nativeElement.blur();
}
}
}
});
}
ngOnInit() {
this.userPreferencesService
.select('textOrientation')
.pipe(takeUntil(this.onDestroy$))
.subscribe((direction: Direction) => {
this.dir = direction;
this.subscriptAnimationState = this.getDefaultState(this.dir);
});
this.subscriptAnimationState = this.getDefaultState(this.dir);
this.setValueChangeHandler();
this.setupFocusEventHandlers();
}
applySearchFocus(animationDoneEvent) {
if (animationDoneEvent.toState === 'active' && this.defaultState !== SearchTextStateEnum.expanded) {
this.searchInput.nativeElement.focus();
}
}
getAutoComplete(): string {
return this.autocomplete ? 'on' : 'off';
}
private toggleAnimation() {
if (this.dir === 'ltr') {
return this.subscriptAnimationState.value === 'inactive' ?
{ value: 'active', params: { 'margin-left': 13 } } :
{ value: 'inactive', params: { 'transform': 'translateX(82%)' } };
} else {
return this.subscriptAnimationState.value === 'inactive' ?
{ value: 'active', params: { 'margin-right': 13 } } :
{ value: 'inactive', params: { 'transform': 'translateX(-82%)' } };
}
}
private getDefaultState(dir: string): SearchAnimationState {
if (this.dir) {
return this.getAnimationState(dir);
}
return this.animationStates.ltr.inactive;
}
private getAnimationState(dir: string): SearchAnimationState {
if ( this.expandable && this.defaultState === SearchTextStateEnum.expanded ) {
return this.animationStates[dir].active;
} else if ( this.expandable ) {
return this.animationStates[dir].inactive;
} else {
return { value: 'no-animation' };
}
}
private setupFocusEventHandlers() {
if ( this.focusListener ) {
const focusEvents: Observable<FocusEvent> = this.focusListener
.pipe(
debounceTime(50),
filter(($event: any) => {
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout' || $event.type === 'focus');
}),
takeUntil(this.onDestroy$)
);
this.focusSubscription = focusEvents.subscribe( (event: FocusEvent) => {
if ( event.type === 'focus') {
this.searchInput.nativeElement.focus();
} else {
this.toggleSearchBar();
}
});
}
}
private setValueChangeHandler() {
this.valueChange.pipe(
debounceTime(this.debounceTime),
takeUntil(this.onDestroy$)
).subscribe( (value: string) => {
this.searchChange.emit(value);
});
}
selectFirstResult($event) {
this.selectResult.emit($event);
}
onBlur($event) {
if (!$event.relatedTarget && this.defaultState === SearchTextStateEnum.collapsed) {
this.searchTerm = '';
this.subscriptAnimationState = this.animationStates[this.dir].inactive;
}
}
inputChange($event: any) {
this.valueChange.next($event);
}
toggleSearchBar() {
if (this.toggleSearch) {
this.toggleSearch.next();
}
}
searchSubmit(event: any) {
this.submit.emit(event);
if (this.collapseOnSubmit) {
this.toggleSearchBar();
}
}
activateToolbar(): boolean {
if (!this.isSearchBarActive()) {
this.toggleSearchBar();
}
return false;
}
isSearchBarActive(): boolean {
return this.subscriptAnimationState.value === 'active' && this.liveSearchEnabled;
}
ngOnDestroy() {
if (this.toggleSearch) {
this.toggleSearch.complete();
this.toggleSearch = null;
}
if (this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = null;
this.focusListener = null;
}
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
}