UNPKG

igniteui-angular

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

1,555 lines 173 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import * as tslib_1 from "tslib"; import { ConnectedPositioningStrategy } from './../services/overlay/position/connected-positioning-strategy'; import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgModule, Output, TemplateRef, ViewChild, Optional, Inject, Injector, forwardRef } from '@angular/core'; import { IgxComboItemDirective, IgxComboEmptyDirective, IgxComboHeaderItemDirective, IgxComboHeaderDirective, IgxComboFooterDirective, IgxComboAddItemDirective, IgxComboToggleIconDirective, IgxComboClearIconDirective } from './combo.directives'; import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; import { IgxCheckboxModule } from '../checkbox/checkbox.component'; import { IgxSelectionAPIService } from '../core/selection'; import { cloneArray } from '../core/utils'; import { IgxStringFilteringOperand, IgxBooleanFilteringOperand } from '../data-operations/filtering-condition'; import { FilteringLogic } from '../data-operations/filtering-expression.interface'; import { SortingDirection } from '../data-operations/sorting-expression.interface'; import { IgxForOfModule, IgxForOfDirective } from '../directives/for-of/for_of.directive'; import { IgxIconModule } from '../icon/index'; import { IgxRippleModule } from '../directives/ripple/ripple.directive'; import { IgxToggleModule } from '../directives/toggle/toggle.directive'; import { IgxButtonModule } from '../directives/button/button.directive'; import { IgxDropDownModule } from '../drop-down/index'; import { IgxInputGroupModule } from '../input-group/input-group.component'; import { IgxComboItemComponent } from './combo-item.component'; import { IgxComboDropDownComponent } from './combo-dropdown.component'; import { IgxComboFilterConditionPipe, IgxComboFilteringPipe, IgxComboGroupingPipe, IgxComboSortingPipe } from './combo.pipes'; import { AbsoluteScrollStrategy } from '../services'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { DeprecateProperty } from '../core/deprecateDecorators'; import { DefaultSortingStrategy } from '../data-operations/sorting-strategy'; import { DisplayDensityBase, DisplayDensityToken } from '../core/density'; import { IGX_COMBO_COMPONENT } from './combo.common'; import { IgxComboAddItemComponent } from './combo-add-item.component'; import { IgxComboAPIService } from './combo.api'; import { take } from 'rxjs/operators'; /** * Custom strategy to provide the combo with callback on initial positioning */ class ComboConnectedPositionStrategy extends ConnectedPositioningStrategy { /** * @param {?} callback */ constructor(callback) { super(); this._callback = callback; } /** * @param {?} contentElement * @param {?} size * @param {?=} document * @param {?=} initialCall * @return {?} */ position(contentElement, size, document, initialCall) { if (initialCall) { this._callback(); } super.position(contentElement, size); } } if (false) { /** * @type {?} * @private */ ComboConnectedPositionStrategy.prototype._callback; } /** @enum {string} */ const DataTypes = { EMPTY: 'empty', PRIMITIVE: 'primitive', COMPLEX: 'complex', PRIMARYKEY: 'valueKey', }; /** @enum {number} */ const IgxComboState = { /** * Combo with initial state. */ INITIAL: 0, /** * Combo with valid state. */ VALID: 1, /** * Combo with invalid state. */ INVALID: 2, }; export { IgxComboState }; IgxComboState[IgxComboState.INITIAL] = 'INITIAL'; IgxComboState[IgxComboState.VALID] = 'VALID'; IgxComboState[IgxComboState.INVALID] = 'INVALID'; /** * @record */ export function IComboSelectionChangeEventArgs() { } if (false) { /** @type {?} */ IComboSelectionChangeEventArgs.prototype.oldSelection; /** @type {?} */ IComboSelectionChangeEventArgs.prototype.newSelection; /** @type {?|undefined} */ IComboSelectionChangeEventArgs.prototype.event; } /** * @record */ export function IComboItemAdditionEvent() { } if (false) { /** @type {?} */ IComboItemAdditionEvent.prototype.oldCollection; /** @type {?} */ IComboItemAdditionEvent.prototype.addedItem; /** @type {?} */ IComboItemAdditionEvent.prototype.newCollection; } /** @type {?} */ let NEXT_ID = 0; /** @type {?} */ const noop = () => { }; const ɵ0 = noop; export class IgxComboComponent extends DisplayDensityBase { /** * @param {?} elementRef * @param {?} cdr * @param {?} selection * @param {?} comboAPI * @param {?} _displayDensityOptions * @param {?} injector */ constructor(elementRef, cdr, selection, comboAPI, _displayDensityOptions, injector) { super(_displayDensityOptions); this.elementRef = elementRef; this.cdr = cdr; this.selection = selection; this.comboAPI = comboAPI; this._displayDensityOptions = _displayDensityOptions; this.injector = injector; /** * @hidden \@internal */ this.customValueFlag = true; /** * @hidden \@internal */ this.defaultFallbackGroup = 'Other'; this.stringFilters = IgxStringFilteringOperand; this.booleanFilters = IgxBooleanFilteringOperand; this._filteringLogic = FilteringLogic.Or; this._filteringExpressions = []; this._sortingExpressions = []; this._groupKey = ''; this._prevInputValue = ''; this._dataType = ''; this.ngControl = null; this.destroy$ = new Subject(); this._data = []; this._filteredData = []; this._onChangeCallback = noop; this.overlaySettings = { scrollStrategy: new AbsoluteScrollStrategy(), modal: false, closeOnOutsideClick: true, excludePositionTarget: true }; this._value = ''; /** * @hidden \@internal */ this.searchInput = null; /** * @hidden \@internal */ this.comboInput = null; /** * The custom template, if any, that should be used when rendering ITEMS in the combo list * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.itemTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboItem> * <div class="custom-item" let-item let-key="valueKey"> * <div class="custom-item__name">{{ item[key] }}</div> * <div class="custom-item__cost">{{ item.cost }}</div> * </div> * </ng-template> * </igx-combo> * ``` */ this.itemTemplate = null; /** * The custom template, if any, that should be used when rendering the HEADER for the combo items list * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.headerTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboHeader> * <div class="combo__header"> * This is a custom header * </div> * </ng-template> * </igx-combo> * ``` */ this.headerTemplate = null; /** * The custom template, if any, that should be used when rendering the FOOTER for the combo items list * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.footerTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboFooter> * <div class="combo__footer"> * This is a custom footer * </div> * </ng-template> * </igx-combo> * ``` */ this.footerTemplate = null; /** * The custom template, if any, that should be used when rendering HEADER ITEMS for groups in the combo list * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.headerItemTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboHeaderItem let-item let-key="groupKey"> * <div class="custom-item--group">Group header for {{ item[key] }}</div> * </ng-template> * </igx-combo> * ``` */ this.headerItemTemplate = null; /** * The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.addItemTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboAddItem> * <button class="combo__add-button"> * Click to add item * </button> * </ng-template> * </igx-combo> * ``` */ this.addItemTemplate = null; /** * The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.emptyTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboEmpty> * <div class="combo--emtpy"> * There are no items to display * </div> * </ng-template> * </igx-combo> * ``` */ this.emptyTemplate = null; /** * The custom template, if any, that should be used when rendering the combo TOGGLE(open/close) button * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.toggleIconTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboToggleIcon let-collapsed> * <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon> * </ng-template> * </igx-combo> * ``` */ this.toggleIconTemplate = null; /** * The custom template, if any, that should be used when rendering the combo CLEAR button * * ```typescript * // Set in typescript * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate; * myComponent.combo.clearIconTemplate = myCustomTemplate; * ``` * ```html * <!-- Set in markup --> * <igx-combo #combo> * ... * <ng-template igxComboClearIcon> * <igx-icon>clear</igx-icon> * </ng-template> * </igx-combo> * ``` */ this.clearIconTemplate = null; this.dropdownContainer = null; /** * Emitted when item selection is changing, before the selection completes * * ```html * <igx-combo (onSelectionChange)='handleSelection()'></igx-combo> * ``` */ this.onSelectionChange = new EventEmitter(); /** * Emitted before the dropdown is opened * * ```html * <igx-combo onOpening='handleOpening($event)'></igx-combo> * ``` */ this.onOpening = new EventEmitter(); /** * Emitted after the dropdown is opened * * ```html * <igx-combo (onOpened)='handleOpened()'></igx-combo> * ``` */ this.onOpened = new EventEmitter(); /** * Emitted before the dropdown is closed * * ```html * <igx-combo (onClosing)='handleClosing($event)'></igx-combo> * ``` */ this.onClosing = new EventEmitter(); /** * Emitted after the dropdown is closed * * ```html * <igx-combo (onClosed)='handleClosed()'></igx-combo> * ``` */ this.onClosed = new EventEmitter(); /** * Emitted when an item is being added to the data collection * * ```html * <igx-combo (onAddition)='handleAdditionEvent()'></igx-combo> * ``` */ this.onAddition = new EventEmitter(); /** * Emitted when the value of the search input changes (e.g. typing, pasting, clear, etc.) * * ```html * <igx-combo (onSearchInput)='handleSearchInputEvent()'></igx-combo> * ``` */ this.onSearchInput = new EventEmitter(); /** * Emitted when new chunk of data is loaded from the virtualization * * ```html * <igx-combo (onDataPreLoad)='handleDataPreloadEvent()'></igx-combo> * ``` */ this.onDataPreLoad = new EventEmitter(); /** * Gets/gets combo id. * * ```typescript * // get * let id = this.combo.id; * ``` * * ```html * <!--set--> * <igx-combo [id]='combo1'></igx-combo> * ``` */ this.id = `igx-combo-${NEXT_ID++}`; /** * @hidden \@internal */ this.cssClass = 'igx-combo'; // Independent of display density, at the time being // Independent of display density, at the time being /** * @hidden \@internal */ this.role = 'combobox'; /** * Controls whether custom values can be added to the collection * * ```typescript * // get * let comboAllowsCustomValues = this.combo.allowCustomValues; * ``` * * ```html * <!--set--> * <igx-combo [allowCustomValues]='true'></igx-combo> * ``` */ this.allowCustomValues = false; /** * Configures the drop down list height * * ```typescript * // get * let myComboItemsMaxHeight = this.combo.itemsMaxHeight; * ``` * * ```html * <!--set--> * <igx-combo [itemsMaxHeight]='320'></igx-combo> * ``` */ this.itemsMaxHeight = 480; /** * Configures the drop down list item height * * ```typescript * // get * let myComboItemHeight = this.combo.itemHeight; * ``` * * ```html * <!--set--> * <igx-combo [itemHeight]='32'></igx-combo> * ``` */ this.itemHeight = 48; /** * @hidden \@internal */ this.filteringLogic = FilteringLogic.Or; /** * Defines the placeholder value for the combo value field * * ```typescript * // get * let myComboPlaceholder = this.combo.placeholder; * ``` * * ```html * <!--set--> * <igx-combo [placeholder]='newPlaceHolder'></igx-combo> * ``` */ this.placeholder = ''; /** * Defines the placeholder value for the combo dropdown search field * * ```typescript * // get * let myComboSearchPlaceholder = this.combo.searchPlaceholder; * ``` * * ```html * <!--set--> * <igx-combo [searchPlaceholder]='newPlaceHolder'></igx-combo> * ``` */ this.searchPlaceholder = 'Enter a Search Term'; /** * An \@Input property that enabled/disables filtering in the list. The default is `true`. * ```html * <igx-combo [filterable]="'false'"> * ``` */ this.filterable = true; /** * An \@Input property that enabled/disables combo. The default is `false`. * ```html * <igx-combo [disabled]="'true'"> * ``` */ this.disabled = false; /** * An \@Input property that sets how the combo will be styled. * The allowed values are `line`, `box`, `border` and `search`. The default is `box`. * ```html * <igx-combo [type]="'line'"> * ``` */ this.type = 'box'; /** * Gets/Sets if control is valid, when used in a form * * ```typescript * // get * let valid = this.combo.valid; * ``` * ```typescript * // set * this.combo.valid = IgxComboState.INVALID; * ``` */ this.valid = IgxComboState.INITIAL; /** * @hidden \@internal */ this.searchValue = ''; this.onStatusChanged = () => { if ((this.ngControl.control.touched || this.ngControl.control.dirty) && (this.ngControl.control.validator || this.ngControl.control.asyncValidator)) { this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID; } }; this.comboAPI.register(this); } /** * @hidden \@internal * @return {?} */ get displaySearchInput() { return this.filterable || this.allowCustomValues; } /** * @hidden \@internal * @private * @param {?} template * @return {?} */ set oldEmptyTemplate(template) { if (template) { this.emptyTemplate = template; } } /** * @hidden \@internal * @private * @param {?} template * @return {?} */ set oldHeaderTemplate(template) { if (template) { this.headerTemplate = template; } } /** * @hidden \@internal * @private * @param {?} template * @return {?} */ set oldFooterTemplate(template) { if (template) { this.footerTemplate = template; } } /** * @hidden \@internal * @private * @param {?} template * @return {?} */ set oldItemTemplate(template) { if (template) { this.itemTemplate = template; } } /** * @hidden \@internal * @private * @param {?} template * @return {?} */ set oldAddItemTemplate(template) { if (template) { this.addItemTemplate = template; } } /** * @hidden \@internal * @private * @param {?} template * @return {?} */ set oldHeaderItemTemplate(template) { if (template) { this.headerItemTemplate = template; } } /** * @hidden \@internal * @return {?} */ get validClass() { return this.valid === IgxComboState.VALID; } /** * @hidden \@internal * @return {?} */ get invalidClass() { return this.valid === IgxComboState.INVALID; } /** * @hidden \@internal * @return {?} */ get ariaExpanded() { return !this.dropdown.collapsed; } /** * @hidden \@internal * @return {?} */ get hasPopUp() { return 'listbox'; } /** * @hidden \@internal * @return {?} */ get ariaOwns() { return this.dropdown.id; } /** * @hidden \@internal * @return {?} */ get inputEmpty() { return !this.value && !this.placeholder; } /** * Combo data source. * * ```html * <!--set--> * <igx-combo [data]='items'></igx-combo> * ``` * @return {?} */ get data() { return this._data; } /** * @param {?} val * @return {?} */ set data(val) { this._data = (val) ? val : []; } /** * @param {?} val * @return {?} */ set displayKey(val) { this._displayKey = val; } /** * Combo text data source propery. * * ```typescript * // get * let myComboDisplayKey = this.combo.displayKey; * * // set * this.combo.displayKey = 'val'; * * ``` * * ```html * <!--set--> * <igx-combo [displayKey]='mydisplayKey'></igx-combo> * ``` * @return {?} */ get displayKey() { return this._displayKey ? this._displayKey : this.valueKey; } /** * The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[]. * * ```html * <!--set--> * <igx-combo [groupKey]='newGroupKey'></igx-combo> * ``` * @param {?} val * @return {?} */ set groupKey(val) { this.clearSorting(this._groupKey); this._groupKey = val; this.sort(this._groupKey); } /** * The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[]. * * ```typescript * // get * let currentGroupKey = this.combo.groupKey; * ``` * @return {?} */ get groupKey() { return this._groupKey; } /** * @hidden \@internal * @param {?} event * @return {?} */ onArrowDown(event) { event.preventDefault(); event.stopPropagation(); this.open(); } /** * @hidden \@internal * @param {?} event * @return {?} */ onInputClick(event) { event.stopPropagation(); event.preventDefault(); this.toggle(); } /** * Defines the current state of the virtualized data. It contains `startIndex` and `chunkSize` * * ```typescript * // get * let state = this.combo.virtualizationState; * ``` * @return {?} */ get virtualizationState() { return this.dropdown.verticalScrollContainer.state; } /** * Sets the current state of the virtualized data. * * ```typescript * // set * this.combo.virtualizationState(state); * ``` * @param {?} state * @return {?} */ set virtualizationState(state) { this.dropdown.verticalScrollContainer.state = state; } /** * Gets total count of the virtual data items, when using remote service. * * ```typescript * // get * let count = this.combo.totalItemCount; * ``` * @return {?} */ get totalItemCount() { return this.dropdown.verticalScrollContainer.totalItemCount; } /** * Sets total count of the virtual data items, when using remote service. * * ```typescript * // set * this.combo.totalItemCount(remoteService.count); * ``` * @param {?} count * @return {?} */ set totalItemCount(count) { this.dropdown.verticalScrollContainer.totalItemCount = count; this.cdr.detectChanges(); } /** * @hidden \@internal * @return {?} */ get filteringExpressions() { return this.filterable ? this._filteringExpressions : []; } /** * @hidden \@internal * @param {?} value * @return {?} */ set filteringExpressions(value) { this._filteringExpressions = value; this.cdr.markForCheck(); } /** * @hidden \@internal * @return {?} */ get sortingExpressions() { return this._sortingExpressions; } /** * @hidden \@internal * @param {?} value * @return {?} */ set sortingExpressions(value) { this._sortingExpressions = value; this.cdr.markForCheck(); } /** * @protected * @param {?=} field * @return {?} */ clearSorting(field) { if (field === undefined || field === null) { this.sortingExpressions = []; return; } /** @type {?} */ const currentState = cloneArray(this.sortingExpressions); /** @type {?} */ const index = currentState.findIndex((expr) => expr.fieldName === field); if (index > -1) { currentState.splice(index, 1); this.sortingExpressions = currentState; } } /** * The text displayed in the combo input * * ```typescript * // get * let comboValue = this.combo.value; * ``` * @return {?} */ get value() { return this._value; } /** * @hidden \@internal * @return {?} */ get filteredData() { return this.filterable ? this._filteredData : this.data; } /** * @hidden \@internal * @param {?} val * @return {?} */ set filteredData(val) { this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val; this.checkMatch(); } /** * @hidden \@internal * @param {?} event * @return {?} */ handleKeyUp(event) { if (event.key === 'ArrowDown' || event.key === 'Down') { this.dropdown.focusedItem = this.dropdown.items[0]; this.dropdownContainer.nativeElement.focus(); } else if (event.key === 'Escape' || event.key === 'Esc') { this.toggle(); } } /** * @hidden \@internal * @param {?} event * @return {?} */ handleKeyDown(event) { if (event.key === 'ArrowUp' || event.key === 'Up') { event.preventDefault(); event.stopPropagation(); this.close(); } } /** * @private * @return {?} */ checkMatch() { /** @type {?} */ const displayKey = this.displayKey; /** @type {?} */ const matchFn = (e) => { /** @type {?} */ const value = displayKey ? e[displayKey] : e; return value.toString().toLowerCase() === this.searchValue.trim().toLowerCase(); }; /** @type {?} */ const itemMatch = this.filteredData.some(matchFn); this.customValueFlag = this.allowCustomValues && !itemMatch; } /** * @hidden \@internal * @param {?=} event * @return {?} */ handleInputChange(event) { /** @type {?} */ let cdrFlag = false; /** @type {?} */ const vContainer = this.dropdown.verticalScrollContainer; if (event !== undefined && this._prevInputValue === event) { // Nothing has changed return; } else { this._prevInputValue = event !== undefined ? event : ''; } if (event !== undefined) { // Do not scroll if not scrollable if (vContainer.isScrollable()) { vContainer.scrollTo(0); } else { cdrFlag = true; } this.onSearchInput.emit(event); } if (this.filterable) { this.filter(); // If there was no scroll before filtering, check if there is after and detect changes if (cdrFlag) { vContainer.onChunkLoad.pipe(take(1)).subscribe(() => { if (vContainer.isScrollable()) { this.cdr.detectChanges(); } }); } } else { this.checkMatch(); } } /** * @hidden \@internal * @param {?} fieldName * @param {?=} dir * @param {?=} ignoreCase * @param {?=} strategy * @return {?} */ sort(fieldName, dir = SortingDirection.Asc, ignoreCase = true, strategy = DefaultSortingStrategy.instance()) { if (!fieldName) { return; } /** @type {?} */ const sortingState = cloneArray(this.sortingExpressions, true); this.prepare_sorting_expression(sortingState, fieldName, dir, ignoreCase, strategy); this.sortingExpressions = sortingState; } /** * @hidden \@internal * @param {?} val * @return {?} */ getValueByValueKey(val) { if (!val && val !== 0) { return undefined; } return this.valueKey ? this.data.filter((e) => e[this.valueKey] === val)[0] : this.data.filter((e) => e === val); } /** * @protected * @param {?} state * @param {?} fieldName * @param {?} dir * @param {?} ignoreCase * @param {?} strategy * @return {?} */ prepare_sorting_expression(state, fieldName, dir, ignoreCase, strategy) { if (dir === SortingDirection.None) { state.splice(state.findIndex((expr) => expr.fieldName === fieldName), 1); return; } /** @type {?} */ const expression = state.find((expr) => expr.fieldName === fieldName); if (!expression) { state.push({ fieldName, dir, ignoreCase, strategy }); } else { Object.assign(expression, { fieldName, dir, ignoreCase }); } } /** * @hidden \@internal * @return {?} */ get dataType() { if (this.valueKey) { return DataTypes.COMPLEX; } return DataTypes.PRIMITIVE; } /** * @hidden \@internal * @return {?} */ get isRemote() { return this.totalItemCount > 0 && this.valueKey && this.dataType === DataTypes.COMPLEX; } /** * If the data source is remote, returns JSON.stringify(itemID) * @hidden * \@internal * @private * @param {?} itemID * @return {?} */ _stringifyItemID(itemID) { return this.isRemote && typeof itemID === 'object' ? JSON.stringify(itemID) : itemID; } /** * @private * @param {?} itemID * @return {?} */ _parseItemID(itemID) { return this.isRemote && typeof itemID === 'string' ? JSON.parse(itemID) : itemID; } /** * Returns if the specified itemID is selected * @hidden * \@internal * @param {?} item * @return {?} */ isItemSelected(item) { return this.selection.is_item_selected(this.id, this._stringifyItemID(item)); } /** * Triggers change detection on the combo view * @return {?} */ triggerCheck() { this.cdr.detectChanges(); } /** * @hidden \@internal * @return {?} */ isAddButtonVisible() { // This should always return a boolean value. If this.searchValue was '', it returns '' instead of false; return this.searchValue !== '' && this.customValueFlag; } /** * @hidden \@internal * @param {?} evt * @return {?} */ handleSelectAll(evt) { if (evt.checked) { this.selectAllItems(); } else { this.deselectAllItems(); } } /** * @hidden \@internal * @return {?} */ addItemToCollection() { if (!this.searchValue) { return; } /** @type {?} */ const newValue = this.searchValue.trim(); /** @type {?} */ const addedItem = this.displayKey ? { [this.valueKey]: newValue, [this.displayKey]: newValue } : newValue; if (this.groupKey) { Object.assign(addedItem, { [this.groupKey]: this.defaultFallbackGroup }); } /** @type {?} */ const oldCollection = this.data; /** @type {?} */ const newCollection = [...this.data]; newCollection.push(addedItem); /** @type {?} */ const args = { oldCollection, addedItem, newCollection }; this.onAddition.emit(args); this.data.push(addedItem); // If you mutate the array, no pipe is invoked and the display isn't updated; // if you replace the array, the pipe executes and the display is updated. this.data = cloneArray(this.data); this.selectItems([addedItem], false); this.customValueFlag = false; this.searchInput.nativeElement.focus(); this.dropdown.focusedItem = null; this.handleInputChange(); } /** * @hidden \@internal * @param {?=} opening * @return {?} */ focusSearchInput(opening) { if (this.displaySearchInput && this.searchInput) { this.searchInput.nativeElement.focus(); } else { if (opening) { this.dropdownContainer.nativeElement.focus(); } else { this.comboInput.nativeElement.focus(); this.toggle(); } } } /** * @protected * @param {?} searchVal * @param {?} condition * @param {?} ignoreCase * @param {?=} fieldName * @return {?} */ prepare_filtering_expression(searchVal, condition, ignoreCase, fieldName) { /** @type {?} */ const newArray = [...this.filteringExpressions]; /** @type {?} */ const expression = newArray.find((expr) => expr.fieldName === fieldName); /** @type {?} */ const newExpression = { fieldName, searchVal, condition, ignoreCase }; if (!expression) { newArray.push(newExpression); } else { Object.assign(expression, newExpression); } if (this.groupKey) { /** @type {?} */ const expression2 = newArray.find((expr) => expr.fieldName === 'isHeader'); /** @type {?} */ const headerExpression = { fieldName: 'isHeader', searchVale: '', condition: IgxBooleanFilteringOperand.instance().condition('true'), ignoreCase: true }; if (!expression2) { newArray.push(headerExpression); } else { Object.assign(expression2, headerExpression); } } this.filteringExpressions = newArray; } /** * @hidden \@internal * @return {?} */ onBlur() { if (this.collapsed) { if (this.ngControl && !this.ngControl.valid) { this.valid = IgxComboState.INVALID; } else { this.valid = IgxComboState.INITIAL; } } } /** * @hidden \@internal * @return {?} */ filter() { this.prepare_filtering_expression(this.searchValue.trim(), IgxStringFilteringOperand.instance().condition('contains'), true, this.dataType === DataTypes.PRIMITIVE ? undefined : this.displayKey); } /** * @hidden \@internal * @return {?} */ ngOnInit() { this.ngControl = this.injector.get(NgControl, null); this._positionCallback = () => this.dropdown.updateScrollPosition(); this.overlaySettings.positionStrategy = new ComboConnectedPositionStrategy(this._positionCallback); this.overlaySettings.positionStrategy.settings.target = this.elementRef.nativeElement; this.selection.set(this.id, new Set()); } /** * @hidden \@internal * @return {?} */ ngAfterViewInit() { this.filteredData = [...this.data]; if (this.ngControl) { this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged); } } /** * @hidden \@internal * @return {?} */ ngOnDestroy() { this.destroy$.complete(); this.comboAPI.clear(); this.selection.clear(this.id); } /** * @hidden \@internal * @param {?} event * @return {?} */ dataLoading(event) { this.onDataPreLoad.emit(event); } /** * @hidden \@internal * @param {?} value * @return {?} */ writeValue(value) { // selectItems can handle Array<any>, no valueKey is needed; this.selectItems(value, true); this.cdr.markForCheck(); } /** * @hidden \@internal * @param {?} fn * @return {?} */ registerOnChange(fn) { this._onChangeCallback = fn; } /** * @hidden \@internal * @param {?} fn * @return {?} */ registerOnTouched(fn) { } /** * @hidden \@internal * @param {?} isDisabled * @return {?} */ setDisabledState(isDisabled) { this.disabled = isDisabled; } /** * @hidden * @return {?} */ getEditElement() { return this.comboInput.nativeElement; } /** * @hidden \@internal * @return {?} */ get template() { this._dataType = this.dataType; if (this.itemTemplate) { return this.itemTemplate; } if (this._dataType === DataTypes.COMPLEX) { return this.complexTemplate; } return this.primitiveTemplate; } /** * @hidden \@internal * @return {?} */ get context() { return { $implicit: this }; } /** * @hidden \@internal * @param {?} event * @return {?} */ handleClearItems(event) { this.deselectAllItems(true, event); event.stopPropagation(); } /** * A method that opens/closes the combo. * * ```html * <button (click)="combo.toggle()">Toggle Combo</button> * <igx-combo #combo></igx-combo> * ``` * @return {?} */ toggle() { this.dropdown.toggle(this.overlaySettings); } /** * A method that opens the combo. * * ```html * <button (click)="combo.open()">Open Combo</button> * <igx-combo #combo></igx-combo> * ``` * @return {?} */ open() { this.dropdown.open(this.overlaySettings); } /** * A method that closes the combo. * * ```html * <button (click)="combo.close()">Close Combo</button> * <igx-combo #combo></igx-combo> * ``` * @return {?} */ close() { this.dropdown.close(); } /** * Gets drop down state. * * ```typescript * let state = this.combo.collapsed; * ``` * @return {?} */ get collapsed() { return this.dropdown.collapsed; } /** * Get current selection state * @return {?} Array of selected items * ```typescript * let selectedItems = this.combo.selectedItems(); * ``` */ selectedItems() { /** @type {?} */ const items = Array.from(this.selection.get(this.id)); return this.isRemote ? items.map(item => this._parseItemID(item)) : items; } /** * Select defined items * @param {?} newItems new items to be selected * @param {?=} clearCurrentSelection if true clear previous selected items * ```typescript * this.combo.selectItems(["New York", "New Jersey"]); * ``` * @param {?=} event * @return {?} */ selectItems(newItems, clearCurrentSelection, event) { if (newItems) { /** @type {?} */ const newSelection = this.selection.add_items(this.id, newItems, clearCurrentSelection); this.setSelection(newSelection, event); } } /** * Deselect defined items * @param {?} items items to deselected * ```typescript * this.combo.deselectItems(["New York", "New Jersey"]); * ``` * @param {?=} event * @return {?} */ deselectItems(items, event) { if (items) { /** @type {?} */ const newSelection = this.selection.delete_items(this.id, items); this.setSelection(newSelection, event); } } /** * Select all (filtered) items * @param {?=} ignoreFilter if set to true, selects all items, otherwise selects only the filtered ones. * ```typescript * this.combo.selectAllItems(); * ``` * @param {?=} event * @return {?} */ selectAllItems(ignoreFilter, event) { /** @type {?} */ const allVisible = this.selection.get_all_ids(ignoreFilter ? this.data : this.filteredData); /** @type {?} */ const newSelection = this.selection.add_items(this.id, allVisible); this.setSelection(newSelection, event); } /** * Deselect all (filtered) items * @param {?=} ignoreFilter if set to true, deselects all items, otherwise deselects only the filtered ones. * ```typescript * this.combo.deselectAllItems(); * ``` * @param {?=} event * @return {?} */ deselectAllItems(ignoreFilter, event) { /** @type {?} */ let newSelection = this.selection.get_empty(); if (this.filteredData.length !== this.data.length && !ignoreFilter) { newSelection = this.selection.delete_items(this.id, this.selection.get_all_ids(this.filteredData)); } this.setSelection(newSelection, event); } /** * Selects/Deselects an item using it's valueKey value * @param {?} itemID the valueKey of the specified item * @param {?=} select If the item should be selected (true) or deselcted (false) * * ```typescript * items: { field: string, region: string}[] = data; * this.combo.setSelectedItem('Connecticut', true); * // combo.valueKey === 'field' * // items[n] === { field: 'Connecticut', state: 'New England'} * ``` * @param {?=} event * @return {?} */ setSelectedItem(itemID, select = true, event) { if (itemID === null || itemID === undefined) { return; } /** @type {?} */ const itemValue = this.getValueByValueKey(itemID); if (itemValue !== null && itemValue !== undefined) { if (select) { this.selectItems([itemValue], false, event); } else { this.deselectItems([itemValue], event); } } } /** * @protected * @param {?} newSelection * @param {?=} event * @return {?} */ setSelection(newSelection, event) { /** @type {?} */ const oldSelectionEmit = Array.from(this.selection.get(this.id) || []); /** @type {?} */ const newSelectionEmit = Array.from(newSelection || []); /** @type {?} */ const args = { newSelection: newSelectionEmit, oldSelection: oldSelectionEmit, event, cancel: false }; this.onSelectionChange.emit(args); if (!args.cancel) { this.selection.select_items(this.id, args.newSelection, true); this._value = this.dataType !== DataTypes.PRIMITIVE ? args.newSelection.map((id) => this._parseItemID(id)[this.displayKey]).join(', ') : args.newSelection.join(', '); this._onChangeCallback(args.newSelection); } } /** * Event handlers * @hidden * \@internal * @param {?} event * @return {?} */ handleOpening(event) { this.onOpening.emit(event); if (event.cancel) { return; } this.handleInputChange(); } /** * @hidden \@internal * @return {?} */ handleOpened() { this.triggerCheck(); this.focusSearchInput(true); this.onOpened.emit(); } /** * @hidden \@internal * @param {?} event * @return {?} */ handleClosing(event) { this.onClosing.emit(event); if (event.cancel) { return; } this.searchValue = ''; } /** * @hidden \@internal * @return {?} */ handleClosed() { this.comboInput.nativeElement.focus(); this.onClosed.emit(); } } IgxComboComponent.decorators = [ { type: Component, args: [{ selector: 'igx-combo', template: "<ng-template #complex let-display let-data=\"data\" let-key=\"displayKey\">\n {{display[key]}}\n</ng-template>\n<ng-template #primitive let-display>\n {{display}}\n</ng-template>\n<ng-template #empty>\n <span>The list is empty</span>\n</ng-template>\n<ng-template #addItemDefault let-control>\n <button igxButton=\"flat\" igxRipple>Add item</button>\n</ng-template>\n<ng-template #headerItemBase let-item let-key=\"valueKey\" let-groupKey=\"groupKey\">\n {{ item[key] }}\n</ng-template>\n\n<igx-input-group [displayDensity]=\"displayDensity\" [type]=\"type\" (click)=\"onInputClick($event)\">\n <ng-container ngProjectAs=\"[igxLabel]\">\n <ng-content select=\"[igxLabel]\"></ng-content>\n </ng-container>\n <ng-container ngProjectAs=\"igx-prefix\">\n <ng-content select=\"igx-prefix\"></ng-content>\n </ng-container>\n <ng-container ngProjectAs=\"igx-hint, [igxHint]\">\n <ng-content select=\"igx-hint, [igxHint]\"></ng-content>\n </ng-container>\n <input igxInput #comboInput name=\"comboInput\" type=\"text\" [value]=\"value\" readonly [placeholder]=\"placeholder\"\n [disabled]=\"disabled\" (blur)=\"onBlur()\" />\n <ng-container ngProjectAs=\"igx-suffix\">\n <ng-content select=\"igx-suffix\"></ng-content>\n </ng-container>\n <igx-suffix *ngIf=\"value.length\" aria-label=\"Clear Selection\" class=\"igx-combo__clear-button\" igxRipple (click)=\"handleClearItems($event)\">\n <ng-container *ngIf=\"clearIconTemplate\">\n <ng-container *ngTemplateOutlet=\"clearIconTemplate\"></ng-container>\n </ng-container>\n <igx-icon *ngIf=\"!clearIconTemplate\" fontSet=\"material\">clear</igx-icon>\n </igx-suffix>\n <igx-suffix igxButton=\"icon\" class=\"igx-combo__toggle-button\" igxRipple>\n <ng-container *ngIf=\"toggleIconTemplate\">\n <ng-container *ngTemplateOutlet=\"toggleIconTemplate; context: {$implicit: this.collapsed}\"></ng-container>\n </ng-container>\n <igx-icon *ngIf=\"!toggleIconTemplate\" fontSet=\"material\">{{ dropdown.collapsed ? 'arrow_drop_down' : 'arrow_drop_up'}}</igx-icon>\n </igx-suffix>\n</igx-input-group>\n<igx-combo-drop-down #igxComboDropDown class=\"igx-combo__drop-down\" [width]=\"itemsWidth || '100%'\" (onOpening)=\"handleOpening($event)\"\n (onClosing)=\"handleClosing($event)\" (onOpened)=\"handleOpened()\" (onClosed)=\"handleClosed()\">\n <igx-input-group *ngIf=\"displaySearchInput\" [displayDensity]=\"displayDensity\" class=\"igx-combo__search\">\n <input class=\"igx-combo-input\" igxInput #searchInput name=\"searchInput\" autocomplete=\"off\" type=\"text\"\n [(ngModel)]=\"searchValue\" (ngModelChange)=\"handleInputChange($event)\" (keyup)=\"handleKeyUp($event)\"\n (keydown)=\"handleKeyDown($event)\" (focus)=\"dropdown.onBlur($event)\" [placeholder]=\"searchPlaceholder\"\n aria-autocomplete=\"both\" [attr.aria-owns]=\"dropdown.id\" [attr.aria-labelledby]=\"ariaLabelledBy\" />\n </igx-input-group>\n <ng-container *ngTemplateOutlet=\"headerTemplate\">\n </ng-container>\n <div #dropdownItemContainer class=\"igx-combo__content\" [style.overflow]=\"'hidden'\" [style.maxHeight.px]=\"itemsMaxHeight\"\n [igxDropDownItemNavigation]=\"dropdown\" (focus)=\"dropdown.onFocus()\" [tabindex]=\"dropdown.collapsed ? -1 : 0\"\n role=\"listbox\" [attr.id]=\"dropdown.id\">\n <ng-template igxFor let-item let-index=\"index\" [igxForOf]=\"data | comboFiltering:filteringExpressions:filteringLogic | comboSorting:sortingExpressions | comboGrouping:groupKey\"\n [igxForScrollOrientation]=\"'vertical'\" [igxForContainerSize]=\"itemsMaxHeight\" [igxForItemSize]=\"itemHeight\"\n (onChunkPreload)=\"dataLoading($event)\">\n <igx-combo-item [itemHeight]='itemHeight' [value]=\"item\" isHeader={{item.isHeader}} role=\"option\" [index]=\"index\">\n <ng-container *ngIf=\"item.isHeader\">\n <ng-container *ngTemplateOutlet=\"headerItemTemplate ? headerItemTemplate : headerItemBase; context: {$implicit: item, data: data, valueKey: valueKey, groupKey: groupKey, displayKey: displayKey}\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"!item.isHeader\">\n <ng-container #listItem *ngTemplateOutlet=\"template; context: {$implicit: item, data: data, valueKey: valueKey, displayKey: displayKey};\"></ng-container>\n </ng-container>\n </igx-combo-item>\n </ng-template>\n </div>\n <div class=\"igx-comb