UNPKG

@vsblty/ng-autocomplete

Version:

A fork of Angular autocomplete

849 lines (846 loc) 51.6 kB
import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Pipe, EventEmitter, forwardRef, TemplateRef, Component, ViewEncapsulation, ViewChild, Input, Output, ContentChild, NgModule } from '@angular/core'; import * as i2 from '@angular/forms'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { fromEvent } from 'rxjs'; import { map, filter, debounceTime } from 'rxjs/operators'; class HighlightPipe { transform(text, search, searchKeyword) { let pattern = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); pattern = pattern.split(' ').filter((t) => { return t.length > 0; }).join('|'); const regex = new RegExp(pattern, 'gi'); if (!search) { return text; } if (searchKeyword) { const name = text[searchKeyword].replace(regex, (match) => `<b>${match}</b>`); // copy original object const textCopied = { ...text }; // set bold value into searchKeyword of copied object textCopied[searchKeyword] = name; return textCopied; } else { return search ? text.replace(regex, (match) => `<b>${match}</b>`) : text; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: HighlightPipe, name: "highlight" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HighlightPipe, decorators: [{ type: Pipe, args: [{ name: 'highlight' }] }] }); /** * Keyboard events */ const isArrowUp = keyCode => keyCode === 38; const isArrowDown = keyCode => keyCode === 40; const isArrowUpDown = keyCode => isArrowUp(keyCode) || isArrowDown(keyCode); const isEnter = keyCode => keyCode === 13; const isBackspace = keyCode => keyCode === 8; const isDelete = keyCode => keyCode === 46; const isESC = keyCode => keyCode === 27; const isTab = keyCode => keyCode === 9; class AutocompleteComponent { renderer; searchInput; // input element filteredListElement; // element of items historyListElement; // element of history items inputKeyUp$; inputKeyDown$; query = ''; // search query filteredList = []; // list of items historyList = []; // list of history items isHistoryListVisible = true; elementRef; selectedIdx = -1; toHighlight = ''; notFound = false; isFocused = false; isOpen = false; isScrollToEnd = false; overlay = false; manualOpen = undefined; manualClose = undefined; // @Inputs /** * Data of items list. * It can be array of strings or array of objects. */ data = []; searchKeyword; // keyword to filter the list placeholder = ''; heading = ''; initialValue; /** * History identifier of history list * When valid history identifier is given, then component stores selected item to local storage of user's browser. * If it is null then history is hidden. * History list is visible if at least one history item is stored. */ historyIdentifier; /** * Heading text of history list. * If it is null then history heading is hidden. */ historyHeading = 'Recently selected'; historyListMaxNumber = 15; // maximum number of items in the history list. notFoundText = 'Not found'; // set custom text when filter returns empty result isLoading; // loading mask debounceTime; // delay time while typing disabled; // input disable/enable /** * The minimum number of characters the user must type before a search is performed. */ minQueryLength = 1; /** * Focus first item in the list */ focusFirst = false; /** * Custom filter function */ customFilter; /** * Custom result render function * @param value - selected value to be rendered inside input field */ selectedValueRender; // @Output events /** Event that is emitted whenever an item from the list is selected. */ selected = new EventEmitter(); /** Event that is emitted whenever an input is changed. */ inputChanged = new EventEmitter(); /** Event that is emitted whenever an input is focused. */ inputFocused = new EventEmitter(); /** Event that is emitted whenever an input is cleared. */ inputCleared = new EventEmitter(); /** Event that is emitted when the autocomplete panel is opened. */ opened = new EventEmitter(); /** Event that is emitted when the autocomplete panel is closed. */ closed = new EventEmitter(); /** Event that is emitted when scrolled to the end of items. */ scrolledToEnd = new EventEmitter(); // Custom templates itemTemplate; notFoundTemplate; customTemplate; /** * Propagates new value when model changes */ propagateChange = () => { }; onTouched = () => { }; /** * Writes a new value from the form model into the view, * Updates model */ writeValue(value = '') { this.query = this.selectedValueRender !== undefined ? this.selectedValueRender(value) : this.defaultWriteValue(value); } defaultWriteValue(value) { return value && !this.isTypeString(value) ? value[this.searchKeyword] : value; } /** * Registers a handler that is called when something in the view has changed */ registerOnChange(fn) { this.propagateChange = fn; } /** * Registers a handler specifically for when a control receives a touch event */ registerOnTouched(fn) { this.onTouched = fn; } /** * Event that is called when the value of an input element is changed */ onChange(event) { this.propagateChange(event.target.value); } constructor(elementRef, renderer) { this.renderer = renderer; this.elementRef = elementRef; } /** * Event that is called when the control status changes to or from DISABLED */ setDisabledState(isDisabled) { this.disabled = isDisabled; } ngOnInit() { } ngAfterViewInit() { this.initEventStream(); this.handleScroll(); } /** * Set initial value * @param value */ setInitialValue(value) { if (this.initialValue) { this.select(value); } } /** * Update search results */ ngOnChanges(changes) { this.setInitialValue(this.initialValue); if (changes && changes.data && Array.isArray(changes.data.currentValue)) { this.handleItemsChange(); if (!changes.data.firstChange && this.isFocused) { this.handleOpen(); } } } /** * Items change */ handleItemsChange() { this.isScrollToEnd = false; if (!this.isOpen) { return; } this.filteredList = this.data; this.notFound = !this.filteredList || this.filteredList.length === 0; // Filter list when updating data and panel is open if (this.isOpen) { this.filterList(); } } /** * Filter data */ filterList() { this.selectedIdx = -1; this.initSearchHistory(); if (this.query != null && this.data) { this.toHighlight = this.query; this.filteredList = this.customFilter !== undefined ? this.customFilter([...this.data], this.query) : this.defaultFilterFunction(); // If [focusFirst]="true" automatically focus the first match if (this.filteredList.length > 0 && this.focusFirst) { this.selectedIdx = 0; } } else { this.notFound = false; } } /** * Default filter function, used unless customFilter is provided */ defaultFilterFunction() { return this.data.filter((item) => { if (typeof item === 'string') { // string logic, check equality of strings return item.toLowerCase().indexOf(this.query.toLowerCase()) > -1; } else if (typeof item === 'object' && item instanceof Object) { // object logic, check property equality return item[this.searchKeyword] ? item[this.searchKeyword].toLowerCase().indexOf(this.query.toLowerCase()) > -1 : ""; } }); } /** * Check if item is a string in the list. * @param item */ isTypeString(item) { return typeof item === 'string'; } /** * Select item in the list. * @param item */ select(item) { this.query = !this.isTypeString(item) ? item[this.searchKeyword] : item; this.isOpen = true; this.overlay = false; this.selected.emit(item); this.propagateChange(item); if (this.initialValue) { // check if history already exists in localStorage and then update const history = window.localStorage.getItem(`${this.historyIdentifier}`); if (history) { let existingHistory = JSON.parse(localStorage[`${this.historyIdentifier}`]); if (!(existingHistory instanceof Array)) existingHistory = []; // check if selected item exists in existingHistory if (!existingHistory.some((existingItem) => !this.isTypeString(existingItem) ? existingItem[this.searchKeyword] == item[this.searchKeyword] : existingItem == item)) { existingHistory.unshift(item); localStorage.setItem(`${this.historyIdentifier}`, JSON.stringify(existingHistory)); // check if items don't exceed max allowed number if (existingHistory.length >= this.historyListMaxNumber) { existingHistory.splice(existingHistory.length - 1, 1); localStorage.setItem(`${this.historyIdentifier}`, JSON.stringify(existingHistory)); } } else { // if selected item exists in existingHistory swap to top in array if (!this.isTypeString(item)) { // object logic const copiedExistingHistory = existingHistory.slice(); // copy original existingHistory array const selectedIndex = copiedExistingHistory.map((el) => el[this.searchKeyword]).indexOf(item[this.searchKeyword]); copiedExistingHistory.splice(selectedIndex, 1); copiedExistingHistory.splice(0, 0, item); localStorage.setItem(`${this.historyIdentifier}`, JSON.stringify(copiedExistingHistory)); } else { // string logic const copiedExistingHistory = existingHistory.slice(); // copy original existingHistory array copiedExistingHistory.splice(copiedExistingHistory.indexOf(item), 1); copiedExistingHistory.splice(0, 0, item); localStorage.setItem(`${this.historyIdentifier}`, JSON.stringify(copiedExistingHistory)); } } } else { this.saveHistory(item); } } else { this.saveHistory(item); } this.handleClose(); } /** * Document click * @param e event */ handleClick(e) { let clickedComponent = e.target; let inside = false; do { if (clickedComponent === this.elementRef.nativeElement) { inside = true; if (this.filteredList.length) { this.handleOpen(); } } clickedComponent = clickedComponent.parentNode; } while (clickedComponent); if (!inside) { this.handleClose(); } } /** * Handle body overlay */ handleOverlay() { this.overlay = false; } /** * Scroll items */ handleScroll() { this.renderer.listen(this.filteredListElement.nativeElement, 'scroll', () => { this.scrollToEnd(); }); } /** * Define panel state */ setPanelState(event) { if (event) { event.stopPropagation(); } // If controls are untouched if (typeof this.manualOpen === 'undefined' && typeof this.manualClose === 'undefined') { this.isOpen = false; this.handleOpen(); } // If one of the controls is untouched and other is deactivated if (typeof this.manualOpen === 'undefined' && this.manualClose === false || typeof this.manualClose === 'undefined' && this.manualOpen === false) { this.isOpen = false; this.handleOpen(); } // if controls are touched but both are deactivated if (this.manualOpen === false && this.manualClose === false) { this.isOpen = false; this.handleOpen(); } // if open control is touched and activated if (this.manualOpen) { this.isOpen = false; this.handleOpen(); this.manualOpen = false; } // if close control is touched and activated if (this.manualClose) { this.isOpen = true; this.handleClose(); this.manualClose = false; } } /** * Manual controls */ open() { this.manualOpen = true; this.isOpen = false; this.handleOpen(); } close() { this.manualClose = true; this.isOpen = true; this.handleClose(); } focus() { this.handleFocus(event); } clear() { this.remove(event); } /** * Remove search query */ remove(e) { e.stopPropagation(); this.query = ''; this.inputCleared.emit(); this.propagateChange(this.query); this.setPanelState(e); if (this.data && !this.data.length) { this.notFound = false; } } /** * Initialize historyList search */ initSearchHistory() { this.isHistoryListVisible = false; if (this.historyIdentifier && !this.query) { const history = window.localStorage.getItem(`${this.historyIdentifier}`); if (history) { this.isHistoryListVisible = true; this.filteredList = []; this.historyList = history ? JSON.parse(history) : []; } else { this.isHistoryListVisible = false; } } else { this.isHistoryListVisible = false; } } handleOpen() { if (this.isOpen || this.isOpen && !this.isLoading) { return; } // If data exists if (this.data && this.data.length) { this.isOpen = true; this.overlay = true; this.filterList(); this.opened.emit(); } } handleClose() { if (!this.isOpen) { this.isFocused = false; return; } this.isOpen = false; this.overlay = false; this.filteredList = []; this.selectedIdx = -1; this.notFound = false; this.isHistoryListVisible = false; this.isFocused = false; this.closed.emit(); } handleFocus(e) { this.searchInput.nativeElement.focus(); if (this.isFocused) { return; } this.inputFocused.emit(e); // if data exists then open if (this.data && this.data.length) { this.setPanelState(e); } this.isFocused = true; } scrollToEnd() { if (this.isScrollToEnd) { return; } const scrollTop = this.filteredListElement.nativeElement .scrollTop; const scrollHeight = this.filteredListElement.nativeElement .scrollHeight; const elementHeight = this.filteredListElement.nativeElement .clientHeight; const atBottom = elementHeight != 0 && scrollTop > ((scrollHeight - elementHeight) * 0.95); if (atBottom) { this.scrolledToEnd.emit(); this.isScrollToEnd = true; } } /** * Initialize keyboard events */ initEventStream() { this.inputKeyUp$ = fromEvent(this.searchInput.nativeElement, 'keyup').pipe(map((e) => e)); this.inputKeyDown$ = fromEvent(this.searchInput.nativeElement, 'keydown').pipe(map((e) => e)); this.listenEventStream(); } /** * Listen keyboard events */ listenEventStream() { // key up event this.inputKeyUp$ .pipe(filter(e => !isArrowUpDown(e.keyCode) && !isEnter(e.keyCode) && !isESC(e.keyCode) && !isTab(e.keyCode)), debounceTime(this.debounceTime)).subscribe(e => { this.onKeyUp(e); }); // cursor up & down this.inputKeyDown$.pipe(filter(e => isArrowUpDown(e.keyCode))).subscribe(e => { e.preventDefault(); this.onFocusItem(e); }); // enter keyup this.inputKeyUp$.pipe(filter(e => isEnter(e.keyCode))).subscribe(e => { //this.onHandleEnter(); }); // enter keydown this.inputKeyDown$.pipe(filter(e => isEnter(e.keyCode))).subscribe(e => { this.onHandleEnter(); }); // ESC this.inputKeyUp$.pipe(filter(e => isESC(e.keyCode), debounceTime(100))).subscribe(e => { this.onEsc(); }); // TAB this.inputKeyDown$.pipe(filter(e => isTab(e.keyCode))).subscribe(e => { this.onTab(); }); // delete this.inputKeyDown$.pipe(filter(e => isBackspace(e.keyCode) || isDelete(e.keyCode))).subscribe(e => { this.onDelete(); }); } /** * on keyup == when input changed * @param e event */ onKeyUp(e) { this.notFound = false; // search results are unknown while typing // if input is empty if (!this.query) { this.notFound = false; this.inputChanged.emit(e.target.value); this.inputCleared.emit(); this.setPanelState(e); } // note that '' can be a valid query if (!this.query && this.query !== '') { return; } // if query >= to minQueryLength if (this.query.length >= this.minQueryLength) { this.inputChanged.emit(e.target.value); this.filterList(); // If no results found if (!this.filteredList.length && !this.isLoading) { this.notFoundText ? this.notFound = true : this.notFound = false; } if (this.data && !this.data.length) { this.isOpen = true; } } } /** * Keyboard arrow top and arrow bottom * @param e event */ onFocusItem(e) { // move arrow up and down on filteredList or historyList if (!this.historyList.length || !this.isHistoryListVisible) { // filteredList const totalNumItem = this.filteredList.length; if (e.key === 'ArrowDown') { let sum = this.selectedIdx; sum = (this.selectedIdx === null) ? 0 : sum + 1; this.selectedIdx = (totalNumItem + sum) % totalNumItem; this.scrollToFocusedItem(this.selectedIdx); } else if (e.key === 'ArrowUp') { if (this.selectedIdx == -1) { this.selectedIdx = 0; } this.selectedIdx = (totalNumItem + this.selectedIdx - 1) % totalNumItem; this.scrollToFocusedItem(this.selectedIdx); } } else { // historyList const totalNumItem = this.historyList.length; if (e.key === 'ArrowDown') { let sum = this.selectedIdx; sum = (this.selectedIdx === null) ? 0 : sum + 1; this.selectedIdx = (totalNumItem + sum) % totalNumItem; this.scrollToFocusedItem(this.selectedIdx); } else if (e.key === 'ArrowUp') { if (this.selectedIdx == -1) { this.selectedIdx = 0; } this.selectedIdx = (totalNumItem + this.selectedIdx - 1) % totalNumItem; this.scrollToFocusedItem(this.selectedIdx); } } } /** * Scroll to focused item * * @param index */ scrollToFocusedItem(index) { let listElement = null; // Define list element if (!this.historyList.length || !this.isHistoryListVisible) { // filteredList element listElement = this.filteredListElement.nativeElement; } else { // historyList element listElement = this.historyListElement.nativeElement; } const items = Array.prototype.slice.call(listElement.childNodes).filter((node) => { if (node.nodeType === 1) { // if node is element return node.className.includes('item'); } else { return false; } }); if (!items.length) { return; } const listHeight = listElement.offsetHeight; const itemHeight = items[index].offsetHeight; const visibleTop = listElement.scrollTop; const visibleBottom = listElement.scrollTop + listHeight - itemHeight; const targetPosition = items[index].offsetTop; if (targetPosition < visibleTop) { listElement.scrollTop = targetPosition; } if (targetPosition > visibleBottom) { listElement.scrollTop = targetPosition - listHeight + itemHeight; } } /** * Select item on enter click */ onHandleEnter() { // click enter to choose item from filteredList or historyList if (this.selectedIdx > -1) { if (!this.historyList.length || !this.isHistoryListVisible) { // filteredList this.query = !this.isTypeString(this.filteredList[this.selectedIdx]) ? this.filteredList[this.selectedIdx][this.searchKeyword] : this.filteredList[this.selectedIdx]; this.saveHistory(this.filteredList[this.selectedIdx]); this.select(this.filteredList[this.selectedIdx]); } else { // historyList this.query = !this.isTypeString(this.historyList[this.selectedIdx]) ? this.historyList[this.selectedIdx][this.searchKeyword] : this.historyList[this.selectedIdx]; this.saveHistory(this.historyList[this.selectedIdx]); this.select(this.historyList[this.selectedIdx]); } } this.isHistoryListVisible = false; this.handleClose(); } /** * Esc click */ onEsc() { this.searchInput.nativeElement.blur(); this.handleClose(); } /** * Tab click */ onTab() { this.searchInput.nativeElement.blur(); this.handleClose(); } /** * Delete click */ onDelete() { this.isOpen = true; } /** * Select item to save in localStorage * @param selected */ saveHistory(selected) { if (this.historyIdentifier) { // check if selected item exists in historyList if (!this.historyList.some((item) => !this.isTypeString(item) ? item[this.searchKeyword] == selected[this.searchKeyword] : item == selected)) { this.saveHistoryToLocalStorage([selected, ...this.historyList]); // check if items don't exceed max allowed number if (this.historyList.length >= this.historyListMaxNumber) { this.historyList.splice(this.historyList.length - 1, 1); this.saveHistoryToLocalStorage([selected, ...this.historyList]); } } else { // if selected item exists in historyList swap to top in array if (!this.isTypeString(selected)) { // object logic const copiedHistoryList = this.historyList.slice(); // copy original historyList array const selectedIndex = copiedHistoryList.map((item) => item[this.searchKeyword]).indexOf(selected[this.searchKeyword]); copiedHistoryList.splice(selectedIndex, 1); copiedHistoryList.splice(0, 0, selected); this.saveHistoryToLocalStorage([...copiedHistoryList]); } else { // string logic const copiedHistoryList = this.historyList.slice(); // copy original historyList array copiedHistoryList.splice(this.historyList.indexOf(selected), 1); copiedHistoryList.splice(0, 0, selected); this.saveHistoryToLocalStorage([...copiedHistoryList]); } } } } /** * Save item in localStorage * @param selected */ saveHistoryToLocalStorage(selected) { window.localStorage.setItem(`${this.historyIdentifier}`, JSON.stringify(selected)); } /** * Remove item from localStorage * @param index * @param e event */ removeHistoryItem(index, e) { e.stopPropagation(); this.historyList = this.historyList.filter((v, i) => i !== index); this.saveHistoryToLocalStorage(this.historyList); if (this.historyList.length == 0) { window.localStorage.removeItem(`${this.historyIdentifier}`); this.filterList(); } } /** * Reset localStorage * @param e event */ resetHistoryList(e) { e.stopPropagation(); this.historyList = []; window.localStorage.removeItem(`${this.historyIdentifier}`); this.filterList(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AutocompleteComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AutocompleteComponent, selector: "ng-autocomplete", inputs: { data: "data", searchKeyword: "searchKeyword", placeholder: "placeholder", heading: "heading", initialValue: "initialValue", historyIdentifier: "historyIdentifier", historyHeading: "historyHeading", historyListMaxNumber: "historyListMaxNumber", notFoundText: "notFoundText", isLoading: "isLoading", debounceTime: "debounceTime", disabled: "disabled", minQueryLength: "minQueryLength", focusFirst: "focusFirst", customFilter: "customFilter", selectedValueRender: "selectedValueRender", itemTemplate: "itemTemplate", notFoundTemplate: "notFoundTemplate" }, outputs: { selected: "selected", inputChanged: "inputChanged", inputFocused: "inputFocused", inputCleared: "inputCleared", opened: "opened", closed: "closed", scrolledToEnd: "scrolledToEnd" }, host: { listeners: { "document:click": "handleClick($event)" }, classAttribute: "ng-autocomplete" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true } ], queries: [{ propertyName: "customTemplate", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "filteredListElement", first: true, predicate: ["filteredListElement"], descendants: true }, { propertyName: "historyListElement", first: true, predicate: ["historyListElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"autocomplete-container\" aria-owns=\"suggestions suggestions-history\" [attr.aria-expanded]=\"isOpen\"\n [ngClass]=\"{ 'active': isOpen}\">\n <div class=\"input-container\">\n <input #searchInput\n type=\"text\"\n attr.aria-label=\"{{placeholder}}\"\n aria-autocomplete=\"list\"\n role=\"combobox\"\n placeholder={{placeholder}}\n [(ngModel)]=query\n (input)=\"onChange($event)\"\n (focus)=handleFocus($event)\n (blur)=onTouched($event)\n [disabled]=\"disabled\"\n autocomplete=\"off\">\n <div class=\"x\" *ngIf=\"query && !isLoading && !disabled\" (click)=\"remove($event)\">\n <i class=\"material-icons\" aria-label=\"Close\">close</i>\n </div>\n <!--Loading mask-->\n <div class=\"sk-fading-circle\" *ngIf=\"isLoading\">\n <div class=\"sk-circle1 sk-circle\"></div>\n <div class=\"sk-circle2 sk-circle\"></div>\n <div class=\"sk-circle3 sk-circle\"></div>\n <div class=\"sk-circle4 sk-circle\"></div>\n <div class=\"sk-circle5 sk-circle\"></div>\n <div class=\"sk-circle6 sk-circle\"></div>\n <div class=\"sk-circle7 sk-circle\"></div>\n <div class=\"sk-circle8 sk-circle\"></div>\n <div class=\"sk-circle9 sk-circle\"></div>\n <div class=\"sk-circle10 sk-circle\"></div>\n <div class=\"sk-circle11 sk-circle\"></div>\n <div class=\"sk-circle12 sk-circle\"></div>\n </div>\n </div>\n\n <!--FilteredList items-->\n <div class=\"suggestions-container\" id=\"suggestions\" role=\"listbox\"\n [ngClass]=\"{ 'is-hidden': isHistoryListVisible, 'is-visible': !isHistoryListVisible}\">\n <!--FilteredList heading-->\n <div class=\"heading\" *ngIf=\"filteredList.length > 0 && heading\">\n <div class=\"text\">{{heading}}</div>\n </div>\n\n <ul #filteredListElement>\n <li *ngFor=\"let item of filteredList; let idx = index\" class=\"item\">\n <!--string logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='isTypeString(item)'\n (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item | highlight: toHighlight }\">\n </ng-container>\n </div>\n <!--object logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='!isTypeString(item)'\n (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item | highlight: toHighlight : searchKeyword }\">\n </ng-container>\n </div>\n </li>\n </ul>\n </div>\n\n <!--HistoryList items-->\n <div class=\"suggestions-container\" id=\"suggestions-history\" role=\"listbox\"\n [ngClass]=\"{ 'is-hidden': !isHistoryListVisible, 'is-visible': isHistoryListVisible}\">\n <!--HistoryList heading-->\n <div class=\"heading\" *ngIf=\"historyList.length > 0 && historyHeading\">\n <div class=\"text\">{{historyHeading}}</div>\n <div class=\"x\" (click)=\"resetHistoryList($event)\">\n <i class=\"material-icons\" aria-label=\"Delete\">delete</i>\n </div>\n </div>\n\n <ul #historyListElement>\n <li *ngFor=\"let item of historyList; let idx = index\" class=\"item\">\n <!--string logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='isTypeString(item)' (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\">\n </ng-container>\n </div>\n <!--object logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='!isTypeString(item)' (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\">\n </ng-container>\n </div>\n <div class=\"x\" (click)=\"removeHistoryItem(idx, $event)\">\n <i class=\"material-icons\" aria-label=\"Close\">close</i>\n </div>\n </li>\n </ul>\n </div>\n\n <!--Not found-->\n <div class=\"not-found\" *ngIf=\"isLoading ? !isLoading && notFound : notFound\">\n <ng-container\n *ngTemplateOutlet=\"notFoundTemplate; context: { $implicit: notFoundText }\">\n </ng-container>\n </div>\n</div>\n<div class=\"autocomplete-overlay\" *ngIf=\"overlay\" (click)=\"handleOverlay()\"></div>\n", styles: ["@import\"https://fonts.googleapis.com/icon?family=Material+Icons\";.ng-autocomplete{width:600px}.autocomplete-container{box-shadow:0 1px 3px #0003,0 1px 1px #00000024,0 2px 1px -1px #0000001f;position:relative;overflow:visible;height:40px}.autocomplete-container .input-container input{font-size:14px;box-sizing:border-box;border:none;box-shadow:none;outline:none;background-color:#fff;color:#000000de;width:100%;padding:0 15px;line-height:40px;height:40px}.autocomplete-container .input-container input:disabled{background-color:#eee;color:#666}.autocomplete-container .input-container .x{position:absolute;right:10px;margin:auto;cursor:pointer;top:50%;transform:translateY(-50%)}.autocomplete-container .input-container .x i{color:#0000008a;font-size:22px;vertical-align:middle}.autocomplete-container .suggestions-container{position:absolute;width:100%;background:#fff;height:auto;box-shadow:0 2px 5px #00000040;box-sizing:border-box}.autocomplete-container .suggestions-container ul{padding:0;margin:0;max-height:240px;overflow-y:auto}.autocomplete-container .suggestions-container ul li{position:relative;list-style:none;padding:0;margin:0;cursor:pointer}.autocomplete-container .suggestions-container ul li a{padding:14px 15px;display:block;text-decoration:none;color:#333;cursor:pointer;color:#000000de;font-size:15px}.autocomplete-container .suggestions-container ul li:hover,.autocomplete-container .suggestions-container .complete-selected{background-color:#9e9e9e2e}.autocomplete-container .suggestions-container .heading{position:relative;padding:10px 15px;border:solid 1px #f1f1f1}.autocomplete-container .suggestions-container .heading .text{font-size:.85em}.autocomplete-container .suggestions-container .x{position:absolute;right:10px;margin:auto;cursor:pointer;top:50%;transform:translateY(-50%)}.autocomplete-container .suggestions-container .x i{color:#0000008a;font-size:18px;vertical-align:middle}.autocomplete-container .suggestions-container.is-hidden{visibility:hidden}.autocomplete-container .suggestions-container.is-visible{visibility:visible}.autocomplete-container .not-found{padding:0 .75em;border:solid 1px #f1f1f1;background:#fff}.autocomplete-container .not-found div{padding:.4em 0;font-size:.95em;line-height:1.4;border-bottom:1px solid rgba(230,230,230,.7)}.autocomplete-container.active{z-index:999}.highlight{font-weight:700}.autocomplete-overlay{position:fixed;background-color:transparent;width:100%;height:100%;inset:0;z-index:50}input[type=text]::-ms-clear{display:none}.sk-fading-circle{width:20px;height:20px;position:absolute;right:10px;top:0;bottom:0;margin:auto}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:\"\";display:block;margin:0 auto;width:15%;height:15%;background-color:#333;border-radius:100%;animation:sk-circleFadeDelay 1.2s infinite ease-in-out both}.sk-fading-circle .sk-circle2{transform:rotate(30deg)}.sk-fading-circle .sk-circle3{transform:rotate(60deg)}.sk-fading-circle .sk-circle4{transform:rotate(90deg)}.sk-fading-circle .sk-circle5{transform:rotate(120deg)}.sk-fading-circle .sk-circle6{transform:rotate(150deg)}.sk-fading-circle .sk-circle7{transform:rotate(180deg)}.sk-fading-circle .sk-circle8{transform:rotate(210deg)}.sk-fading-circle .sk-circle9{transform:rotate(240deg)}.sk-fading-circle .sk-circle10{transform:rotate(270deg)}.sk-fading-circle .sk-circle11{transform:rotate(300deg)}.sk-fading-circle .sk-circle12{transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{animation-delay:-1s}.sk-fading-circle .sk-circle4:before{animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{animation-delay:-.1s}@keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: HighlightPipe, name: "highlight" }], encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AutocompleteComponent, decorators: [{ type: Component, args: [{ selector: 'ng-autocomplete', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true } ], encapsulation: ViewEncapsulation.None, host: { '(document:click)': 'handleClick($event)', 'class': 'ng-autocomplete' }, template: "<div class=\"autocomplete-container\" aria-owns=\"suggestions suggestions-history\" [attr.aria-expanded]=\"isOpen\"\n [ngClass]=\"{ 'active': isOpen}\">\n <div class=\"input-container\">\n <input #searchInput\n type=\"text\"\n attr.aria-label=\"{{placeholder}}\"\n aria-autocomplete=\"list\"\n role=\"combobox\"\n placeholder={{placeholder}}\n [(ngModel)]=query\n (input)=\"onChange($event)\"\n (focus)=handleFocus($event)\n (blur)=onTouched($event)\n [disabled]=\"disabled\"\n autocomplete=\"off\">\n <div class=\"x\" *ngIf=\"query && !isLoading && !disabled\" (click)=\"remove($event)\">\n <i class=\"material-icons\" aria-label=\"Close\">close</i>\n </div>\n <!--Loading mask-->\n <div class=\"sk-fading-circle\" *ngIf=\"isLoading\">\n <div class=\"sk-circle1 sk-circle\"></div>\n <div class=\"sk-circle2 sk-circle\"></div>\n <div class=\"sk-circle3 sk-circle\"></div>\n <div class=\"sk-circle4 sk-circle\"></div>\n <div class=\"sk-circle5 sk-circle\"></div>\n <div class=\"sk-circle6 sk-circle\"></div>\n <div class=\"sk-circle7 sk-circle\"></div>\n <div class=\"sk-circle8 sk-circle\"></div>\n <div class=\"sk-circle9 sk-circle\"></div>\n <div class=\"sk-circle10 sk-circle\"></div>\n <div class=\"sk-circle11 sk-circle\"></div>\n <div class=\"sk-circle12 sk-circle\"></div>\n </div>\n </div>\n\n <!--FilteredList items-->\n <div class=\"suggestions-container\" id=\"suggestions\" role=\"listbox\"\n [ngClass]=\"{ 'is-hidden': isHistoryListVisible, 'is-visible': !isHistoryListVisible}\">\n <!--FilteredList heading-->\n <div class=\"heading\" *ngIf=\"filteredList.length > 0 && heading\">\n <div class=\"text\">{{heading}}</div>\n </div>\n\n <ul #filteredListElement>\n <li *ngFor=\"let item of filteredList; let idx = index\" class=\"item\">\n <!--string logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='isTypeString(item)'\n (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item | highlight: toHighlight }\">\n </ng-container>\n </div>\n <!--object logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='!isTypeString(item)'\n (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item | highlight: toHighlight : searchKeyword }\">\n </ng-container>\n </div>\n </li>\n </ul>\n </div>\n\n <!--HistoryList items-->\n <div class=\"suggestions-container\" id=\"suggestions-history\" role=\"listbox\"\n [ngClass]=\"{ 'is-hidden': !isHistoryListVisible, 'is-visible': isHistoryListVisible}\">\n <!--HistoryList heading-->\n <div class=\"heading\" *ngIf=\"historyList.length > 0 && historyHeading\">\n <div class=\"text\">{{historyHeading}}</div>\n <div class=\"x\" (click)=\"resetHistoryList($event)\">\n <i class=\"material-icons\" aria-label=\"Delete\">delete</i>\n </div>\n </div>\n\n <ul #historyListElement>\n <li *ngFor=\"let item of historyList; let idx = index\" class=\"item\">\n <!--string logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='isTypeString(item)' (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\">\n </ng-container>\n </div>\n <!--object logic-->\n <div [class.complete-selected]=\"idx === selectedIdx\" *ngIf='!isTypeString(item)' (click)=\"select(item)\">\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\">\n </ng-container>\n </div>\n <div class=\"x\" (click)=\"removeHistoryItem(idx, $event)\">\n <i class=\"material-icons\" aria-label=\"Close\">close</i>\n </div>\n </li>\n </ul>\n </div>\n\n <!--Not found-->\n <div class=\"not-found\" *ngIf=\"isLoading ? !isLoading && notFound : notFound\">\n <ng-container\n *ngTemplateOutlet=\"notFoundTemplate; context: { $implicit: notFoundText }\">\n </ng-container>\n </div>\n</div>\n<div class=\"autocomplete-overlay\" *ngIf=\"overlay\" (click)=\"handleOverlay()\"></div>\n", styles: ["@import\"https://fonts.googleapis.com/icon?family=Material+Icons\";.ng-autocomplete{width:600px}.autocomplete-container{box-shadow:0 1px 3px #0003,0 1px 1px #00000024,0 2px 1px -1px #0000001f;position:relative;overflow:visible;height:40px}.autocomplete-container .input-container input{font-size:14px;box-sizing:border-box;border:none;box-shadow:none;outline:none;background-color:#fff;color:#000000de;width:100%;padding:0 15px;line-height:40px;height:40px}.autocomplete-container .input-container input:disabled{background-color:#eee;color:#666}.autocomplete-container .input-container .x{position:absolute;right:10px;margin:auto;cursor:pointer;top:50%;transform:translateY(-50%)}.autocomplete-container .input-container .x i{color:#0000008a;font-size:22px;vertical-align:middle}.autocomplete-container .suggestions-container{position:absolute;width:100%;background:#fff;height:auto;box-shadow:0 2px 5px #00000040;box-sizing:border-box}.autocomplete-container .suggestions-container ul{padding:0;margin:0;max-height:240px;overflow-y:auto}.autocomplete-container .suggestions-container ul li{position:relative;list-style:none;padding:0;margin:0;cursor:pointer}.autocomplete-container .suggestions-container ul li a{padding:14px 15px;display:block;text-decoration:none;color:#333;cursor:pointer;color:#000000de;font-size:15px}.autocomplete-container .suggestions-container ul li:hover,.autocomplete-container .suggestions-container .complete-selected{background-color:#9e9e9e2e}.autocomplete-container .suggestions-container .heading{position:relative;padding:10px 15px;border:solid 1px #f1f1f1}.autocomplete-container .suggestions-container .heading .text{font-size:.85em}.autocomplete-container .suggestions-container .x{position:absolute;right:10px;margin:auto;cursor:pointer;top:50%;transform:translateY(-50%)}.autocomplete-container .suggestions-container .x i{color:#0000008a;font-size:18px;vertical-align:middle}.autocomplete-container .suggestions-container.is-hidden{visibility:hidden}.autocomplete-container .suggestions-container.is-visible{visibility:visible}.autocomplete-container .not-found{padding:0 .75em;border:solid 1px #f1f1f1;background:#fff}.autocomplete-container .not-found div{padding:.4em 0;font-size:.95em;line-height:1.4;border-bottom:1px solid rgba(230,230,230,.7)}.autocomplete-container.active{z-index:999}.highlight{font-weight:700}.autocomplete-overlay{position:fixed;background-color:transparent;width:100%;height:100%;inset:0;z-index:50}input[type=text]::-ms-clear{display:none}.sk-fading-circle{width:20px;height:20px;position:absolute;right:10px;top:0;bottom:0;margin:auto}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:\"\";display:block;margin:0 auto;width:15%;height:15%;background-color:#333;border-radius:100%;animation:sk-circleFadeDelay 1.2s infinite ease-in-out both}.sk-fading-circle .sk-circle2{transform:rotate(30deg)}.sk-fading-circle .sk-circle3{transform:rotate(60deg)}.sk-fading-circle .sk-circle4{transform:rotate(90deg)}.sk-fading-circle .sk-circle5{transform:rotate(120deg)}.sk-fading-circle .sk-circle6{transform:rotate(150deg)}.sk-fading-circle .sk-circle7{transform:rotate(180deg)}.sk-fading-circle .sk-circle8{transform:rotate(210deg)}.sk-fading-circle .sk-circle9{transform:rotate(240deg)}.sk-fading-circle .sk-circle10{transform:rotate(270deg)}.sk-fading-circle .sk-circle11{transform:rotate(300deg)}.sk-fading-circle .sk-circle12{transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{animation-delay:-1s}.sk-fading-circle .sk-circle4:before{animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{animation-delay:-.1s}@keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}\n"] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { searchInput: [{ type: ViewChild, args: ['searchInput'] }], filteredListElement: [{ type: ViewChild, args: ['filteredListElement'] }], historyListElement: [{ type: ViewChild, args: ['historyListElement'] }], data: [{ type: Input }], searchKeyword: [{ type: Input }], placeholder: [{ type: Input }], heading: [{ type: Input }], initialValue: [{ type: Input }], historyIdentifier: [{ type: Input }], historyHeading: [{ type: Input }], historyListMaxNumber: [{ type: Input }], notFoundText: [{ type: Input }], isLoading: [{ type: Input }], debounceTime: [{ type: Input }], disabled: [{ type: Input }], minQueryLength: [{ type: Input }], focusFirst: [{ type: Input }], customFilter: [{ type: Input }], selectedValueRender: [{ type: Input }], selected: [{ type: Output }], inputChanged: [{ type: Output }], inputFocused: [{ type: Output }], inputCleared: [{ type: Output }], opened: [{ type: Output }], closed: [{ type: Output }], scrolledToEnd: [{ type: Output }], itemTemplate: [{ type: Input }], notFoundTemplate: [{