UNPKG

igniteui-angular

Version:

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

1,431 lines (1,424 loc) 66.3 kB
import * as i0 from '@angular/core'; import { booleanAttribute, Input, HostBinding, Component, InjectionToken, inject, ElementRef, EventEmitter, HostListener, Output, Directive, ChangeDetectorRef, DOCUMENT, ViewChild, ContentChild, forwardRef, ContentChildren, NgModule } from '@angular/core'; import { IgxSelectionAPIService, ConnectedPositioningStrategy, AutoPositionStrategy, AbsoluteScrollStrategy } from 'igniteui-angular/core'; import { IgxToggleDirective, IgxForOfToken } from 'igniteui-angular/directives'; import { Subject } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; import { NgModel, FormControlName } from '@angular/forms'; import { IgxInputGroupComponent } from 'igniteui-angular/input-group'; let NEXT_ID$2 = 0; /** * The `<igx-drop-down-item>` is a container intended for row items in * a `<igx-drop-down>` container. */ class IgxDropDownGroupComponent { constructor() { /** * @hidden @internal */ this.role = 'group'; /** @hidden @internal */ this.groupClass = true; /** * Sets/gets if the item group is disabled * * ```typescript * const myDropDownGroup: IgxDropDownGroupComponent = this.dropdownGroup; * // get * ... * const groupState: boolean = myDropDownGroup.disabled; * ... * //set * ... * myDropDownGroup,disabled = false; * ... * ``` * * ```html * <igx-drop-down-item-group [label]="'My Items'" [disabled]="true"> * <igx-drop-down-item *ngFor="let item of items[index]" [value]="item.value"> * {{ item.text }} * </igx-drop-down-item> * </igx-drop-down-item-group> * ``` * * **NOTE:** All items inside of a disabled drop down group will be treated as disabled */ this.disabled = false; this._id = NEXT_ID$2++; } /** * @hidden @internal */ get labelId() { return `igx-item-group-label-${this._id}`; } get labelledBy() { return this.labelId; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "21.0.2", type: IgxDropDownGroupComponent, isStandalone: true, selector: "igx-drop-down-item-group", inputs: { disabled: ["disabled", "disabled", booleanAttribute], label: "label" }, host: { properties: { "attr.aria-labelledby": "this.labelledBy", "attr.role": "this.role", "class.igx-drop-down__group": "this.groupClass", "attr.aria-disabled": "this.disabled", "class.igx-drop-down__group--disabled": "this.disabled" } }, ngImport: i0, template: ` <label id="{{labelId}}">{{ label }}</label> <ng-content select="igx-drop-down-item"></ng-content> `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownGroupComponent, decorators: [{ type: Component, args: [{ selector: 'igx-drop-down-item-group', template: ` <label id="{{labelId}}">{{ label }}</label> <ng-content select="igx-drop-down-item"></ng-content> `, standalone: true }] }], propDecorators: { labelledBy: [{ type: HostBinding, args: [`attr.aria-labelledby`] }], role: [{ type: HostBinding, args: ['attr.role'] }], groupClass: [{ type: HostBinding, args: ['class.igx-drop-down__group'] }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }, { type: HostBinding, args: [`attr.aria-disabled`] }, { type: HostBinding, args: ['class.igx-drop-down__group--disabled'] }], label: [{ type: Input }] } }); /** @hidden */ var Navigate; (function (Navigate) { Navigate[Navigate["Up"] = -1] = "Up"; Navigate[Navigate["Down"] = 1] = "Down"; })(Navigate || (Navigate = {})); /** Key actions that have designated handlers in IgxDropDownComponent */ const DropDownActionKey = { ESCAPE: 'escape', ENTER: 'enter', SPACE: 'space', TAB: 'tab' }; const IGX_DROPDOWN_BASE = /*@__PURE__*/ new InjectionToken('IgxDropDownBaseToken'); let NEXT_ID$1 = 0; /** * An abstract class defining a drop-down item: * With properties / styles for selection, highlight, height * Bindable property for passing data (`value: any`) * Parent component (has to be used under a parent with type `IDropDownBase`) * Method for handling click on Host() */ class IgxDropDownItemBaseDirective { constructor() { this.dropDown = inject(IGX_DROPDOWN_BASE); this.elementRef = inject(ElementRef); this.group = inject(IgxDropDownGroupComponent, { optional: true }); this.selection = inject(IgxSelectionAPIService, { optional: true }); /** * Sets/gets the `id` of the item. * ```html * <igx-drop-down-item [id] = 'igx-drop-down-item-0'></igx-drop-down-item> * ``` * ```typescript * let itemId = this.item.id; * ``` * * @memberof IgxSelectItemComponent */ this.id = `igx-drop-down-item-${NEXT_ID$1++}`; /** * @hidden */ this.selectedChange = new EventEmitter(); /** * Gets/sets the `role` attribute of the item. Default is 'option'. * * ```html * <igx-drop-down-item [role]="customRole"></igx-drop-down-item> * ``` */ this.role = 'option'; /** * @hidden */ this._focused = false; this._selected = false; this._index = null; this._disabled = false; this._label = null; } get ariaLabel() { return this._label ? this._label : this.value ? this.value : null; } set ariaLabel(value) { this._label = value; } /** * @hidden @internal */ get itemID() { return this; } /** * The data index of the dropdown item. * * ```typescript * // get the data index of the selected dropdown item * let selectedItemIndex = this.dropdown.selectedItem.index * ``` */ get index() { if (this._index === null) { return this.itemIndex; } return this._index; } set index(value) { this._index = value; } /** * @hidden @internal */ get itemStyle() { return !this.isHeader; } /** * Sets/Gets if the item is the currently selected one in the dropdown * * ```typescript * let mySelectedItem = this.dropdown.selectedItem; * let isMyItemSelected = mySelectedItem.selected; // true * ``` * * Two-way data binding * ```html * <igx-drop-down-item [(selected)]='model.isSelected'></igx-drop-down-item> * ``` */ get selected() { return this._selected; } set selected(value) { if (this.isHeader) { return; } this._selected = value; this.selectedChange.emit(this._selected); } /** * Sets/gets if the given item is focused * ```typescript * let mySelectedItem = this.dropdown.selectedItem; * let isMyItemFocused = mySelectedItem.focused; * ``` */ get focused() { return this.isSelectable && this._focused; } /** * ```html * <igx-drop-down-item *ngFor="let item of items" focused={{!item.focused}}> * <div> * {{item.field}} * </div> * </igx-drop-down-item> * ``` */ set focused(value) { this._focused = value; } /** * Sets/gets if the given item is disabled * * ```typescript * // get * let mySelectedItem = this.dropdown.selectedItem; * let myItemIsDisabled = mySelectedItem.disabled; * ``` * * ```html * <igx-drop-down-item *ngFor="let item of items" disabled={{!item.disabled}}> * <div> * {{item.field}} * </div> * </igx-drop-down-item> * ``` * **NOTE:** Drop-down items inside of a disabled `IgxDropDownGroup` will always count as disabled */ get disabled() { return this.group ? this.group.disabled || this._disabled : this._disabled; } set disabled(value) { this._disabled = value; } /** * Gets item index * * @hidden @internal */ get itemIndex() { return this.dropDown.items.indexOf(this); } /** * Gets item element height * * @hidden @internal */ get elementHeight() { return this.elementRef.nativeElement.clientHeight; } /** * Get item html element * * @hidden @internal */ get element() { return this.elementRef; } get hasIndex() { return this._index !== null && this._index !== undefined; } /** * @hidden * @internal */ clicked(event) { } /** * @hidden * @internal */ handleMousedown(event) { if (!this.dropDown.allowItemsFocus) { event.preventDefault(); } } ngDoCheck() { if (this._selected) { const dropDownSelectedItem = this.dropDown.selectedItem; if (!dropDownSelectedItem) { this.dropDown.selectItem(this, undefined, false); } else if (this.hasIndex ? this._index !== dropDownSelectedItem.index || this.value !== dropDownSelectedItem.value : this !== dropDownSelectedItem) { this.dropDown.selectItem(this, undefined, false); } } } /** Returns true if the items is not a header or disabled */ get isSelectable() { return !(this.disabled || this.isHeader); } /** If `allowItemsFocus` is enabled, keep the browser focus on the active item */ ensureItemFocus() { if (this.dropDown.allowItemsFocus) { const focusedItem = this.dropDown.items.find((item) => item.focused); if (!focusedItem) { return; } focusedItem.element.nativeElement.focus({ preventScroll: true }); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownItemBaseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: IgxDropDownItemBaseDirective, isStandalone: true, selector: "[igxDropDownItemBase]", inputs: { id: "id", ariaLabel: "ariaLabel", index: "index", value: "value", selected: ["selected", "selected", booleanAttribute], isHeader: ["isHeader", "isHeader", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], role: "role" }, outputs: { selectedChange: "selectedChange" }, host: { listeners: { "click": "clicked($event)", "mousedown": "handleMousedown($event)" }, properties: { "attr.id": "this.id", "attr.aria-label": "this.ariaLabel", "class.igx-drop-down__item": "this.itemStyle", "attr.aria-selected": "this.selected", "class.igx-drop-down__item--selected": "this.selected", "class.igx-drop-down__item--focused": "this.focused", "class.igx-drop-down__header": "this.isHeader", "attr.aria-disabled": "this.disabled", "class.igx-drop-down__item--disabled": "this.disabled", "attr.role": "this.role" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownItemBaseDirective, decorators: [{ type: Directive, args: [{ selector: '[igxDropDownItemBase]', standalone: true }] }], propDecorators: { id: [{ type: HostBinding, args: ['attr.id'] }, { type: Input }], ariaLabel: [{ type: HostBinding, args: ['attr.aria-label'] }, { type: Input }], index: [{ type: Input }], value: [{ type: Input }], itemStyle: [{ type: HostBinding, args: ['class.igx-drop-down__item'] }], selected: [{ type: Input, args: [{ transform: booleanAttribute }] }, { type: HostBinding, args: ['attr.aria-selected'] }, { type: HostBinding, args: ['class.igx-drop-down__item--selected'] }], selectedChange: [{ type: Output }], focused: [{ type: HostBinding, args: ['class.igx-drop-down__item--focused'] }], isHeader: [{ type: Input, args: [{ transform: booleanAttribute }] }, { type: HostBinding, args: ['class.igx-drop-down__header'] }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }, { type: HostBinding, args: ['attr.aria-disabled'] }, { type: HostBinding, args: ['class.igx-drop-down__item--disabled'] }], role: [{ type: Input }, { type: HostBinding, args: ['attr.role'] }], clicked: [{ type: HostListener, args: ['click', ['$event']] }], handleMousedown: [{ type: HostListener, args: ['mousedown', ['$event']] }] } }); /** * The `<igx-drop-down-item>` is a container intended for row items in * a `<igx-drop-down>` container. */ class IgxDropDownItemComponent extends IgxDropDownItemBaseDirective { /** * Sets/gets if the given item is focused * ```typescript * let mySelectedItem = this.dropdown.selectedItem; * let isMyItemFocused = mySelectedItem.focused; * ``` */ get focused() { let focusedState = this._focused; if (this.hasIndex) { const focusedItem = this.selection.first_item(`${this.dropDown.id}-active`); const focusedIndex = focusedItem ? focusedItem.index : -1; focusedState = this._index === focusedIndex; } return this.isSelectable && focusedState; } /** * Sets/gets if the given item is focused * ```typescript * let mySelectedItem = this.dropdown.selectedItem; * let isMyItemFocused = mySelectedItem.focused; * ``` */ set focused(value) { this._focused = value; } /** * Sets/Gets if the item is the currently selected one in the dropdown * * ```typescript * let mySelectedItem = this.dropdown.selectedItem; * let isMyItemSelected = mySelectedItem.selected; // true * ``` * * Two-way data binding * ```html * <igx-drop-down-item [(selected)]='model.isSelected'></igx-drop-down-item> * ``` */ get selected() { if (this.hasIndex) { const item = this.selection.first_item(`${this.dropDown.id}`); return item ? item.index === this._index && item.value === this.value : false; } return this._selected; } /** * Sets/Gets if the item is the currently selected one in the dropdown * */ set selected(value) { if (this.isHeader) { return; } this._selected = value; this.selectedChange.emit(this._selected); } /** * @hidden @internal */ get setTabIndex() { const shouldSetTabIndex = this.dropDown.allowItemsFocus && this.isSelectable; if (shouldSetTabIndex) { return 0; } else { return null; } } clicked(event) { if (!this.isSelectable) { this.ensureItemFocus(); return; } if (this.selection) { this.dropDown.selectItem(this, event); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.2", type: IgxDropDownItemComponent, isStandalone: true, selector: "igx-drop-down-item", host: { properties: { "attr.tabindex": "this.setTabIndex" } }, 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\">\n <ng-content></ng-content>\n </span>\n <ng-content select=\"igx-suffix, [igxSuffix]\"></ng-content>\n <ng-content select=\"igx-divider\"></ng-content>\n</span>\n" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownItemComponent, decorators: [{ type: Component, args: [{ selector: 'igx-drop-down-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\">\n <ng-content></ng-content>\n </span>\n <ng-content select=\"igx-suffix, [igxSuffix]\"></ng-content>\n <ng-content select=\"igx-divider\"></ng-content>\n</span>\n" }] }], propDecorators: { setTabIndex: [{ type: HostBinding, args: ['attr.tabindex'] }] } }); /** * Navigation Directive that handles keyboard events on its host and controls a targeted IgxDropDownBaseDirective component */ class IgxDropDownItemNavigationDirective { constructor() { this.dropdown = inject(IGX_DROPDOWN_BASE, { self: true, optional: true }); this._target = null; } /** * Gets the target of the navigation directive; * * ```typescript * // Get * export class MyComponent { * ... * @ContentChild(IgxDropDownNavigationDirective) * navDirective: IgxDropDownNavigationDirective = null * ... * const navTarget: IgxDropDownBaseDirective = navDirective.navTarget * } * ``` */ get target() { return this._target; } /** * Sets the target of the navigation directive; * If no valid target is passed, it falls back to the drop down context * * ```html * <!-- Set --> * <input [igxDropDownItemNavigation]="dropdown" /> * ... * <igx-drop-down #dropdown> * ... * </igx-drop-down> * ``` */ set target(target) { this._target = target ? target : this.dropdown; } get activeDescendant() { return this._target?.activeDescendant; } /** * Captures keydown events and calls the appropriate handlers on the target component */ handleKeyDown(event) { if (event) { const key = event.key.toLowerCase(); if (!this.target.collapsed) { // If dropdown is opened const navKeys = ['esc', 'escape', 'enter', 'space', 'spacebar', ' ', 'arrowup', 'up', 'arrowdown', 'down', 'home', 'end', 'tab']; if (navKeys.indexOf(key) === -1) { // If key has appropriate function in DD return; } event.preventDefault(); event.stopPropagation(); } else { // If dropdown is closed, do nothing return; } switch (key) { case 'esc': case 'escape': this.target.onItemActionKey(DropDownActionKey.ESCAPE, event); break; case 'enter': this.target.onItemActionKey(DropDownActionKey.ENTER, event); break; case 'space': case 'spacebar': case ' ': this.target.onItemActionKey(DropDownActionKey.SPACE, event); break; case 'arrowup': case 'up': this.onArrowUpKeyDown(); break; case 'arrowdown': case 'down': this.onArrowDownKeyDown(); break; case 'home': this.onHomeKeyDown(); break; case 'end': this.onEndKeyDown(); break; case 'tab': this.target.onItemActionKey(DropDownActionKey.TAB, event); break; default: return; } } } /** * Navigates to previous item */ onArrowDownKeyDown() { this.target.navigateNext(); } /** * Navigates to previous item */ onArrowUpKeyDown() { this.target.navigatePrev(); } /** * Navigates to target's last item */ onEndKeyDown() { this.target.navigateLast(); } /** * Navigates to target's first item */ onHomeKeyDown() { this.target.navigateFirst(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownItemNavigationDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxDropDownItemNavigationDirective, isStandalone: true, selector: "[igxDropDownItemNavigation]", inputs: { target: ["igxDropDownItemNavigation", "target"] }, host: { listeners: { "keydown": "handleKeyDown($event)" }, properties: { "attr.aria-activedescendant": "this.activeDescendant" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownItemNavigationDirective, decorators: [{ type: Directive, args: [{ selector: '[igxDropDownItemNavigation]', standalone: true }] }], propDecorators: { target: [{ type: Input, args: ['igxDropDownItemNavigation'] }], activeDescendant: [{ type: HostBinding, args: ['attr.aria-activedescendant'] }], handleKeyDown: [{ type: HostListener, args: ['keydown', ['$event']] }] } }); let NEXT_ID = 0; /** * An abstract class, defining a drop-down component, with: * Properties for display styles and classes * A collection items of type `IgxDropDownItemBaseDirective` * Properties and methods for navigating (highlighting/focusing) items from the collection * Properties and methods for selecting items from the collection */ class IgxDropDownBaseDirective { constructor() { this.elementRef = inject(ElementRef); this.cdr = inject(ChangeDetectorRef); this.document = inject(DOCUMENT); /** * Emitted when item selection is changing, before the selection completes * * ```html * <igx-drop-down (selectionChanging)='handleSelection()'></igx-drop-down> * ``` */ this.selectionChanging = new EventEmitter(); /** * Gets/Sets the drop down's container max height. * * ```typescript * // get * let maxHeight = this.dropdown.maxHeight; * ``` * ```html * <!--set--> * <igx-drop-down [maxHeight]='200px'></igx-drop-down> * ``` */ this.maxHeight = null; /** * @hidden @internal */ this.cssClass = true; this._focusedItem = null; this._id = `igx-drop-down-${NEXT_ID++}`; } /** * Gets/Sets the drop down's id * * ```typescript * // get * let myDropDownCurrentId = this.dropdown.id; * ``` * ```html * <!--set--> * <igx-drop-down [id]='newDropDownId'></igx-drop-down> * ``` */ get id() { return this._id; } set id(value) { this._id = value; } /** * Get all non-header items * * ```typescript * let myDropDownItems = this.dropdown.items; * ``` */ get items() { const items = []; if (this.children !== undefined) { for (const child of this.children.toArray()) { if (!child.isHeader) { items.push(child); } } } return items; } /** * Get all header items * * ```typescript * let myDropDownHeaderItems = this.dropdown.headers; * ``` */ get headers() { const headers = []; if (this.children !== undefined) { for (const child of this.children.toArray()) { if (child.isHeader) { headers.push(child); } } } return headers; } /** * Get dropdown html element * * ```typescript * let myDropDownElement = this.dropdown.element; * ``` */ get element() { return this.elementRef.nativeElement; } /** * @hidden @internal * Get dropdown's html element of its scroll container */ get scrollContainer() { return this.element; } /** * @hidden @internal * Gets the id of the focused item during dropdown navigation. * This is used to update the `aria-activedescendant` attribute of * the IgxDropDownNavigationDirective host element. */ get activeDescendant() { return this.focusedItem ? this.focusedItem.id : null; } ngOnInit() { this.computedStyles = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement); } /** Keydown Handler */ onItemActionKey(key, event) { switch (key) { case DropDownActionKey.ENTER: case DropDownActionKey.SPACE: this.selectItem(this.focusedItem, event); break; case DropDownActionKey.ESCAPE: case DropDownActionKey.TAB: } } /** * Emits selectionChanging with the target item & event * * @hidden @internal * @param newSelection the item selected * @param event the event that triggered the call */ selectItem(newSelection, event, emit = true) { this.selectionChanging.emit({ newSelection, oldSelection: null, cancel: false }); } /** * @hidden @internal */ get focusedItem() { return this._focusedItem; } /** * @hidden @internal */ set focusedItem(item) { this._focusedItem = item; } /** * Navigates to the item on the specified index * * @param newIndex number - the index of the item in the `items` collection */ navigateItem(newIndex) { if (newIndex !== -1) { const oldItem = this._focusedItem; const newItem = this.items[newIndex]; if (oldItem) { oldItem.focused = false; } this.focusedItem = newItem; this.scrollToHiddenItem(newItem); this.focusedItem.focused = true; } } /** * @hidden @internal */ navigateFirst() { this.navigate(Navigate.Down, -1); } /** * @hidden @internal */ navigateLast() { this.navigate(Navigate.Up, this.items.length); } /** * @hidden @internal */ navigateNext() { this.navigate(Navigate.Down); } /** * @hidden @internal */ navigatePrev() { this.navigate(Navigate.Up); } scrollToHiddenItem(newItem) { const elementRect = newItem.element.nativeElement.getBoundingClientRect(); const parentRect = this.scrollContainer.getBoundingClientRect(); if (parentRect.top > elementRect.top) { this.scrollContainer.scrollTop -= (parentRect.top - elementRect.top); } if (parentRect.bottom < elementRect.bottom) { this.scrollContainer.scrollTop += (elementRect.bottom - parentRect.bottom); } } navigate(direction, currentIndex) { let index = -1; if (this._focusedItem) { index = currentIndex ? currentIndex : this.focusedItem.itemIndex; } const newIndex = this.getNearestSiblingFocusableItemIndex(index, direction); this.navigateItem(newIndex); } getNearestSiblingFocusableItemIndex(startIndex, direction) { let index = startIndex; const items = this.items; while (items[index + direction] && items[index + direction].disabled) { index += direction; } index += direction; if (index >= 0 && index < items.length) { return index; } else { return -1; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownBaseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxDropDownBaseDirective, isStandalone: true, inputs: { width: "width", height: "height", id: "id", maxHeight: "maxHeight" }, outputs: { selectionChanging: "selectionChanging" }, host: { properties: { "attr.id": "this.id", "style.maxHeight": "this.maxHeight", "class.igx-drop-down": "this.cssClass" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownBaseDirective, decorators: [{ type: Directive }], propDecorators: { selectionChanging: [{ type: Output }], width: [{ type: Input }], height: [{ type: Input }], id: [{ type: HostBinding, args: ['attr.id'] }, { type: Input }], maxHeight: [{ type: Input }, { type: HostBinding, args: ['style.maxHeight'] }], cssClass: [{ type: HostBinding, args: ['class.igx-drop-down'] }] } }); /** * **Ignite UI for Angular DropDown** - * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/drop-down) * * The Ignite UI for Angular Drop Down displays a scrollable list of items which may be visually grouped and * supports selection of a single item. Clicking or tapping an item selects it and closes the Drop Down * * Example: * ```html * <igx-drop-down> * <igx-drop-down-item *ngFor="let item of items" disabled={{item.disabled}} isHeader={{item.header}}> * {{ item.value }} * </igx-drop-down-item> * </igx-drop-down> * ``` */ class IgxDropDownComponent extends IgxDropDownBaseDirective { constructor() { super(...arguments); this.selection = inject(IgxSelectionAPIService); /** * Emitted before the dropdown is opened * * ```html * <igx-drop-down (opening)='handleOpening($event)'></igx-drop-down> * ``` */ this.opening = new EventEmitter(); /** * Emitted after the dropdown is opened * * ```html * <igx-drop-down (opened)='handleOpened($event)'></igx-drop-down> * ``` */ this.opened = new EventEmitter(); /** * Emitted before the dropdown is closed * * ```html * <igx-drop-down (closing)='handleClosing($event)'></igx-drop-down> * ``` */ this.closing = new EventEmitter(); /** * Emitted after the dropdown is closed * * ```html * <igx-drop-down (closed)='handleClosed($event)'></igx-drop-down> * ``` */ this.closed = new EventEmitter(); /** * Gets/sets whether items take focus. Disabled by default. * When enabled, drop down items gain tab index and are focused when active - * this includes activating the selected item when opening the drop down and moving with keyboard navigation. * * Note: Keep that focus shift in mind when using the igxDropDownItemNavigation directive * and ensure it's placed either on each focusable item or a common ancestor to allow it to handle keyboard events. * * ```typescript * // get * let dropDownAllowsItemFocus = this.dropdown.allowItemsFocus; * ``` * * ```html * <!--set--> * <igx-drop-down [allowItemsFocus]='true'></igx-drop-down> * ``` */ this.allowItemsFocus = false; /** * Gets/sets the `role` attribute of the drop down. Default is 'listbox'. * * ```html * <igx-drop-down [role]="customRole"></igx-drop-down-item> * ``` */ this.role = 'listbox'; this.destroy$ = new Subject(); } /** * @hidden @internal */ get focusedItem() { if (this.virtDir) { return this._focusedItem && this._focusedItem.index !== -1 ? (this.children.find(e => e.index === this._focusedItem.index) || null) : null; } return this._focusedItem; } set focusedItem(value) { if (!value) { this.selection.clear(`${this.id}-active`); this._focusedItem = null; return; } this._focusedItem = value; if (this.virtDir) { this._focusedItem = { value: value.value, index: value.index }; } this.selection.set(`${this.id}-active`, new Set([this._focusedItem])); } get id() { return this._id; } set id(value) { this.selection.set(value, this.selection.get(this.id)); this.selection.clear(this.id); this.selection.set(value, this.selection.get(`${this.id}-active`)); this.selection.clear(`${this.id}-active`); this._id = value; } /** Id of the internal listbox of the drop down */ get listId() { return this.id + '-list'; } /** * Get currently selected item * * ```typescript * let currentItem = this.dropdown.selectedItem; * ``` */ get selectedItem() { const selectedItem = this.selection.first_item(this.id); if (selectedItem) { return selectedItem; } return null; } /** * Gets if the dropdown is collapsed * * ```typescript * let isCollapsed = this.dropdown.collapsed; * ``` */ get collapsed() { return this.toggleDirective.collapsed; } /** @hidden @internal */ get scrollContainer() { return this.scrollContainerRef.nativeElement; } get collectionLength() { if (this.virtDir) { return this.virtDir.totalItemCount || this.virtDir.igxForOf.length; } } /** * Opens the dropdown * * ```typescript * this.dropdown.open(); * ``` */ open(overlaySettings) { const settings = { ...{}, ...this.getDefaultOverlaySettings(), ...overlaySettings }; this.toggleDirective.open(settings); this.updateScrollPosition(); } /** * @hidden @internal */ getDefaultOverlaySettings() { return { closeOnOutsideClick: true, modal: false, positionStrategy: new ConnectedPositioningStrategy() }; } /** * Closes the dropdown * * ```typescript * this.dropdown.close(); * ``` */ close(event) { this.toggleDirective.close(event); } /** * Toggles the dropdown * * ```typescript * this.dropdown.toggle(); * ``` */ toggle(overlaySettings) { if (this.collapsed || this.toggleDirective.isClosing) { this.open(overlaySettings); } else { this.close(); } } /** * Select an item by index * * @param index of the item to select; If the drop down uses *igxFor, pass the index in data */ setSelectedItem(index) { if (index < 0 || index >= this.items.length) { return; } let newSelection; if (this.virtDir) { newSelection = { value: this.virtDir.igxForOf[index], index }; } else { newSelection = this.items[index]; } this.selectItem(newSelection); } /** * Navigates to the item on the specified index * If the data in the drop-down is virtualized, pass the index of the item in the virtualized data. * * @param newIndex number */ navigateItem(index) { if (this.virtDir) { if (index === -1 || index >= this.collectionLength) { return; } const direction = index > (this.focusedItem ? this.focusedItem.index : -1) ? Navigate.Down : Navigate.Up; const subRequired = this.isIndexOutOfBounds(index, direction); this.focusedItem = { value: this.virtDir.igxForOf[index], index }; if (subRequired) { this.virtDir.scrollTo(index); } if (subRequired) { this.virtDir.chunkLoad.pipe(take(1)).subscribe(() => { this.skipHeader(direction); }); } else { this.skipHeader(direction); } } else { super.navigateItem(index); } if (this.allowItemsFocus && this.focusedItem) { this.focusedItem.element.nativeElement.focus(); this.cdr.markForCheck(); } } /** * @hidden @internal */ updateScrollPosition() { if (!this.virtDir) { return; } if (!this.selectedItem) { this.virtDir.scrollTo(0); return; } let targetScroll = this.virtDir.getScrollForIndex(this.selectedItem.index); // TODO: This logic _cannot_ be right, those are optional user-provided inputs that can be strings with units, refactor: const itemsInView = this.virtDir.igxForContainerSize / this.virtDir.igxForItemSize; targetScroll -= (itemsInView / 2 - 1) * this.virtDir.igxForItemSize; this.virtDir.getScroll().scrollTop = targetScroll; } /** * @hidden @internal */ onToggleOpening(e) { const args = { owner: this, event: e.event, cancel: false }; this.opening.emit(args); e.cancel = args.cancel; if (e.cancel) { return; } if (this.virtDir) { this.virtDir.scrollPosition = this._scrollPosition; } } /** * @hidden @internal */ onToggleContentAppended(_event) { if (!this.virtDir && this.selectedItem) { this.scrollToItem(this.selectedItem); } } /** * @hidden @internal */ onToggleOpened() { this.updateItemFocus(); this.opened.emit({ owner: this }); } /** * @hidden @internal */ onToggleClosing(e) { const args = { owner: this, event: e.event, cancel: false }; this.closing.emit(args); e.cancel = args.cancel; if (e.cancel) { return; } if (this.virtDir) { this._scrollPosition = this.virtDir.scrollPosition; } } /** * @hidden @internal */ onToggleClosed() { this.focusItem(false); this.closed.emit({ owner: this }); } /** * @hidden @internal */ ngOnDestroy() { this.destroy$.next(true); this.destroy$.complete(); this.selection.delete(this.id); this.selection.delete(`${this.id}-active`); } /** @hidden @internal */ calculateScrollPosition(item) { if (!item) { return 0; } const elementRect = item.element.nativeElement.getBoundingClientRect(); const parentRect = this.scrollContainer.getBoundingClientRect(); const scrollDelta = parentRect.top - elementRect.top; let scrollPosition = this.scrollContainer.scrollTop - scrollDelta; const dropDownHeight = this.scrollContainer.clientHeight; scrollPosition -= dropDownHeight / 2; scrollPosition += item.elementHeight / 2; return Math.floor(scrollPosition); } /** * @hidden @internal */ ngOnChanges(changes) { if (changes.id) { // temp workaround until fix --> https://github.com/angular/angular/issues/34992 this.toggleDirective.id = changes.id.currentValue; } } ngAfterViewInit() { if (this.virtDir) { this.virtDir.igxForItemSize = 28; } } /** Keydown Handler */ onItemActionKey(key, event) { super.onItemActionKey(key, event); this.close(event); } /** * Virtual scroll implementation * * @hidden @internal */ navigateFirst() { if (this.virtDir) { this.navigateItem(0); } else { super.navigateFirst(); } } /** * @hidden @internal */ navigateLast() { if (this.virtDir) { this.navigateItem(this.virtDir.totalItemCount ? this.virtDir.totalItemCount - 1 : this.virtDir.igxForOf.length - 1); } else { super.navigateLast(); } } /** * @hidden @internal */ navigateNext() { if (this.virtDir) { this.navigateItem(this._focusedItem ? this._focusedItem.index + 1 : 0); } else { super.navigateNext(); } } /** * @hidden @internal */ navigatePrev() { if (this.virtDir) { this.navigateItem(this._focusedItem ? this._focusedItem.index - 1 : 0); } else { super.navigatePrev(); } } /** * Handles the `selectionChanging` emit and the drop down toggle when selection changes * * @hidden * @internal * @param newSelection * @param emit * @param event */ selectItem(newSelection, event, emit = true) { const oldSelection = this.selectedItem; if (!newSelection) { newSelection = this.focusedItem; } if (newSelection === null) { return; } if (newSelection instanceof IgxDropDownItemBaseDirective && newSelection.isHeader) { return; } if (this.virtDir) { newSelection = { value: newSelection.value, index: newSelection.index }; } const args = { oldSelection, newSelection, cancel: false, owner: this }; if (emit) { this.selectionChanging.emit(args); } if (!args.cancel) { if (this.isSelectionValid(args.newSelection)) { this.selection.set(this.id, new Set([args.newSelection])); if (!this.virtDir) { if (oldSelection) { oldSelection.selected = false; } if (args.newSelection) { args.newSelection.selected = true; } } if (event) { this.toggleDirective.close(event); } } else { throw new Error('Please provide a valid drop-down item for the selection!'); } } } /** * Clears the selection of the dropdown * ```typescript * this.dropdown.clearSelection(); * ``` */ clearSelection() { const oldSelection = this.selectedItem; const newSelection = null; const args = { oldSelection, newSelection, cancel: false, owner: this }; this.selectionChanging.emit(args); if (this.selectedItem && !args.cancel) { this.selectedItem.selected = false; this.selection.clear(this.id); } } /** * Checks whether the selection is valid * `null` - the selection should be emptied * Virtual? - the selection should at least have and `index` and `value` property * Non-virtual? - the selection should be a valid drop-down item and **not** be a header */ isSelectionValid(selection) { return selection === null || (this.virtDir && selection.hasOwnProperty('value') && selection.hasOwnProperty('index')) || (selection instanceof IgxDropDownItemComponent && !selection.isHeader); } scrollToItem(item) { this.scrollContainer.scrollTop = this.calculateScrollPosition(item); } focusItem(value) { if (value || this._focusedItem) { this._focusedItem.focused = value; } } updateItemFocus() { if (this.selectedItem) { this.focusedItem = this.selectedItem; this.focusItem(true); } else if (this.allowItemsFocus) { this.navigateFirst(); } } skipHeader(direction) { if (!this.focusedItem) { return; } if (this.focusedItem.isHeader || this.focusedItem.disabled) { if (direction === Navigate.Up) { this.navigatePrev(); } else { this.navigateNext(); } } } isIndexOutOfBounds(index, direction) { const virtState = this.virtDir.state; const currentPosition = this.virtDir.getScroll().scrollTop; const itemPosition = this.virtDir.getScrollForIndex(index, direction === Navigate.Down); const indexOutOfChunk = index < virtState.startIndex || index > virtState.chunkSize + virtState.startIndex; const scrollNeeded = direction === Navigate.Down ? currentPosition < itemPosition : currentPosition > itemPosition; const subRequired = indexOutOfChunk || scrollNeeded; return subRequired; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDropDownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxDropDownComponent, isStandalone: true, selector: "igx-drop-down", inputs: { allowItemsFocus: ["allowItemsFocus", "allowItemsFocus", booleanAttribute], labelledBy: "labelledBy", role: "role" }, outputs: { opening: "opening", opened: "opened", closing: "closing", closed: "closed" }, providers: [{ provide: IGX_DROPDOWN_BASE, useExisting: IgxDropDownComponent }], queries: [{ propertyName: "virtDir", first: true, predicate: IgxForOfToken, descendants: true }, { propertyName: "children", predicate: i0.forwardRef(() => IgxDropDownItemComponent), descendants: true }], viewQueries: [{ propertyName: "toggleDirective", first: true, predicate: IgxToggleDirective, descendants: true, static: true }, { propertyName: "scrollContainerRef", first: true, predicate: ["scrollContainer"], descendants: true, static: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"igx-drop-down__list\" [style.width]=\"width\"\n igxToggle\n (appended)=\"onToggleContentAppended($event)\"\n (opening)=\"onToggleOpening($event)\" (opened)=\"onToggleOpened()\"\n (closing)=\"onToggleClosing($event)\" (closed)=\"onToggleClosed()\">\n <div class=\"igx-drop-down__list-scroll\" #scrollContainer [attr.id]=\"listId\" [attr.role]=\"role\" [attr.aria-labelledby]=\"labelledBy\"\n [style.height]=\"height\"\n [style.maxHeight]=\"maxHeight\">\n @if (!collapsed) {\n <ng-content></ng-content>\n }\n </div>\n</div>\n", dependencies: [{ 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: IgxDropDownComponent, decorators: [{ type: Component, args: [{ selector: 'igx-drop-down', providers: [{ provide: IGX_DROPDOWN_BASE, useExisting: IgxDropDownComponent }], imports: [IgxToggleDirective], template: "<div class=\"igx-drop-down__list\" [style.width]=\"width\"\n igxToggle\n (appended)=\"onToggleContentAppended($event)\"\n (opening)=\"onToggleOpening($event)\" (opened)=\"onToggleOpened()\"\n (closing)=\"onToggleClosing($event)\" (closed)=\"onToggleClosed()\">\n <div class=\"igx-drop-down__list-scroll\" #scrollContainer [attr.id]=\"listI