UNPKG

angular-dropdown-component

Version:

Even though a dropdown (select-option) is a pretty common utility but it still doesn't support basics such as search.

362 lines (355 loc) 17.2 kB
import { Component, Input, Output, EventEmitter, ViewChild, Pipe, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ class DropdownComponent { constructor() { this.selectedOptionChange = new EventEmitter(); this.ifContainerFocused = false; } /** * @return {?} */ ngAfterViewInit() { this.mutationObserverDOM = new MutationObserver(mutations => { this.setContainerDimensions(); }); this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement, { childList: true }); this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement.children[1], { childList: true }); if (this.filter) this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement.children[1].children[1], { childList: true }); else this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement.children[1].children[0], { childList: true }); } /** * @param {?} simpleChanges * @return {?} */ ngOnChanges(simpleChanges) { if (simpleChanges.hasOwnProperty("data")) { this._data = this.data; } if (this.data && !this.selectedOption) this.setDefaultOption(); else if (this.data && this.selectedOption) { if (this.selectedOption.hasOwnProperty("id")) { this.data.forEach(obj => { if (obj.id == this.selectedOption["id"]) this._selectedOption = obj; }); } else if (this.selectedOption.hasOwnProperty("name")) { this.data.forEach(obj => { if (obj.name == this.selectedOption["name"]) this._selectedOption = obj; }); } else if (this.editable) { this._selectedOption = this.selectedOption; return; } if (!this._selectedOption) this.setDefaultOption(); } } /** * @return {?} */ setDefaultOption() { if (this.editable) this._selectedOption = ""; else this._selectedOption = this.data[0]; } /** * @return {?} */ setContainerDimensions() { let /** @type {?} */ defaultDropdownHeight = this.dropdownMenu.nativeElement.children[0].offsetHeight; let /** @type {?} */ actualList; if (this.filter) { defaultDropdownHeight += this.dropdownMenu.nativeElement.children[1].children[0].offsetHeight; actualList = this.dropdownMenu.nativeElement.children[1].children[1].children; } else actualList = this.dropdownMenu.nativeElement.children[1].children[0].children; defaultDropdownHeight += Array.from(actualList).reduce((accumulator, currentValue) => accumulator + currentValue["offsetHeight"], 0); let /** @type {?} */ dropdownButtonElement = this.dropdownButton.nativeElement; let /** @type {?} */ dropdownButtonRect = dropdownButtonElement.getBoundingClientRect(); let /** @type {?} */ dropdownFilterHeight = this.filter ? this.dropdownFilter.nativeElement.offsetHeight : 0; let /** @type {?} */ distanceFromTop = dropdownButtonRect.top; let /** @type {?} */ distanceFromLeft = dropdownButtonRect.left; let /** @type {?} */ distanceFromBottom = window.innerHeight - distanceFromTop - dropdownButtonElement.offsetHeight; let /** @type {?} */ distanceFromRight = window.innerWidth - distanceFromLeft - dropdownButtonElement.offsetWidth; this.selectedItemFocusedStyles = { "width": dropdownButtonElement.offsetWidth - 2 + "px", "height": dropdownButtonElement.offsetHeight - 2 + "px" }; this.restOfListWithoutFilterFocusedStyles = { "margin-top": dropdownFilterHeight + "px" }; // If the space below is more than the dropdown height if (distanceFromBottom > defaultDropdownHeight) { // Un-reversify the list of array this.containerFocusedStyles = { "top": distanceFromTop + "px", "left": distanceFromLeft + "px", "width": dropdownButtonElement.offsetWidth + 'px', "flex-direction": "column" }; this.restOfListFocusedStyles = { "margin-top": dropdownButtonElement.offsetHeight + "px" }; } else if (distanceFromTop > defaultDropdownHeight) { // If the space above is more than the dropdown height // Reversify the list of array this.containerFocusedStyles = { "bottom": distanceFromBottom + "px", "left": distanceFromLeft + "px", "width": dropdownButtonElement.offsetWidth + 'px', "flex-direction": "column-reverse" }; this.restOfListFocusedStyles = { "margin-bottom": dropdownButtonElement.offsetHeight + "px" }; this.selectedItemFocusedStyles = Object.assign({}, this.selectedItemFocusedStyles, { "border-top": "1px solid #e0e0e0" }); } else { // If space above and below both are less, show where it is maximum // When space below is more if (distanceFromBottom > distanceFromTop) { this.containerFocusedStyles = { "top": distanceFromTop + "px", "bottom": "20px", "left": distanceFromLeft + "px", "width": dropdownButtonElement.offsetWidth + 'px', "flex-direction": "column" }; this.restOfListFocusedStyles = { "margin-top": dropdownButtonElement.offsetHeight + "px" }; } else { // When space above is more this.containerFocusedStyles = { "top": "20px", "bottom": distanceFromBottom + "px", "left": distanceFromLeft + "px", "width": dropdownButtonElement.offsetWidth + 'px', "flex-direction": "column-reverse" }; this.restOfListFocusedStyles = { "margin-bottom": dropdownButtonElement.offsetHeight + "px" }; this.selectedItemFocusedStyles = Object.assign({}, this.selectedItemFocusedStyles, { "border-top": "1px solid #e0e0e0" }); } } } /** * @param {?} $event * @return {?} */ onFilterSearch($event) { this.setSearchedItems($event.target.value); } /** * @param {?} value * @return {?} */ setSearchedItems(value) { this._data = this.data.filter(element => element.name .toLowerCase() .indexOf(value.toLowerCase()) !== -1); } /** * @param {?} $event * @return {?} */ onInputFocus($event) { this.dropdownInput.nativeElement.focus(); this.ifContainerFocused = false; } /** * @param {?} $event * @return {?} */ onInputChange($event) { this._selectedOption = $event.target.value; // Broadcast Event this.selectedOptionChange.emit(this._selectedOption); } /** * @param {?} $event * @param {?} option * @return {?} */ onDropdownItemSelect($event, option) { this._selectedOption = option; // if (this.selectedOption.hasOwnProperty("id")) // this.selectedOption = { id: this._selectedOption.id }; // else this.selectedOption = { name: this._selectedOption.name }; // Broadcast Event this.selectedOptionChange.emit(option); this.hideDropdown(); } /** * @param {?} $event * @return {?} */ onDropdownMenuClick($event) { this.clearSearchFilter(); this.setContainerDimensions(); if ($event.target instanceof HTMLInputElement) return; else this.showDropdown(); } /** * @param {?} $event * @return {?} */ onDropdownBlur($event) { if (this.dropdownFilter && $event.relatedTarget == this.dropdownFilter.nativeElement && $event.relatedTarget instanceof HTMLInputElement) return; this.ifContainerFocused = false; this.hideDropdown(); } /** * @return {?} */ clearSearchFilter() { if (!this.dropdownFilter) return; this.dropdownFilter.nativeElement.value = ""; this.setSearchedItems(""); } /** * @return {?} */ showDropdown() { this.dropdownMenu.nativeElement.focus(); } /** * @return {?} */ hideDropdown() { this.dropdownMenu.nativeElement.blur(); } /** * @return {?} */ ngOnDestroy() { this.mutationObserverDOM.disconnect(); } } DropdownComponent.decorators = [ { type: Component, args: [{ selector: "ng-dropdown", template: `<ng-container> <div class="ng-dropdown"> <span *ngIf="dTitle" [innerText]="dTitle" class="ng-dropdown__title"></span> <ul class="ng-dropdown__container" tabindex="0" (focus)="ifContainerFocused=true" (blur)="onDropdownBlur($event)" [ngStyle]="ifContainerFocused ? containerFocusedStyles: {}" #dropdownMenu> <li [ngStyle]="ifContainerFocused ? selectedItemFocusedStyles: {}"> <a (click)="hideDropdown()"> <span *ngIf="!editable; else ifEditableList" (focus)="onInputFocus($event)" [innerText]="_selectedOption?.name"></span> <ng-template #ifEditableList> <input class="ng-dropdown__container--editable" [ngModel]="_selectedOption | GetSelectedNamePipe" (focus)="onInputFocus($event)"/> </ng-template> <img src="assets/downPopup.png" /> </a> </li> <ul class="ng-dropdown__container--list" [ngStyle]="ifContainerFocused ? restOfListFocusedStyles: {}"> <li *ngIf="filter" [ngStyle]="ifContainerFocused ? selectedItemFocusedStyles: {}"> <input #dropdownFilter class="ng-dropdown__container--list__filter" placeholder="Search" (keyup)="onFilterSearch($event)" (blur)="onDropdownBlur($event)" /> </li> <ul class="ng-dropdown__container--list__sublist" [ngStyle]="ifContainerFocused ? restOfListWithoutFilterFocusedStyles: {}"> <ng-container *ngIf="_data?.length else noResults"> <li *ngFor="let option of _data" [ngClass]="_selectedOption?.name == option.name ? 'active' : ''"> <a [innerText]="option.name" (click)="onDropdownItemSelect($event, option)"></a> </li> </ng-container> <ng-template #noResults> <li> <a>No Results Found</a> </li> </ng-template> </ul> </ul> </ul> <div class="ng-dropdown__button" [ngClass]="disabled ? 'disabled' : ''" (mouseup)="onDropdownMenuClick($event)" #dropdownButton> <span *ngIf="!editable; else ifEditable" [innerText]="_selectedOption?.name"></span> <ng-template #ifEditable> <input #dropdownInput (keyup)="onInputChange($event)" [ngModel]="_selectedOption | GetSelectedNamePipe" /> </ng-template> <img src="assets/downPopup.png" /> </div> </div> </ng-container> `, styles: [`.ng-dropdown{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;position:relative;padding-bottom:10px}.ng-dropdown .ng-dropdown__title{padding-bottom:5px}.ng-dropdown .ng-dropdown__button{background:#fff;border:1px solid #c9c9c9;padding:2px 10px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;height:18px}.ng-dropdown .ng-dropdown__button span,.ng-dropdown__container>li a span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ng-dropdown .ng-dropdown__button img{height:10px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ng-dropdown .ng-dropdown__button input,.ng-dropdown .ng-dropdown__container li input.ng-dropdown__container--editable{width:100%;height:100%;border:none;outline:0;overflow:hidden;text-overflow:ellipsis}.ng-dropdown .ng-dropdown__button.disabled{pointer-events:none;opacity:.7}.ng-dropdown .ng-dropdown__container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;background:#fff;list-style-type:none;border:1px solid #c9c9c9;position:fixed;left:-10000px;outline:0;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:100000;padding:0;margin:0;-webkit-box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.ng-dropdown .ng-dropdown__container:focus{left:0}.ng-dropdown .ng-dropdown__container:focus+.ng-dropdown__button{pointer-events:none}.ng-dropdown .ng-dropdown__container li:hover{background-color:#e0e0e0;color:#000}.ng-dropdown .ng-dropdown__container>li:first-child a{height:100%}.ng-dropdown .ng-dropdown__container>li:first-child,.ng-dropdown__container--list>li:first-child{position:fixed;background-color:#fff}.ng-dropdown .ng-dropdown__container>li:first-child a,.ng-dropdown__container--list>li:first-child a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.ng-dropdown .ng-dropdown__container>li:first-child a img,.ng-dropdown__container--list>li:first-child a img{height:10px}.ng-dropdown .ng-dropdown__container>li:first-child:hover,.ng-dropdown__container--list>li:first-child:hover{color:initial}.ng-dropdown .ng-dropdown__container li a,.ng-dropdown__container--list>li:first-child li a{padding:0 10px;height:22px;line-height:22px;cursor:pointer;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;display:block;text-align:left}.ng-dropdown .ng-dropdown__container li input.ng-dropdown__container--list__filter{width:100%;padding:0 9px;height:22px;outline:0;-webkit-box-sizing:border-box;box-sizing:border-box;border:none;border-bottom:1px solid #e0e0e0;border-top:1px solid #e0e0e0}.ng-dropdown .ng-dropdown__container ul{list-style-type:none;padding:0;margin:0;overflow:auto}.ng-dropdown__container--list__sublist li:first-child{position:initial}.ng-dropdown__container--list__sublist li.active{background-color:#6fbbff;color:#fff}`] },] }, ]; /** @nocollapse */ DropdownComponent.ctorParameters = () => []; DropdownComponent.propDecorators = { "data": [{ type: Input },], "disabled": [{ type: Input },], "dTitle": [{ type: Input },], "selectedOption": [{ type: Input },], "filter": [{ type: Input },], "editable": [{ type: Input },], "selectedOptionChange": [{ type: Output },], "dropdownMenu": [{ type: ViewChild, args: ["dropdownMenu",] },], "dropdownButton": [{ type: ViewChild, args: ["dropdownButton",] },], "dropdownInput": [{ type: ViewChild, args: ["dropdownInput",] },], "dropdownFilter": [{ type: ViewChild, args: ["dropdownFilter",] },], }; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ class GetSelectedNamePipe { /** * @param {?} selectedOption * @return {?} */ transform(selectedOption) { return typeof selectedOption == 'object' ? selectedOption.name : selectedOption; } } GetSelectedNamePipe.decorators = [ { type: Pipe, args: [{ name: 'GetSelectedNamePipe' },] }, ]; /** @nocollapse */ GetSelectedNamePipe.ctorParameters = () => []; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ class DropdownModule { } DropdownModule.decorators = [ { type: NgModule, args: [{ imports: [CommonModule, FormsModule], declarations: [DropdownComponent, GetSelectedNamePipe], exports: [DropdownComponent] },] }, ]; /** @nocollapse */ DropdownModule.ctorParameters = () => []; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ /** * Generated bundle index. Do not edit. */ export { DropdownModule, DropdownComponent as ɵa, GetSelectedNamePipe as ɵb }; //# sourceMappingURL=angular-dropdown-component.js.map