UNPKG

primeng

Version:

PrimeNG is an open source UI library for Angular featuring a rich set of 80+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeB

1,410 lines (1,378 loc) 107 kB
import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, forwardRef, EventEmitter, booleanAttribute, numberAttribute, Output, Input, Component, inject, signal, computed, effect, ContentChildren, HostBinding, ViewChild, ViewEncapsulation, ChangeDetectionStrategy, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { deepEquals, isNotEmpty, isEmpty, uuid, findSingle, scrollInView, equals, resolveFieldData, focus, unblockBodyScroll, isPrintableCharacter, findLastIndex, getFirstFocusableElement, getLastFocusableElement, getFocusableElements } from '@primeuix/utils'; import * as i2 from 'primeng/api'; import { TranslationKeys, PrimeTemplate, SharedModule } from 'primeng/api'; import * as i5 from 'primeng/autofocus'; import { AutoFocusModule } from 'primeng/autofocus'; import { BaseComponent } from 'primeng/basecomponent'; import { IconField } from 'primeng/iconfield'; import { BlankIcon, CheckIcon, TimesIcon, ChevronDownIcon, SearchIcon } from 'primeng/icons'; import { InputIcon } from 'primeng/inputicon'; import * as i6 from 'primeng/inputtext'; import { InputTextModule } from 'primeng/inputtext'; import * as i3 from 'primeng/overlay'; import { OverlayModule } from 'primeng/overlay'; import { Ripple } from 'primeng/ripple'; import { Scroller } from 'primeng/scroller'; import * as i4 from 'primeng/tooltip'; import { TooltipModule } from 'primeng/tooltip'; import { BaseStyle } from 'primeng/base'; const theme = ({ dt }) => ` .p-select { display: inline-flex; cursor: pointer; position: relative; user-select: none; background: ${dt('select.background')}; border: 1px solid ${dt('select.border.color')}; transition: background ${dt('select.transition.duration')}, color ${dt('select.transition.duration')}, border-color ${dt('select.transition.duration')}, outline-color ${dt('select.transition.duration')}, box-shadow ${dt('select.transition.duration')}; border-radius: ${dt('select.border.radius')}; outline-color: transparent; box-shadow: ${dt('select.shadow')}; } .p-select:not(.p-disabled):hover { border-color: ${dt('select.hover.border.color')}; } .p-select:not(.p-disabled).p-focus { border-color: ${dt('select.focus.border.color')}; box-shadow: ${dt('select.focus.ring.shadow')}; outline: ${dt('select.focus.ring.width')} ${dt('select.focus.ring.style')} ${dt('select.focus.ring.color')}; outline-offset: ${dt('select.focus.ring.offset')}; } .p-select.p-variant-filled { background: ${dt('select.filled.background')}; } .p-select.p-variant-filled.p-focus { background: ${dt('select.filled.focus.background')}; } .p-select.p-disabled { opacity: 1; background: ${dt('select.disabled.background')}; } .p-select-clear-icon { position: absolute; top: 50%; margin-top: -0.5rem; color: ${dt('select.clear.icon.color')}; right: ${dt('select.dropdown.width')}; } .p-select-dropdown { display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: transparent; color: ${dt('select.dropdown.color')}; width: ${dt('select.dropdown.width')}; border-start-end-radius: ${dt('select.border.radius')}; border-end-end-radius: ${dt('select.border.radius')}; } .p-select-label { display: block; white-space: nowrap; overflow: hidden; flex: 1 1 auto; width: 1%; padding: ${dt('select.padding.y')} ${dt('select.padding.x')}; text-overflow: ellipsis; cursor: pointer; color: ${dt('select.color')}; background: transparent; border: 0 none; outline: 0 none; } .p-select-label.p-placeholder { color: ${dt('select.placeholder.color')}; } .p-select:has(.p-select-clear-icon) .p-select-label { padding-right: calc(1rem + ${dt('select.padding.x')}); } .p-select.p-disabled .p-select-label { color: ${dt('select.disabled.color')}; } .p-select-label-empty { overflow: hidden; opacity: 0; } input.p-select-label { cursor: default; } .p-select .p-select-overlay { min-width: 100%; } .p-select-overlay { position: absolute; top: 0; left: 0; background: ${dt('select.overlay.background')}; color: ${dt('select.overlay.color')}; border: 1px solid ${dt('select.overlay.border.color')}; border-radius: ${dt('select.overlay.border.radius')}; box-shadow: ${dt('select.overlay.shadow')}; } .p-select-header { padding: ${dt('select.list.header.padding')}; } .p-select-filter { width: 100%; } .p-select-list-container { overflow: auto; } .p-select-option-group { cursor: auto; margin: 0; padding: ${dt('select.option.group.padding')}; background: ${dt('select.option.group.background')}; color: ${dt('select.option.group.color')}; font-weight: ${dt('select.option.group.font.weight')}; } .p-select-list { margin: 0; padding: 0; list-style-type: none; padding: ${dt('select.list.padding')}; gap: ${dt('select.list.gap')}; display: flex; flex-direction: column; } .p-select-option { cursor: pointer; font-weight: normal; white-space: nowrap; position: relative; overflow: hidden; display: flex; align-items: center; padding: ${dt('select.option.padding')}; border: 0 none; color: ${dt('select.option.color')}; background: transparent; transition: background ${dt('select.transition.duration')}, color ${dt('select.transition.duration')}, border-color ${dt('select.transition.duration')}, box-shadow ${dt('select.transition.duration')}, outline-color ${dt('select.transition.duration')}; border-radius: ${dt('select.option.border.radius')}; } .p-select-option:not(.p-select-option-selected):not(.p-disabled).p-focus { background: ${dt('select.option.focus.background')}; color: ${dt('select.option.focus.color')}; } .p-select-option.p-select-option-selected { background: ${dt('select.option.selected.background')}; color: ${dt('select.option.selected.color')}; } .p-select-option.p-select-option-selected.p-focus { background: ${dt('select.option.selected.focus.background')}; color: ${dt('select.option.selected.focus.color')}; } .p-select-option-check-icon { position: relative; margin-inline-start: ${dt('select.checkmark.gutter.start')}; margin-inline-end: ${dt('select.checkmark.gutter.end')}; color: ${dt('select.checkmark.color')}; } .p-select-empty-message { padding: ${dt('select.empty.message.padding')}; } .p-select-fluid { display: flex; } /*For PrimeNG*/ .p-dropdown.ng-invalid.ng-dirty, .p-select.ng-invalid.ng-dirty { outline: 1px solid ${dt('select.invalid.border.color')}; outline-offset: 0; } .p-dropdown.ng-invalid.ng-dirty .p-dropdown-label.p-placeholder, .p-select.ng-invalid.ng-dirty .p-select-label.p-placeholder { color: ${dt('select.invalid.placeholder.color')}; } `; const classes = { root: ({ instance }) => [ 'p-dropdown p-select p-component p-inputwrapper', { 'p-disabled': instance.disabled, 'p-variant-filled': instance.variant === 'filled' || instance.config.inputVariant() === 'filled' || instance.config.inputStyle() === 'filled', 'p-focus': instance.focused, 'p-inputwrapper-filled': instance.modelValue() !== undefined && instance.modelValue() !== null && !instance.modelValue().length, 'p-inputwrapper-focus': instance.focused || instance.overlayVisible, 'p-select-open': instance.overlayVisible, 'p-select-fluid': instance.hasFluid, 'p-select-sm p-inputfield-sm': instance.size === 'small', 'p-select-lg p-inputfield-lg': instance.size === 'large' } ], label: ({ instance, props }) => [ 'p-select-label', { 'p-placeholder': !props.editable && instance.label === props.placeholder, 'p-select-label-empty': !props.editable && !instance.$slots['value'] && (instance.label === 'p-emptylabel' || instance.label.length === 0) } ], clearIcon: 'p-select-clear-icon', dropdown: 'p-select-dropdown', loadingicon: 'p-select-loading-icon', dropdownIcon: 'p-select-dropdown-icon', overlay: 'p-select-overlay p-component', header: 'p-select-header', pcFilter: 'p-select-filter', listContainer: 'p-select-list-container', list: 'p-select-list', optionGroup: 'p-select-option-group', optionGroupLabel: 'p-select-option-group-label', option: ({ instance, props, state, option, focusedOption }) => [ 'p-select-option', { 'p-select-option-selected': instance.isSelected(option) && props.highlightOnSelect, 'p-focus': state.focusedOptionIndex === focusedOption, 'p-disabled': instance.isOptionDisabled(option) } ], optionLabel: 'p-select-option-label', optionCheckIcon: 'p-select-option-check-icon', optionBlankIcon: 'p-select-option-blank-icon', emptyMessage: 'p-select-empty-message' }; class DropdownStyle extends BaseStyle { name = 'select'; theme = theme; classes = classes; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DropdownStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DropdownStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DropdownStyle, decorators: [{ type: Injectable }] }); var DropdownClasses; (function (DropdownClasses) { })(DropdownClasses || (DropdownClasses = {})); const DROPDOWN_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => Dropdown), multi: true }; class DropdownItem extends BaseComponent { id; option; selected; focused; label; disabled; visible; itemSize; ariaPosInset; ariaSetSize; template; checkmark; onClick = new EventEmitter(); onMouseEnter = new EventEmitter(); onOptionClick(event) { this.onClick.emit(event); } onOptionMouseEnter(event) { this.onMouseEnter.emit(event); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DropdownItem, deps: null, target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.2.5", type: DropdownItem, isStandalone: false, selector: "p-dropdownItem", inputs: { id: "id", option: "option", selected: ["selected", "selected", booleanAttribute], focused: ["focused", "focused", booleanAttribute], label: "label", disabled: ["disabled", "disabled", booleanAttribute], visible: ["visible", "visible", booleanAttribute], itemSize: ["itemSize", "itemSize", numberAttribute], ariaPosInset: "ariaPosInset", ariaSetSize: "ariaSetSize", template: "template", checkmark: ["checkmark", "checkmark", booleanAttribute] }, outputs: { onClick: "onClick", onMouseEnter: "onMouseEnter" }, usesInheritance: true, ngImport: i0, template: ` <li [id]="id" (click)="onOptionClick($event)" (mouseenter)="onOptionMouseEnter($event)" role="option" pRipple [attr.aria-label]="label" [attr.aria-setsize]="ariaSetSize" [attr.aria-posinset]="ariaPosInset" [attr.aria-selected]="selected" [attr.data-p-focused]="focused" [attr.data-p-highlight]="selected" [attr.data-p-disabled]="disabled" [ngStyle]="{ height: itemSize + 'px' }" [ngClass]="{ 'p-select-option': true, 'p-select-option-selected': selected, 'p-disabled': disabled, 'p-focus': focused }" > <ng-container *ngIf="checkmark"> <CheckIcon *ngIf="selected" [styleClass]="'p-select-option-check-icon'" /> <BlankIcon *ngIf="!selected" [styleClass]="'p-select-option-blank-icon'" /> </ng-container> <span *ngIf="!template">{{ label ?? 'empty' }}</span> <ng-container *ngTemplateOutlet="template; context: { $implicit: option }"></ng-container> </li> `, isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(() => i1.NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgIf), selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgStyle), selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i0.forwardRef(() => Ripple), selector: "[pRipple]" }, { kind: "component", type: i0.forwardRef(() => BlankIcon), selector: "BlankIcon" }, { kind: "component", type: i0.forwardRef(() => CheckIcon), selector: "CheckIcon" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DropdownItem, decorators: [{ type: Component, args: [{ selector: 'p-dropdownItem', standalone: false, template: ` <li [id]="id" (click)="onOptionClick($event)" (mouseenter)="onOptionMouseEnter($event)" role="option" pRipple [attr.aria-label]="label" [attr.aria-setsize]="ariaSetSize" [attr.aria-posinset]="ariaPosInset" [attr.aria-selected]="selected" [attr.data-p-focused]="focused" [attr.data-p-highlight]="selected" [attr.data-p-disabled]="disabled" [ngStyle]="{ height: itemSize + 'px' }" [ngClass]="{ 'p-select-option': true, 'p-select-option-selected': selected, 'p-disabled': disabled, 'p-focus': focused }" > <ng-container *ngIf="checkmark"> <CheckIcon *ngIf="selected" [styleClass]="'p-select-option-check-icon'" /> <BlankIcon *ngIf="!selected" [styleClass]="'p-select-option-blank-icon'" /> </ng-container> <span *ngIf="!template">{{ label ?? 'empty' }}</span> <ng-container *ngTemplateOutlet="template; context: { $implicit: option }"></ng-container> </li> ` }] }], propDecorators: { id: [{ type: Input }], option: [{ type: Input }], selected: [{ type: Input, args: [{ transform: booleanAttribute }] }], focused: [{ type: Input, args: [{ transform: booleanAttribute }] }], label: [{ type: Input }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }], visible: [{ type: Input, args: [{ transform: booleanAttribute }] }], itemSize: [{ type: Input, args: [{ transform: numberAttribute }] }], ariaPosInset: [{ type: Input }], ariaSetSize: [{ type: Input }], template: [{ type: Input }], checkmark: [{ type: Input, args: [{ transform: booleanAttribute }] }], onClick: [{ type: Output }], onMouseEnter: [{ type: Output }] } }); /** * Dropdown also known as Select, is used to choose an item from a collection of options. * @group Components */ class Dropdown extends BaseComponent { zone; filterService; /** * Unique identifier of the component * @group Props */ id; /** * 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 = 0; /** * Default text to display when no option is selected. * @group Props */ set placeholder(val) { this._placeholder.set(val); } get placeholder() { return this._placeholder.asReadonly(); } /** * Icon to display in loading state. * @group Props */ loadingIcon; /** * 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; /** * Specifies the input variant of the component. * @group Props */ variant; /** * Identifier of the accessible input element. * @group Props */ inputId; /** * 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; /** * Fields used when filtering the options, defaults to optionLabel. * @group Props */ filterFields; /** * 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; /** * Whether the selected option will be shown with a check mark. * @group Props */ checkmark = false; /** * Icon class of the dropdown icon. * @group Props */ dropdownIcon; /** * Whether the dropdown is in loading state. * @group Props */ loading = false; /** * 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 = 'label'; /** * 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. * @deprecated since v17.3.0, set initial value by model instead. * @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 aria label 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; /** * Fields used when filtering the options, defaults to optionLabel. * @group Props */ focusOnHover = false; /** * Determines if the option will be selected on focus. * @group Props */ selectOnFocus = false; /** * Whether to focus on the first visible or selected element when the overlay panel is shown. * @group Props */ autoOptionFocus = true; /** * Applies focus to the filter element when the overlay is shown. * @group Props */ autofocusFilter = true; /** * Whether the component should span the full width of its parent. * @group Props */ fluid; /** * 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.log('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.log('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.log('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.log('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.log('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) { setTimeout(() => { this._filterValue.set(val); }); } /** * An array of objects to display as the available options. * @group Props */ get options() { const options = this._options(); return options; } set options(val) { if (!deepEquals(val, this._options())) { this._options.set(val); } } /** * 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(); _componentStyle = inject(DropdownStyle); containerViewChild; filterViewChild; focusInputViewChild; editableInputViewChild; itemsViewChild; scroller; overlayViewChild; firstHiddenFocusableElementOnOverlay; lastHiddenFocusableElementOnOverlay; // @todo to be refactored get hostClass() { const classes = this._componentStyle.classes .root({ instance: this }) .map((cls) => { if (typeof cls === 'string') { return cls; } else { return Object.keys(cls) .filter((key) => cls[key]) .join(' '); } }) .join(' '); return classes + ' ' + this.styleClass; } get hostStyle() { return this.style; } _disabled; itemsWrapper; itemTemplate; groupTemplate; loaderTemplate; selectedItemTemplate; headerTemplate; filterTemplate; footerTemplate; emptyFilterTemplate; emptyTemplate; dropdownIconTemplate; loadingIconTemplate; clearIconTemplate; filterIconTemplate; filterOptions; _options = signal(null); _placeholder = signal(undefined); modelValue = signal(null); value; onModelChange = () => { }; onModelTouched = () => { }; hover; focused; overlayVisible; optionsChanged; panel; selectedOptionUpdated; _filterValue = signal(null); searchValue; searchTimeout; preventModelTouched; focusedOptionIndex = signal(-1); clicked = signal(false); get emptyMessageLabel() { return this.emptyMessage || this.config.getTranslation(TranslationKeys.EMPTY_MESSAGE); } get emptyFilterMessageLabel() { return this.emptyFilterMessage || this.config.getTranslation(TranslationKeys.EMPTY_FILTER_MESSAGE); } get isVisibleClearIcon() { return this.modelValue() != null && this.hasSelectedOption() && this.showClear && !this.disabled; } get listLabel() { return this.config.getTranslation(TranslationKeys.ARIA)['listLabel']; } get hasFluid() { const nativeElement = this.el.nativeElement; const fluidComponent = nativeElement.closest('p-fluid'); return this.fluid || !!fluidComponent; } get inputClass() { const label = this.label(); return { 'p-select-label': true, 'p-placeholder': this.placeholder() && label === this.placeholder(), 'p-select-label-empty': !this.editable && !this.selectedItemTemplate && (label === undefined || label === null || label === 'p-emptylabel' || label.length === 0) }; } get focusedOptionId() { return this.focusedOptionIndex() !== -1 ? `${this.id}_${this.focusedOptionIndex()}` : null; } visibleOptions = computed(() => { const options = this.getAllVisibleAndNonVisibleOptions(); if (this._filterValue()) { const _filterBy = this.filterBy || this.optionLabel; const filteredOptions = !_filterBy && !this.filterFields && !this.optionValue ? this.options.filter((option) => { if (option.label) { return option.label.toString().toLowerCase().indexOf(this._filterValue().toLowerCase().trim()) !== -1; } return option.toString().toLowerCase().indexOf(this._filterValue().toLowerCase().trim()) !== -1; }) : this.filterService.filter(options, this.searchFields(), this._filterValue().trim(), this.filterMatchMode, this.filterLocale); if (this.group) { const optionGroups = this.options || []; const filtered = []; optionGroups.forEach((group) => { const groupChildren = this.getOptionGroupChildren(group); const filteredItems = groupChildren.filter((item) => filteredOptions.includes(item)); if (filteredItems.length > 0) filtered.push({ ...group, [typeof this.optionGroupChildren === 'string' ? this.optionGroupChildren : 'items']: [...filteredItems] }); }); return this.flatOptions(filtered); } return filteredOptions; } return options; }); label = computed(() => { // use getAllVisibleAndNonVisibleOptions verses just visible options // this will find the selected option whether or not the user is currently filtering because the filtered (i.e. visible) options, are a subset of all the options const options = this.getAllVisibleAndNonVisibleOptions(); // use isOptionEqualsModelValue for the use case where the dropdown is initalized with a disabled option const selectedOptionIndex = options.findIndex((option) => this.isOptionValueEqualsModelValue(option)); return selectedOptionIndex !== -1 ? this.getOptionLabel(options[selectedOptionIndex]) : this.placeholder() || 'p-emptylabel'; }); filled = computed(() => { if (typeof this.modelValue() === 'string') return !!this.modelValue(); return this.label() !== 'p-emptylabel' && this.modelValue() !== undefined && this.modelValue() !== null; }); selectedOption; editableInputValue = computed(() => this.getOptionLabel(this.selectedOption) || this.modelValue() || ''); constructor(zone, filterService) { super(); this.zone = zone; this.filterService = filterService; effect(() => { const modelValue = this.modelValue(); const visibleOptions = this.visibleOptions(); if (visibleOptions && isNotEmpty(visibleOptions)) { const selectedOptionIndex = this.findSelectedOptionIndex(); if (selectedOptionIndex !== -1 || modelValue === undefined || (typeof modelValue === 'string' && modelValue.length === 0) || this.isModelValueNotSet() || this.editable) { this.selectedOption = visibleOptions[selectedOptionIndex]; } } if (isEmpty(visibleOptions) && (modelValue === undefined || this.isModelValueNotSet()) && isNotEmpty(this.selectedOption)) { this.selectedOption = null; } if (modelValue !== undefined && this.editable) { this.updateEditableLabel(); } this.cd.markForCheck(); }); } isModelValueNotSet() { return this.modelValue() === null && !this.isOptionValueEqualsModelValue(this.selectedOption); } getAllVisibleAndNonVisibleOptions() { return this.group ? this.flatOptions(this.options) : this.options || []; } ngOnInit() { super.ngOnInit(); console.log('Dropdown component is deprecated as of v18, use Select component instead.'); this.id = this.id || uuid('pn_id_'); this.autoUpdateModel(); if (this.filterBy) { this.filterOptions = { filter: (value) => this.onFilterInputChange(value), reset: () => this.resetFilter() }; } } 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 = findSingle(this.overlayViewChild?.overlayViewChild?.nativeElement, 'li.p-highlight'); if (selectedItem) { scrollInView(this.itemsWrapper, selectedItem); } this.selectedOptionUpdated = false; } } templates; 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 'loadingicon': this.loadingIconTemplate = item.template; break; case 'clearicon': this.clearIconTemplate = item.template; break; case 'filtericon': this.filterIconTemplate = item.template; break; default: this.itemTemplate = item.template; break; } }); } flatOptions(options) { return (options || []).reduce((result, option, index) => { result.push({ optionGroup: option, group: true, index }); const optionGroupChildren = this.getOptionGroupChildren(option); optionGroupChildren && optionGroupChildren.forEach((o) => result.push(o)); return result; }, []); } autoUpdateModel() { if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption()) { this.focusedOptionIndex.set(this.findFirstFocusedOptionIndex()); this.onOptionSelect(null, this.visibleOptions()[this.focusedOptionIndex()], false); } if (this.autoDisplayFirst && (this.modelValue() === null || this.modelValue() === undefined)) { if (!this.placeholder()) { const ind = this.findFirstOptionIndex(); this.onOptionSelect(null, this.visibleOptions()[ind], false, true); } } } onOptionSelect(event, option, isHide = true, preventChange = false) { if (!this.isSelected(option)) { const value = this.getOptionValue(option); this.updateModel(value, event); this.focusedOptionIndex.set(this.findSelectedOptionIndex()); preventChange === false && this.onChange.emit({ originalEvent: event, value: value }); } if (isHide) { this.hide(true); } } onOptionMouseEnter(event, index) { if (this.focusOnHover) { this.changeFocusedOptionIndex(event, index); } } updateModel(value, event) { this.value = value; this.onModelChange(value); this.modelValue.set(value); this.selectedOptionUpdated = true; } writeValue(value) { if (this.filter) { this.resetFilter(); } this.value = value; this.allowModelChange() && this.onModelChange(value); this.modelValue.set(this.value); this.updateEditableLabel(); this.cd.markForCheck(); } allowModelChange() { return this.autoDisplayFirst && !this.placeholder() && (this.modelValue() === undefined || this.modelValue() === null) && !this.editable && this.options && this.options.length; } isSelected(option) { return this.isValidOption(option) && this.isOptionValueEqualsModelValue(option); } isOptionValueEqualsModelValue(option) { return equals(this.modelValue(), this.getOptionValue(option), this.equalityKey()); } ngAfterViewInit() { super.ngAfterViewInit(); if (this.editable) { this.updateEditableLabel(); } this.updatePlaceHolderForFloatingLabel(); } updatePlaceHolderForFloatingLabel() { const parentElement = this.el.nativeElement.parentElement; const isInFloatingLabel = parentElement?.classList.contains('p-float-label'); if (parentElement && isInFloatingLabel && !this.selectedOption) { const label = parentElement.querySelector('label'); if (label) { this._placeholder.set(label.textContent); } } } updateEditableLabel() { if (this.editableInputViewChild) { this.editableInputViewChild.nativeElement.value = this.getOptionLabel(this.selectedOption) || this.modelValue() || ''; } } clearEditableLabel() { if (this.editableInputViewChild) { this.editableInputViewChild.nativeElement.value = ''; } } getOptionIndex(index, scrollerOptions) { return this.virtualScrollerDisabled ? index : scrollerOptions && scrollerOptions.getItemOptions(index)['index']; } getOptionLabel(option) { return this.optionLabel !== undefined && this.optionLabel !== null ? resolveFieldData(option, this.optionLabel) : option && option.label !== undefined ? option.label : option; } getOptionValue(option) { return this.optionValue && this.optionValue !== null ? resolveFieldData(option, this.optionValue) : !this.optionLabel && option && option.value !== undefined ? option.value : option; } isOptionDisabled(option) { if (this.getOptionValue(this.modelValue()) === this.getOptionValue(option) || (this.getOptionLabel(this.modelValue() === this.getOptionLabel(option)) && option.disabled === false)) { return false; } else { return this.optionDisabled ? resolveFieldData(option, this.optionDisabled) : option && option.disabled !== undefined ? option.disabled : false; } } getOptionGroupLabel(optionGroup) { return this.optionGroupLabel !== undefined && this.optionGroupLabel !== null ? resolveFieldData(optionGroup, this.optionGroupLabel) : optionGroup && optionGroup.label !== undefined ? optionGroup.label : optionGroup; } getOptionGroupChildren(optionGroup) { return this.optionGroupChildren !== undefined && this.optionGroupChildren !== null ? resolveFieldData(optionGroup, this.optionGroupChildren) : optionGroup.items; } getAriaPosInset(index) { return ((this.optionGroupLabel ? index - this.visibleOptions() .slice(0, index) .filter((option) => this.isOptionGroup(option)).length : index) + 1); } get ariaSetSize() { return this.visibleOptions().filter((option) => !this.isOptionGroup(option)).length; } /** * Callback to invoke on filter reset. * @group Method */ resetFilter() { this._filterValue.set(null); if (this.filterViewChild && this.filterViewChild.nativeElement) { this.filterViewChild.nativeElement.value = ''; } } registerOnChange(fn) { this.onModelChange = fn; } registerOnTouched(fn) { this.onModelTouched = fn; } setDisabledState(val) { this.disabled = val; this.cd.markForCheck(); } onContainerClick(event) { if (this.disabled || this.readonly || this.loading) { return; } this.focusInputViewChild?.nativeElement.focus({ preventScroll: true }); if (event.target.tagName === 'INPUT' || event.target.getAttribute('data-pc-section') === 'clearicon' || event.target.closest('[data-pc-section="clearicon"]')) { return; } else if (!this.overlayViewChild || !this.overlayViewChild.el.nativeElement.contains(event.target)) { this.overlayVisible ? this.hide(true) : this.show(true); } this.onClick.emit(event); this.clicked.set(true); this.cd.detectChanges(); } isEmpty() { return !this._options() || (this.visibleOptions() && this.visibleOptions().length === 0); } onEditableInput(event) { const value = event.target.value; this.searchValue = ''; const matched = this.searchOptions(event, value); !matched && this.focusedOptionIndex.set(-1); this.onModelChange(value); this.updateModel(value, event); setTimeout(() => { this.onChange.emit({ originalEvent: event, value: value }); }, 1); !this.overlayVisible && isNotEmpty(value) && this.show(); } /** * Displays the panel. * @group Method */ show(isFocus) { this.overlayVisible = true; const focusedOptionIndex = this.focusedOptionIndex() !== -1 ? this.focusedOptionIndex() : this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : this.editable ? -1 : this.findSelectedOptionIndex(); this.focusedOptionIndex.set(focusedOptionIndex); if (isFocus) { focus(this.focusInputViewChild?.nativeElement); } this.cd.markForCheck(); } onOverlayAnimationStart(event) { if (event.toState === 'visible') { this.itemsWrapper = 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.modelValue() ? this.focusedOptionIndex() : -1; if (selectedIndex !== -1) { this.scroller?.scrollToIndex(selectedIndex); } } else { let selectedListItem = findSingle(this.itemsWrapper, '.p-dropdown-item.p-highlight'); if (selectedListItem) { selectedListItem.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } } } if (this.filterViewChild && this.filterViewChild.nativeElement) { this.preventModelTouched = true; if (this.autofocusFilter && !this.editable) { 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(isFocus) { this.overlayVisible = false; this.focusedOptionIndex.set(-1); this.clicked.set(false); this.searchValue = ''; if (this.overlayOptions?.mode === 'modal') { unblockBodyScroll(); } if (this.filter && this.resetFilterOnHide) { this.resetFilter(); } if (isFocus) { if (this.focusInputViewChild) { focus(this.focusInputViewChild?.nativeElement); } if (this.editable && this.editableInputViewChild) { focus(this.editableInputViewChild?.nativeElement); } } this.cd.markForCheck(); } onInputFocus(event) { if (this.disabled) { // For ScreenReaders return; } this.focused = true; const focusedOptionIndex = this.focusedOptionIndex() !== -1 ? this.focusedOptionIndex() : this.overlayVisible && this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1; this.focusedOptionIndex.set(focusedOptionIndex); this.overlayVisible && this.scrollInView(this.focusedOptionIndex()); this.onFocus.emit(event); } onInputBlur(event) { this.focused = false; this.onBlur.emit(event); if (!this.preventModelTouched) { this.onModelTouched(); } this.preventModelTouched = false; } onKeyDown(event, search) { if (this.disabled || this.readonly || this.loading) { return; } switch (event.code) { //down case 'ArrowDown': this.onArrowDownKey(event); break; //up case 'ArrowUp': this.onArrowUpKey(event, this.editable); break; case 'ArrowLeft': case 'ArrowRight': this.onArrowLeftKey(event, this.editable); break; case 'Delete': this.onDeleteKey(event); break; case 'Home': this.onHomeKey(event, this.editable); break; case 'End': this.onEndKey(event, this.editable); break; case 'PageDown': this.onPageDownKey(event); break; case 'PageUp': this.onPageUpKey(event); break; //space case 'Space': this.o