mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-534] Search - extract search text input from SearchControlComponent (#5213)
* [AAE-534] - add search cloud component * change doc file * more changes on doc file * remove empty scss file * add animation and more customizations * add preselect value property * fix unit tests * [AAE-534] Search - extract search text input from SearchControlComponent * rename component and fix build * fix import scss and lint * change in doc files * PR changes * more changes * add return type * fix unit test
This commit is contained in:
committed by
Eugenio Romano
parent
c11ce016fa
commit
f20b78a2c5
@@ -1,33 +0,0 @@
|
||||
/*!
|
||||
* @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 { trigger, transition, animate, style, state, AnimationTriggerMetadata } from '@angular/animations';
|
||||
|
||||
export const searchAnimation: AnimationTriggerMetadata = trigger('transitionMessages', [
|
||||
state('active', style({
|
||||
'margin-left': '{{ margin-left }}px',
|
||||
'margin-right': '{{ margin-right }}px',
|
||||
'transform': '{{ transform }}'
|
||||
}), { params: { 'margin-left': 0, 'margin-right': 0, 'transform': 'translateX(0%)' } }),
|
||||
state('inactive', style({
|
||||
'margin-left': '{{ margin-left }}px',
|
||||
'margin-right': '{{ margin-right }}px',
|
||||
'transform': '{{ transform }}'
|
||||
}), { params: { 'margin-left': 0, 'margin-right': 0, 'transform': 'translateX(0%)' } }),
|
||||
state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })),
|
||||
transition('active <=> inactive', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
|
||||
]);
|
@@ -1,81 +1,63 @@
|
||||
<div class="adf-search-container" [attr.state]="subscriptAnimationState.value">
|
||||
<div *ngIf="isLoggedIn()" [@transitionMessages]="subscriptAnimationState"
|
||||
(@transitionMessages.done)="applySearchFocus($event)">
|
||||
<button mat-icon-button
|
||||
*ngIf="expandable"
|
||||
id="adf-search-button"
|
||||
class="adf-search-button"
|
||||
[title]="'SEARCH.BUTTON.TOOLTIP' | translate"
|
||||
(click)="toggleSearchBar()"
|
||||
(keyup.enter)="toggleSearchBar()">
|
||||
<mat-icon [attr.aria-label]="'SEARCH.BUTTON.ARIA-LABEL' | translate">search</mat-icon>
|
||||
</button>
|
||||
<mat-form-field class="adf-input-form-field-divider">
|
||||
<input matInput
|
||||
#searchInput
|
||||
[attr.aria-label]="'SEARCH.INPUT.ARIA-LABEL' | translate"
|
||||
[attr.type]="inputType"
|
||||
[autocomplete]="getAutoComplete()"
|
||||
id="adf-control-input"
|
||||
[(ngModel)]="searchTerm"
|
||||
(focus)="activateToolbar()"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.escape)="toggleSearchBar()"
|
||||
(keyup.arrowdown)="selectFirstResult()"
|
||||
(ngModelChange)="inputChange($event)"
|
||||
[searchAutocomplete]="auto"
|
||||
(keyup.enter)="searchSubmit($event)">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isLoggedIn()">
|
||||
<adf-search-text-input #searchTextInput
|
||||
[searchTerm]= "searchTerm"
|
||||
[expandable]="expandable"
|
||||
[autocomplete]="autocomplete"
|
||||
[liveSearchEnabled]="liveSearchEnabled"
|
||||
[focusListener]="focusSubject.asObservable()"
|
||||
[searchAutocomplete]="auto"
|
||||
(searchChange)="inputChange($event)"
|
||||
(reset)="onReset($event)"
|
||||
(selectResult)="onSelectFirstResult()"
|
||||
(submit)="submit.emit($event)"></adf-search-text-input>
|
||||
|
||||
<adf-search #search
|
||||
#auto="searchAutocomplete"
|
||||
class="adf-search-result-autocomplete"
|
||||
[maxResults]="liveSearchMaxResults">
|
||||
<ng-template let-data>
|
||||
<mat-list *ngIf="isSearchBarActive()" id="autocomplete-search-result-list">
|
||||
<mat-list-item
|
||||
*ngFor="let item of data?.list?.entries; let idx = index"
|
||||
id="result_option_{{idx}}"
|
||||
[attr.data-automation-id]="'autocomplete_for_' + item.entry.name"
|
||||
[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)"
|
||||
(touchend)="elementClicked(item)">
|
||||
<!-- This is a comment -->
|
||||
<mat-icon mat-list-icon>
|
||||
<img [alt]="getMimeType(item)" [src]="getMimeTypeIcon(item)"/>
|
||||
</mat-icon>
|
||||
<h4 mat-line id="result_name_{{idx}}"
|
||||
*ngIf="highlight; else elseBlock"
|
||||
class="adf-search-fixed-text"
|
||||
[innerHtml]="item.entry.name | highlight: searchTerm">
|
||||
{{ item?.entry.name }}
|
||||
</h4>
|
||||
<ng-template #elseBlock>
|
||||
<h4 class="adf-search-fixed-text" mat-line id="result_name_{{idx}}"
|
||||
[innerHtml]="item.entry.name"></h4>
|
||||
</ng-template>
|
||||
<p mat-line class="adf-search-fixed-text"> {{item?.entry.createdByUser.displayName}} </p>
|
||||
</mat-list-item>
|
||||
<mat-list-item id="search_no_result"
|
||||
data-automation-id="search_no_result_found"
|
||||
*ngIf="data?.list?.entries.length === 0">
|
||||
<ng-content
|
||||
selector="adf-empty-search-result"
|
||||
*ngIf="isNoSearchTemplatePresent() else defaultNoResult">
|
||||
</ng-content>
|
||||
<ng-template #defaultNoResult>
|
||||
<p mat-line class="adf-search-fixed-text">{{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm:
|
||||
searchTerm} }}</p>
|
||||
</ng-template>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
<adf-search #search
|
||||
#auto="searchAutocomplete"
|
||||
class="adf-search-result-autocomplete"
|
||||
[maxResults]="liveSearchMaxResults">
|
||||
<ng-template let-data>
|
||||
<mat-list *ngIf="searchTextInput?.isSearchBarActive()" id="autocomplete-search-result-list">
|
||||
<mat-list-item
|
||||
*ngFor="let item of data?.list?.entries; let idx = index"
|
||||
id="result_option_{{idx}}"
|
||||
[attr.data-automation-id]="'autocomplete_for_' + item.entry.name"
|
||||
[tabindex]="0"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.arrowdown)="onRowArrowDown($event)"
|
||||
(keyup.arrowup)="onRowArrowUp($event)"
|
||||
class="adf-search-autocomplete-item"
|
||||
(click)="elementClicked(item)"
|
||||
(keyup.enter)="elementClicked(item)"
|
||||
(touchend)="elementClicked(item)">
|
||||
<!-- This is a comment -->
|
||||
<mat-icon mat-list-icon>
|
||||
<img [alt]="getMimeType(item)" [src]="getMimeTypeIcon(item)"/>
|
||||
</mat-icon>
|
||||
<h4 mat-line id="result_name_{{idx}}"
|
||||
*ngIf="highlight; else elseBlock"
|
||||
class="adf-search-fixed-text"
|
||||
[innerHtml]="item.entry.name | highlight: searchTerm">
|
||||
{{ item?.entry.name }}
|
||||
</h4>
|
||||
<ng-template #elseBlock>
|
||||
<h4 class="adf-search-fixed-text" mat-line id="result_name_{{idx}}"
|
||||
[innerHtml]="item.entry.name"></h4>
|
||||
</ng-template>
|
||||
<p mat-line class="adf-search-fixed-text"> {{item?.entry.createdByUser.displayName}} </p>
|
||||
</mat-list-item>
|
||||
<mat-list-item id="search_no_result"
|
||||
data-automation-id="search_no_result_found"
|
||||
*ngIf="data?.list?.entries.length === 0">
|
||||
<ng-content
|
||||
selector="adf-empty-search-result"
|
||||
*ngIf="isNoSearchTemplatePresent() else defaultNoResult">
|
||||
</ng-content>
|
||||
<ng-template #defaultNoResult>
|
||||
<p mat-line class="adf-search-fixed-text">{{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm:
|
||||
searchTerm} }}</p>
|
||||
</ng-template>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
</div>
|
||||
|
@@ -7,38 +7,8 @@
|
||||
$mat-menu-overlay-min-width: 112px !default; // 56 * 2
|
||||
$mat-menu-overlay-max-width: 280px !default; // 56 * 5
|
||||
|
||||
.adf-search-container {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.adf-search-button {
|
||||
left: -13px;
|
||||
}
|
||||
|
||||
[dir='rtl'] .adf-search-button {
|
||||
right: -13px;
|
||||
}
|
||||
|
||||
[dir='ltr'] .adf-search-button {
|
||||
left: -13px;
|
||||
}
|
||||
|
||||
.adf {
|
||||
|
||||
&-search-fixed-text {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&-input-form-field-divider {
|
||||
.mat-form-field-underline {
|
||||
background-color: mat-color($primary, 50);
|
||||
.mat-form-field-ripple {
|
||||
background-color: mat-color($primary, 50);
|
||||
}
|
||||
}
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&-search-result-autocomplete {
|
||||
@include mat-overridable-elevation(2);
|
||||
|
||||
@@ -74,8 +44,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-highlight {
|
||||
color: mat-color($primary, 900);
|
||||
}
|
||||
}
|
||||
|
@@ -16,19 +16,19 @@
|
||||
*/
|
||||
|
||||
import { Component, DebugElement, ViewChild } from '@angular/core';
|
||||
import { async, discardPeriodicTasks, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import {
|
||||
AuthenticationService,
|
||||
SearchService,
|
||||
setupTestBed,
|
||||
CoreModule,
|
||||
UserPreferencesService
|
||||
UserPreferencesService,
|
||||
SearchTextInputComponent
|
||||
} from '@alfresco/adf-core';
|
||||
import { ThumbnailService } from '@alfresco/adf-core';
|
||||
import { noResult, results } from '../../mock';
|
||||
import { SearchControlComponent } from './search-control.component';
|
||||
import { SearchTriggerDirective } from './search-trigger.directive';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { EmptySearchResultComponent } from './empty-search-result.component';
|
||||
import { of } from 'rxjs';
|
||||
@@ -51,6 +51,9 @@ export class SimpleSearchTestCustomEmptyComponent {
|
||||
@ViewChild(SearchControlComponent)
|
||||
searchComponent: SearchControlComponent;
|
||||
|
||||
@ViewChild(SearchTextInputComponent)
|
||||
searchTextInputComponent: SearchTextInputComponent;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@@ -82,7 +85,6 @@ describe('SearchControlComponent', () => {
|
||||
declarations: [
|
||||
SearchControlComponent,
|
||||
SearchComponent,
|
||||
SearchTriggerDirective,
|
||||
EmptySearchResultComponent,
|
||||
SimpleSearchTestCustomEmptyComponent
|
||||
],
|
||||
@@ -104,6 +106,7 @@ describe('SearchControlComponent', () => {
|
||||
element = fixture.nativeElement;
|
||||
|
||||
searchServiceSpy = spyOn(searchService, 'search').and.returnValue(of(''));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -139,24 +142,8 @@ describe('SearchControlComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should update FAYT search when user inputs a valid term', (done) => {
|
||||
typeWordIntoSearchInput('customSearchTerm');
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#result_option_0')).not.toBeNull();
|
||||
expect(element.querySelector('#result_option_1')).not.toBeNull();
|
||||
expect(element.querySelector('#result_option_2')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT update FAYT term when user inputs an empty string as search term ', (done) => {
|
||||
typeWordIntoSearchInput('');
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
|
||||
fixture.detectChanges();
|
||||
@@ -183,23 +170,6 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandable option false', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.expandable = false;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('search button should be hide', () => {
|
||||
const searchButton: any = element.querySelector('#adf-search-button');
|
||||
expect(searchButton).toBe(null);
|
||||
});
|
||||
|
||||
it('should not have animation', () => {
|
||||
expect(component.subscriptAnimationState.value).toBe('no-animation');
|
||||
});
|
||||
});
|
||||
|
||||
describe('component rendering', () => {
|
||||
|
||||
it('should display a text input field by default', async(() => {
|
||||
@@ -215,18 +185,6 @@ describe('SearchControlComponent', () => {
|
||||
expect(attr).toBe('off');
|
||||
}));
|
||||
|
||||
it('should display a search input field when specified', async(() => {
|
||||
component.inputType = 'search';
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('input[type="search"]').length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should set browser autocomplete to on when configured', async(() => {
|
||||
component.autocomplete = true;
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#adf-control-input').getAttribute('autocomplete')).toBe('on');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('autocomplete list', () => {
|
||||
@@ -241,8 +199,8 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should make autocomplete list control visible when search box has focus and there is a search result', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
fixture.detectChanges();
|
||||
|
||||
typeWordIntoSearchInput('TEST');
|
||||
@@ -255,8 +213,8 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should show autocomplete list noe results when search box has focus and there is search result with length 0', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
it('should show autocomplete list no results when search box has focus and there is search result with length 0', (done) => {
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(noResult));
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -271,8 +229,8 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should hide autocomplete list results when the search box loses focus', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
fixture.detectChanges();
|
||||
|
||||
const inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
@@ -292,8 +250,8 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should keep autocomplete list control visible when user tabs into results', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
fixture.detectChanges();
|
||||
|
||||
const inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
@@ -313,8 +271,8 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should close the autocomplete when user press ESCAPE', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
fixture.detectChanges();
|
||||
|
||||
const inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
@@ -337,7 +295,7 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should close the autocomplete when user press ENTER on input', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -361,7 +319,7 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should focus input element when autocomplete list is cancelled', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -393,108 +351,10 @@ describe('SearchControlComponent', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('search button', () => {
|
||||
|
||||
it('should NOT display a autocomplete list control when configured not to', fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
component.subscriptAnimationState.value = 'active';
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('active');
|
||||
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('inactive');
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('click on the search button should open the input box when is close', fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('active');
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('Search button should not change the input state too often', fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
component.subscriptAnimationState.value = 'active';
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('active');
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('inactive');
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('Search bar should close when user press ESC button', fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
const inputDebugElement = debugElement.query(By.css('#adf-control-input'));
|
||||
component.subscriptAnimationState.value = 'active';
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('active');
|
||||
|
||||
inputDebugElement.triggerEventHandler('keyup.escape', {});
|
||||
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toBe('inactive');
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('option click', () => {
|
||||
|
||||
it('should emit a option clicked event when item is clicked', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
const clickDisposable = component.optionClicked.subscribe((item) => {
|
||||
expect(item.entry.id).toBe('123');
|
||||
@@ -512,10 +372,10 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should set deactivate the search after element is clicked', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
const clickDisposable = component.optionClicked.subscribe(() => {
|
||||
expect(component.subscriptAnimationState.value).toBe('inactive');
|
||||
expect(component.searchTextInput.subscriptAnimationState.value).toBe('inactive');
|
||||
clickDisposable.unsubscribe();
|
||||
done();
|
||||
});
|
||||
@@ -531,11 +391,11 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should NOT reset the search term after element is clicked', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(component.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(JSON.parse(JSON.stringify(results))));
|
||||
const clickDisposable = component.optionClicked.subscribe(() => {
|
||||
expect(component.searchTerm).not.toBeFalsy();
|
||||
expect(component.searchTerm).toBe('TEST');
|
||||
expect(component.searchTextInput.searchTerm).not.toBeFalsy();
|
||||
expect(component.searchTextInput.searchTerm).toBe('TEST');
|
||||
clickDisposable.unsubscribe();
|
||||
done();
|
||||
});
|
||||
@@ -557,13 +417,16 @@ describe('SearchControlComponent', () => {
|
||||
fixtureCustom = TestBed.createComponent(SimpleSearchTestCustomEmptyComponent);
|
||||
componentCustom = fixtureCustom.componentInstance;
|
||||
elementCustom = fixtureCustom.nativeElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should display the custom no results when it is configured', (done) => {
|
||||
const noResultCustomMessage = 'BANDI IS NOTHING';
|
||||
spyOn(componentCustom.searchComponent, 'isSearchBarActive').and.returnValue(true);
|
||||
componentCustom.setCustomMessageForNoResult(noResultCustomMessage);
|
||||
spyOn(componentCustom.searchComponent, 'isLoggedIn').and.returnValue(true);
|
||||
fixtureCustom.detectChanges();
|
||||
spyOn(componentCustom.searchComponent.searchTextInput, 'isSearchBarActive').and.returnValue(true);
|
||||
searchServiceSpy.and.returnValue(of(noResult));
|
||||
componentCustom.setCustomMessageForNoResult(noResultCustomMessage);
|
||||
fixtureCustom.detectChanges();
|
||||
|
||||
const inputDebugElement = fixtureCustom.debugElement.query(By.css('#adf-control-input'));
|
||||
@@ -589,82 +452,14 @@ describe('SearchControlComponent', () => {
|
||||
it('should have positive transform translation', () => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'ltr');
|
||||
fixture.detectChanges();
|
||||
expect(component.subscriptAnimationState.params.transform).toBe('translateX(82%)');
|
||||
expect(component.searchTextInput.subscriptAnimationState.params.transform).toBe('translateX(82%)');
|
||||
});
|
||||
|
||||
it('should have negative transform translation ', () => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'rtl');
|
||||
fixture.detectChanges();
|
||||
expect(component.subscriptAnimationState.params.transform).toBe('translateX(-82%)');
|
||||
expect(component.searchTextInput.subscriptAnimationState.params.transform).toBe('translateX(-82%)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggle animation', () => {
|
||||
beforeEach(() => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should have margin-left set when active and direction is ltr', fakeAsync(() => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'ltr');
|
||||
fixture.detectChanges();
|
||||
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.params).toEqual({ 'margin-left': 13 });
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('should have positive transform translateX set when inactive and direction is ltr', fakeAsync(() => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'ltr');
|
||||
component.subscriptAnimationState.value = 'active';
|
||||
|
||||
fixture.detectChanges();
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.params).toEqual({ 'transform': 'translateX(82%)' });
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('should have margin-right set when active and direction is rtl', fakeAsync(() => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'rtl');
|
||||
fixture.detectChanges();
|
||||
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.params).toEqual({ 'margin-right': 13 });
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
|
||||
it('should have negative transform translateX set when inactive and direction is rtl', fakeAsync(() => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'rtl');
|
||||
component.subscriptAnimationState.value = 'active';
|
||||
|
||||
fixture.detectChanges();
|
||||
const searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
|
||||
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
expect(component.subscriptAnimationState.params).toEqual({ 'transform': 'translateX(-82%)' });
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -15,33 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthenticationService, ThumbnailService, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output,
|
||||
QueryList, ViewEncapsulation, ViewChild, ViewChildren, ElementRef, TemplateRef, ContentChild } from '@angular/core';
|
||||
import { AuthenticationService, ThumbnailService, SearchTextInputComponent } from '@alfresco/adf-core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, Output,
|
||||
QueryList, ViewEncapsulation, ViewChild, ViewChildren, TemplateRef, ContentChild } from '@angular/core';
|
||||
import { NodeEntry } from '@alfresco/js-api';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { searchAnimation } from './animations';
|
||||
import { MatListItem } from '@angular/material';
|
||||
import { EmptySearchResultComponent } from './empty-search-result.component';
|
||||
import { debounceTime, filter, takeUntil } from 'rxjs/operators';
|
||||
import { Direction } from '@angular/cdk/bidi';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-control',
|
||||
templateUrl: './search-control.component.html',
|
||||
styleUrls: ['./search-control.component.scss'],
|
||||
animations: [searchAnimation],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-control' }
|
||||
})
|
||||
export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
|
||||
/** Toggles whether to use an expanding search control. If false
|
||||
* then a regular input is used.
|
||||
*/
|
||||
@Input()
|
||||
expandable: boolean = true;
|
||||
export class SearchControlComponent implements OnDestroy {
|
||||
|
||||
/** Toggles highlighting of the search term in the results. */
|
||||
@Input()
|
||||
@@ -51,13 +41,19 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
inputType: string = 'text';
|
||||
|
||||
/** Toggles "find-as-you-type" suggestions for possible matches. */
|
||||
@Input()
|
||||
liveSearchEnabled: boolean = true;
|
||||
|
||||
/** Toggles auto-completion of the search input field. */
|
||||
@Input()
|
||||
autocomplete: boolean = false;
|
||||
|
||||
/** Toggles "find-as-you-type" suggestions for possible matches. */
|
||||
/** Toggles whether to use an expanding search control. If false
|
||||
* then a regular input is used.
|
||||
*/
|
||||
@Input()
|
||||
liveSearchEnabled: boolean = true;
|
||||
expandable: boolean = true;
|
||||
|
||||
/** Maximum number of results to show in the live search. */
|
||||
@Input()
|
||||
@@ -81,87 +77,34 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
optionClicked: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@ViewChild('searchTextInput')
|
||||
searchTextInput: SearchTextInputComponent;
|
||||
|
||||
@ViewChild('search')
|
||||
searchAutocomplete: SearchComponent;
|
||||
|
||||
@ViewChild('searchInput')
|
||||
searchInput: ElementRef;
|
||||
|
||||
@ViewChildren(MatListItem)
|
||||
private listResultElement: QueryList<MatListItem>;
|
||||
|
||||
@ContentChild(EmptySearchResultComponent)
|
||||
emptySearchTemplate: EmptySearchResultComponent;
|
||||
|
||||
searchTerm: string = '';
|
||||
subscriptAnimationState: any;
|
||||
focusSubject = new Subject<FocusEvent>();
|
||||
noSearchResultTemplate: TemplateRef <any> = null;
|
||||
searchTerm: string = '';
|
||||
|
||||
private toggleSearch = new Subject<any>();
|
||||
private focusSubject = new Subject<FocusEvent>();
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
private dir = 'ltr';
|
||||
|
||||
constructor(
|
||||
public authService: AuthenticationService,
|
||||
private thumbnailService: ThumbnailService,
|
||||
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.searchAutocomplete.resetResults();
|
||||
if ( document.activeElement.id === this.searchInput.nativeElement.id) {
|
||||
this.searchInput.nativeElement.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applySearchFocus(animationDoneEvent) {
|
||||
if (animationDoneEvent.toState === 'active') {
|
||||
this.searchInput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.userPreferencesService
|
||||
.select('textOrientation')
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((direction: Direction) => {
|
||||
this.dir = direction;
|
||||
this.subscriptAnimationState = this.getAnimationState();
|
||||
});
|
||||
|
||||
this.subscriptAnimationState = this.getAnimationState();
|
||||
this.setupFocusEventHandlers();
|
||||
}
|
||||
private thumbnailService: ThumbnailService
|
||||
) {}
|
||||
|
||||
isNoSearchTemplatePresent(): boolean {
|
||||
return this.emptySearchTemplate ? true : false;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.focusSubject) {
|
||||
this.focusSubject.complete();
|
||||
this.focusSubject = null;
|
||||
}
|
||||
|
||||
if (this.toggleSearch) {
|
||||
this.toggleSearch.complete();
|
||||
this.toggleSearch = null;
|
||||
}
|
||||
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
@@ -170,17 +113,9 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
return this.authService.isEcmLoggedIn();
|
||||
}
|
||||
|
||||
searchSubmit(event: any) {
|
||||
this.submit.emit(event);
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
|
||||
inputChange(event: any) {
|
||||
this.searchChange.emit(event);
|
||||
}
|
||||
|
||||
getAutoComplete(): string {
|
||||
return this.autocomplete ? 'on' : 'off';
|
||||
inputChange(value: string) {
|
||||
this.searchTerm = value;
|
||||
this.searchChange.emit(value);
|
||||
}
|
||||
|
||||
getMimeTypeIcon(node: NodeEntry): string {
|
||||
@@ -200,20 +135,10 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
isSearchBarActive() {
|
||||
return this.subscriptAnimationState.value === 'active' && this.liveSearchEnabled;
|
||||
}
|
||||
|
||||
toggleSearchBar() {
|
||||
if (this.toggleSearch) {
|
||||
this.toggleSearch.next();
|
||||
}
|
||||
}
|
||||
|
||||
elementClicked(item: any) {
|
||||
if (item.entry) {
|
||||
this.optionClicked.next(item);
|
||||
this.toggleSearchBar();
|
||||
this.focusSubject.next(new FocusEvent('blur'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,16 +147,13 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onBlur($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
activateToolbar() {
|
||||
if (!this.isSearchBarActive()) {
|
||||
this.toggleSearchBar();
|
||||
const nextElement: any = this.getNextElementSibling(<Element> $event.target);
|
||||
if (!nextElement && !this.isListElement($event)) {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
}
|
||||
|
||||
selectFirstResult() {
|
||||
onSelectFirstResult() {
|
||||
if ( this.listResultElement && this.listResultElement.length > 0) {
|
||||
const firstElement: MatListItem = <MatListItem> this.listResultElement.first;
|
||||
firstElement._getHostElement().focus();
|
||||
@@ -250,24 +172,18 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
if (previousElement) {
|
||||
previousElement.focus();
|
||||
} else {
|
||||
this.searchInput.nativeElement.focus();
|
||||
this.focusSubject.next(new FocusEvent('focus'));
|
||||
}
|
||||
}
|
||||
|
||||
private setupFocusEventHandlers() {
|
||||
const focusEvents: Observable<FocusEvent> = this.focusSubject
|
||||
.pipe(
|
||||
debounceTime(50),
|
||||
filter(($event: any) => {
|
||||
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout');
|
||||
}),
|
||||
takeUntil(this.onDestroy$)
|
||||
);
|
||||
onReset(status: boolean) {
|
||||
if (status) {
|
||||
this.searchAutocomplete.resetResults();
|
||||
}
|
||||
}
|
||||
|
||||
focusEvents.subscribe(() => {
|
||||
this.toggleSearchBar();
|
||||
});
|
||||
private isListElement($event: any): boolean {
|
||||
return $event.relatedTarget && $event.relatedTarget.children[0].className === 'mat-list-item-content';
|
||||
}
|
||||
|
||||
private getNextElementSibling(node: Element): Element {
|
||||
@@ -277,28 +193,4 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
private getPreviousElementSibling(node: Element): Element {
|
||||
return node.previousElementSibling;
|
||||
}
|
||||
|
||||
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 getAnimationState() {
|
||||
if (this.dir === 'ltr') {
|
||||
return this.expandable ?
|
||||
{ value: 'inactive', params: { 'transform': 'translateX(82%)' } } :
|
||||
{ value: 'no-animation' };
|
||||
} else {
|
||||
return this.expandable ?
|
||||
{ value: 'inactive', params: { 'transform': 'translateX(-82%)' } } :
|
||||
{ value: 'no-animation' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,222 +0,0 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* tslint:disable: no-input-rename no-use-before-declare no-input-rename */
|
||||
|
||||
import { ENTER, ESCAPE } from '@angular/cdk/keycodes';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Inject,
|
||||
Input,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
Optional
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Observable, Subject, Subscription, merge, of, fromEvent } from 'rxjs';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { filter, switchMap, takeUntil } from 'rxjs/operators';
|
||||
|
||||
export const SEARCH_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SearchTriggerDirective),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Directive selectors without adf- prefix will be deprecated on 3.0.0
|
||||
*/
|
||||
@Directive({
|
||||
// tslint:disable-next-line:directive-selector
|
||||
selector: `input[searchAutocomplete], textarea[searchAutocomplete]`,
|
||||
host: {
|
||||
'role': 'combobox',
|
||||
'[attr.autocomplete]': 'autocomplete',
|
||||
'aria-autocomplete': 'list',
|
||||
'[attr.aria-expanded]': 'panelOpen.toString()',
|
||||
'(blur)': 'onTouched()',
|
||||
'(input)': 'handleInput($event)',
|
||||
'(keydown)': 'handleKeydown($event)'
|
||||
},
|
||||
providers: [SEARCH_AUTOCOMPLETE_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
@Input('searchAutocomplete')
|
||||
searchPanel: SearchComponent;
|
||||
|
||||
@Input()
|
||||
autocomplete: string = 'off';
|
||||
|
||||
private _panelOpen: boolean = false;
|
||||
private closingActionsSubscription: Subscription;
|
||||
private escapeEventStream = new Subject<void>();
|
||||
|
||||
onChange: (value: any) => void = () => { };
|
||||
|
||||
onTouched = () => { };
|
||||
|
||||
constructor(private element: ElementRef,
|
||||
private ngZone: NgZone,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() @Inject(DOCUMENT) private document: any) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
|
||||
if (this.escapeEventStream) {
|
||||
this.escapeEventStream = null;
|
||||
}
|
||||
if ( this.closingActionsSubscription ) {
|
||||
this.closingActionsSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
get panelOpen(): boolean {
|
||||
return this._panelOpen && this.searchPanel.showPanel;
|
||||
}
|
||||
|
||||
openPanel(): void {
|
||||
this.searchPanel.isOpen = this._panelOpen = true;
|
||||
this.closingActionsSubscription = this.subscribeToClosingActions();
|
||||
}
|
||||
|
||||
closePanel(): void {
|
||||
if (this._panelOpen) {
|
||||
this.closingActionsSubscription.unsubscribe();
|
||||
this._panelOpen = false;
|
||||
this.searchPanel.resetResults();
|
||||
this.searchPanel.hidePanel();
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
get panelClosingActions(): Observable<any> {
|
||||
return merge(
|
||||
this.escapeEventStream,
|
||||
this.outsideClickStream
|
||||
);
|
||||
}
|
||||
|
||||
private get outsideClickStream(): Observable<any> {
|
||||
if (!this.document) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
return merge(
|
||||
fromEvent(this.document, 'click'),
|
||||
fromEvent(this.document, 'touchend')
|
||||
).pipe(
|
||||
filter((event: MouseEvent | TouchEvent) => {
|
||||
const clickTarget = event.target as HTMLElement;
|
||||
return this._panelOpen && clickTarget !== this.element.nativeElement;
|
||||
}),
|
||||
takeUntil(this.onDestroy$)
|
||||
);
|
||||
}
|
||||
|
||||
writeValue(value: any): void {
|
||||
Promise.resolve(null).then(() => this.setTriggerValue(value));
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => {}): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: () => {}) {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
handleKeydown(event: KeyboardEvent): void {
|
||||
const keyCode = event.keyCode;
|
||||
|
||||
if (keyCode === ESCAPE && this.panelOpen) {
|
||||
this.escapeEventStream.next();
|
||||
event.stopPropagation();
|
||||
} else if (keyCode === ENTER) {
|
||||
this.escapeEventStream.next();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleInput(event: KeyboardEvent): void {
|
||||
if (document.activeElement === event.target) {
|
||||
const inputValue: string = (event.target as HTMLInputElement).value;
|
||||
this.onChange(inputValue);
|
||||
if (inputValue) {
|
||||
this.searchPanel.keyPressedStream.next(inputValue);
|
||||
this.openPanel();
|
||||
} else {
|
||||
this.searchPanel.resetResults();
|
||||
this.closePanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isPanelOptionClicked(event: MouseEvent) {
|
||||
let isPanelOption: boolean = false;
|
||||
if ( event ) {
|
||||
const clickTarget = event.target as HTMLElement;
|
||||
isPanelOption = !this.isNoResultOption() &&
|
||||
!!this.searchPanel.panel &&
|
||||
!!this.searchPanel.panel.nativeElement.contains(clickTarget);
|
||||
}
|
||||
return isPanelOption;
|
||||
}
|
||||
|
||||
private isNoResultOption() {
|
||||
return this.searchPanel.results.list ? this.searchPanel.results.list.entries.length === 0 : true;
|
||||
}
|
||||
|
||||
private subscribeToClosingActions(): Subscription {
|
||||
const firstStable = this.ngZone.onStable.asObservable();
|
||||
const optionChanges = this.searchPanel.keyPressedStream.asObservable();
|
||||
|
||||
return merge(firstStable, optionChanges)
|
||||
.pipe(
|
||||
switchMap(() => {
|
||||
this.searchPanel.setVisibility();
|
||||
return this.panelClosingActions;
|
||||
}),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe((event) => this.setValueAndClose(event));
|
||||
}
|
||||
|
||||
private setTriggerValue(value: any): void {
|
||||
const toDisplay = this.searchPanel && this.searchPanel.displayWith ?
|
||||
this.searchPanel.displayWith(value) : value;
|
||||
const inputValue = toDisplay != null ? toDisplay : '';
|
||||
this.element.nativeElement.value = inputValue;
|
||||
}
|
||||
|
||||
private setValueAndClose(event: any | null): void {
|
||||
if (this.isPanelOptionClicked(event) && !event.defaultPrevented) {
|
||||
this.setTriggerValue(event.target.textContent.trim());
|
||||
this.onChange(event.target.textContent.trim());
|
||||
this.element.nativeElement.focus();
|
||||
}
|
||||
this.closePanel();
|
||||
}
|
||||
}
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SearchService } from '@alfresco/adf-core';
|
||||
import { SearchService, SearchComponentInterface } from '@alfresco/adf-core';
|
||||
import {
|
||||
AfterContentInit,
|
||||
Component,
|
||||
@@ -45,7 +45,7 @@ import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
'class': 'adf-search'
|
||||
}
|
||||
})
|
||||
export class SearchComponent implements AfterContentInit, OnChanges, OnDestroy {
|
||||
export class SearchComponent implements SearchComponentInterface, AfterContentInit, OnChanges, OnDestroy {
|
||||
|
||||
@ViewChild('panel')
|
||||
panel: ElementRef;
|
||||
|
@@ -28,7 +28,6 @@ export { SearchRange } from './search-range.interface';
|
||||
|
||||
export * from './components/search.component';
|
||||
export * from './components/search-control.component';
|
||||
export * from './components/search-trigger.directive';
|
||||
export * from './components/empty-search-result.component';
|
||||
export * from './components/search-filter/search-filter.component';
|
||||
export * from './components/search-filter/search-filter.service';
|
||||
|
@@ -22,8 +22,6 @@ import { MaterialModule } from '../material.module';
|
||||
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
|
||||
import { SearchTriggerDirective } from './components/search-trigger.directive';
|
||||
|
||||
import { SearchControlComponent } from './components/search-control.component';
|
||||
import { SearchComponent } from './components/search.component';
|
||||
import { EmptySearchResultComponent } from './components/empty-search-result.component';
|
||||
@@ -41,7 +39,6 @@ import { SearchSortingPickerComponent } from './components/search-sorting-picker
|
||||
export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
||||
SearchComponent,
|
||||
SearchControlComponent,
|
||||
SearchTriggerDirective,
|
||||
EmptySearchResultComponent,
|
||||
SearchFilterComponent,
|
||||
SearchChipListComponent
|
||||
|
Reference in New Issue
Block a user