igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
966 lines (957 loc) • 50.3 kB
JavaScript
import * as i4 from 'igniteui-angular/input-group';
import { IGX_INPUT_GROUP_TYPE, IgxInputState, IgxInputGroupComponent, IgxInputDirective, IgxSuffixDirective, IgxReadOnlyInputDirective, IgxHintDirective, IgxPrefixDirective, IgxLabelDirective } from 'igniteui-angular/input-group';
import * as i0 from '@angular/core';
import { Component, Input, Optional, Directive, inject, Injector, EventEmitter, QueryList, TemplateRef, ElementRef, booleanAttribute, ContentChild, Output, HostBinding, forwardRef, ViewChildren, ContentChildren, ViewChild, NgModule } from '@angular/core';
import { IgxDropDownGroupComponent, IgxDropDownItemComponent, IgxDropDownItemNavigationDirective, IgxDropDownComponent, IGX_DROPDOWN_BASE } from 'igniteui-angular/drop-down';
import { NgTemplateOutlet } from '@angular/common';
import { NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subscription, timer, noop } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BaseFitPositionStrategy, VerticalAlignment, HorizontalAlignment, Util, IgxOverlayService, AbsoluteScrollStrategy } from 'igniteui-angular/core';
import { __decorate, __param } from 'tslib';
import { fadeOut, fadeIn } from 'igniteui-angular/animations';
import { IgxToggleDirective } from 'igniteui-angular/directives';
import { IgxIconComponent } from 'igniteui-angular/icon';
/**
* The `<igx-select-item>` is a container intended for row items in
* a `<igx-select>` container.
*/
class IgxSelectGroupComponent extends IgxDropDownGroupComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectGroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.2", type: IgxSelectGroupComponent, isStandalone: true, selector: "igx-select-item-group", usesInheritance: true, ngImport: i0, template: `
<label id="{{labelId}}">{{ label }}</label>
<ng-content select="igx-select-item"></ng-content>
`, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectGroupComponent, decorators: [{
type: Component,
args: [{
selector: 'igx-select-item-group',
template: `
<label id="{{labelId}}">{{ label }}</label>
<ng-content select="igx-select-item"></ng-content>
`,
standalone: true
}]
}] });
class IgxSelectItemComponent extends IgxDropDownItemComponent {
/**
* Gets/Sets the item's text to be displayed in the select component's input when the item is selected.
*
* ```typescript
* //get
* let mySelectedItem = this.dropDown.selectedItem;
* let selectedItemText = mySelectedItem.text;
* ```
*
* ```html
* // set
* <igx-select-item [text]="'London'"></igx-select-item>
* ```
*/
get text() {
return this._text;
}
set text(text) {
this._text = text;
}
/** @hidden @internal */
get itemText() {
if (this._text !== undefined) {
return this._text;
}
// If text @Input is undefined, try extract a meaningful item text out of the item template
return this.elementRef.nativeElement.textContent.trim();
}
/**
* Sets/Gets if the item is the currently selected one in the select
*
* ```typescript
* let mySelectedItem = this.select.selectedItem;
* let isMyItemSelected = mySelectedItem.selected; // true
* ```
*/
get selected() {
return !this.isHeader && !this.disabled && this.selection.is_item_selected(this.dropDown.id, this);
}
set selected(value) {
if (value && !this.isHeader && !this.disabled) {
this.dropDown.selectItem(this);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.2", type: IgxSelectItemComponent, isStandalone: true, selector: "igx-select-item", inputs: { text: "text" }, usesInheritance: true, ngImport: i0, template: "<span class=\"igx-drop-down__content\">\n <ng-content select=\"igx-prefix, [igxPrefix]\"></ng-content>\n <span class=\"igx-drop-down__inner\"><ng-content></ng-content></span>\n <ng-content select=\"igx-suffix, [igxSuffix]\"></ng-content>\n</span>\n" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectItemComponent, decorators: [{
type: Component,
args: [{ selector: 'igx-select-item', standalone: true, template: "<span class=\"igx-drop-down__content\">\n <ng-content select=\"igx-prefix, [igxPrefix]\"></ng-content>\n <span class=\"igx-drop-down__inner\"><ng-content></ng-content></span>\n <ng-content select=\"igx-suffix, [igxSuffix]\"></ng-content>\n</span>\n" }]
}], propDecorators: { text: [{
type: Input
}] } });
/** @hidden @internal */
let SelectPositioningStrategy = class SelectPositioningStrategy extends BaseFitPositionStrategy {
constructor(select, settings, platform) {
super();
this.select = select;
this.platform = platform;
this._selectDefaultSettings = {
horizontalDirection: HorizontalAlignment.Right,
verticalDirection: VerticalAlignment.Bottom,
horizontalStartPoint: HorizontalAlignment.Left,
verticalStartPoint: VerticalAlignment.Top,
openAnimation: fadeIn,
closeAnimation: fadeOut
};
// Global variables required for cases of !initialCall (page scroll/overlay repositionAll)
this.global_yOffset = 0;
this.global_xOffset = 0;
this.global_styles = {};
this.settings = Object.assign({}, this._selectDefaultSettings, settings);
}
/**
* Position the element based on the PositionStrategy implementing this interface.
*
* @param contentElement The HTML element to be positioned
* @param size Size of the element
* @param document reference to the Document object
* @param initialCall should be true if this is the initial call to the method
* @param target attaching target for the component to show
* ```typescript
* settings.positionStrategy.position(content, size, document, true);
* ```
*/
position(contentElement, size, document, initialCall, target) {
const targetElement = target;
const rects = super.calculateElementRectangles(contentElement, targetElement);
// selectFit obj, to be used for both cases of initialCall and !initialCall(page scroll/overlay repositionAll)
const selectFit = {
verticalOffset: this.global_yOffset,
horizontalOffset: this.global_xOffset,
targetRect: rects.targetRect,
contentElementRect: rects.elementRect,
styles: this.global_styles,
scrollContainer: this.select.scrollContainer,
scrollContainerRect: this.select.scrollContainer.getBoundingClientRect()
};
if (initialCall) {
this.select.scrollContainer.scrollTop = 0;
// Fill in the required selectFit object properties.
selectFit.viewPortRect = Util.getViewportRect(document);
selectFit.itemElement = this.getInteractionItemElement();
selectFit.itemRect = selectFit.itemElement.getBoundingClientRect();
// Calculate input and selected item elements style related variables
selectFit.styles = this.calculateStyles(selectFit, targetElement);
selectFit.scrollAmount = this.calculateScrollAmount(selectFit);
// Calculate how much to offset the overlay container.
this.calculateYoffset(selectFit);
this.calculateXoffset(selectFit);
super.updateViewPortFit(selectFit);
// container does not fit in viewPort and is out on Top or Bottom
if (selectFit.fitVertical.back < 0 || selectFit.fitVertical.forward < 0) {
this.fitInViewport(contentElement, selectFit);
}
// Calculate scrollTop independently of the dropdown, as we cover all `igsSelect` specific positioning and
// scrolling to selected item scenarios here.
this.select.scrollContainer.scrollTop = selectFit.scrollAmount;
}
this.setStyles(contentElement, selectFit);
}
/**
* Obtain the selected item if there is such one or otherwise use the first one
*/
getInteractionItemElement() {
let itemElement;
if (this.select.selectedItem) {
itemElement = this.select.selectedItem.element.nativeElement;
}
else {
itemElement = this.select.getFirstItemElement();
}
return itemElement;
}
/**
* Position the items outer container so selected item text is positioned over input text and if header
* And/OR footer - both header/footer are visible
*
* @param selectFit selectFit to use for computation.
*/
fitInViewport(contentElement, selectFit) {
const footer = selectFit.scrollContainerRect.bottom - selectFit.contentElementRect.bottom;
const header = selectFit.scrollContainerRect.top - selectFit.contentElementRect.top;
const lastItemFitSize = selectFit.targetRect.bottom + selectFit.styles.itemTextToInputTextDiff - footer;
const firstItemFitSize = selectFit.targetRect.top - selectFit.styles.itemTextToInputTextDiff - header;
// out of viewPort on Top
if (selectFit.fitVertical.back < 0) {
const possibleScrollAmount = selectFit.scrollContainer.scrollHeight -
selectFit.scrollContainerRect.height - selectFit.scrollAmount;
if (possibleScrollAmount + selectFit.fitVertical.back > 0 && firstItemFitSize > selectFit.viewPortRect.top) {
selectFit.scrollAmount -= selectFit.fitVertical.back;
selectFit.verticalOffset -= selectFit.fitVertical.back;
this.global_yOffset = selectFit.verticalOffset;
}
else {
selectFit.verticalOffset = 0;
this.global_yOffset = 0;
}
// out of viewPort on Bottom
}
else if (selectFit.fitVertical.forward < 0) {
if (selectFit.scrollAmount + selectFit.fitVertical.forward > 0 && lastItemFitSize < selectFit.viewPortRect.bottom) {
selectFit.scrollAmount += selectFit.fitVertical.forward;
selectFit.verticalOffset += selectFit.fitVertical.forward;
this.global_yOffset = selectFit.verticalOffset;
}
else {
selectFit.verticalOffset = -selectFit.contentElementRect.height + selectFit.targetRect.height;
this.global_yOffset = selectFit.verticalOffset;
}
}
}
/**
* Sets element's style which effectively positions the provided element
*
* @param element Element to position
* @param selectFit selectFit to use for computation.
* @param initialCall should be true if this is the initial call to the position method calling setStyles
*/
setStyles(contentElement, selectFit) {
super.setStyle(contentElement, selectFit.targetRect, selectFit.contentElementRect, selectFit);
contentElement.style.width = `${selectFit.styles.contentElementNewWidth}px`; // manage container based on paddings?
this.global_styles.contentElementNewWidth = selectFit.styles.contentElementNewWidth;
}
/**
* Calculate selected item scroll position.
*/
calculateScrollAmount(selectFit) {
const itemElementRect = selectFit.itemRect;
const scrollContainer = selectFit.scrollContainer;
const scrollContainerRect = selectFit.scrollContainerRect;
const scrollDelta = scrollContainerRect.top - itemElementRect.top;
let scrollPosition = scrollContainer.scrollTop - scrollDelta;
const dropDownHeight = scrollContainer.clientHeight;
scrollPosition -= dropDownHeight / 2;
scrollPosition += itemElementRect.height / 2;
return Math.round(Math.min(Math.max(0, scrollPosition), scrollContainer.scrollHeight - scrollContainerRect.height));
}
/**
* Calculate the necessary input and selected item styles to be used for positioning item text over input text.
* Calculate & Set default items container width.
*
* @param selectFit selectFit to use for computation.
*/
calculateStyles(selectFit, target) {
const styles = {};
const inputElementStyles = window.getComputedStyle(target);
const itemElementStyles = window.getComputedStyle(selectFit.itemElement);
const numericInputFontSize = parseFloat(inputElementStyles.fontSize);
const numericInputPaddingTop = parseFloat(inputElementStyles.paddingTop);
const numericInputPaddingBottom = parseFloat(inputElementStyles.paddingBottom);
const numericItemFontSize = parseFloat(itemElementStyles.fontSize);
const inputTextToInputTop = ((selectFit.targetRect.bottom - numericInputPaddingBottom)
- (selectFit.targetRect.top + numericInputPaddingTop) - numericInputFontSize) / 2;
const itemTextToItemTop = (selectFit.itemRect.height - numericItemFontSize) / 2;
styles.itemTextToInputTextDiff = Math.round(itemTextToItemTop - inputTextToInputTop - numericInputPaddingTop);
const numericLeftPadding = parseFloat(itemElementStyles.paddingLeft);
const numericTextIndent = parseFloat(itemElementStyles.textIndent);
styles.itemTextPadding = numericLeftPadding;
styles.itemTextIndent = numericTextIndent;
// 24 is the input's toggle ddl icon width
styles.contentElementNewWidth = selectFit.targetRect.width + 24 + numericLeftPadding * 2;
return styles;
}
/**
* Calculate how much to offset the overlay container for Y-axis.
*/
calculateYoffset(selectFit) {
selectFit.verticalOffset = -(selectFit.itemRect.top - selectFit.contentElementRect.top +
selectFit.styles.itemTextToInputTextDiff - selectFit.scrollAmount);
this.global_yOffset = selectFit.verticalOffset;
}
/**
* Calculate how much to offset the overlay container for X-axis.
*/
calculateXoffset(selectFit) {
selectFit.horizontalOffset = selectFit.styles.itemTextIndent - selectFit.styles.itemTextPadding;
this.global_xOffset = selectFit.horizontalOffset;
}
};
SelectPositioningStrategy = __decorate([
__param(2, Optional())
], SelectPositioningStrategy);
/** @hidden @internal */
class IgxSelectItemNavigationDirective extends IgxDropDownItemNavigationDirective {
constructor() {
super(...arguments);
this._target = null;
this.inputStream = '';
this.clearStream$ = Subscription.EMPTY;
}
get target() {
return this._target;
}
set target(target) {
this._target = target ? target : this.dropdown;
}
/** Captures keydown events and calls the appropriate handlers on the target component */
handleKeyDown(event) {
if (!event) {
return;
}
const key = event.key.toLowerCase();
if (event.altKey && (key === 'arrowdown' || key === 'arrowup' || key === 'down' || key === 'up')) {
this.target.toggle();
return;
}
if (this.target.collapsed) {
switch (key) {
case 'space':
case 'spacebar':
case ' ':
case 'enter':
event.preventDefault();
this.target.open();
return;
case 'arrowdown':
case 'down':
this.target.navigateNext();
this.target.selectItem(this.target.focusedItem);
event.preventDefault();
return;
case 'arrowup':
case 'up':
this.target.navigatePrev();
this.target.selectItem(this.target.focusedItem);
event.preventDefault();
return;
default:
break;
}
}
else if (key === 'tab' || event.shiftKey && key === 'tab') {
this.target.close();
}
super.handleKeyDown(event);
this.captureKey(event);
}
captureKey(event) {
// relying only on key, available on all major browsers:
// https://caniuse.com/#feat=keyboardevent-key (IE/Edge quirk doesn't affect letter typing)
if (!event || !event.key || event.key.length > 1 || event.key === ' ' || event.key === 'spacebar') {
// ignore longer keys ('Alt', 'ArrowDown', etc) AND spacebar (used of open/close)
return;
}
this.clearStream$.unsubscribe();
this.clearStream$ = timer(500).subscribe(() => {
this.inputStream = '';
});
this.inputStream += event.key;
const focusedItem = this.target.focusedItem;
// select the item
if (focusedItem && this.inputStream.length > 1 && focusedItem.itemText.toLowerCase().startsWith(this.inputStream.toLowerCase())) {
return;
}
this.activateItemByText(this.inputStream);
}
activateItemByText(text) {
const items = this.target.items;
// ^ this is focused OR selected if the dd is closed
let nextItem = this.findNextItem(items, text);
// If there is no such an item starting with the current text input stream AND the last Char in the input stream
// is the same as the first one, find next item starting with the same first Char.
// Covers cases of holding down the same key Ex: "pppppp" that iterates trough list items starting with "p".
if (!nextItem && text.charAt(0) === text.charAt(text.length - 1)) {
text = text.slice(0, 1);
nextItem = this.findNextItem(items, text);
}
// If there is no other item to be found, do not change the active item.
if (!nextItem) {
return;
}
if (this.target.collapsed) {
this.target.selectItem(nextItem);
}
this.target.navigateItem(items.indexOf(nextItem));
}
ngOnDestroy() {
this.clearStream$.unsubscribe();
}
findNextItem(items, text) {
const activeItemIndex = items.indexOf(this.target.focusedItem) || 0;
// Match next item in ddl items and wrap around if needed
return items.slice(activeItemIndex + 1).find(x => !x.disabled && (x.itemText.toLowerCase().startsWith(text.toLowerCase()))) ||
items.slice(0, activeItemIndex).find(x => !x.disabled && (x.itemText.toLowerCase().startsWith(text.toLowerCase())));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectItemNavigationDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxSelectItemNavigationDirective, isStandalone: true, selector: "[igxSelectItemNavigation]", inputs: { target: ["igxSelectItemNavigation", "target"] }, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectItemNavigationDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxSelectItemNavigation]',
standalone: true
}]
}], propDecorators: { target: [{
type: Input,
args: ['igxSelectItemNavigation']
}] } });
/** @hidden @internal */
class IgxSelectToggleIconDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectToggleIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxSelectToggleIconDirective, isStandalone: true, selector: "[igxSelectToggleIcon]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectToggleIconDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxSelectToggleIcon]',
standalone: true
}]
}] });
/** @hidden @internal */
class IgxSelectHeaderDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxSelectHeaderDirective, isStandalone: true, selector: "[igxSelectHeader]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectHeaderDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxSelectHeader]',
standalone: true
}]
}] });
/** @hidden @internal */
class IgxSelectFooterDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxSelectFooterDirective, isStandalone: true, selector: "[igxSelectFooter]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectFooterDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxSelectFooter]',
standalone: true
}]
}] });
/**
* **Ignite UI for Angular Select** -
* [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/select)
*
* The `igxSelect` provides an input with dropdown list allowing selection of a single item.
*
* Example:
* ```html
* <igx-select #select1 [placeholder]="'Pick One'">
* <label igxLabel>Select Label</label>
* <igx-select-item *ngFor="let item of items" [value]="item.field">
* {{ item.field }}
* </igx-select-item>
* </igx-select>
* ```
*/
class IgxSelectComponent extends IgxDropDownComponent {
constructor() {
super(...arguments);
this.overlayService = inject(IgxOverlayService);
this._inputGroupType = inject(IGX_INPUT_GROUP_TYPE, { optional: true });
this._injector = inject(Injector);
/**
* Disables the component.
* ```html
* <igx-select [disabled]="'true'"></igx-select>
* ```
*/
this.disabled = false;
/** @hidden @internal */
this.maxHeight = '256px';
/**
* Emitted before the dropdown is opened
*
* ```html
* <igx-select opening='handleOpening($event)'></igx-select>
* ```
*/
this.opening = new EventEmitter();
/**
* Emitted after the dropdown is opened
*
* ```html
* <igx-select (opened)='handleOpened($event)'></igx-select>
* ```
*/
this.opened = new EventEmitter();
/**
* Emitted before the dropdown is closed
*
* ```html
* <igx-select (closing)='handleClosing($event)'></igx-select>
* ```
*/
this.closing = new EventEmitter();
/**
* Emitted after the dropdown is closed
*
* ```html
* <igx-select (closed)='handleClosed($event)'></igx-select>
* ```
*/
this.closed = new EventEmitter();
/**
* The custom template, if any, that should be used when rendering the select TOGGLE(open/close) button
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.select.toggleIconTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-select #select>
* ...
* <ng-template igxSelectToggleIcon let-collapsed>
* <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon>
* </ng-template>
* </igx-select>
* ```
*/
this.toggleIconTemplate = null;
/**
* The custom template, if any, that should be used when rendering the HEADER for the select items list
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.select.headerTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-select #select>
* ...
* <ng-template igxSelectHeader>
* <div class="select__header">
* This is a custom header
* </div>
* </ng-template>
* </igx-select>
* ```
*/
this.headerTemplate = null;
/**
* The custom template, if any, that should be used when rendering the FOOTER for the select items list
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.select.footerTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-select #select>
* ...
* <ng-template igxSelectFooter>
* <div class="select__footer">
* This is a custom footer
* </div>
* </ng-template>
* </igx-select>
* ```
*/
this.footerTemplate = null;
/** @hidden @internal do not use the drop-down container class */
this.cssClass = false;
/** @hidden @internal */
this.allowItemsFocus = false;
this.ngControl = null;
this._type = null;
this._onChangeCallback = noop;
this._onTouchedCallback = noop;
//#region ControlValueAccessor
/** @hidden @internal */
this.writeValue = (value) => {
this.value = value;
};
}
/**
* Gets/Sets the component value.
*
* ```typescript
* // get
* let selectValue = this.select.value;
* ```
*
* ```typescript
* // set
* this.select.value = 'London';
* ```
* ```html
* <igx-select [value]="value"></igx-select>
* ```
*/
get value() {
return this._value;
}
set value(v) {
if (this._value === v) {
return;
}
this._value = v;
this.setSelection(this.items.find(x => x.value === this.value));
}
/**
* Sets how the select will be styled.
* The allowed values are `line`, `box` and `border`. The input-group default is `line`.
* ```html
* <igx-select [type]="'box'"></igx-select>
* ```
*/
get type() {
return this._type || this._inputGroupType || 'line';
}
set type(val) {
this._type = val;
}
/** @hidden @internal */
get selectionValue() {
const selectedItem = this.selectedItem;
return selectedItem ? selectedItem.itemText : '';
}
/** @hidden @internal */
get selectedItem() {
return this.selection.first_item(this.id);
}
/** @hidden @internal */
registerOnChange(fn) {
this._onChangeCallback = fn;
}
/** @hidden @internal */
registerOnTouched(fn) {
this._onTouchedCallback = fn;
}
/** @hidden @internal */
setDisabledState(isDisabled) {
this.disabled = isDisabled;
}
//#endregion
/** @hidden @internal */
getEditElement() {
return this.input.nativeElement;
}
/** @hidden @internal */
selectItem(newSelection, event) {
const oldSelection = this.selectedItem ?? {};
if (newSelection === null || newSelection.disabled || newSelection.isHeader) {
return;
}
if (newSelection === oldSelection) {
this.toggleDirective.close();
return;
}
const args = { oldSelection, newSelection, cancel: false, owner: this };
this.selectionChanging.emit(args);
if (args.cancel) {
return;
}
this.setSelection(newSelection);
this._value = newSelection.value;
if (event) {
this.toggleDirective.close();
}
this.cdr.detectChanges();
this._onChangeCallback(this.value);
}
/** @hidden @internal */
getFirstItemElement() {
return this.children.first.element.nativeElement;
}
/**
* Opens the select
*
* ```typescript
* this.select.open();
* ```
*/
open(overlaySettings) {
if (this.disabled || this.items.length === 0) {
return;
}
if (!this.selectedItem) {
this.navigateFirst();
}
super.open(Object.assign({}, this._overlayDefaults, this.overlaySettings, overlaySettings));
}
inputGroupClick(event, overlaySettings) {
const targetElement = event.target;
if (this.hintElement && targetElement.contains(this.hintElement.nativeElement)) {
return;
}
this.toggle(Object.assign({}, this._overlayDefaults, this.overlaySettings, overlaySettings));
}
/** @hidden @internal */
ngAfterContentInit() {
this._overlayDefaults = {
target: this.getEditElement(),
modal: false,
positionStrategy: new SelectPositioningStrategy(this),
scrollStrategy: new AbsoluteScrollStrategy(),
excludeFromOutsideClick: [this.inputGroup.element.nativeElement]
};
const changes$ = this.children.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.setSelection(this.items.find(x => x.value === this.value));
this.cdr.detectChanges();
});
Promise.resolve().then(() => {
if (!changes$.closed) {
this.children.notifyOnChanges();
}
});
}
/**
* Event handlers
*
* @hidden @internal
*/
handleOpening(e) {
const args = { owner: this, event: e.event, cancel: e.cancel };
this.opening.emit(args);
e.cancel = args.cancel;
if (args.cancel) {
return;
}
}
/** @hidden @internal */
onToggleContentAppended(event) {
const info = this.overlayService.getOverlayById(event.id);
if (info?.settings?.positionStrategy instanceof SelectPositioningStrategy) {
return;
}
super.onToggleContentAppended(event);
}
/** @hidden @internal */
handleOpened() {
this.updateItemFocus();
this.opened.emit({ owner: this });
}
/** @hidden @internal */
handleClosing(e) {
const args = { owner: this, event: e.event, cancel: e.cancel };
this.closing.emit(args);
e.cancel = args.cancel;
}
/** @hidden @internal */
handleClosed() {
this.focusItem(false);
this.closed.emit({ owner: this });
}
/** @hidden @internal */
onBlur() {
this._onTouchedCallback();
if (this.ngControl && this.ngControl.invalid) {
this.input.valid = IgxInputState.INVALID;
}
else {
this.input.valid = IgxInputState.INITIAL;
}
}
/** @hidden @internal */
onFocus() {
this._onTouchedCallback();
}
/**
* @hidden @internal
*/
ngOnInit() {
this.ngControl = this._injector.get(NgControl, null);
}
/**
* @hidden @internal
*/
ngAfterViewInit() {
super.ngAfterViewInit();
if (this.ngControl) {
this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged.bind(this));
this.manageRequiredAsterisk();
}
this.cdr.detectChanges();
}
/** @hidden @internal */
ngAfterContentChecked() {
if (this.inputGroup && this.prefixes?.length > 0) {
this.inputGroup.prefixes = this.prefixes;
}
if (this.inputGroup) {
const suffixesArray = this.suffixes?.toArray() ?? [];
const internalSuffixesArray = this.internalSuffixes?.toArray() ?? [];
const mergedSuffixes = new QueryList();
mergedSuffixes.reset([
...suffixesArray,
...internalSuffixesArray
]);
this.inputGroup.suffixes = mergedSuffixes;
}
}
/** @hidden @internal */
get toggleIcon() {
return this.collapsed ? 'input_expand' : 'input_collapse';
}
/**
* @hidden @internal
* Prevent input blur - closing the items container on Header/Footer Template click.
*/
mousedownHandler(event) {
event.preventDefault();
}
onStatusChanged() {
this.manageRequiredAsterisk();
if (this.ngControl && !this.disabled && this.isTouchedOrDirty) {
if (this.hasValidators && this.inputGroup.isFocused) {
this.input.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
}
else {
// B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
this.input.valid = this.ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
}
}
else {
this.input.valid = IgxInputState.INITIAL;
}
}
get isTouchedOrDirty() {
return (this.ngControl.control.touched || this.ngControl.control.dirty);
}
get hasValidators() {
return (!!this.ngControl.control.validator || !!this.ngControl.control.asyncValidator);
}
navigate(direction, currentIndex) {
if (this.collapsed && this.selectedItem) {
this.navigateItem(this.selectedItem.itemIndex);
}
super.navigate(direction, currentIndex);
}
manageRequiredAsterisk() {
const hasRequiredHTMLAttribute = this.elementRef.nativeElement.hasAttribute('required');
let isRequired = false;
if (this.ngControl && this.ngControl.control.validator) {
const error = this.ngControl.control.validator({});
isRequired = !!(error && error.required);
}
this.inputGroup.isRequired = isRequired;
if (this.input?.nativeElement) {
this.input.nativeElement.setAttribute('aria-required', isRequired.toString());
}
// Handle validator removal case
if (!isRequired && !hasRequiredHTMLAttribute) {
this.input.valid = IgxInputState.INITIAL;
}
this.cdr.markForCheck();
}
setSelection(item) {
if (item && item.value !== undefined && item.value !== null) {
this.selection.set(this.id, new Set([item]));
}
else {
this.selection.clear(this.id);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxSelectComponent, isStandalone: true, selector: "igx-select", inputs: { placeholder: "placeholder", disabled: ["disabled", "disabled", booleanAttribute], overlaySettings: "overlaySettings", value: "value", type: "type" }, outputs: { opening: "opening", opened: "opened", closing: "closing", closed: "closed" }, host: { properties: { "style.maxHeight": "this.maxHeight" } }, providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: IgxSelectComponent, multi: true },
{ provide: IGX_DROPDOWN_BASE, useExisting: IgxSelectComponent }
], queries: [{ propertyName: "label", first: true, predicate: i0.forwardRef(() => IgxLabelDirective), descendants: true, static: true }, { propertyName: "toggleIconTemplate", first: true, predicate: IgxSelectToggleIconDirective, descendants: true, read: TemplateRef }, { propertyName: "headerTemplate", first: true, predicate: IgxSelectHeaderDirective, descendants: true, read: TemplateRef }, { propertyName: "footerTemplate", first: true, predicate: IgxSelectFooterDirective, descendants: true, read: TemplateRef }, { propertyName: "hintElement", first: true, predicate: IgxHintDirective, descendants: true, read: ElementRef }, { propertyName: "children", predicate: i0.forwardRef(() => IgxSelectItemComponent), descendants: true }, { propertyName: "prefixes", predicate: IgxPrefixDirective, descendants: true }, { propertyName: "suffixes", predicate: IgxSuffixDirective, descendants: true }], viewQueries: [{ propertyName: "inputGroup", first: true, predicate: ["inputGroup"], descendants: true, read: IgxInputGroupComponent, static: true }, { propertyName: "input", first: true, predicate: ["input"], descendants: true, read: IgxInputDirective, static: true }, { propertyName: "internalSuffixes", predicate: IgxSuffixDirective, descendants: true }], usesInheritance: true, ngImport: i0, template: "<igx-input-group #inputGroup class=\"input-group\" (click)=\"inputGroupClick($event)\" [type]=\"type === 'search' ? 'line' : type\">\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,[igxPrefix]\"></ng-content>\n </ng-container>\n <input #input class=\"input\" type=\"text\" igxInput [igxSelectItemNavigation]=\"this\"\n [disabled]=\"disabled\"\n readonly=\"true\"\n [igxReadOnlyInput]=\"false\"\n [attr.placeholder]=\"this.placeholder\"\n [value]=\"this.selectionValue\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-labelledby]=\"this.label?.id\"\n [attr.aria-expanded]=\"!this.collapsed\"\n [attr.aria-owns]=\"this.listId\"\n [attr.aria-activedescendant]=\"!this.collapsed ? this.focusedItem?.id : null\"\n (blur)=\"onBlur()\"\n (focus)=\"onFocus()\"\n />\n <ng-container ngProjectAs=\"igx-suffix\">\n <ng-content select=\"igx-suffix,[igxSuffix]\"></ng-content>\n </ng-container>\n <igx-suffix class=\"igx-select__toggle-button\">\n @if (toggleIconTemplate) {\n <ng-container *ngTemplateOutlet=\"toggleIconTemplate; context: {$implicit: this.collapsed}\"></ng-container>\n }\n @if (!toggleIconTemplate) {\n <igx-icon family=\"default\" [name]=\"toggleIcon\"></igx-icon>\n }\n </igx-suffix>\n <ng-container ngProjectAs=\"igx-hint, [igxHint]\" >\n <ng-content select=\"igx-hint, [igxHint]\"></ng-content>\n </ng-container>\n</igx-input-group>\n<div igxToggle class=\"igx-drop-down__list\" (mousedown)=\"mousedownHandler($event);\"\n (appended)=\"onToggleContentAppended($event)\"\n (opening)=\"handleOpening($event)\"\n (opened)=\"handleOpened()\"\n (closing)=\"handleClosing($event)\"\n (closed)=\"handleClosed()\">\n\n @if (headerTemplate) {\n <div class=\"igx-drop-down__select-header\">\n <ng-content *ngTemplateOutlet=\"headerTemplate\"></ng-content>\n </div>\n }\n\n <!-- #7436 LMB scrolling closes items container - unselectable attribute is IE specific -->\n <div #scrollContainer class=\"igx-drop-down__list-scroll\" unselectable=\"on\" [style.maxHeight]=\"maxHeight\"\n [attr.id]=\"this.listId\" role=\"listbox\" [attr.aria-labelledby]=\"this.label?.id\">\n <ng-content select=\"igx-select-item, igx-select-item-group\"></ng-content>\n </div>\n\n @if (footerTemplate) {\n <div class=\"igx-drop-down__select-footer\">\n <ng-container *ngTemplateOutlet=\"footerTemplate\"></ng-container>\n </div>\n }\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: IgxInputGroupComponent, selector: "igx-input-group", inputs: ["resourceStrings", "suppressInputAutofocus", "type", "theme"] }, { kind: "directive", type: IgxInputDirective, selector: "[igxInput]", inputs: ["value", "disabled", "required"], exportAs: ["igxInput"] }, { kind: "directive", type: IgxSelectItemNavigationDirective, selector: "[igxSelectItemNavigation]", inputs: ["igxSelectItemNavigation"] }, { kind: "directive", type: IgxSuffixDirective, selector: "igx-suffix,[igxSuffix],[igxEnd]" }, { kind: "directive", type: IgxReadOnlyInputDirective, selector: "[igxReadOnlyInput]", inputs: ["igxReadOnlyInput"], exportAs: ["igxReadOnlyInput"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IgxIconComponent, selector: "igx-icon", inputs: ["ariaHidden", "family", "name", "active"] }, { kind: "directive", type: IgxToggleDirective, selector: "[igxToggle]", inputs: ["id"], outputs: ["opened", "opening", "closed", "closing", "appended"], exportAs: ["toggle"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectComponent, decorators: [{
type: Component,
args: [{ selector: 'igx-select', providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: IgxSelectComponent, multi: true },
{ provide: IGX_DROPDOWN_BASE, useExisting: IgxSelectComponent }
], imports: [IgxInputGroupComponent, IgxInputDirective, IgxSelectItemNavigationDirective, IgxSuffixDirective, IgxReadOnlyInputDirective, NgTemplateOutlet, IgxIconComponent, IgxToggleDirective], template: "<igx-input-group #inputGroup class=\"input-group\" (click)=\"inputGroupClick($event)\" [type]=\"type === 'search' ? 'line' : type\">\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,[igxPrefix]\"></ng-content>\n </ng-container>\n <input #input class=\"input\" type=\"text\" igxInput [igxSelectItemNavigation]=\"this\"\n [disabled]=\"disabled\"\n readonly=\"true\"\n [igxReadOnlyInput]=\"false\"\n [attr.placeholder]=\"this.placeholder\"\n [value]=\"this.selectionValue\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-labelledby]=\"this.label?.id\"\n [attr.aria-expanded]=\"!this.collapsed\"\n [attr.aria-owns]=\"this.listId\"\n [attr.aria-activedescendant]=\"!this.collapsed ? this.focusedItem?.id : null\"\n (blur)=\"onBlur()\"\n (focus)=\"onFocus()\"\n />\n <ng-container ngProjectAs=\"igx-suffix\">\n <ng-content select=\"igx-suffix,[igxSuffix]\"></ng-content>\n </ng-container>\n <igx-suffix class=\"igx-select__toggle-button\">\n @if (toggleIconTemplate) {\n <ng-container *ngTemplateOutlet=\"toggleIconTemplate; context: {$implicit: this.collapsed}\"></ng-container>\n }\n @if (!toggleIconTemplate) {\n <igx-icon family=\"default\" [name]=\"toggleIcon\"></igx-icon>\n }\n </igx-suffix>\n <ng-container ngProjectAs=\"igx-hint, [igxHint]\" >\n <ng-content select=\"igx-hint, [igxHint]\"></ng-content>\n </ng-container>\n</igx-input-group>\n<div igxToggle class=\"igx-drop-down__list\" (mousedown)=\"mousedownHandler($event);\"\n (appended)=\"onToggleContentAppended($event)\"\n (opening)=\"handleOpening($event)\"\n (opened)=\"handleOpened()\"\n (closing)=\"handleClosing($event)\"\n (closed)=\"handleClosed()\">\n\n @if (headerTemplate) {\n <div class=\"igx-drop-down__select-header\">\n <ng-content *ngTemplateOutlet=\"headerTemplate\"></ng-content>\n </div>\n }\n\n <!-- #7436 LMB scrolling closes items container - unselectable attribute is IE specific -->\n <div #scrollContainer class=\"igx-drop-down__list-scroll\" unselectable=\"on\" [style.maxHeight]=\"maxHeight\"\n [attr.id]=\"this.listId\" role=\"listbox\" [attr.aria-labelledby]=\"this.label?.id\">\n <ng-content select=\"igx-select-item, igx-select-item-group\"></ng-content>\n </div>\n\n @if (footerTemplate) {\n <div class=\"igx-drop-down__select-footer\">\n <ng-container *ngTemplateOutlet=\"footerTemplate\"></ng-container>\n </div>\n }\n</div>\n", styles: [":host{display:block}\n"] }]
}], propDecorators: { inputGroup: [{
type: ViewChild,
args: ['inputGroup', { read: IgxInputGroupComponent, static: true }]
}], input: [{
type: ViewChild,
args: ['input', { read: IgxInputDirective, static: true }]
}], children: [{
type: ContentChildren,
args: [forwardRef(() => IgxSelectItemComponent), { descendants: true }]
}], prefixes: [{
type: ContentChildren,
args: [IgxPrefixDirective, { descendants: true }]
}], suffixes: [{
type: ContentChildren,
args: [IgxSuffixDirective, { descendants: true }]
}], internalSuffixes: [{
type: ViewChildren,
args: [IgxSuffixDirective]
}], label: [{
type: ContentChild,
args: [forwardRef(() => IgxLabelDirective), { static: true }]
}], placeholder: [{
type: Input
}], disabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], overlaySettings: [{
type: Input
}], maxHeight: [{
type: HostBinding,
args: ['style.maxHeight']
}], opening: [{
type: Output
}], opened: [{
type: Output
}], closing: [{
type: Output
}], closed: [{
type: Output
}], toggleIconTemplate: [{
type: ContentChild,
args: [IgxSelectToggleIconDirective, { read: TemplateRef }]
}], headerTemplate: [{
type: ContentChild,
args: [IgxSelectHeaderDirective, { read: TemplateRef, static: false }]
}], footerTemplate: [{
type: ContentChild,
args: [IgxSelectFooterDirective, { read: TemplateRef, static: false }]
}], hintElement: [{
type: ContentChild,
args: [IgxHintDirective, { read: ElementRef }]
}], value: [{
type: Input
}], type: [{
type: Input
}] } });
/* NOTE: Select directives collection for ease-of-use import in standalone components scenario */
const IGX_SELECT_DIRECTIVES = [
IgxSelectComponent,
IgxSelectItemComponent,
IgxSelectGroupComponent,
IgxSelectHeaderDirective,
IgxSelectFooterDirective,
IgxSelectToggleIconDirective,
IgxLabelDirective,
IgxPrefixDirective,
IgxSuffixDirective,
IgxHintDirective
];
/**
* @hidden
* @deprecated
* IMPORTANT: The following is NgModule exported for backwards-compatibility before standalone components
*/
class IgxSelectModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectModule, imports: [IgxSelectComponent, IgxSelectItemComponent, IgxSelectGroupComponent, IgxSelectHeaderDirective, IgxSelectFooterDirective, IgxSelectToggleIconDirective, i4.IgxLabelDirective, i4.IgxPrefixDirective, i4.IgxSuffixDirective, i4.IgxHintDirective], exports: [IgxSelectComponent, IgxSelectItemComponent, IgxSelectGroupComponent, IgxSelectHeaderDirective, IgxSelectFooterDirective, IgxSelectToggleIconDirective, i4.IgxLabelDirective, i4.IgxPrefixDirective, i4.IgxSuffixDirective, i4.IgxHintDirective] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectModule, imports: [IgxSelectComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectModule, decorators: [{
type: NgModule,
args: [{
imports: [
...IGX_SELECT_DIRECTIVES
],
exports: [
...IGX_SELECT_DIRECTIVES
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { IGX_SELECT_DIRECTIVES, IgxSelectComponent, IgxSelectFooterDirective, IgxSelectG