UNPKG

igniteui-angular

Version:

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

1 lines • 146 kB
{"version":3,"file":"igniteui-angular-combo.mjs","sources":["../../../projects/igniteui-angular/combo/src/combo/combo.api.ts","../../../projects/igniteui-angular/combo/src/combo/combo-item.component.ts","../../../projects/igniteui-angular/combo/src/combo/combo-item.component.html","../../../projects/igniteui-angular/combo/src/combo/combo.directives.ts","../../../projects/igniteui-angular/combo/src/combo/combo.common.ts","../../../projects/igniteui-angular/combo/src/combo/combo-add-item.component.ts","../../../projects/igniteui-angular/combo/src/combo/combo-dropdown.component.ts","../../../projects/igniteui-angular/drop-down/src/drop-down/drop-down.component.html","../../../projects/igniteui-angular/combo/src/combo/combo.pipes.ts","../../../projects/igniteui-angular/combo/src/combo/combo.component.ts","../../../projects/igniteui-angular/combo/src/combo/combo.component.html","../../../projects/igniteui-angular/combo/src/combo/public_api.ts","../../../projects/igniteui-angular/combo/src/combo/combo.module.ts","../../../projects/igniteui-angular/combo/src/igniteui-angular-combo.ts"],"sourcesContent":["import { IgxComboBase } from './combo.common';\nimport { Injectable } from '@angular/core';\n\n/**\n * @hidden\n */\n@Injectable()\nexport class IgxComboAPIService {\n public disableTransitions = false;\n protected combo: IgxComboBase;\n\n public get valueKey() {\n return this.combo.valueKey !== null && this.combo.valueKey !== undefined ? this.combo.valueKey : null;\n }\n\n public get item_focusable(): boolean {\n return false;\n }\n public get isRemote(): boolean {\n return this.combo.isRemote;\n }\n\n public get comboID(): string {\n return this.combo.id;\n }\n\n public register(combo: IgxComboBase) {\n this.combo = combo;\n }\n\n public clear(): void {\n this.combo = null;\n }\n\n public add_custom_item(): void {\n if (!this.combo) {\n return;\n }\n this.combo.addItemToCollection();\n }\n\n public set_selected_item(itemID: any, event?: Event): void {\n const selected = this.combo.isItemSelected(itemID);\n if (itemID === undefined) {\n return;\n }\n if (!selected) {\n this.combo.select([itemID], false, event);\n } else {\n this.combo.deselect([itemID], event);\n }\n }\n\n public is_item_selected(itemID: any): boolean {\n return this.combo.isItemSelected(itemID);\n }\n}\n","import {\n Component,\n HostBinding,\n Input,\n booleanAttribute,\n inject\n} from '@angular/core';\nimport { IgxComboAPIService } from './combo.api';\nimport { rem } from 'igniteui-angular/core';\nimport { IgxCheckboxComponent } from 'igniteui-angular/checkbox';\nimport { IgxDropDownItemComponent, Navigate } from 'igniteui-angular/drop-down';\n\n/** @hidden */\n@Component({\n selector: 'igx-combo-item',\n templateUrl: 'combo-item.component.html',\n imports: [IgxCheckboxComponent]\n})\nexport class IgxComboItemComponent extends IgxDropDownItemComponent {\n protected comboAPI = inject(IgxComboAPIService);\n\n\n /**\n * Gets the height of a list item\n *\n * @hidden\n */\n @Input()\n public itemHeight: string | number = '';\n\n /** @hidden @internal */\n @HostBinding('style.height.rem')\n public get _itemHeightToRem() {\n if (this.itemHeight) {\n return rem(this.itemHeight);\n }\n }\n\n @HostBinding('attr.aria-label')\n @Input()\n public override get ariaLabel(): string {\n const valueKey = this.comboAPI.valueKey;\n return (valueKey !== null && this.value != null) ? this.value[valueKey] : this.value;\n }\n\n /** @hidden @internal */\n @Input({ transform: booleanAttribute })\n public singleMode: boolean;\n\n /**\n * @hidden\n */\n public override get itemID() {\n const valueKey = this.comboAPI.valueKey;\n return valueKey !== null ? this.value[valueKey] : this.value;\n }\n\n /**\n * @hidden\n */\n public get comboID() {\n return this.comboAPI.comboID;\n }\n\n /**\n * @hidden\n * @internal\n */\n public get disableTransitions() {\n return this.comboAPI.disableTransitions;\n }\n\n /**\n * @hidden\n */\n public override get selected(): boolean {\n return this.comboAPI.is_item_selected(this.itemID);\n }\n\n public override set selected(value: boolean) {\n if (this.isHeader) {\n return;\n }\n this._selected = value;\n }\n\n /**\n * @hidden\n */\n public isVisible(direction: Navigate): boolean {\n const rect = this.element.nativeElement.getBoundingClientRect();\n const parentDiv = this.element.nativeElement.parentElement.parentElement.getBoundingClientRect();\n if (direction === Navigate.Down) {\n return rect.y + rect.height <= parentDiv.y + parentDiv.height;\n }\n return rect.y >= parentDiv.y;\n }\n\n public override clicked(event): void {\n this.comboAPI.disableTransitions = false;\n if (!this.isSelectable) {\n return;\n }\n this.dropDown.navigateItem(this.index);\n this.comboAPI.set_selected_item(this.itemID, event);\n }\n\n /**\n * @hidden\n * @internal\n * The event that is prevented is the click on the checkbox label element.\n * That is the only visible element that a user can interact with.\n * The click propagates to the host and the preventDefault is to stop it from\n * switching focus to the input it's base on.\n * The toggle happens in an internal handler in the drop-down on the next task queue cycle.\n */\n public disableCheck(event: MouseEvent) {\n event.preventDefault();\n }\n}\n","@if (!isHeader && !singleMode) {\n <!-- checkbox should not allow changing its state from UI click (that's why it should be readonly=true), becasue when cancelling the selectionChange event in the combo, then checkbox will still change state.-->\n <igx-checkbox [checked]=\"selected\" [readonly]=\"true\" [disableRipple]=\"true\" [disableTransitions]=\"disableTransitions\" [tabindex]=\"-1\" (click)=\"disableCheck($event)\" class=\"igx-combo__checkbox\"></igx-checkbox>\n}\n<span class=\"igx-drop-down__inner\"><ng-content></ng-content></span>\n","import { Directive } from '@angular/core';\n\n/**\n * Allows a custom element to be added at the beginning of the combo list.\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo>\n * <ng-template igxComboHeader>\n * <div class=\"header-class\">Custom header</div>\n * <img src=\"\"/>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboHeader]',\n standalone: true\n})\nexport class IgxComboHeaderDirective { }\n\n/**\n * Allows a custom element to be added at the end of the combo list.\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo>\n * <ng-template igxComboFooter>\n * <div class=\"footer-class\">Custom footer</div>\n * <img src=\"\"/>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboFooter]',\n standalone: true\n})\nexport class IgxComboFooterDirective { }\n\n/**\n * Allows the combo's items to be modified with a custom template\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo>\n *\t<ng-template igxComboItem let-display let-key=\"valueKey\">\n *\t\t<div class=\"item\">\n *\t\t\t<span class=\"state\">State: {{ display[key] }}</span>\n *\t \t\t<span class=\"region\">Region: {{ display.region }}</span>\n *\t \t</div>\n *\t </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboItem]',\n standalone: true\n})\nexport class IgxComboItemDirective { }\n\n/**\n * Defines the custom template that will be displayed when the combo's list is empty\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo>\n * <ng-template igxComboEmpty>\n * <div class=\"combo--empty\">\n * There are no items to display\n * </div>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboEmpty]',\n standalone: true\n})\nexport class IgxComboEmptyDirective { }\n\n/**\n * Defines the custom template that will be used when rendering header items for groups in the combo's list\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo>\n * <ng-template igxComboHeaderItem let-item let-key=\"groupKey\">\n * <div class=\"custom-item--group\">Group header for {{ item[key] }}</div>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboHeaderItem]',\n standalone: true\n})\nexport class IgxComboHeaderItemDirective { }\n\n/**\n * Defines the custom template that will be used to display the `ADD` button\n *\n * @remarks To show the `ADD` button, the `allowCustomValues` option must be enabled\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo #combo>\n * <ng-template igxComboAddItem>\n * <button type=\"button\" class=\"combo__add-button\">\n * Click to add item\n * </button>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboAddItem]',\n standalone: true\n})\nexport class IgxComboAddItemDirective { }\n\n/**\n * The custom template that will be used when rendering the combo's toggle button\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo #combo>\n * <ng-template igxComboToggleIcon let-collapsed>\n * <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboToggleIcon]',\n standalone: true\n})\nexport class IgxComboToggleIconDirective { }\n\n/**\n * Defines the custom template that will be used when rendering the combo's clear icon\n *\n * @igxModule IgxComboModule\n * @igxTheme igx-combo-theme\n * @igxKeywords combobox, combo selection\n * @igxGroup Grids & Lists\n *\n * @example\n * <igx-combo #combo>\n * <ng-template igxComboClearIcon>\n * <igx-icon>clear</igx-icon>\n * </ng-template>\n * </igx-combo>\n */\n@Directive({\n selector: '[igxComboClearIcon]',\n standalone: true\n})\nexport class IgxComboClearIconDirective { }\n","import {\n AfterContentChecked,\n AfterViewChecked,\n AfterViewInit,\n booleanAttribute,\n ChangeDetectorRef,\n ContentChild,\n ContentChildren,\n Directive,\n ElementRef,\n EventEmitter,\n forwardRef,\n HostBinding,\n InjectionToken,\n Injector,\n Input,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n TemplateRef,\n ViewChild,\n DOCUMENT,\n ViewChildren,\n inject\n} from '@angular/core';\nimport { AbstractControl, ControlValueAccessor, NgControl } from '@angular/forms';\nimport { caseSensitive } from '@igniteui/material-icons-extended';\nimport { noop, Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport {\n IgxSelectionAPIService,\n SortingDirection,\n CancelableBrowserEventArgs,\n cloneArray,\n IBaseCancelableBrowserEventArgs,\n IBaseEventArgs,\n rem,\n AbsoluteScrollStrategy,\n AutoPositionStrategy,\n OverlaySettings,\n ComboResourceStringsEN,\n IComboResourceStrings,\n getCurrentResourceStrings\n} from 'igniteui-angular/core';\nimport { IForOfState, IgxForOfDirective } from 'igniteui-angular/directives';\nimport { IgxIconService } from 'igniteui-angular/icon';\nimport { IGX_INPUT_GROUP_TYPE, IgxInputDirective, IgxInputGroupComponent, IgxInputGroupType, IgxInputState, IgxLabelDirective, IgxPrefixDirective, IgxSuffixDirective } from 'igniteui-angular/input-group';\nimport { IgxComboDropDownComponent } from './combo-dropdown.component';\nimport { IgxComboAPIService } from './combo.api';\nimport {\n IgxComboAddItemDirective, IgxComboClearIconDirective, IgxComboEmptyDirective,\n IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboHeaderItemDirective, IgxComboItemDirective, IgxComboToggleIconDirective\n} from './combo.directives';\nimport { isEqual } from 'lodash-es';\nimport { IComboItemAdditionEvent, IComboSearchInputEventArgs } from './combo.component';\n\nexport const IGX_COMBO_COMPONENT = /*@__PURE__*/new InjectionToken<IgxComboBase>('IgxComboComponentToken');\n\n/** @hidden @internal TODO: Evaluate */\nexport interface IgxComboBase {\n id: string;\n data: any[] | null;\n valueKey: string;\n groupKey: string;\n isRemote: boolean;\n filteredData: any[] | null;\n totalItemCount: number;\n itemsMaxHeight: number;\n itemHeight: number;\n searchValue: string;\n searchInput: ElementRef<HTMLInputElement>;\n comboInput: ElementRef<HTMLInputElement>;\n opened: EventEmitter<IBaseEventArgs>;\n opening: EventEmitter<CancelableBrowserEventArgs>;\n closing: EventEmitter<CancelableBrowserEventArgs>;\n closed: EventEmitter<IBaseEventArgs>;\n focusSearchInput(opening?: boolean): void;\n triggerCheck(): void;\n addItemToCollection(): void;\n isAddButtonVisible(): boolean;\n handleInputChange(event?: string): void;\n isItemSelected(itemID: any): boolean;\n select(item: any): void;\n select(itemIDs: any[], clearSelection?: boolean, event?: Event): void;\n deselect(...args: [] | [itemIDs: any[], event?: Event]): void;\n setActiveDescendant(): void;\n}\n\nlet NEXT_ID = 0;\n\n\n/** @hidden @internal */\nexport const enum DataTypes {\n EMPTY = 'empty',\n PRIMITIVE = 'primitive',\n COMPLEX = 'complex',\n PRIMARYKEY = 'valueKey'\n}\n\n/** The filtering criteria to be applied on data search */\nexport interface IComboFilteringOptions {\n /** Defines filtering case-sensitivity */\n caseSensitive?: boolean;\n /** Defines optional key to filter against complex list items. Default to displayKey if provided.*/\n filteringKey?: string;\n}\n\n@Directive()\nexport abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewChecked, OnInit,\n AfterViewInit, AfterContentChecked, OnDestroy, ControlValueAccessor {\n protected elementRef = inject(ElementRef);\n protected cdr = inject(ChangeDetectorRef);\n protected selectionService = inject(IgxSelectionAPIService);\n protected comboAPI = inject(IgxComboAPIService);\n public document = inject<Document>(DOCUMENT);\n protected _inputGroupType = inject<IgxInputGroupType>(IGX_INPUT_GROUP_TYPE, { optional: true });\n protected _injector = inject(Injector, { optional: true });\n protected _iconService = inject(IgxIconService, { optional: true });\n\n /**\n * Defines whether the caseSensitive icon should be shown in the search input\n *\n * ```typescript\n * // get\n * let myComboShowSearchCaseIcon = this.combo.showSearchCaseIcon;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [showSearchCaseIcon]='true'></igx-combo>\n * ```\n */\n @Input({ transform: booleanAttribute })\n public showSearchCaseIcon = false;\n\n /**\n * Enables/disables filtering in the list. The default is `false`.\n */\n @Input({ transform: booleanAttribute })\n public get disableFiltering(): boolean {\n return this._disableFiltering;\n }\n public set disableFiltering(value: boolean) {\n this._disableFiltering = value;\n }\n\n /**\n * Set custom overlay settings that control how the combo's list of items is displayed.\n * Set:\n * ```html\n * <igx-combo [overlaySettings]=\"customOverlaySettings\"></igx-combo>\n * ```\n *\n * ```typescript\n * const customSettings = { positionStrategy: { settings: { target: myTarget } } };\n * combo.overlaySettings = customSettings;\n * ```\n * Get any custom overlay settings used by the combo:\n * ```typescript\n * const comboOverlaySettings: OverlaySettings = myCombo.overlaySettings;\n * ```\n */\n @Input()\n public overlaySettings: OverlaySettings = null;\n\n /**\n * Gets/gets combo id.\n *\n * ```typescript\n * // get\n * let id = this.combo.id;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [id]='combo1'></igx-combo>\n * ```\n */\n @HostBinding('attr.id')\n @Input()\n public get id(): string {\n return this._id;\n }\n\n public set id(value: string) {\n if (!value) {\n return;\n }\n const selection = this.selectionService.get(this._id);\n this.selectionService.clear(this._id);\n this._id = value;\n if (selection) {\n this.selectionService.set(this._id, selection);\n }\n }\n\n /**\n * Sets the style width of the element\n *\n * ```typescript\n * // get\n * let myComboWidth = this.combo.width;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [width]='250px'></igx-combo>\n * ```\n */\n @HostBinding('style.width')\n @Input()\n public width: string;\n\n /**\n * Controls whether custom values can be added to the collection\n *\n * ```typescript\n * // get\n * let comboAllowsCustomValues = this.combo.allowCustomValues;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [allowCustomValues]='true'></igx-combo>\n * ```\n */\n @Input({ transform: booleanAttribute })\n public allowCustomValues = false;\n\n /**\n * Configures the drop down list height\n *\n * ```typescript\n * // get\n * let myComboItemsMaxHeight = this.combo.itemsMaxHeight;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [itemsMaxHeight]='320'></igx-combo>\n * ```\n */\n @Input()\n public get itemsMaxHeight(): number {\n if (this.itemHeight && !this._itemsMaxHeight) {\n return this.itemHeight * this.itemsInContainer;\n }\n return this._itemsMaxHeight;\n }\n\n public set itemsMaxHeight(val: number) {\n this._itemsMaxHeight = val;\n }\n\n /** @hidden */\n public get itemsMaxHeightInRem() {\n if (this.itemsMaxHeight) {\n return rem(this.itemsMaxHeight);\n }\n }\n\n /**\n * Configures the drop down list item height\n *\n * ```typescript\n * // get\n * let myComboItemHeight = this.combo.itemHeight;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [itemHeight]='32'></igx-combo>\n * ```\n */\n @Input()\n public get itemHeight(): number {\n return this._itemHeight;\n }\n\n public set itemHeight(val: number) {\n this._itemHeight = val;\n }\n\n /**\n * Configures the drop down list width\n *\n * ```typescript\n * // get\n * let myComboItemsWidth = this.combo.itemsWidth;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [itemsWidth] = '\"180px\"'></igx-combo>\n * ```\n */\n @Input()\n public itemsWidth: string;\n\n /**\n * Defines the placeholder value for the combo value field\n *\n * ```typescript\n * // get\n * let myComboPlaceholder = this.combo.placeholder;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [placeholder]='newPlaceHolder'></igx-combo>\n * ```\n */\n @Input()\n public placeholder: string;\n\n /**\n * Combo data source.\n *\n * ```html\n * <!--set-->\n * <igx-combo [data]='items'></igx-combo>\n * ```\n */\n @Input()\n public get data(): any[] | null {\n return this._data;\n }\n public set data(val: any[] | null) {\n // igxFor directive ignores undefined values\n // if the combo uses simple data and filtering is applied\n // an error will occur due to the mismatch of the length of the data\n // this can occur during filtering for the igx-combo and\n // during filtering & selection for the igx-simple-combo\n // since the simple combo's input is both a container for the selection and a filter\n this._data = (val) ? val.filter(x => x !== undefined) : [];\n }\n\n /**\n * Determines which column in the data source is used to determine the value.\n *\n * ```typescript\n * // get\n * let myComboValueKey = this.combo.valueKey;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [valueKey]='myKey'></igx-combo>\n * ```\n */\n @Input()\n public valueKey: string = null;\n\n @Input()\n public set displayKey(val: string) {\n this._displayKey = val;\n }\n\n /**\n * Determines which column in the data source is used to determine the display value.\n *\n * ```typescript\n * // get\n * let myComboDisplayKey = this.combo.displayKey;\n *\n * // set\n * this.combo.displayKey = 'val';\n *\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [displayKey]='myDisplayKey'></igx-combo>\n * ```\n */\n public get displayKey() {\n return this._displayKey ? this._displayKey : this.valueKey;\n }\n\n /**\n * The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[].\n *\n * ```html\n * <!--set-->\n * <igx-combo [groupKey]='newGroupKey'></igx-combo>\n * ```\n */\n @Input()\n public set groupKey(val: string) {\n this._groupKey = val;\n }\n\n /**\n * The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[].\n *\n * ```typescript\n * // get\n * let currentGroupKey = this.combo.groupKey;\n * ```\n */\n public get groupKey(): string {\n return this._groupKey;\n }\n\n /**\n * Sets groups sorting order.\n *\n * @example\n * ```html\n * <igx-combo [groupSortingDirection]=\"groupSortingDirection\"></igx-combo>\n * ```\n * ```typescript\n * public groupSortingDirection = SortingDirection.Asc;\n * ```\n */\n @Input()\n public get groupSortingDirection(): SortingDirection {\n return this._groupSortingDirection;\n }\n public set groupSortingDirection(val: SortingDirection) {\n this._groupSortingDirection = val;\n }\n\n /**\n * Gets/Sets the custom filtering function of the combo.\n *\n * @example\n * ```html\n * <igx-comb #combo [data]=\"localData\" [filterFunction]=\"filterFunction\"></igx-combo>\n * ```\n */\n @Input()\n public filterFunction: (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions) => any[];\n\n /**\n * Sets aria-labelledby attribute value.\n * ```html\n * <igx-combo [ariaLabelledBy]=\"'label1'\">\n * ```\n */\n @Input()\n public ariaLabelledBy: string;\n\n /** @hidden @internal */\n @HostBinding('class.igx-combo')\n public cssClass = 'igx-combo'; // Independent of display density for the time being\n\n /**\n * Disables the combo. The default is `false`.\n * ```html\n * <igx-combo [disabled]=\"'true'\">\n * ```\n */\n @Input({ transform: booleanAttribute })\n public disabled = false;\n\n /**\n * Sets the visual combo type.\n * The allowed values are `line`, `box`, `border` and `search`. The default is `box`.\n * ```html\n * <igx-combo [type]=\"'line'\">\n * ```\n */\n @Input()\n public get type(): IgxInputGroupType {\n return this._type || this._inputGroupType || 'box';\n }\n\n public set type(val: IgxInputGroupType) {\n this._type = val;\n }\n\n /**\n * Gets/Sets the resource strings.\n *\n * @remarks\n * By default it uses EN resources.\n */\n @Input()\n public get resourceStrings(): IComboResourceStrings {\n return this._resourceStrings;\n }\n public set resourceStrings(value: IComboResourceStrings) {\n this._resourceStrings = Object.assign({}, this._resourceStrings, value);\n }\n\n /**\n * Emitted before the dropdown is opened\n *\n * ```html\n * <igx-combo opening='handleOpening($event)'></igx-combo>\n * ```\n */\n @Output()\n public opening = new EventEmitter<IBaseCancelableBrowserEventArgs>();\n\n /**\n * Emitted after the dropdown is opened\n *\n * ```html\n * <igx-combo (opened)='handleOpened($event)'></igx-combo>\n * ```\n */\n @Output()\n public opened = new EventEmitter<IBaseEventArgs>();\n\n /**\n * Emitted before the dropdown is closed\n *\n * ```html\n * <igx-combo (closing)='handleClosing($event)'></igx-combo>\n * ```\n */\n @Output()\n public closing = new EventEmitter<IBaseCancelableBrowserEventArgs>();\n\n /**\n * Emitted after the dropdown is closed\n *\n * ```html\n * <igx-combo (closed)='handleClosed($event)'></igx-combo>\n * ```\n */\n @Output()\n public closed = new EventEmitter<IBaseEventArgs>();\n\n /**\n * Emitted when an item is being added to the data collection\n *\n * ```html\n * <igx-combo (addition)='handleAdditionEvent($event)'></igx-combo>\n * ```\n */\n @Output()\n public addition = new EventEmitter<IComboItemAdditionEvent>();\n\n /**\n * Emitted when the value of the search input changes (e.g. typing, pasting, clear, etc.)\n *\n * ```html\n * <igx-combo (searchInputUpdate)='handleSearchInputEvent($event)'></igx-combo>\n * ```\n */\n @Output()\n public searchInputUpdate = new EventEmitter<IComboSearchInputEventArgs>();\n\n /**\n * Emitted when new chunk of data is loaded from the virtualization\n *\n * ```html\n * <igx-combo (dataPreLoad)='handleDataPreloadEvent($event)'></igx-combo>\n * ```\n */\n @Output()\n public dataPreLoad = new EventEmitter<IForOfState>();\n\n /**\n * The custom template, if any, that should be used when rendering ITEMS in the combo list\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.itemTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboItem let-item let-key=\"valueKey\">\n * <div class=\"custom-item\">\n * <div class=\"custom-item__name\">{{ item[key] }}</div>\n * <div class=\"custom-item__cost\">{{ item.cost }}</div>\n * </div>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboItemDirective, { read: TemplateRef })\n public itemTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering the HEADER for the combo items list\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.headerTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboHeader>\n * <div class=\"combo__header\">\n * This is a custom header\n * </div>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboHeaderDirective, { read: TemplateRef })\n public headerTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering the FOOTER for the combo items list\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.footerTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboFooter>\n * <div class=\"combo__footer\">\n * This is a custom footer\n * </div>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboFooterDirective, { read: TemplateRef })\n public footerTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering HEADER ITEMS for groups in the combo list\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.headerItemTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboHeaderItem let-item let-key=\"groupKey\">\n * <div class=\"custom-item--group\">Group header for {{ item[key] }}</div>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboHeaderItemDirective, { read: TemplateRef })\n public headerItemTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.addItemTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboAddItem>\n * <button type=\"button\" igxButton=\"contained\" class=\"combo__add-button\">\n * Click to add item\n * </button>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboAddItemDirective, { read: TemplateRef })\n public addItemTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.emptyTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboEmpty>\n * <div class=\"combo--empty\">\n * There are no items to display\n * </div>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboEmptyDirective, { read: TemplateRef })\n public emptyTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering the combo TOGGLE(open/close) button\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.toggleIconTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboToggleIcon let-collapsed>\n * <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboToggleIconDirective, { read: TemplateRef })\n public toggleIconTemplate: TemplateRef<any> = null;\n\n /**\n * The custom template, if any, that should be used when rendering the combo CLEAR button\n *\n * ```typescript\n * // Set in typescript\n * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;\n * myComponent.combo.clearIconTemplate = myCustomTemplate;\n * ```\n * ```html\n * <!-- Set in markup -->\n * <igx-combo #combo>\n * ...\n * <ng-template igxComboClearIcon>\n * <igx-icon>clear</igx-icon>\n * </ng-template>\n * </igx-combo>\n * ```\n */\n @ContentChild(IgxComboClearIconDirective, { read: TemplateRef })\n public clearIconTemplate: TemplateRef<any> = null;\n\n /** @hidden @internal */\n @ContentChild(forwardRef(() => IgxLabelDirective), { static: true }) public label: IgxLabelDirective;\n\n /** @hidden @internal */\n @ViewChild('inputGroup', { read: IgxInputGroupComponent, static: true })\n public inputGroup: IgxInputGroupComponent;\n\n /** @hidden @internal */\n @ViewChild('comboInput', { read: IgxInputDirective, static: true })\n public comboInput: IgxInputDirective;\n\n /** @hidden @internal */\n @ViewChild('searchInput')\n public searchInput: ElementRef<HTMLInputElement> = null;\n\n /** @hidden @internal */\n @ViewChild(IgxForOfDirective, { static: true })\n public virtualScrollContainer: IgxForOfDirective<any>;\n\n @ViewChild(IgxForOfDirective, { read: IgxForOfDirective, static: true })\n protected virtDir: IgxForOfDirective<any>;\n\n @ViewChild('dropdownItemContainer', { static: true })\n protected dropdownContainer: ElementRef = null;\n\n @ViewChild('primitive', { read: TemplateRef, static: true })\n protected primitiveTemplate: TemplateRef<any>;\n\n @ViewChild('complex', { read: TemplateRef, static: true })\n protected complexTemplate: TemplateRef<any>;\n\n @ContentChildren(IgxPrefixDirective, { descendants: true })\n protected prefixes: QueryList<IgxPrefixDirective>;\n\n @ContentChildren(IgxSuffixDirective, { descendants: true })\n protected suffixes: QueryList<IgxSuffixDirective>;\n\n @ViewChildren(IgxSuffixDirective)\n protected internalSuffixes: QueryList<IgxSuffixDirective>;\n\n /** @hidden @internal */\n public get searchValue(): string {\n return this._searchValue;\n }\n public set searchValue(val: string) {\n this.filterValue = val;\n this._searchValue = val;\n }\n\n /** @hidden @internal */\n public get isRemote() {\n return this.totalItemCount > 0 &&\n this.valueKey &&\n this.dataType === DataTypes.COMPLEX;\n }\n\n /** @hidden @internal */\n public get dataType(): string {\n if (this.displayKey) {\n return DataTypes.COMPLEX;\n }\n return DataTypes.PRIMITIVE;\n }\n\n /**\n * Gets if control is valid, when used in a form\n *\n * ```typescript\n * // get\n * let valid = this.combo.valid;\n * ```\n */\n public get valid(): IgxInputState {\n return this._valid;\n }\n\n /**\n * Sets if control is valid, when used in a form\n *\n * ```typescript\n * // set\n * this.combo.valid = IgxInputState.INVALID;\n * ```\n */\n public set valid(valid: IgxInputState) {\n this._valid = valid;\n this.comboInput.valid = valid;\n }\n\n /**\n * The value of the combo\n *\n * ```typescript\n * // get\n * let comboValue = this.combo.value;\n * ```\n */\n public get value(): any[] {\n return this._value;\n }\n\n /**\n * The text displayed in the combo input\n *\n * ```typescript\n * // get\n * let comboDisplayValue = this.combo.displayValue;\n * ```\n */\n public get displayValue(): string {\n return this._displayValue;\n }\n\n /**\n * Defines the current state of the virtualized data. It contains `startIndex` and `chunkSize`\n *\n * ```typescript\n * // get\n * let state = this.combo.virtualizationState;\n * ```\n */\n public get virtualizationState(): IForOfState {\n return this.virtDir.state;\n }\n /**\n * Sets the current state of the virtualized data.\n *\n * ```typescript\n * // set\n * this.combo.virtualizationState(state);\n * ```\n */\n public set virtualizationState(state: IForOfState) {\n this.virtDir.state = state;\n }\n\n /**\n * Gets drop down state.\n *\n * ```typescript\n * let state = this.combo.collapsed;\n * ```\n */\n public get collapsed(): boolean {\n return this.dropdown.collapsed;\n }\n\n /**\n * Gets total count of the virtual data items, when using remote service.\n *\n * ```typescript\n * // get\n * let count = this.combo.totalItemCount;\n * ```\n */\n public get totalItemCount(): number {\n return this.virtDir.totalItemCount;\n }\n /**\n * Sets total count of the virtual data items, when using remote service.\n *\n * ```typescript\n * // set\n * this.combo.totalItemCount(remoteService.count);\n * ```\n */\n public set totalItemCount(count: number) {\n this.virtDir.totalItemCount = count;\n }\n\n /** @hidden @internal */\n public get template(): TemplateRef<any> {\n this._dataType = this.dataType;\n if (this.itemTemplate) {\n return this.itemTemplate;\n }\n if (this._dataType === DataTypes.COMPLEX) {\n return this.complexTemplate;\n }\n return this.primitiveTemplate;\n }\n\n /** @hidden @internal */\n public customValueFlag = true;\n /** @hidden @internal */\n public filterValue = '';\n /** @hidden @internal */\n public defaultFallbackGroup = 'Other';\n /** @hidden @internal */\n public activeDescendant = '';\n\n /**\n * Configures the way combo items will be filtered.\n *\n * ```typescript\n * // get\n * let myFilteringOptions = this.combo.filteringOptions;\n * ```\n *\n * ```html\n * <!--set-->\n * <igx-combo [filteringOptions]='myFilteringOptions'></igx-combo>\n * ```\n */\n\n @Input()\n public get filteringOptions(): IComboFilteringOptions {\n return this._filteringOptions || this._defaultFilteringOptions;\n }\n public set filteringOptions(value: IComboFilteringOptions) {\n this._filteringOptions = value;\n }\n\n protected containerSize = undefined;\n protected itemSize = undefined;\n protected _data = [];\n protected _value = [];\n protected _displayValue = '';\n protected _groupKey = '';\n protected _searchValue = '';\n protected _filteredData = [];\n protected _displayKey: string;\n protected _remoteSelection = {};\n protected _resourceStrings = getCurrentResourceStrings(ComboResourceStringsEN);\n protected _valid = IgxInputState.INITIAL;\n protected ngControl: NgControl = null;\n protected destroy$ = new Subject<void>();\n protected _onTouchedCallback: () => void = noop;\n protected _onChangeCallback: (_: any) => void = noop;\n protected compareCollator = new Intl.Collator();\n protected computedStyles;\n\n private _id: string = `igx-combo-${NEXT_ID++}`;\n private _disableFiltering = false;\n private _type = null;\n private _dataType = '';\n private _itemHeight = undefined;\n private _itemsMaxHeight = null;\n private _overlaySettings: OverlaySettings;\n private _groupSortingDirection: SortingDirection = SortingDirection.Asc;\n private _filteringOptions: IComboFilteringOptions;\n private _defaultFilteringOptions: IComboFilteringOptions = { caseSensitive: false };\n private itemsInContainer = 10;\n\n public abstract dropdown: IgxComboDropDownComponent;\n public abstract selectionChanging: EventEmitter<any>;\n\n public ngAfterViewChecked() {\n const targetElement = this.inputGroup.element.nativeElement.querySelector('.igx-input-group__bundle') as HTMLElement;\n\n this._overlaySettings = {\n target: targetElement,\n scrollStrategy: new AbsoluteScrollStrategy(),\n positionStrategy: new AutoPositionStrategy(),\n modal: false,\n closeOnOutsideClick: true,\n excludeFromOutsideClick: [targetElement]\n };\n }\n\n /** @hidden @internal */\n public ngAfterContentChecked(): void {\n if (this.inputGroup && this.prefixes?.length > 0) {\n this.inputGroup.prefixes = this.prefixes;\n }\n\n if (this.inputGroup) {\n const suffixesArray = this.suffixes?.toArray() ?? [];\n const internalSuffixesArray = this.internalSuffixes?.toArray() ?? [];\n const mergedSuffixes = new QueryList<IgxSuffixDirective>();\n mergedSuffixes.reset([\n ...suffixesArray,\n ...internalSuffixesArray\n ]);\n this.inputGroup.suffixes = mergedSuffixes;\n }\n }\n\n /** @hidden @internal */\n public ngOnInit() {\n this.ngControl = this._injector.get<NgControl>(NgControl, null);\n this.selectionService.set(this.id, new Set());\n this._iconService?.addSvgIconFromText(caseSensitive.name, caseSensitive.value, 'imx-icons');\n this.computedStyles = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);\n }\n\n /** @hidden @internal */\n public ngAfterViewInit(): void {\n this.filteredData = [...this.data];\n if (this.ngControl) {\n this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged);\n this.manageRequiredAsterisk();\n this.cdr.detectChanges();\n }\n this.virtDir.chunkPreload.pipe(takeUntil(this.destroy$)).subscribe((e: IForOfState) => {\n const eventArgs: IForOfState = Object.assign({}, e, { owner: this });\n this.dataPreLoad.emit(eventArgs);\n });\n this.dropdown?.opening.subscribe((_args: IBaseCancelableBrowserEventArgs) => {\n // calculate the container size and item size based on the sizes from the DOM\n const dropdownContainerHeight = this.dropdownContainer.nativeElement.getBoundingClientRect().height;\n if (dropdownContainerHeight) {\n this.containerSize = parseFloat(dropdownContainerHeight);\n }\n if (this.dropdown.children?.first) {\n this.itemSize = this.dropdown.children.first.element.nativeElement.getBoundingClientRect().height;\n }\n });\n }\n\n /** @hidden @internal */\n public ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n this.comboAPI.clear();\n this.selectionService.delete(this.id);\n }\n\n /**\n * A method that opens/closes the combo.\n *\n * ```html\n * <button type=\"button\" (click)=\"combo.toggle()\">Toggle Combo</button>\n * <igx-combo #combo></igx-combo>\n * ```\n */\n public toggle(): void {\n if (this.collapsed && this._displayValue.length !== 0) {\n this.filterValue = '';\n this.cdr.detectChanges();\n }\n const overlaySettings = Object.assign({}, this._overlaySettings, this.overlaySettings);\n this.dropdown.toggle(overlaySettings);\n if (!this.collapsed) {\n this.setActiveDescendant();\n }\n }\n\n /**\n * A method that opens the combo.\n *\n * ```html\n * <button type=\"button\" (click)=\"combo.open()\">Open Combo</button>\n * <igx-combo #combo></igx-combo>\n * ```\n */\n public open(): void {\n if (this.collapsed && this._displayValue.length !== 0) {\n this.filterValue = '';\n this.cdr.detectChanges();\n }\n const overlaySettings = Object.assign({}, this._overlaySettings, this.overlaySettings);\n this.dropdown.open(overlaySettings);\n this.setActiveDescendant();\n }\n\n /**\n * A method that closes the combo.\n *\n * ```html\n * <button type=\"button\" (click)=\"combo.close()\">Close Combo</button>\n * <igx-combo #combo></igx-combo>\n * ```\n */\n public close(): void {\n this.dropdown.close();\n }\n\n /**\n * Triggers change detection on the combo view\n */\n public triggerCheck() {\n this.cdr.detectChanges();\n }\n\n /**\n * Get current selection state\n *\n * @returns Array of selected items\n * ```typescript\n * let mySelection = this.combo.selection;\n * ```\n */\n public get selection(): any[] {\n const serviceRef = this.selectionService.get(this.id);\n return serviceRef ? this.convertKeysToItems(Array.from(serviceRef)) : [];\n }\n\n /**\n * Returns if the specified itemID is selected\n *\n * @hidden\n * @internal\n */\n public isItemSelected(item: any): boolean {\n return this.selectionService.is_item_selected(this.id, item);\n }\n\n /** @hidden @internal */\n public get toggleIcon(): string {\n return this.dropdown.collapsed ? 'input_expand' : 'input_collapse';\n }\n\n /** @hidden @internal */\n public addItemToCollection() {\n if (!this.searchValue) {\n return;\n }\n const addedItem = this.displayKey ? {\n [this.valueKey]: this.searchValue,\n [this.displayKey]: this.searchValue\n } : this.searchValue;\n if (this.groupKey) {\n Object.assign(addedItem, { [this.groupKey]: this.defaultFallbackGroup });\n }\n // expose shallow copy instead of this.data in event args so this.data can't be mutated\n const oldCollection = [...this.data];\n const newCollection = [...this.data, addedItem];\n const args: IComboItemAdditionEvent = {\n oldCollection, addedItem, newCollection, owner: this, cancel: false\n };\n this.addition.emit(args);\n if (args.cancel) {\n return;\n }\n this.data.push(args.addedItem);\n // trigger re-render\n this.data = cloneArray(this.data);\n this.select(this.valueKey !== null && this.valueKey !== undefined ?\n [args.addedItem[this.valueKey]] : [args.addedItem], false);\n this.customValueFlag = false;\n this.searchInput?.nativeElement.focus();\n this.dropdown.focusedItem = null;\n this.virtDir.scrollTo(0);\n }\n\n /** @hidden @internal */\n public isAddButtonVisible(): boolean {\n // This should always return a boolean value. If this.searchValue was '', it returns '' instead of false;\n return this.searchValue !== '' && this.customValueFlag;\n }\n\n /** @hidden @internal */\n public handleInputChange(event?: any) {\n if (event !== undefined) {\n const args: IComboSearchInputEventArgs = {\n searchText: typeof event === 'string' ? event : event.target.value,\n owner: this,\n cancel: false\n };\n this.searchInputUpdate.emit(args);\n if (args.cancel) {\n this.filterValue = null;\n }\n }\n this.checkMatch();\n }\n\n /**\n * Event handlers\n *\n * @hidden\n * @internal\n */\n public handleOpening(e: IBaseCancelableBrowserEventArgs) {\n const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };\n this.opening.emit(args);\n e.cancel = args.cancel;\n }\n\n /** @hidden @internal */\n public handleClosing(e: IBaseCancelableBrowserEventArgs) {\n const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };\n this.closing.emit(args);\n e.cancel = args.cancel;\n if (e.cancel) {\n return;\n }\n this.searchValue = '';\n if (!e.event) {\n this.comboInput?.nativeElement.focus();\n } else {\n this._onTouchedCallback();\n this.updateValidity();\n }\n }\n\n /** @hidden @internal */\n public handleClosed() {\n this.closed.emit({ owner: this });\n }\n\n /** @hidden @internal */\n public handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'ArrowUp' || event.key === 'Up') {\n event.preventDefault();\n event.stopPropagation();\n this.close();\n }\n }\n\n /** @hidden @internal */\n public handleToggleKeyDown(eventArgs: KeyboardEvent) {\n if (eventArgs.key === 'Enter' || eventArgs.key === ' ') {\n eventArgs.preventDefault();\n this.toggle();\n }\n }\n\n /** @hidden @internal */\n public getAriaLabel(): string {\n return this.displayValue ? this.resourceStrings.igx_combo_aria_label_options : this.resourceStrings.igx_combo_aria_label_no_options;\n }\n\n\n /** @hidden @internal */\n public registerOnChange(fn: any): void {\n this._onChangeCallback = fn;\n }\n\n /** @hidden @internal */\n public registerOnTouched(fn: any): void {\n this._onTouchedCallback = fn;\n }\n\n /** @hidden @internal */\n public setDisabledState(isDisabled: boolean): void {\n