mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
Move search-input-text from content-services to core (#8239)
* Move search-input-text from content-services to core * Move SearchConfigurationInterface into the content-services since is content related * Rename search-configuration.interface file to search-component.interface * Update import SearchConfigurationInterface from content-services * Decouple SearchComponentInterface from js-api * Decaplre NodeEntry instead using type any * Remove comment * Update component import to fix unit test * Import SearchTextModule into core
This commit is contained in:
@@ -15,9 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { QueryBody, NodePaging } from '@alfresco/js-api';
|
||||
import { Subject } from 'rxjs';
|
||||
import { ElementRef } from '@angular/core';
|
||||
import { QueryBody } from '@alfresco/js-api';
|
||||
|
||||
export interface SearchConfigurationInterface {
|
||||
|
||||
@@ -32,17 +30,3 @@ export interface SearchConfigurationInterface {
|
||||
generateQueryBody(searchTerm: string, maxResults: number, skipCount: number): QueryBody;
|
||||
|
||||
}
|
||||
|
||||
export interface SearchComponentInterface {
|
||||
|
||||
panel: ElementRef;
|
||||
showPanel: boolean;
|
||||
results: NodePaging;
|
||||
isOpen: boolean;
|
||||
keyPressedStream: Subject<string>;
|
||||
displayWith: ((value: any) => string) | null;
|
||||
|
||||
resetResults(): void;
|
||||
hidePanel(): void;
|
||||
setVisibility(): void;
|
||||
}
|
@@ -18,4 +18,5 @@
|
||||
export * from './services/favorites-api.service';
|
||||
export * from './services/card-view-content-update.service';
|
||||
export * from './services/sites.service';
|
||||
export * from './interface/search-configuration.interface';
|
||||
|
||||
export * from './interfaces/search-configuration.interface';
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule, ModuleWithProviders, APP_INITIALIZER } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { CoreModule, TRANSLATION_PROVIDER } from '@alfresco/adf-core';
|
||||
import { CoreModule, TRANSLATION_PROVIDER, SearchTextModule } from '@alfresco/adf-core';
|
||||
|
||||
import { MaterialModule } from './material.module';
|
||||
|
||||
@@ -47,7 +47,6 @@ import { VersionCompatibilityService } from './version-compatibility/version-com
|
||||
import { ContentPipeModule } from './pipes/content-pipe.module';
|
||||
import { NodeCommentsModule } from './node-comments/node-comments.module';
|
||||
import { TreeModule } from './tree/tree.module';
|
||||
import { SearchTextModule } from './search-text/search-text-input.module';
|
||||
import { AlfrescoViewerModule } from './viewer/alfresco-viewer.module';
|
||||
|
||||
@NgModule({
|
||||
|
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
|
||||
import { QueryBody } from '@alfresco/js-api';
|
||||
import { SearchConfigurationInterface } from '../../../common/interface/search-configuration.interface';
|
||||
import { Injectable, Optional, Inject, InjectionToken } from '@angular/core';
|
||||
import { SearchConfigurationInterface } from '../../../common/interfaces/search-configuration.interface';
|
||||
|
||||
export const SEARCH_QUERY_TOKEN = new InjectionToken<QueryProvider>('Alfresco Search Query Token');
|
||||
export interface QueryProvider {
|
||||
|
@@ -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,18 +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.
|
||||
*/
|
||||
|
||||
export * from './public-api';
|
@@ -1,37 +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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum SearchTextStateEnum {
|
||||
expanded = 'expanded',
|
||||
collapsed = 'collapsed'
|
||||
}
|
||||
|
||||
export interface SearchAnimationState {
|
||||
value: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface SearchAnimationControl {
|
||||
active: SearchAnimationState;
|
||||
inactive: SearchAnimationState;
|
||||
}
|
||||
|
||||
export interface SearchAnimationDirection {
|
||||
ltr: SearchAnimationControl;
|
||||
rtl: SearchAnimationControl;
|
||||
}
|
@@ -1,22 +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.
|
||||
*/
|
||||
|
||||
export * from './animations';
|
||||
export * from './search-text-input.component';
|
||||
export * from './search-trigger.directive';
|
||||
export * from './search-text-input.module';
|
||||
export * from './models/search-text-input.model';
|
@@ -1,38 +0,0 @@
|
||||
<div class="adf-search-container" [attr.state]="subscriptAnimationState.value">
|
||||
<div [@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" [hintLabel]="hintLabel">
|
||||
<input matInput
|
||||
#searchInput
|
||||
[attr.aria-label]="'SEARCH.INPUT.ARIA-LABEL' | translate"
|
||||
[attr.type]="inputType"
|
||||
[autocomplete]="getAutoComplete()"
|
||||
id="adf-control-input"
|
||||
[(ngModel)]="searchTerm"
|
||||
[placeholder]="placeholder"
|
||||
(focus)="activateToolbar()"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.escape)="toggleSearchBar()"
|
||||
(keyup.arrowdown)="selectFirstResult($event)"
|
||||
(ngModelChange)="inputChange($event)"
|
||||
[searchAutocomplete]="searchAutocomplete ? searchAutocomplete : null"
|
||||
(keyup.enter)="searchSubmit($event)">
|
||||
<button mat-icon-button matSuffix
|
||||
data-automation-id="adf-clear-search-button"
|
||||
class="adf-clear-search-button"
|
||||
*ngIf="canShowClearSearch()"
|
||||
(mousedown)="resetSearch()">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
@@ -1,37 +0,0 @@
|
||||
.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: var(--adf-search-input-bg-color);
|
||||
|
||||
.mat-form-field-ripple {
|
||||
background-color: var(--adf-search-input-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
font-size: var(--theme-subheading-2-font-size);
|
||||
}
|
||||
}
|
||||
|
||||
.adf-highlight {
|
||||
color: var(--adf-search-input-highlight-color);
|
||||
}
|
@@ -1,384 +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 { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { CoreTestingModule, UserPreferencesService, setupTestBed } from '@alfresco/adf-core';
|
||||
import { SearchTextInputComponent } from './search-text-input.component';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('SearchTextInputComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<SearchTextInputComponent>;
|
||||
let component: SearchTextInputComponent;
|
||||
let debugElement: DebugElement;
|
||||
let element: HTMLElement;
|
||||
let userPreferencesService: UserPreferencesService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchTextInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
debugElement = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
userPreferencesService = TestBed.inject(UserPreferencesService);
|
||||
component.focusListener = new Subject<any>();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
describe('component rendering', () => {
|
||||
|
||||
it('should display a search input field when specified', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
component.inputType = 'search';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelectorAll('input[type="search"]').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandable option false', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.expandable = false;
|
||||
});
|
||||
|
||||
it('search button should be hide', () => {
|
||||
fixture.detectChanges();
|
||||
const searchButton: any = element.querySelector('#adf-search-button');
|
||||
expect(searchButton).toBe(null);
|
||||
});
|
||||
|
||||
it('should not have animation', () => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'rtl');
|
||||
fixture.detectChanges();
|
||||
expect(component.subscriptAnimationState.value).toBe('no-animation');
|
||||
});
|
||||
});
|
||||
|
||||
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('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();
|
||||
}));
|
||||
|
||||
it('should set browser autocomplete to on when configured', async () => {
|
||||
component.autocomplete = true;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('#adf-control-input').getAttribute('autocomplete')).toBe('on');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search visibility', () => {
|
||||
beforeEach(() => {
|
||||
userPreferencesService.setWithoutStore('textOrientation', 'ltr');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should emit an event when the search becomes active', fakeAsync(() => {
|
||||
const searchVisibilityChangeSpy = spyOn(component.searchVisibility, 'emit');
|
||||
component.toggleSearchBar();
|
||||
tick(200);
|
||||
|
||||
expect(searchVisibilityChangeSpy).toHaveBeenCalledWith(true);
|
||||
}));
|
||||
|
||||
it('should emit an event when the search becomes inactive', fakeAsync(() => {
|
||||
component.toggleSearchBar();
|
||||
tick(200);
|
||||
expect(component.subscriptAnimationState.value).toEqual('active');
|
||||
|
||||
const searchVisibilityChangeSpy = spyOn(component.searchVisibility, 'emit');
|
||||
component.toggleSearchBar();
|
||||
tick(200);
|
||||
|
||||
expect(component.subscriptAnimationState.value).toEqual('inactive');
|
||||
expect(searchVisibilityChangeSpy).toHaveBeenCalledWith(false);
|
||||
}));
|
||||
|
||||
it('should reset emit when the search becomes inactive', fakeAsync(() => {
|
||||
const resetSpy = spyOn(component.reset, 'emit');
|
||||
|
||||
component.toggleSearchBar();
|
||||
tick(200);
|
||||
expect(component.subscriptAnimationState.value).toEqual('active');
|
||||
component.searchTerm = 'fake-search-term';
|
||||
|
||||
component.toggleSearchBar();
|
||||
tick(200);
|
||||
|
||||
expect(resetSpy).toHaveBeenCalled();
|
||||
expect(component.searchTerm).toEqual('');
|
||||
}));
|
||||
|
||||
describe('Clear button', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
component.subscriptAnimationState.value = 'active';
|
||||
fixture.detectChanges();
|
||||
tick(200);
|
||||
}));
|
||||
|
||||
it('should clear button be visible when showClearButton is set to true', async () => {
|
||||
component.showClearButton = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const clearButton = fixture.debugElement.query(By.css('[data-automation-id="adf-clear-search-button"]'));
|
||||
|
||||
expect(clearButton).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should clear button not be visible when showClearButton is set to false', () => {
|
||||
component.showClearButton = false;
|
||||
fixture.detectChanges();
|
||||
const clearButton = fixture.debugElement.query(By.css('[data-automation-id="adf-clear-search-button"]'));
|
||||
|
||||
expect(clearButton).toBeNull();
|
||||
});
|
||||
|
||||
it('should reset the search when clicking the clear button', async () => {
|
||||
const resetEmitSpy = spyOn(component.reset, 'emit');
|
||||
const searchVisibilityChangeSpy = spyOn(component.searchVisibility, 'emit');
|
||||
|
||||
component.searchTerm = 'fake-search-term';
|
||||
component.showClearButton = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const clearButton = fixture.debugElement.query(By.css('[data-automation-id="adf-clear-search-button"]'));
|
||||
clearButton.nativeElement.dispatchEvent(new MouseEvent('mousedown'));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(resetEmitSpy).toHaveBeenCalled();
|
||||
expect(searchVisibilityChangeSpy).toHaveBeenCalledWith(false);
|
||||
expect(component.subscriptAnimationState.value).toEqual('inactive');
|
||||
expect(component.searchTerm).toEqual('');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Collapse on blur', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
component.toggleSearchBar();
|
||||
tick(200);
|
||||
}));
|
||||
|
||||
it('should collapse search on blur when the collapseOnBlur is set to true', fakeAsync (() => {
|
||||
const searchVisibilityChangeSpy = spyOn(component.searchVisibility, 'emit');
|
||||
const resetEmitSpy = spyOn(component.reset, 'emit');
|
||||
component.collapseOnBlur = true;
|
||||
component.searchTerm = 'fake-search-term';
|
||||
component.onBlur({ relatedTarget: null });
|
||||
tick(200);
|
||||
|
||||
expect(searchVisibilityChangeSpy).toHaveBeenCalledWith(false);
|
||||
expect(component.subscriptAnimationState.value).toEqual('inactive');
|
||||
expect(component.searchTerm).toEqual('');
|
||||
expect(resetEmitSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not collapse search on blur when the collapseOnBlur is set to false', () => {
|
||||
const searchVisibilityChangeSpy = spyOn(component.searchVisibility, 'emit');
|
||||
component.searchTerm = 'fake-search-term';
|
||||
component.collapseOnBlur = false;
|
||||
component.onBlur({ relatedTarget: null });
|
||||
|
||||
expect(searchVisibilityChangeSpy).not.toHaveBeenCalled();
|
||||
expect(component.subscriptAnimationState.value).toEqual('active');
|
||||
expect(component.searchTerm).toEqual('fake-search-term');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,323 +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 { 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 '@alfresco/adf-core';
|
||||
import { SearchTextStateEnum, SearchAnimationState, SearchAnimationDirection } from './models/search-text-input.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-text-input',
|
||||
templateUrl: './search-text-input.component.html',
|
||||
styleUrls: ['./search-text-input.component.scss'],
|
||||
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;
|
||||
|
||||
/** Toggles whether to collapse the search on blur. */
|
||||
@Input()
|
||||
collapseOnBlur: boolean = true;
|
||||
|
||||
/** Toggles whether to show a clear button that closes the search */
|
||||
@Input()
|
||||
showClearButton: boolean = false;
|
||||
|
||||
/** Placeholder text to show in the input field */
|
||||
@Input()
|
||||
placeholder: string = '';
|
||||
|
||||
/** Hint label */
|
||||
@Input()
|
||||
hintLabel = '';
|
||||
|
||||
/** 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();
|
||||
|
||||
/** Emitted when the search visibility changes. True when the search is active, false when it is inactive */
|
||||
@Output()
|
||||
searchVisibility: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
this.emitVisibilitySearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.isDefaultStateCollapsed()) {
|
||||
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.isDefaultStateExpanded()) {
|
||||
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) => 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 (this.collapseOnBlur && !$event.relatedTarget) {
|
||||
this.resetSearch();
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
canShowClearSearch(): boolean {
|
||||
return this.showClearButton && this.isSearchBarActive();
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
if (this.isSearchBarActive()) {
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
private isDefaultStateCollapsed(): boolean {
|
||||
return this.defaultState === SearchTextStateEnum.collapsed;
|
||||
}
|
||||
|
||||
private isDefaultStateExpanded(): boolean {
|
||||
return this.defaultState === SearchTextStateEnum.expanded;
|
||||
}
|
||||
|
||||
private emitVisibilitySearch() {
|
||||
this.searchVisibility.emit(this.isSearchBarActive());
|
||||
}
|
||||
}
|
@@ -1,42 +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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SearchTextInputComponent } from './search-text-input.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchTriggerDirective } from './search-trigger.directive';
|
||||
import { MaterialModule } from '../material.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SearchTextInputComponent,
|
||||
SearchTriggerDirective
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
MaterialModule,
|
||||
FormsModule
|
||||
],
|
||||
exports: [
|
||||
SearchTextInputComponent,
|
||||
SearchTriggerDirective
|
||||
]
|
||||
})
|
||||
export class SearchTextModule {}
|
@@ -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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/no-input-rename, @typescript-eslint/no-use-before-define, @angular-eslint/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 { filter, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { SearchComponentInterface } from '../common/interface/search-configuration.interface';
|
||||
|
||||
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({
|
||||
// eslint-disable-next-line @angular-eslint/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: SearchComponentInterface;
|
||||
|
||||
@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) => any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: () => any) {
|
||||
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) {
|
||||
this.searchPanel.keyPressedStream.next(inputValue);
|
||||
this.openPanel();
|
||||
} else if (this.searchPanel) {
|
||||
this.searchPanel.resetResults();
|
||||
this.closePanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isPanelOptionClicked(event: MouseEvent) {
|
||||
let isPanelOption: boolean = false;
|
||||
if ( event && this.searchPanel ) {
|
||||
const clickTarget = event.target as HTMLElement;
|
||||
isPanelOption = !this.isNoResultOption() &&
|
||||
!!this.searchPanel.panel &&
|
||||
!!this.searchPanel.panel.nativeElement.contains(clickTarget);
|
||||
}
|
||||
return isPanelOption;
|
||||
}
|
||||
|
||||
private isNoResultOption(): boolean {
|
||||
return this.searchPanel && 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();
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import {
|
||||
AuthenticationService,
|
||||
SearchTextInputComponent,
|
||||
setupTestBed,
|
||||
UserPreferencesService
|
||||
} from '@alfresco/adf-core';
|
||||
@@ -29,7 +30,6 @@ import { SearchService } from '../services/search.service';
|
||||
import { of } from 'rxjs';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchTextInputComponent } from '../../search-text/search-text-input.component';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthenticationService, ThumbnailService } from '@alfresco/adf-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';
|
||||
@@ -23,7 +23,6 @@ import { Subject } from 'rxjs';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { MatListItem } from '@angular/material/list';
|
||||
import { EmptySearchResultComponent } from './empty-search-result.component';
|
||||
import { SearchTextInputComponent } from '../../search-text/search-text-input.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-control',
|
||||
|
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { SearchService } from '../services/search.service';
|
||||
import { SearchComponentInterface } from '../../common/interface/search-configuration.interface';
|
||||
import {
|
||||
AfterContentInit,
|
||||
Component,
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
import { NodePaging, ResultSetPaging } from '@alfresco/js-api';
|
||||
import { Subject } from 'rxjs';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { SearchComponentInterface } from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search',
|
||||
|
@@ -20,7 +20,7 @@ import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MaterialModule } from '../material.module';
|
||||
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { CoreModule, SearchTextModule } from '@alfresco/adf-core';
|
||||
|
||||
import { SearchControlComponent } from './components/search-control.component';
|
||||
import { SearchComponent } from './components/search.component';
|
||||
@@ -47,7 +47,6 @@ import { SearchFacetFieldComponent } from './components/search-facet-field/searc
|
||||
import { SearchWidgetChipComponent } from './components/search-filter-chips/search-widget-chip/search-widget-chip.component';
|
||||
import { SearchFacetChipComponent } from './components/search-filter-chips/search-facet-chip/search-facet-chip.component';
|
||||
import { ResetSearchDirective } from './components/reset-search.directive';
|
||||
import { SearchTextModule } from '../search-text/search-text-input.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@@ -17,7 +17,7 @@
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { QueryBody } from '@alfresco/js-api';
|
||||
import { SearchConfigurationInterface } from '../../common/interface/search-configuration.interface';
|
||||
import { SearchConfigurationInterface } from '../../common/interfaces/search-configuration.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@@ -43,7 +43,6 @@ export * from './lib/pipes/index';
|
||||
export * from './lib/common/index';
|
||||
export * from './lib/tree/index';
|
||||
export * from './lib/category/index';
|
||||
export * from './lib/search-text/index';
|
||||
export * from './lib/viewer/index';
|
||||
|
||||
export * from './lib/content.module';
|
||||
|
Reference in New Issue
Block a user