primeng
Version:
[](https://badge.fury.io/js/primeng) [](https://www.npmjs.com/package/primeng) [ • 205 kB
JavaScript
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ContentChildren, EventEmitter, forwardRef, Input, NgModule, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { PrimeTemplate, SharedModule, TranslationKeys } from 'primeng/api';
import { AutoFocusModule } from 'primeng/autofocus';
import { DomHandler } from 'primeng/dom';
import { OverlayModule } from 'primeng/overlay';
import { RippleModule } from 'primeng/ripple';
import { ScrollerModule } from 'primeng/scroller';
import { TooltipModule } from 'primeng/tooltip';
import { ObjectUtils, UniqueComponentId } from 'primeng/utils';
import { TimesIcon } from 'primeng/icons/times';
import { ChevronDownIcon } from 'primeng/icons/chevrondown';
import { SearchIcon } from 'primeng/icons/search';
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
import * as i2 from "primeng/ripple";
import * as i3 from "primeng/api";
import * as i4 from "primeng/overlay";
import * as i5 from "primeng/tooltip";
import * as i6 from "primeng/scroller";
import * as i7 from "primeng/autofocus";
export const DROPDOWN_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Dropdown),
multi: true
};
class DropdownItem {
option;
selected;
label;
disabled;
visible;
itemSize;
template;
onClick = new EventEmitter();
onOptionClick(event) {
this.onClick.emit({
originalEvent: event,
option: this.option
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: DropdownItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: DropdownItem, selector: "p-dropdownItem", inputs: { option: "option", selected: "selected", label: "label", disabled: "disabled", visible: "visible", itemSize: "itemSize", template: "template" }, outputs: { onClick: "onClick" }, host: { classAttribute: "p-element" }, ngImport: i0, template: `
<li
(click)="onOptionClick($event)"
role="option"
pRipple
[attr.aria-label]="label"
[attr.aria-selected]="selected"
[ngStyle]="{ height: itemSize + 'px' }"
[id]="selected ? 'p-highlighted-option' : ''"
[ngClass]="{ 'p-dropdown-item': true, 'p-highlight': selected, 'p-disabled': disabled }"
>
<span *ngIf="!template">{{ label ?? 'empty' }}</span>
<ng-container *ngTemplateOutlet="template; context: { $implicit: option }"></ng-container>
</li>
`, isInline: true, dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.Ripple, selector: "[pRipple]" }] });
}
export { DropdownItem };
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: DropdownItem, decorators: [{
type: Component,
args: [{
selector: 'p-dropdownItem',
template: `
<li
(click)="onOptionClick($event)"
role="option"
pRipple
[attr.aria-label]="label"
[attr.aria-selected]="selected"
[ngStyle]="{ height: itemSize + 'px' }"
[id]="selected ? 'p-highlighted-option' : ''"
[ngClass]="{ 'p-dropdown-item': true, 'p-highlight': selected, 'p-disabled': disabled }"
>
<span *ngIf="!template">{{ label ?? 'empty' }}</span>
<ng-container *ngTemplateOutlet="template; context: { $implicit: option }"></ng-container>
</li>
`,
host: {
class: 'p-element'
}
}]
}], propDecorators: { option: [{
type: Input
}], selected: [{
type: Input
}], label: [{
type: Input
}], disabled: [{
type: Input
}], visible: [{
type: Input
}], itemSize: [{
type: Input
}], template: [{
type: Input
}], onClick: [{
type: Output
}] } });
/**
* Dropdown also known as Select, is used to choose an item from a collection of options.
* @group Components
*/
class Dropdown {
el;
renderer;
cd;
zone;
filterService;
config;
/**
* Height of the viewport in pixels, a scrollbar is defined if height of list exceeds this value.
* @group Props
*/
scrollHeight = '200px';
/**
* When specified, displays an input field to filter the items on keyup.
* @group Props
*/
filter;
/**
* Name of the input element.
* @group Props
*/
name;
/**
* Inline style of the element.
* @group Props
*/
style;
/**
* Inline style of the overlay panel element.
* @group Props
*/
panelStyle;
/**
* Style class of the element.
* @group Props
*/
styleClass;
/**
* Style class of the overlay panel element.
* @group Props
*/
panelStyleClass;
/**
* When present, it specifies that the component cannot be edited.
* @group Props
*/
readonly;
/**
* When present, it specifies that an input field must be filled out before submitting the form.
* @group Props
*/
required;
/**
* When present, custom value instead of predefined options can be entered using the editable input field.
* @group Props
*/
editable;
/**
* Target element to attach the overlay, valid values are "body" or a local ng-template variable of another element (note: use binding with brackets for template variables, e.g. [appendTo]="mydiv" for a div element having #mydiv as variable name).
* @group Props
*/
appendTo;
/**
* Index of the element in tabbing order.
* @group Props
*/
tabindex;
/**
* Default text to display when no option is selected.
* @group Props
*/
placeholder;
/**
* Placeholder text to show when filter input is empty.
* @group Props
*/
filterPlaceholder;
/**
* Locale to use in filtering. The default locale is the host environment's current locale.
* @group Props
*/
filterLocale;
/**
* Identifier of the accessible input element.
* @group Props
*/
inputId;
/**
* No description available.
* @group Props
*/
selectId;
/**
* A property to uniquely identify a value in options.
* @group Props
*/
dataKey;
/**
* When filtering is enabled, filterBy decides which field or fields (comma separated) to search against.
* @group Props
*/
filterBy;
/**
* When present, it specifies that the component should automatically get focus on load.
* @group Props
*/
autofocus;
/**
* Clears the filter value when hiding the dropdown.
* @group Props
*/
resetFilterOnHide = false;
/**
* Icon class of the dropdown icon.
* @group Props
*/
dropdownIcon;
/**
* Name of the label field of an option.
* @group Props
*/
optionLabel;
/**
* Name of the value field of an option.
* @group Props
*/
optionValue;
/**
* Name of the disabled field of an option.
* @group Props
*/
optionDisabled;
/**
* Name of the label field of an option group.
* @group Props
*/
optionGroupLabel;
/**
* Name of the options field of an option group.
* @group Props
*/
optionGroupChildren = 'items';
/**
* Whether to display the first item as the label if no placeholder is defined and value is null.
* @group Props
*/
autoDisplayFirst = true;
/**
* Whether to display options as grouped when nested options are provided.
* @group Props
*/
group;
/**
* When enabled, a clear icon is displayed to clear the value.
* @group Props
*/
showClear;
/**
* Text to display when filtering does not return any results. Defaults to global value in i18n translation configuration.
* @group Props
*/
emptyFilterMessage = '';
/**
* Text to display when there is no data. Defaults to global value in i18n translation configuration.
* @group Props
*/
emptyMessage = '';
/**
* Defines if data is loaded and interacted with in lazy manner.
* @group Props
*/
lazy = false;
/**
* Whether the data should be loaded on demand during scroll.
* @group Props
*/
virtualScroll;
/**
* Height of an item in the list for VirtualScrolling.
* @group Props
*/
virtualScrollItemSize;
/**
* Whether to use the scroller feature. The properties of scroller component can be used like an object in it.
* @group Props
*/
virtualScrollOptions;
/**
* Whether to use overlay API feature. The properties of overlay API can be used like an object in it.
* @group Props
*/
overlayOptions;
/**
* Defines a string that labels the filter input.
* @group Props
*/
ariaFilterLabel;
/**
* Used to define a string that autocomplete attribute the current element.
* @group Props
*/
ariaLabel;
/**
* Establishes relationships between the component and label(s) where its value should be one or more element IDs.
* @group Props
*/
ariaLabelledBy;
/**
* Defines how the items are filtered.
* @group Props
*/
filterMatchMode = 'contains';
/**
* Maximum number of character allows in the editable input field.
* @group Props
*/
maxlength;
/**
* Advisory information to display in a tooltip on hover.
* @group Props
*/
tooltip = '';
/**
* Position of the tooltip.
* @group Props
*/
tooltipPosition = 'right';
/**
* Type of CSS position.
* @group Props
*/
tooltipPositionStyle = 'absolute';
/**
* Style class of the tooltip.
* @group Props
*/
tooltipStyleClass;
/**
* Applies focus to the filter element when the overlay is shown.
* @group Props
*/
autofocusFilter = true;
/**
* No description available.
* @group Props
*/
overlayDirection = 'end';
/**
* When present, it specifies that the component should be disabled.
* @group Props
*/
get disabled() {
return this._disabled;
}
set disabled(_disabled) {
if (_disabled) {
this.focused = false;
if (this.overlayVisible)
this.hide();
}
this._disabled = _disabled;
if (!this.cd.destroyed) {
this.cd.detectChanges();
}
}
/**
* Item size of item to be virtual scrolled.
* @group Props
* @deprecated use virtualScrollItemSize property instead.
*/
get itemSize() {
return this._itemSize;
}
set itemSize(val) {
this._itemSize = val;
console.warn('The itemSize property is deprecated, use virtualScrollItemSize property instead.');
}
_itemSize;
/**
* Whether to automatically manage layering.
* @group Props
* @deprecated since v14.2.0, use overlayOptions property instead.
*/
get autoZIndex() {
return this._autoZIndex;
}
set autoZIndex(val) {
this._autoZIndex = val;
console.warn('The autoZIndex property is deprecated since v14.2.0, use overlayOptions property instead.');
}
_autoZIndex;
/**
* Base zIndex value to use in layering.
* @group Props
* @deprecated since v14.2.0, use overlayOptions property instead.
*/
get baseZIndex() {
return this._baseZIndex;
}
set baseZIndex(val) {
this._baseZIndex = val;
console.warn('The baseZIndex property is deprecated since v14.2.0, use overlayOptions property instead.');
}
_baseZIndex;
/**
* Transition options of the show animation.
* @group Props
* @deprecated since v14.2.0, use overlayOptions property instead.
*/
get showTransitionOptions() {
return this._showTransitionOptions;
}
set showTransitionOptions(val) {
this._showTransitionOptions = val;
console.warn('The showTransitionOptions property is deprecated since v14.2.0, use overlayOptions property instead.');
}
_showTransitionOptions;
/**
* Transition options of the hide animation.
* @group Props
* @deprecated since v14.2.0, use overlayOptions property instead.
*/
get hideTransitionOptions() {
return this._hideTransitionOptions;
}
set hideTransitionOptions(val) {
this._hideTransitionOptions = val;
console.warn('The hideTransitionOptions property is deprecated since v14.2.0, use overlayOptions property instead.');
}
_hideTransitionOptions;
/**
* When specified, filter displays with this value.
* @group Props
*/
get filterValue() {
return this._filterValue;
}
set filterValue(val) {
this._filterValue = val;
this.activateFilter();
}
/**
* An array of objects to display as the available options.
* @group Props
*/
get options() {
return this._options;
}
set options(val) {
this._options = val;
this.optionsToDisplay = this._options;
this.updateSelectedOption(this.value);
this.selectedOption = this.findOption(this.value, this.optionsToDisplay);
if (!this.selectedOption && ObjectUtils.isNotEmpty(this.value) && !this.editable) {
this.value = null;
this.onModelChange(this.value);
}
this.optionsChanged = true;
if (this._filterValue && this._filterValue.length) {
this.activateFilter();
}
}
/**
* Callback to invoke when value of dropdown changes.
* @param {DropdownChangeEvent} event - custom change event.
* @group Emits
*/
onChange = new EventEmitter();
/**
* Callback to invoke when data is filtered.
* @param {DropdownFilterEvent} event - custom filter event.
* @group Emits
*/
onFilter = new EventEmitter();
/**
* Callback to invoke when dropdown gets focus.
* @param {Event} event - Browser event.
* @group Emits
*/
onFocus = new EventEmitter();
/**
* Callback to invoke when dropdown loses focus.
* @param {Event} event - Browser event.
* @group Emits
*/
onBlur = new EventEmitter();
/**
* Callback to invoke when component is clicked.
* @param {MouseEvent} event - Mouse event.
* @group Emits
*/
onClick = new EventEmitter();
/**
* Callback to invoke when dropdown overlay gets visible.
* @param {AnimationEvent} event - Animation event.
* @group Emits
*/
onShow = new EventEmitter();
/**
* Callback to invoke when dropdown overlay gets hidden.
* @param {AnimationEvent} event - Animation event.
* @group Emits
*/
onHide = new EventEmitter();
/**
* Callback to invoke when dropdown clears the value.
* @param {Event} event - Browser event.
* @group Emits
*/
onClear = new EventEmitter();
/**
* Callback to invoke in lazy mode to load new data.
* @param {DropdownLazyLoadEvent} event - Lazy load event.
* @group Emits
*/
onLazyLoad = new EventEmitter();
containerViewChild;
filterViewChild;
accessibleViewChild;
editableInputViewChild;
itemsViewChild;
scroller;
overlayViewChild;
templates;
_disabled;
itemsWrapper;
itemTemplate;
groupTemplate;
loaderTemplate;
selectedItemTemplate;
headerTemplate;
filterTemplate;
footerTemplate;
emptyFilterTemplate;
emptyTemplate;
dropdownIconTemplate;
clearIconTemplate;
filterIconTemplate;
filterOptions;
selectedOption;
_options;
value;
onModelChange = () => { };
onModelTouched = () => { };
optionsToDisplay;
hover;
focused;
overlayVisible;
optionsChanged;
panel;
dimensionsUpdated;
hoveredItem;
selectedOptionUpdated;
_filterValue;
searchValue;
searchIndex;
searchTimeout;
previousSearchChar;
currentSearchChar;
preventModelTouched;
id = UniqueComponentId();
labelId;
listId;
constructor(el, renderer, cd, zone, filterService, config) {
this.el = el;
this.renderer = renderer;
this.cd = cd;
this.zone = zone;
this.filterService = filterService;
this.config = config;
}
ngAfterContentInit() {
this.templates.forEach((item) => {
switch (item.getType()) {
case 'item':
this.itemTemplate = item.template;
break;
case 'selectedItem':
this.selectedItemTemplate = item.template;
break;
case 'header':
this.headerTemplate = item.template;
break;
case 'filter':
this.filterTemplate = item.template;
break;
case 'footer':
this.footerTemplate = item.template;
break;
case 'emptyfilter':
this.emptyFilterTemplate = item.template;
break;
case 'empty':
this.emptyTemplate = item.template;
break;
case 'group':
this.groupTemplate = item.template;
break;
case 'loader':
this.loaderTemplate = item.template;
break;
case 'dropdownicon':
this.dropdownIconTemplate = item.template;
break;
case 'clearicon':
this.clearIconTemplate = item.template;
break;
case 'filtericon':
this.filterIconTemplate = item.template;
break;
default:
this.itemTemplate = item.template;
break;
}
});
}
ngOnInit() {
this.optionsToDisplay = this.options;
this.updateSelectedOption(null);
this.labelId = this.id + '_label';
this.listId = this.id + '_list';
if (this.filterBy) {
this.filterOptions = {
filter: (value) => this.onFilterInputChange(value),
reset: () => this.resetFilter()
};
}
}
ngAfterViewInit() {
if (this.editable) {
this.updateEditableLabel();
}
}
get label() {
if (typeof this.selectedOption === 'number') {
this.selectedOption = this.selectedOption.toString();
}
return this.selectedOption ? this.getOptionLabel(this.selectedOption) : null;
}
get emptyMessageLabel() {
return this.emptyMessage || this.config.getTranslation(TranslationKeys.EMPTY_MESSAGE);
}
get emptyFilterMessageLabel() {
return this.emptyFilterMessage || this.config.getTranslation(TranslationKeys.EMPTY_FILTER_MESSAGE);
}
get filled() {
if (typeof this.value === 'string')
return !!this.value;
return this.value || this.value != null || this.value != undefined;
}
get isVisibleClearIcon() {
return this.value != null && this.value !== '' && this.showClear && !this.disabled;
}
updateEditableLabel() {
if (this.editableInputViewChild && this.editableInputViewChild.nativeElement) {
this.editableInputViewChild.nativeElement.value = this.selectedOption ? this.getOptionLabel(this.selectedOption) : this.value || '';
}
}
getOptionLabel(option) {
return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option && option.label !== undefined ? option.label : option;
}
getOptionValue(option) {
return this.optionValue ? ObjectUtils.resolveFieldData(option, this.optionValue) : !this.optionLabel && option && option.value !== undefined ? option.value : option;
}
isOptionDisabled(option) {
return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : option && option.disabled !== undefined ? option.disabled : false;
}
getOptionGroupLabel(optionGroup) {
return this.optionGroupLabel ? ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel) : optionGroup && optionGroup.label !== undefined ? optionGroup.label : optionGroup;
}
getOptionGroupChildren(optionGroup) {
return this.optionGroupChildren ? ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren) : optionGroup.items;
}
onItemClick(event) {
const option = event.option;
if (!this.isOptionDisabled(option)) {
this.selectItem(event.originalEvent, option);
this.accessibleViewChild?.nativeElement.focus({ preventScroll: true });
}
setTimeout(() => {
this.hide();
}, 1);
}
selectItem(event, option) {
if (this.selectedOption != option) {
this.selectedOption = option;
this.value = this.getOptionValue(option);
this.onModelChange(this.value);
this.updateEditableLabel();
this.onChange.emit({
originalEvent: event,
value: this.value
});
}
}
ngAfterViewChecked() {
if (this.optionsChanged && this.overlayVisible) {
this.optionsChanged = false;
this.zone.runOutsideAngular(() => {
setTimeout(() => {
if (this.overlayViewChild) {
this.overlayViewChild.alignOverlay();
}
}, 1);
});
}
if (this.selectedOptionUpdated && this.itemsWrapper) {
let selectedItem = DomHandler.findSingle(this.overlayViewChild?.overlayViewChild?.nativeElement, 'li.p-highlight');
if (selectedItem) {
DomHandler.scrollInView(this.itemsWrapper, selectedItem);
}
this.selectedOptionUpdated = false;
}
}
writeValue(value) {
if (this.filter) {
this.resetFilter();
}
this.value = value;
this.updateSelectedOption(value);
this.updateEditableLabel();
this.cd.markForCheck();
}
/**
* Callback to invoke on filter reset.
* @group Method
*/
resetFilter() {
this._filterValue = null;
if (this.filterViewChild && this.filterViewChild.nativeElement) {
this.filterViewChild.nativeElement.value = '';
}
this.optionsToDisplay = this.options;
}
updateSelectedOption(val) {
this.selectedOption = this.findOption(val, this.optionsToDisplay);
if (this.autoDisplayFirst && !this.placeholder && !this.selectedOption && this.optionsToDisplay && this.optionsToDisplay.length && !this.editable) {
if (this.group) {
this.selectedOption = this.getOptionGroupChildren(this.optionsToDisplay[0])[0];
}
else {
this.selectedOption = this.optionsToDisplay[0];
}
this.value = this.getOptionValue(this.selectedOption);
this.onModelChange(this.value);
}
this.selectedOptionUpdated = true;
}
registerOnChange(fn) {
this.onModelChange = fn;
}
registerOnTouched(fn) {
this.onModelTouched = fn;
}
setDisabledState(val) {
this.disabled = val;
this.cd.markForCheck();
}
onMouseclick(event) {
if (this.disabled || this.readonly || this.isInputClick(event)) {
return;
}
this.onClick.emit(event);
this.accessibleViewChild?.nativeElement.focus({ preventScroll: true });
if (this.overlayVisible)
this.hide();
else
this.show();
this.cd.detectChanges();
}
isInputClick(event) {
return (DomHandler.hasClass(event.target, 'p-dropdown-clear-icon') ||
event.target.isSameNode(this.accessibleViewChild?.nativeElement) ||
(this.editableInputViewChild && event.target.isSameNode(this.editableInputViewChild.nativeElement)));
}
isEmpty() {
return !this.optionsToDisplay || (this.optionsToDisplay && this.optionsToDisplay.length === 0);
}
onEditableInputFocus(event) {
this.focused = true;
this.hide();
this.onFocus.emit(event);
}
onEditableInputChange(event) {
this.value = event.target.value;
this.updateSelectedOption(this.value);
this.onModelChange(this.value);
this.onChange.emit({
originalEvent: event,
value: this.value
});
}
/**
* Displays the panel.
* @group Method
*/
show() {
this.overlayVisible = true;
this.cd.markForCheck();
}
onOverlayAnimationStart(event) {
if (event.toState === 'visible') {
this.itemsWrapper = DomHandler.findSingle(this.overlayViewChild?.overlayViewChild?.nativeElement, this.virtualScroll ? '.p-scroller' : '.p-dropdown-items-wrapper');
this.virtualScroll && this.scroller?.setContentEl(this.itemsViewChild?.nativeElement);
if (this.options && this.options.length) {
if (this.virtualScroll) {
const selectedIndex = this.selectedOption ? this.findOptionIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1;
if (selectedIndex !== -1) {
this.scroller?.scrollToIndex(selectedIndex);
}
}
else {
let selectedListItem = DomHandler.findSingle(this.itemsWrapper, '.p-dropdown-item.p-highlight');
if (selectedListItem) {
selectedListItem.scrollIntoView({ block: 'nearest', inline: 'center' });
}
}
}
if (this.filterViewChild && this.filterViewChild.nativeElement) {
this.preventModelTouched = true;
if (this.autofocusFilter) {
this.filterViewChild.nativeElement.focus();
}
}
this.onShow.emit(event);
}
if (event.toState === 'void') {
this.itemsWrapper = null;
this.onModelTouched();
this.onHide.emit(event);
}
}
/**
* Hides the panel.
* @group Method
*/
hide() {
this.overlayVisible = false;
if (this.filter && this.resetFilterOnHide) {
this.resetFilter();
}
this.cd.markForCheck();
}
onInputFocus(event) {
this.focused = true;
this.onFocus.emit(event);
}
onInputBlur(event) {
this.focused = false;
this.onBlur.emit(event);
if (!this.preventModelTouched) {
this.onModelTouched();
}
this.preventModelTouched = false;
}
findPrevEnabledOption(index) {
let prevEnabledOption;
if (this.optionsToDisplay && this.optionsToDisplay.length) {
for (let i = index - 1; 0 <= i; i--) {
let option = this.optionsToDisplay[i];
if (this.isOptionDisabled(option)) {
continue;
}
else {
prevEnabledOption = option;
break;
}
}
if (!prevEnabledOption) {
for (let i = this.optionsToDisplay.length - 1; i >= index; i--) {
let option = this.optionsToDisplay[i];
if (this.isOptionDisabled(option)) {
continue;
}
else {
prevEnabledOption = option;
break;
}
}
}
}
return prevEnabledOption;
}
findNextEnabledOption(index) {
let nextEnabledOption;
if (this.optionsToDisplay && this.optionsToDisplay.length) {
for (let i = index + 1; i < this.optionsToDisplay.length; i++) {
let option = this.optionsToDisplay[i];
if (this.isOptionDisabled(option)) {
continue;
}
else {
nextEnabledOption = option;
break;
}
}
if (!nextEnabledOption) {
for (let i = 0; i < index; i++) {
let option = this.optionsToDisplay[i];
if (this.isOptionDisabled(option)) {
continue;
}
else {
nextEnabledOption = option;
break;
}
}
}
}
return nextEnabledOption;
}
onKeydown(event, search) {
if (this.readonly || !this.optionsToDisplay || this.optionsToDisplay.length === null) {
return;
}
switch (event.which) {
//down
case 40:
if (!this.overlayVisible && event.altKey) {
this.show();
}
else {
if (this.group) {
let selectedItemIndex = this.selectedOption ? this.findOptionGroupIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1;
if (selectedItemIndex !== -1) {
let nextItemIndex = selectedItemIndex.itemIndex + 1;
if (nextItemIndex < this.getOptionGroupChildren(this.optionsToDisplay[selectedItemIndex.groupIndex]).length) {
this.selectItem(event, this.getOptionGroupChildren(this.optionsToDisplay[selectedItemIndex.groupIndex])[nextItemIndex]);
this.selectedOptionUpdated = true;
}
else if (this.optionsToDisplay[selectedItemIndex.groupIndex + 1]) {
this.selectItem(event, this.getOptionGroupChildren(this.optionsToDisplay[selectedItemIndex.groupIndex + 1])[0]);
this.selectedOptionUpdated = true;
}
}
else {
if (this.optionsToDisplay && this.optionsToDisplay.length > 0) {
this.selectItem(event, this.getOptionGroupChildren(this.optionsToDisplay[0])[0]);
}
}
}
else {
let selectedItemIndex = this.selectedOption ? this.findOptionIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1;
let nextEnabledOption = this.findNextEnabledOption(selectedItemIndex);
if (nextEnabledOption) {
this.selectItem(event, nextEnabledOption);
this.selectedOptionUpdated = true;
}
}
}
event.preventDefault();
break;
//up
case 38:
if (this.group) {
let selectedItemIndex = this.selectedOption ? this.findOptionGroupIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1;
if (selectedItemIndex !== -1) {
let prevItemIndex = selectedItemIndex.itemIndex - 1;
if (prevItemIndex >= 0) {
this.selectItem(event, this.getOptionGroupChildren(this.optionsToDisplay[selectedItemIndex.groupIndex])[prevItemIndex]);
this.selectedOptionUpdated = true;
}
else if (prevItemIndex < 0) {
let prevGroup = this.optionsToDisplay[selectedItemIndex.groupIndex - 1];
if (prevGroup) {
this.selectItem(event, this.getOptionGroupChildren(prevGroup)[this.getOptionGroupChildren(prevGroup).length - 1]);
this.selectedOptionUpdated = true;
}
}
}
}
else {
let selectedItemIndex = this.selectedOption ? this.findOptionIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1;
let prevEnabledOption = this.findPrevEnabledOption(selectedItemIndex);
if (prevEnabledOption) {
this.selectItem(event, prevEnabledOption);
this.selectedOptionUpdated = true;
}
}
event.preventDefault();
break;
//space
case 32:
if (search) {
if (!this.overlayVisible) {
this.show();
}
else {
this.hide();
}
event.preventDefault();
}
break;
//enter
case 13:
if (this.overlayVisible && (!this.filter || (this.optionsToDisplay && this.optionsToDisplay.length > 0))) {
this.hide();
}
else if (!this.overlayVisible) {
this.show();
}
event.preventDefault();
break;
//escape and tab
case 27:
case 9:
this.hide();
break;
//search item based on keyboard input
default:
if (search && !event.metaKey && event.which !== 17) {
this.search(event);
}
break;
}
}
search(event) {
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
const char = event.key;
this.previousSearchChar = this.currentSearchChar;
this.currentSearchChar = char;
if (this.previousSearchChar === this.currentSearchChar)
this.searchValue = this.currentSearchChar;
else
this.searchValue = this.searchValue ? this.searchValue + char : char;
let newOption;
if (this.group) {
let searchIndex = this.selectedOption ? this.findOptionGroupIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : { groupIndex: 0, itemIndex: 0 };
newOption = this.searchOptionWithinGroup(searchIndex);
}
else {
let searchIndex = this.selectedOption ? this.findOptionIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1;
newOption = this.searchOption(++searchIndex);
}
if (newOption && !this.isOptionDisabled(newOption)) {
this.selectItem(event, newOption);
this.selectedOptionUpdated = true;
}
this.searchTimeout = setTimeout(() => {
this.searchValue = null;
}, 250);
}
searchOption(index) {
let option;
if (this.searchValue) {
option = this.searchOptionInRange(index, this.optionsToDisplay.length);
if (!option) {
option = this.searchOptionInRange(0, index);
}
}
return option;
}
searchOptionInRange(start, end) {
for (let i = start; i < end; i++) {
let opt = this.optionsToDisplay[i];
if (this.getOptionLabel(opt)
.toLocaleLowerCase(this.filterLocale)
.startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale)) &&
!this.isOptionDisabled(opt)) {
return opt;
}
}
return null;
}
searchOptionWithinGroup(index) {
let option;
if (this.searchValue) {
if (this.optionsToDisplay) {
for (let i = index.groupIndex; i < this.optionsToDisplay.length; i++) {
for (let j = index.groupIndex === i ? index.itemIndex + 1 : 0; j < this.getOptionGroupChildren(this.optionsToDisplay[i]).length; j++) {
let opt = this.getOptionGroupChildren(this.optionsToDisplay[i])[j];
if (this.getOptionLabel(opt)
.toLocaleLowerCase(this.filterLocale)
.startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale)) &&
!this.isOptionDisabled(opt)) {
return opt;
}
}
}
if (!option) {
for (let i = 0; i <= index.groupIndex; i++) {
for (let j = 0; j < (index.groupIndex === i ? index.itemIndex : this.getOptionGroupChildren(this.optionsToDisplay[i]).length); j++) {
let opt = this.getOptionGroupChildren(this.optionsToDisplay[i])[j];
if (this.getOptionLabel(opt)
.toLocaleLowerCase(this.filterLocale)
.startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale)) &&
!this.isOptionDisabled(opt)) {
return opt;
}
}
}
}
}
}
return null;
}
findOptionIndex(val, opts) {
let index = -1;
if (opts) {
for (let i = 0; i < opts.length; i++) {
if ((val == null && this.getOptionValue(opts[i]) == null) || ObjectUtils.equals(val, this.getOptionValue(opts[i]), this.dataKey)) {
index = i;
break;
}
}
}
return index;
}
findOptionGroupIndex(val, opts) {
let groupIndex, itemIndex;
if (opts) {
for (let i = 0; i < opts.length; i++) {
groupIndex = i;
itemIndex = this.findOptionIndex(val, this.getOptionGroupChildren(opts[i]));
if (itemIndex !== -1) {
break;
}
}
}
if (itemIndex !== -1) {
return { groupIndex: groupIndex, itemIndex: itemIndex };
}
else {
return -1;
}
}
findOption(val, opts, inGroup) {
if (this.group && !inGroup) {
let opt;
if (opts && opts.length) {
for (let optgroup of opts) {
opt = this.findOption(val, this.getOptionGroupChildren(optgroup), true);
if (opt) {
break;
}
}
}
return opt;
}
else {
let index = this.findOptionIndex(val, opts);
return index != -1 ? opts[index] : null;
}
}
onFilterInputChange(event) {
let inputValue = event.target.value;
if (inputValue && inputValue.length) {
this._filterValue = inputValue;
this.activateFilter();
}
else {
this._filterValue = null;
this.optionsToDisplay = this.options;
}
this.virtualScroll && this.scroller?.scrollToIndex(0);
this.optionsChanged = true;
this.onFilter.emit({ originalEvent: event, filter: this._filterValue });
}
activateFilter() {
let searchFields = (this.filterBy || this.optionLabel || 'label').split(',');
if (this.options && this.options.length) {
if (this.group) {
let filteredGroups = [];
for (let optgroup of this.options) {
let filteredSubOptions = this.filterService.filter(this.getOptionGroupChildren(optgroup), searchFields, this.filterValue, this.filterMatchMode, this.filterLocale);
if (filteredSubOptions && filteredSubOptions.length) {
filteredGroups.push({ ...optgroup, ...{ [this.optionGroupChildren]: filteredSubOptions } });
}
}
this.optionsToDisplay = filteredGroups;
}
else {
this.optionsToDisplay = this.filterService.filter(this.options, searchFields, this.filterValue, this.filterMatchMode, this.filterLocale);
}
this.optionsChanged = true;
}
}
applyFocus() {
if (this.editable)
DomHandler.findSingle(this.el.nativeElement, '.p-dropdown-label.p-inputtext').focus();
else
DomHandler.findSingle(this.el.nativeElement, 'input[readonly]').focus();
}
/**
* Applies focus.
* @group Method
*/
focus() {
this.applyFocus();
}
clear(event) {
this.value = null;
this.onModelChange(this.value);
this.onChange.emit({
originalEvent: event,
value: this.value
});
this.updateSelectedOption(this.value);
this.updateEditableLabel();
this.onClear.emit(event);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: Dropdown, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i3.FilterService }, { token: i3.PrimeNGConfig }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: Dropdown, selector: "p-dropdown", inputs: { scrollHeight: "scrollHeight", filter: "filter", name: "name", style: "style", panelStyle: "panelStyle", styleClass: "styleClass", panelStyleClass: "panelStyleClass", readonly: "readonly", required: "required", editable: "editable", appendTo: "appendTo", tabindex: "tabindex", placeholder: "placeholder", filterPlaceholder: "filterPlaceholder", filterLocale: "filterLocale", inputId: "inputId", selectId: "selectId", dataKey: "dataKey", filterBy: "filterBy", autofocus: "autofocus", resetFilterOnHide: "resetFilterOnHide", dropdownIcon: "dropdownIcon", optionLabel: "optionLabel", optionValue: "optionValue", optionDisabled: "optionDisabled", optionGroupLabel: "optionGroupLabel", optionGroupChildren: "optionGroupChildren", autoDisplayFirst: "autoDisplayFirst", group: "group", showClear: "showClear", emptyFilterMessage: "emptyFilterMessage", emptyMessage: "emptyMessage", lazy: "lazy", virtualScroll: "virtualScroll", virtualScrollItemSize: "virtualScrollItemSize", virtualScrollOptions: "virtualScrollOptions", overlayOptions: "overlayOptions", ariaFilterLabel: "ariaFilterLabel", ariaLabel: "ariaLabel", ariaLabelledBy: "ariaLabelledBy", filterMatchMode: "filterMatchMode", maxlength: "maxlength", tooltip: "tooltip", tooltipPosition: "tooltipPosition", tooltipPositionStyle: "tooltipPositionStyle", tooltipStyleClass: "tooltipStyleClass", autofocusFilter: "autofocusFilter", overlayDirection: "overlayDirection", disabled: "disabled", itemSize: "itemSize", autoZIndex: "autoZIndex", baseZIndex: "baseZIndex", showTransitionOptions: "showTransitionOptions", hideTransitionOptions: "hideTransitionOptions", filterValue: "filterValue", options: "options" }, outputs: { onChange: "onChange", onFilter: "onFilter", onFocus: "onFocus", onBlur: "onBlur", onClick: "onClick", onShow: "onShow", onHide: "onHide", onClear: "onClear", onLazyLoad: "onLazyLoad" }, host: { properties: { "class.p-inputwrapper-filled": "filled", "class.p-inputwrapper-focus": "focused || overlayVisible" }, classAttribute: "p-element p-inputwrapper" }, providers: [DROPDOWN_VALUE_ACCESSOR], queries: [{ propertyName: "templates", predicate: PrimeTemplate }], viewQueries: [{ propertyName: "containerViewChild", first: true, predicate: ["container"], descendants: true }, { propertyName: "filterViewChild", first: true, predicate: ["filter"], descendants: true }, { propertyName: "accessibleViewChild", first: true, predicate: ["in"], descendants: true }, { propertyName: "editableInputViewChild", first: true, predicate: ["editableInput"], descendants: true }, { propertyName: "itemsViewChild", first: true, predicate: ["items"], descendants: true }, { propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true }, { propertyName: "overlayViewChild", first: true, predicate: ["overlay"], descendants: true }], ngImport: i0, template: `
<div
#container
[ngClass]="{ 'p-dropdown p-component': true, 'p-disabled': disabled, 'p-dropdown-open': overlayVisible, 'p-focus': focused, 'p-dropdown-clearable': showClear && !disabled }"
(click)="onMouseclick($event)"
[ngStyle]="style"
[class]="styleClass"
>
<div class="p-hidden-accessible">
<input
#in
[attr.id]="inputId"
type="text"
readonly
(focus)="onInputFocus($event)"
aria-haspopup="listbox"
[attr.placeholder]="placeholder"
aria-haspopup="listbox"
[attr.aria-label]="ariaLabel"
[attr.aria-expanded]="false"
[attr.aria-labelledby]="ariaLabelledBy"
(blur)="onInputBlur($event)"
(keydown)="onKeydown($event, true)"
[disabled]="disabled"
[attr.tabindex]="tabindex"
pAutoFocus
[autofocus]="autofocus"
[attr.aria-activedescendant]="overlayVisible ? labelId : null"
role="combobox"
/>
</div>
<span
[attr.id]="labelId"
[ngClass]="{ 'p-dropdown-label p-inputtext': true, 'p-dropdown-label-empty': label == null || label.length === 0 }"
*ngIf="!editable && label != null"
[pTooltip]="tooltip"
[tooltipPosition]="tooltipPosition"
[positionStyle]="tooltipPositionStyle"
[tooltipStyleClass]="tooltipStyleClass"
>
<ng-container *ngIf="!selectedItemTemplate">{{ label || 'empty' }}</ng-container>
<ng-container *ngTemplateOutlet="selectedItemTemplate; context: { $implicit: selectedOption }"></ng-container>
</span>
<span [ngClass]="{ 'p-dropdown-label p-inputtext p-placeholder': true, 'p-dropdown-label-empty': placeholder == null || placeholder.length === 0 }" *ngIf="!editable && label == null">{{ placeholder || 'empty' }}</span>
<input
#editableInput
type="text"
[attr.maxlength]="maxlength"
class="p-dropdown-label p-inputtext"
*ngIf="editable"
[disabled]="disabled"
[attr.placeholder]="placeholder"
aria-haspopup="listbox"
[attr.aria-expanded]="overlayVisible"
(input)="onEditableInputChange($event)"
(focus)="onEditableInputFocus($event)"
(blur)="onInputBlur($event)"
/>
<ng-container *ngIf="isVisibleClearIcon">
<TimesIcon [styleClass]="'p-dropdown-clear-icon'" (click)="clear($event)" *ngIf="!clearIconTemplate" />
<span class="p-dropdown-clear-icon" (click)="clear($event)" *ngIf="clearIconTemplate">
<ng-template *ngTemplateOutlet="clearIconTemplate"></ng-template>
</span>
</ng-container>
<div class="p-dropdown-trigger" role="button" aria-label="dropdown trigger" aria-haspopup="listbox" [attr.aria-expanded]="overlayVisible">
<ng-container *ngIf="!dropdownIconTemplate">
<span class="p-dropdown-trigger-icon" *ngIf="dropdownIcon" [ngClass]="dropdownIcon"></span>
<ChevronDownIcon *ngIf="!dropdownIcon" [styleClass]="'p-dropdown-trigger-icon'" />
</ng-container>
<span *ngIf="dropdownIconTemplate" class="p-dropdown-trigger-icon">
<ng-template *ngTemplateOutlet="dropdownIconTemplate"></ng-template>
</span>
</div>
<p-overlay
#overlay
[(visible)]="overlayVisible"
[option