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