UNPKG

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
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