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,499 lines (1,455 loc) 105 kB
import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, forwardRef, EventEmitter, inject, signal, computed, effect, booleanAttribute, numberAttribute, ContentChildren, ContentChild, ViewChild, Output, Input, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { resolveFieldData, isNotEmpty, uuid, findLastIndex, equals, focus, isEmpty, findSingle } from '@primeuix/utils'; import * as i1 from 'primeng/api'; import { TranslationKeys, SharedModule, PrimeTemplate } from 'primeng/api'; import { AutoFocus } from 'primeng/autofocus'; import { BaseComponent } from 'primeng/basecomponent'; import { Chip } from 'primeng/chip'; import { PrimeNG } from 'primeng/config'; import { TimesCircleIcon, SpinnerIcon, TimesIcon, ChevronDownIcon } from 'primeng/icons'; import { InputText } from 'primeng/inputtext'; import { Overlay } from 'primeng/overlay'; import { Ripple } from 'primeng/ripple'; import { Scroller } from 'primeng/scroller'; import { BaseStyle } from 'primeng/base'; const theme = ({ dt }) => ` .p-autocomplete { display: inline-flex; } .p-autocomplete-loader { position: absolute; top: 50%; margin-top: -0.5rem; inset-inline-end: ${dt('autocomplete.padding.x')}; } .p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-loader { inset-inline-end: calc(${dt('autocomplete.dropdown.width')} + ${dt('autocomplete.padding.x')}); } .p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-input { flex: 1 1 auto; width: 1%; } .p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-input, .p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-input-multiple { border-start-end-radius: 0; border-end-end-radius: 0; } .p-autocomplete-dropdown { cursor: pointer; display: inline-flex; user-select: none; align-items: center; justify-content: center; overflow: hidden; position: relative; width: ${dt('autocomplete.dropdown.width')}; border-start-end-radius: ${dt('autocomplete.dropdown.border.radius')}; border-end-end-radius: ${dt('autocomplete.dropdown.border.radius')}; background: ${dt('autocomplete.dropdown.background')}; border: 1px solid ${dt('autocomplete.dropdown.border.color')}; border-inline-start: 0 none; color: ${dt('autocomplete.dropdown.color')}; transition: background ${dt('autocomplete.transition.duration')}, color ${dt('autocomplete.transition.duration')}, border-color ${dt('autocomplete.transition.duration')}, outline-color ${dt('autocomplete.transition.duration')}, box-shadow ${dt('autocomplete.transition.duration')}; outline-color: transparent; } .p-autocomplete-dropdown:not(:disabled):hover { background: ${dt('autocomplete.dropdown.hover.background')}; border-color: ${dt('autocomplete.dropdown.hover.border.color')}; color: ${dt('autocomplete.dropdown.hover.color')}; } .p-autocomplete-dropdown:not(:disabled):active { background: ${dt('autocomplete.dropdown.active.background')}; border-color: ${dt('autocomplete.dropdown.active.border.color')}; color: ${dt('autocomplete.dropdown.active.color')}; } .p-autocomplete-dropdown:focus-visible { box-shadow: ${dt('autocomplete.dropdown.focus.ring.shadow')}; outline: ${dt('autocomplete.dropdown.focus.ring.width')} ${dt('autocomplete.dropdown.focus.ring.style')} ${dt('autocomplete.dropdown.focus.ring.color')}; outline-offset: ${dt('autocomplete.dropdown.focus.ring.offset')}; } .p-autocomplete .p-autocomplete-overlay { min-width: 100%; } .p-autocomplete-overlay { background: ${dt('autocomplete.overlay.background')}; color: ${dt('autocomplete.overlay.color')}; border: 1px solid ${dt('autocomplete.overlay.border.color')}; border-radius: ${dt('autocomplete.overlay.border.radius')}; box-shadow: ${dt('autocomplete.overlay.shadow')}; } .p-autocomplete-list-container { overflow: auto; } .p-autocomplete-list { margin: 0; list-style-type: none; display: flex; flex-direction: column; gap: ${dt('autocomplete.list.gap')}; padding: ${dt('autocomplete.list.padding')}; } .p-autocomplete-option { cursor: pointer; white-space: nowrap; position: relative; overflow: hidden; display: flex; align-items: center; padding: ${dt('autocomplete.option.padding')}; border: 0 none; color: ${dt('autocomplete.option.color')}; background: transparent; transition: background ${dt('autocomplete.transition.duration')}, color ${dt('autocomplete.transition.duration')}, border-color ${dt('autocomplete.transition.duration')}; border-radius: ${dt('autocomplete.option.border.radius')}; } .p-autocomplete-option:not(.p-autocomplete-option-selected):not(.p-disabled).p-focus { background: ${dt('autocomplete.option.focus.background')}; color: ${dt('autocomplete.option.focus.color')}; } .p-autocomplete-option-selected { background: ${dt('autocomplete.option.selected.background')}; color: ${dt('autocomplete.option.selected.color')}; } .p-autocomplete-option-selected.p-focus { background: ${dt('autocomplete.option.selected.focus.background')}; color: ${dt('autocomplete.option.selected.focus.color')}; } .p-autocomplete-option-group { margin: 0; padding: ${dt('autocomplete.option.group.padding')}; color: ${dt('autocomplete.option.group.color')}; background: ${dt('autocomplete.option.group.background')}; font-weight: ${dt('autocomplete.option.group.font.weight')}; } .p-autocomplete-input-multiple { margin: 0; list-style-type: none; cursor: text; overflow: hidden; display: flex; align-items: center; flex-wrap: wrap; padding: calc(${dt('autocomplete.padding.y')} / 2) ${dt('autocomplete.padding.x')}; gap: calc(${dt('autocomplete.padding.y')} / 2); color: ${dt('autocomplete.color')}; background: ${dt('autocomplete.background')}; border: 1px solid ${dt('autocomplete.border.color')}; border-radius: ${dt('autocomplete.border.radius')}; width: 100%; transition: background ${dt('autocomplete.transition.duration')}, color ${dt('autocomplete.transition.duration')}, border-color ${dt('autocomplete.transition.duration')}, outline-color ${dt('autocomplete.transition.duration')}, box-shadow ${dt('autocomplete.transition.duration')}; outline-color: transparent; box-shadow: ${dt('autocomplete.shadow')}; } .p-autocomplete:not(.p-disabled):hover .p-autocomplete-input-multiple { border-color: ${dt('autocomplete.hover.border.color')}; } .p-autocomplete:not(.p-disabled).p-focus .p-autocomplete-input-multiple { border-color: ${dt('autocomplete.focus.border.color')}; box-shadow: ${dt('autocomplete.focus.ring.shadow')}; outline: ${dt('autocomplete.focus.ring.width')} ${dt('autocomplete.focus.ring.style')} ${dt('autocomplete.focus.ring.color')}; outline-offset: ${dt('autocomplete.focus.ring.offset')}; } .p-autocomplete.p-invalid .p-autocomplete-input-multiple { border-color: ${dt('autocomplete.invalid.border.color')}; } .p-variant-filled.p-autocomplete-input-multiple { background: ${dt('autocomplete.filled.background')}; } .p-autocomplete:not(.p-disabled):hover .p-variant-filled.p-autocomplete-input-multiple { background: ${dt('autocomplete.filled.hover.background')}; } .p-autocomplete:not(.p-disabled).p-focus .p-variant-filled.p-autocomplete-input-multiple { background: ${dt('autocomplete.filled.focus.background')}; } .p-autocomplete.p-disabled { opacity: 1; } .p-autocomplete.p-disabled .p-autocomplete-input-multiple { opacity: 1; background: ${dt('autocomplete.disabled.background')}; color: ${dt('autocomplete.disabled.color')}; } .p-autocomplete-chip.p-chip { padding-block-start: calc(${dt('autocomplete.padding.y')} / 2); padding-block-end: calc(${dt('autocomplete.padding.y')} / 2); border-radius: ${dt('autocomplete.chip.border.radius')}; } .p-autocomplete-input-multiple:has(.p-autocomplete-chip) { padding-inline-start: calc(${dt('autocomplete.padding.y')} / 2); padding-inline-end: calc(${dt('autocomplete.padding.y')} / 2); } .p-autocomplete-chip-item.p-focus .p-autocomplete-chip { background: ${dt('autocomplete.chip.focus.background')}; color: ${dt('autocomplete.chip.focus.color')}; } .p-autocomplete-input-chip { flex: 1 1 auto; display: inline-flex; padding-block-start: calc(${dt('autocomplete.padding.y')} / 2); padding-block-end: calc(${dt('autocomplete.padding.y')} / 2); } .p-autocomplete-input-chip input { border: 0 none; outline: 0 none; background: transparent; margin: 0; padding: 0; box-shadow: none; border-radius: 0; width: 100%; font-family: inherit; font-feature-settings: inherit; font-size: 1rem; color: inherit; } .p-autocomplete-input-chip input::placeholder { color: ${dt('autocomplete.placeholder.color')}; } .p-autocomplete-empty-message { padding: ${dt('autocomplete.empty.message.padding')}; } .p-autocomplete-fluid { display: flex; } .p-autocomplete-fluid:has(.p-autocomplete-dropdown) .p-autocomplete-input { width: 1%; } .p-autocomplete:has(.p-inputtext-sm) .p-autocomplete-dropdown { width: ${dt('autocomplete.dropdown.sm.width')}; } .p-autocomplete:has(.p-inputtext-sm) .p-autocomplete-dropdown .p-icon { font-size: ${dt('form.field.sm.font.size')}; width: ${dt('form.field.sm.font.size')}; height: ${dt('form.field.sm.font.size')}; } .p-autocomplete:has(.p-inputtext-lg) .p-autocomplete-dropdown { width: ${dt('autocomplete.dropdown.lg.width')}; } .p-autocomplete:has(.p-inputtext-lg) .p-autocomplete-dropdown .p-icon { font-size: ${dt('form.field.lg.font.size')}; width: ${dt('form.field.lg.font.size')}; height: ${dt('form.field.lg.font.size')}; } .p-autocomplete-clear-icon { position: absolute; top: 50%; margin-top: -0.5rem; cursor: pointer; right: ${dt('autocomplete.padding.x')}; color: ${dt('autocomplete.dropdown.color')}; } .p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-clear-icon { right: calc(${dt('autocomplete.padding.x')} + ${dt('autocomplete.dropdown.width')}); } p-autoComplete.ng-invalid.ng-dirty .p-autocomplete-input, p-autoComplete.ng-invalid.ng-dirty .p-autocomplete-input-multiple, p-auto-complete.ng-invalid.ng-dirty .p-autocomplete-input, p-auto-complete.ng-invalid.ng-dirty .p-autocomplete-input-multiple p-autocomplete.ng-invalid.ng-dirty .p-autocomplete-input, p-autocomplete.ng-invalid.ng-dirty .p-autocomplete-input-multiple { border-color: ${dt('autocomplete.invalid.border.color')}; } p-autoComplete.ng-invalid.ng-dirty .p-autocomplete-input:enabled:focus, p-autoComplete.ng-invalid.ng-dirty:not(.p-disabled).p-focus .p-autocomplete-input-multiple, p-auto-complete.ng-invalid.ng-dirty .p-autocomplete-input:enabled:focus, p-auto-complete.ng-invalid.ng-dirty:not(.p-disabled).p-focus .p-autocomplete-input-multiple, p-autocomplete.ng-invalid.ng-dirty .p-autocomplete-input:enabled:focus, p-autocomplete.ng-invalid.ng-dirty:not(.p-disabled).p-focus .p-autocomplete-input-multiple { border-color: ${dt('autocomplete.focus.border.color')}; } p-autoComplete.ng-invalid.ng-dirty .p-autocomplete-input-chip input::placeholder, p-auto-complete.ng-invalid.ng-dirty .p-autocomplete-input-chip input::placeholder, p-autocomplete.ng-invalid.ng-dirty .p-autocomplete-input-chip input::placeholder { color: ${dt('autocomplete.invalid.placeholder.color')}; } p-autoComplete.ng-invalid.ng-dirty .p-autocomplete-input::placeholder, p-auto-complete.ng-invalid.ng-dirty .p-autocomplete-input::placeholder, p-autocomplete.ng-invalid.ng-dirty .p-autocomplete-input::placeholder { color: ${dt('autocomplete.invalid.placeholder.color')}; }`; const inlineStyles = { root: { position: 'relative' } }; const classes = { root: ({ instance }) => ({ 'p-autocomplete p-component p-inputwrapper': true, 'p-disabled': instance.disabled, 'p-focus': instance.focused, 'p-inputwrapper-filled': instance.filled, 'p-inputwrapper-focus': (instance.focused && !instance.disabled) || instance.autofocus || instance.overlayVisible, 'p-autocomplete-open': instance.overlayVisible, 'p-autocomplete-clearable': instance.showClear && !instance.disabled, // 'p-invalid': instance.invalid, 'p-autocomplete-fluid': instance.hasFluid }), pcInput: 'p-autocomplete-input', inputMultiple: ({ instance }) => ({ 'p-autocomplete-input-multiple': true, 'p-variant-filled': (instance.variant ?? (instance.config.inputStyle() || instance.config.inputVariant())) === 'filled' }), chipItem: ({ instance, i }) => [ 'p-autocomplete-chip-item', { 'p-focus': instance.focusedMultipleOptionIndex === i } ], pcChip: 'p-autocomplete-chip', chipIcon: 'p-autocomplete-chip-icon', inputChip: 'p-autocomplete-input-chip', loader: 'p-autocomplete-loader', dropdown: 'p-autocomplete-dropdown', overlay: 'p-autocomplete-overlay p-component', list: 'p-autocomplete-list', optionGroup: 'p-autocomplete-option-group', option: ({ instance, option, i, getItemOptions }) => ({ 'p-autocomplete-option': true, 'p-autocomplete-option-selected': instance.isSelected(option), 'p-focus': instance.focusedOptionIndex === instance.getOptionIndex(i, getItemOptions), 'p-disabled': instance.isOptionDisabled(option) }), emptyMessage: 'p-autocomplete-empty-message' }; class AutoCompleteStyle extends BaseStyle { name = 'autocomplete'; theme = theme; classes = classes; inlineStyles = inlineStyles; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: AutoCompleteStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: AutoCompleteStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: AutoCompleteStyle, decorators: [{ type: Injectable }] }); /** * * AutoComplete is an input component that provides real-time suggestions while being typed. * * [Live Demo](https://www.primeng.org/autocomplete/) * * @module autocompletestyle * */ var AutoCompleteClasses; (function (AutoCompleteClasses) { /** * Class name of the root element */ AutoCompleteClasses["root"] = "p-autocomplete"; /** * Class name of the input element */ AutoCompleteClasses["pcInput"] = "p-autocomplete-input"; /** * Class name of the input multiple element */ AutoCompleteClasses["inputMultiple"] = "p-autocomplete-input-multiple"; /** * Class name of the chip item element */ AutoCompleteClasses["chipItem"] = "p-autocomplete-chip-item"; /** * Class name of the chip element */ AutoCompleteClasses["pcChip"] = "p-autocomplete-chip"; /** * Class name of the chip icon element */ AutoCompleteClasses["chipIcon"] = "p-autocomplete-chip-icon"; /** * Class name of the input chip element */ AutoCompleteClasses["inputChip"] = "p-autocomplete-input-chip"; /** * Class name of the loader element */ AutoCompleteClasses["loader"] = "p-autocomplete-loader"; /** * Class name of the dropdown element */ AutoCompleteClasses["dropdown"] = "p-autocomplete-dropdown"; /** * Class name of the panel element */ AutoCompleteClasses["panel"] = "p-autocomplete-overlay"; /** * Class name of the list element */ AutoCompleteClasses["list"] = "p-autocomplete-list"; /** * Class name of the option group element */ AutoCompleteClasses["optionGroup"] = "p-autocomplete-option-group"; /** * Class name of the option element */ AutoCompleteClasses["option"] = "p-autocomplete-option"; /** * Class name of the empty message element */ AutoCompleteClasses["emptyMessage"] = "p-autocomplete-empty-message"; })(AutoCompleteClasses || (AutoCompleteClasses = {})); const AUTOCOMPLETE_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutoComplete), multi: true }; /** * AutoComplete is an input component that provides real-time suggestions when being typed. * @group Components */ class AutoComplete extends BaseComponent { overlayService; zone; /** * Minimum number of characters to initiate a search. * @group Props */ minLength = 1; /** * Delay between keystrokes to wait before sending a query. * @group Props */ delay = 300; /** * Inline style of the component. * @group Props */ style; /** * Inline style of the overlay panel element. * @group Props */ panelStyle; /** * Style class of the component. * @group Props */ styleClass; /** * Style class of the overlay panel element. * @group Props */ panelStyleClass; /** * Inline style of the input field. * @group Props */ inputStyle; /** * Identifier of the focus input to match a label defined for the component. * @group Props */ inputId; /** * Inline style of the input field. * @group Props */ inputStyleClass; /** * Hint text for the input field. * @group Props */ placeholder; /** * When present, it specifies that the input cannot be typed. * @group Props */ readonly; /** * When present, it specifies that the component should be disabled. * @group Props */ disabled; /** * Maximum height of the suggestions panel. * @group Props */ scrollHeight = '200px'; /** * 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; /** * Maximum number of character allows in the input field. * @group Props */ maxlength; /** * Name of the input element. * @group Props */ name; /** * When present, it specifies that an input field must be filled out before submitting the form. * @group Props */ required; /** * Defines the size of the component. * @group Props */ size; /** * 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; /** * When enabled, highlights the first item in the list by default. * @group Props */ autoHighlight; /** * When present, autocomplete clears the manual input if it does not match of the suggestions to force only accepting values from the suggestions. * @group Props */ forceSelection; /** * Type of the input, defaults to "text". * @group Props */ type = 'text'; /** * Whether to automatically manage layering. * @group Props */ autoZIndex = true; /** * Base zIndex value to use in layering. * @group Props */ baseZIndex = 0; /** * Defines a string that labels the input for accessibility. * @group Props */ ariaLabel; /** * Defines a string that labels the dropdown button for accessibility. * @group Props */ dropdownAriaLabel; /** * Specifies one or more IDs in the DOM that labels the input field. * @group Props */ ariaLabelledBy; /** * Icon class of the dropdown icon. * @group Props */ dropdownIcon; /** * Ensures uniqueness of selected items on multiple mode. * @group Props */ unique = true; /** * Whether to display options as grouped when nested options are provided. * @group Props */ group; /** * Whether to run a query when input receives focus. * @group Props */ completeOnFocus = false; /** * When enabled, a clear icon is displayed to clear the value. * @group Props */ showClear = false; /** * Field of a suggested object to resolve and display. * @group Props * @deprecated use optionLabel property instead */ field; /** * Displays a button next to the input field when enabled. * @group Props */ dropdown; /** * Whether to show the empty message or not. * @group Props */ showEmptyMessage = true; /** * Specifies the behavior dropdown button. Default "blank" mode sends an empty string and "current" mode sends the input value. * @group Props */ dropdownMode = 'blank'; /** * Specifies if multiple values can be selected. * @group Props */ multiple; /** * Index of the element in tabbing order. * @group Props */ tabindex; /** * A property to uniquely identify a value in options. * @group Props */ dataKey; /** * Text to display when there is no data. Defaults to global value in i18n translation configuration. * @group Props */ emptyMessage; /** * Transition options of the show animation. * @group Props */ showTransitionOptions = '.12s cubic-bezier(0, 0, 0.2, 1)'; /** * Transition options of the hide animation. * @group Props */ hideTransitionOptions = '.1s linear'; /** * When present, it specifies that the component should automatically get focus on load. * @group Props */ autofocus; /** * Used to define a string that autocomplete attribute the current element. * @group Props */ autocomplete = 'off'; /** * Name of the options field of an option group. * @group Props */ optionGroupChildren = 'items'; /** * Name of the label field of an option group. * @group Props */ optionGroupLabel = 'label'; /** * Options for the overlay element. * @group Props */ overlayOptions; /** * An array of suggestions to display. * @group Props */ get suggestions() { return this._suggestions(); } set suggestions(value) { this._suggestions.set(value); this.handleSuggestionsChange(); } /** * Element dimensions of option for virtual scrolling. * @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.'); } /** * Property name or getter function to use as the label of an option. * @group Props */ optionLabel; /** * Property name or getter function to use as the value of an option. * @group Props */ optionValue; /** * Unique identifier of the component. * @group Props */ id; /** * Text to display when the search is active. Defaults to global value in i18n translation configuration. * @group Props * @defaultValue '{0} results are available' */ searchMessage; /** * Text to display when filtering does not return any results. Defaults to global value in i18n translation configuration. * @group Props * @defaultValue 'No selected item' */ emptySelectionMessage; /** * Text to be displayed in hidden accessible field when options are selected. Defaults to global value in i18n translation configuration. * @group Props * @defaultValue '{0} items selected' */ selectionMessage; /** * Whether to focus on the first visible or selected element when the overlay panel is shown. * @group Props */ autoOptionFocus = false; /** * When enabled, the focused option is selected. * @group Props */ selectOnFocus; /** * Locale to use in searching. The default locale is the host environment's current locale. * @group Props */ searchLocale; /** * Property name or getter function to use as the disabled flag of an option, defaults to false when not defined. * @group Props */ optionDisabled; /** * When enabled, the hovered option will be focused. * @group Props */ focusOnHover = true; /** * Whether typeahead is active or not. * @defaultValue true * @group Props */ typeahead = true; /** * Specifies the input variant of the component. * @group Props */ variant; /** * Spans 100% width of the container when enabled. * @group Props */ fluid = false; /** * Callback to invoke to search for suggestions. * @param {AutoCompleteCompleteEvent} event - Custom complete event. * @group Emits */ completeMethod = new EventEmitter(); /** * Callback to invoke when a suggestion is selected. * @param {AutoCompleteSelectEvent} event - custom select event. * @group Emits */ onSelect = new EventEmitter(); /** * Callback to invoke when a selected value is removed. * @param {AutoCompleteUnselectEvent} event - custom unselect event. * @group Emits */ onUnselect = new EventEmitter(); /** * Callback to invoke when the component receives focus. * @param {Event} event - Browser event. * @group Emits */ onFocus = new EventEmitter(); /** * Callback to invoke when the component loses focus. * @param {Event} event - Browser event. * @group Emits */ onBlur = new EventEmitter(); /** * Callback to invoke to when dropdown button is clicked. * @param {AutoCompleteDropdownClickEvent} event - custom dropdown click event. * @group Emits */ onDropdownClick = new EventEmitter(); /** * Callback to invoke when clear button is clicked. * @param {Event} event - Browser event. * @group Emits */ onClear = new EventEmitter(); /** * Callback to invoke on input key up. * @param {KeyboardEvent} event - Keyboard event. * @group Emits */ onKeyUp = new EventEmitter(); /** * Callback to invoke on overlay is shown. * @param {Event} event - Browser event. * @group Emits */ onShow = new EventEmitter(); /** * Callback to invoke on overlay is hidden. * @param {Event} event - Browser event. * @group Emits */ onHide = new EventEmitter(); /** * Callback to invoke on lazy load data. * @param {AutoCompleteLazyLoadEvent} event - Lazy load event. * @group Emits */ onLazyLoad = new EventEmitter(); containerEL; inputEL; multiInputEl; multiContainerEL; dropdownButton; itemsViewChild; scroller; overlayViewChild; _itemSize; itemsWrapper; /** * Custom item template. * @group Templates */ itemTemplate; /** * Custom empty message template. * @group Templates */ emptyTemplate; /** * Custom header template. * @group Templates */ headerTemplate; /** * Custom footer template. * @group Templates */ footerTemplate; /** * Custom selected item template. * @group Templates */ selectedItemTemplate; /** * Custom group item template. * @group Templates */ groupTemplate; /** * Custom loader template. * @group Templates */ loaderTemplate; /** * Custom remove icon template. * @group Templates */ removeIconTemplate; /** * Custom loading icon template. * @group Templates */ loadingIconTemplate; /** * Custom clear icon template. * @group Templates */ clearIconTemplate; /** * Custom dropdown icon template. * @group Templates */ dropdownIconTemplate; primeng = inject(PrimeNG); value; _suggestions = signal(null); onModelChange = () => { }; onModelTouched = () => { }; timeout; overlayVisible; suggestionsUpdated; highlightOption; highlightOptionChanged; focused = false; _filled; get filled() { return this._filled; } set filled(value) { this._filled = value; } loading; scrollHandler; listId; searchTimeout; dirty = false; _itemTemplate; _groupTemplate; _selectedItemTemplate; _headerTemplate; _emptyTemplate; _footerTemplate; _loaderTemplate; _removeIconTemplate; _loadingIconTemplate; _clearIconTemplate; _dropdownIconTemplate; modelValue = signal(null); focusedMultipleOptionIndex = signal(-1); focusedOptionIndex = signal(-1); _componentStyle = inject(AutoCompleteStyle); visibleOptions = computed(() => { return this.group ? this.flatOptions(this._suggestions()) : this._suggestions() || []; }); inputValue = computed(() => { const modelValue = this.modelValue(); const selectedOption = this.optionValueSelected ? (this.suggestions || []).find((item) => resolveFieldData(item, this.optionValue) === modelValue) : modelValue; if (isNotEmpty(modelValue)) { if (typeof modelValue === 'object' || this.optionValueSelected) { const label = this.getOptionLabel(selectedOption); return label != null ? label : modelValue; } else { return modelValue; } } else { return ''; } }); get focusedMultipleOptionId() { return this.focusedMultipleOptionIndex() !== -1 ? `${this.id}_multiple_option_${this.focusedMultipleOptionIndex()}` : null; } get focusedOptionId() { return this.focusedOptionIndex() !== -1 ? `${this.id}_${this.focusedOptionIndex()}` : null; } get rootClass() { return this._componentStyle.classes.root({ instance: this }); } get inputMultipleClass() { return this._componentStyle.classes.inputMultiple({ instance: this }); } get panelClass() { return { 'p-autocomplete-overlay p-component': true, 'p-input-filled': this.config.inputStyle() === 'filled' || this.config.inputVariant() === 'filled', 'p-ripple-disabled': this.config.ripple() === false }; } get inputClass() { return { 'p-autocomplete-input': !this.multiple, 'p-autocomplete-dd-input': this.dropdown }; } get searchResultMessageText() { return isNotEmpty(this.visibleOptions()) && this.overlayVisible ? this.searchMessageText.replaceAll('{0}', this.visibleOptions().length) : this.emptySearchMessageText; } get searchMessageText() { return this.searchMessage || this.config.translation.searchMessage || ''; } get emptySearchMessageText() { return this.emptyMessage || this.config.translation.emptySearchMessage || ''; } get selectionMessageText() { return this.selectionMessage || this.config.translation.selectionMessage || ''; } get emptySelectionMessageText() { return this.emptySelectionMessage || this.config.translation.emptySelectionMessage || ''; } get selectedMessageText() { return this.hasSelectedOption() ? this.selectionMessageText.replaceAll('{0}', this.multiple ? this.modelValue().length : '1') : this.emptySelectionMessageText; } get ariaSetSize() { return this.visibleOptions().filter((option) => !this.isOptionGroup(option)).length; } get listLabel() { return this.config.getTranslation(TranslationKeys.ARIA)['listLabel']; } get virtualScrollerDisabled() { return !this.virtualScroll; } get optionValueSelected() { return typeof this.modelValue() === 'string' && this.optionValue; } chipItemClass(index) { return this._componentStyle.classes.chipItem({ instance: this, i: index }); } optionClass(option, i, scrollerOptions) { return { 'p-autocomplete-option': true, 'p-autocomplete-option-selected': this.isSelected(option), 'p-focus': this.focusedOptionIndex() === this.getOptionIndex(i, scrollerOptions), 'p-disabled': this.isOptionDisabled(option) }; } constructor(overlayService, zone) { super(); this.overlayService = overlayService; this.zone = zone; effect(() => { this.filled = isNotEmpty(this.modelValue()); }); } ngOnInit() { super.ngOnInit(); this.id = this.id || uuid('pn_id_'); this.cd.detectChanges(); } templates; ngAfterContentInit() { this.templates.forEach((item) => { switch (item.getType()) { case 'item': this._itemTemplate = item.template; break; case 'group': this._groupTemplate = item.template; break; case 'selecteditem': this._selectedItemTemplate = item.template; break; case 'selectedItem': this._selectedItemTemplate = item.template; break; case 'header': this._headerTemplate = item.template; break; case 'empty': this._emptyTemplate = item.template; break; case 'footer': this._footerTemplate = item.template; break; case 'loader': this._loaderTemplate = item.template; break; case 'removetokenicon': this._removeIconTemplate = item.template; break; case 'loadingicon': this._loadingIconTemplate = item.template; break; case 'clearicon': this._clearIconTemplate = item.template; break; case 'dropdownicon': this._dropdownIconTemplate = item.template; break; default: this._itemTemplate = item.template; break; } }); } ngAfterViewChecked() { //Use timeouts as since Angular 4.2, AfterViewChecked is broken and not called after panel is updated if (this.suggestionsUpdated && this.overlayViewChild) { this.zone.runOutsideAngular(() => { setTimeout(() => { if (this.overlayViewChild) { this.overlayViewChild.alignOverlay(); } }, 1); this.suggestionsUpdated = false; }); } } handleSuggestionsChange() { if (this.loading) { this._suggestions()?.length > 0 || this.showEmptyMessage || !!this.emptyTemplate ? this.show() : this.hide(); const focusedOptionIndex = this.overlayVisible && this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1; this.focusedOptionIndex.set(focusedOptionIndex); this.suggestionsUpdated = true; this.loading = false; this.cd.markForCheck(); } } 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; }, []); } isOptionGroup(option) { return this.optionGroupLabel && option.optionGroup && option.group; } findFirstOptionIndex() { return this.visibleOptions().findIndex((option) => this.isValidOption(option)); } findLastOptionIndex() { return findLastIndex(this.visibleOptions(), (option) => this.isValidOption(option)); } findFirstFocusedOptionIndex() { const selectedIndex = this.findSelectedOptionIndex(); return selectedIndex < 0 ? this.findFirstOptionIndex() : selectedIndex; } findLastFocusedOptionIndex() { const selectedIndex = this.findSelectedOptionIndex(); return selectedIndex < 0 ? this.findLastOptionIndex() : selectedIndex; } findSelectedOptionIndex() { return this.hasSelectedOption() ? this.visibleOptions().findIndex((option) => this.isValidSelectedOption(option)) : -1; } findNextOptionIndex(index) { const matchedOptionIndex = index < this.visibleOptions().length - 1 ? this.visibleOptions() .slice(index + 1) .findIndex((option) => this.isValidOption(option)) : -1; return matchedOptionIndex > -1 ? matchedOptionIndex + index + 1 : index; } findPrevOptionIndex(index) { const matchedOptionIndex = index > 0 ? findLastIndex(this.visibleOptions().slice(0, index), (option) => this.isValidOption(option)) : -1; return matchedOptionIndex > -1 ? matchedOptionIndex : index; } isValidSelectedOption(option) { return this.isValidOption(option) && this.isSelected(option); } isValidOption(option) { return option && !(this.isOptionDisabled(option) || this.isOptionGroup(option)); } isOptionDisabled(option) { return this.optionDisabled ? resolveFieldData(option, this.optionDisabled) : false; } isSelected(option) { if (this.multiple) { return this.unique ? this.modelValue()?.find((model) => equals(model, this.getOptionValue(option), this.equalityKey())) : false; } return equals(this.modelValue(), this.getOptionValue(option), this.equalityKey()); } isOptionMatched(option, value) { return this.isValidOption(option) && this.getOptionLabel(option).toLocaleLowerCase(this.searchLocale) === value.toLocaleLowerCase(this.searchLocale); } isInputClicked(event) { return event.target === this.inputEL.nativeElement; } isDropdownClicked(event) { return this.dropdownButton?.nativeElement ? event.target === this.dropdownButton.nativeElement || this.dropdownButton.nativeElement.contains(event.target) : false; } equalityKey() { return this.dataKey; // TODO: The 'optionValue' properties can be added. } onContainerClick(event) { if (this.disabled || this.loading || this.isInputClicked(event) || this.isDropdownClicked(event)) { return; } if (!this.overlayViewChild || !this.overlayViewChild.overlayViewChild?.nativeElement.contains(event.target)) { focus(this.inputEL.nativeElement); } } handleDropdownClick(event) { let query = undefined; if (this.overlayVisible) { this.hide(true); } else { focus(this.inputEL.nativeElement); query = this.inputEL.nativeElement.value; if (this.dropdownMode === 'blank') this.search(event, '', 'dropdown'); else if (this.dropdownMode === 'current') this.search(event, query, 'dropdown'); } this.onDropdownClick.emit({ originalEvent: event, query }); } onInput(event) { if (this.typeahead) { if (this.searchTimeout) { clearTimeout(this.searchTimeout); } let query = event.target.value; if (this.maxlength !== null) { query = query.split('').slice(0, this.maxlength).join(''); } if (!this.multiple && !this.forceSelection) { this.updateModel(query); } if (query.length === 0 && !this.multiple) { this.onClear.emit(); setTimeout(() => { this.hide(); }, this.delay / 2); } else { if (query.length >= this.minLength) { this.focusedOptionIndex.set(-1); this.searchTimeout = setTimeout(() => { this.search(event, query, 'input'); }, this.delay); } else { this.hide(); } } } } onInputChange(event) { if (this.forceSelection) { let valid = false; if (this.visibleOptions()) { const matchedValue = this.visibleOptions().find((option) => this.isOptionMatched(option, this.inputEL.nativeElement.value || '')); if (matchedValue !== undefined) { valid = true; !this.isSelected(matchedValue) && this.onOptionSelect(event, matchedValue); } } if (!valid) { this.inputEL.nativeElement.value = ''; !this.multiple && this.updateModel(null); } } } onInputFocus(event) { if (this.disabled) { // For ScreenReaders return; } if (!this.dirty && this.completeOnFocus) { this.search(event, event.target.value, 'focus'); } this.dirty = true; 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); } onMultipleContainerFocus(event) { if (this.disabled) { // For ScreenReaders return; } this.focused = true; } onMultipleContainerBlur(event) { this.focusedMultipleOptionIndex.set(-1); this.focused = false; } onMultipleContainerKeyDown(event) { if (this.disabled) { event.preventDefault(); return; } switch (event.code) { case 'ArrowLeft': this.onArrowLeftKeyOnMultiple(event); break; case 'ArrowRight': this.onArrowRightKeyOnMultiple(event); break; case 'Backspace': this.onBackspaceKeyOnMultiple(event); break; default: break; } } onInputBlur(event) { this.dirty = false; this.focused = false; this.focusedOptionIndex.set(-1); this.onModelTouched(); this.onBlur.emit(event); } onInputPaste(event) { this.onKeyDown(event); } onInputKeyUp(event) { this.onKeyUp.emit(event); } onKeyDown(event) { if (this.disabled) { event.preventDefault(); return; } switch (event.code) { case 'ArrowDown': this.onArrowDownKey(event); break; case 'ArrowUp': this.onArrowUpKey(event); break; case 'ArrowLeft': this.onArrowLeftKey(event); break; case 'ArrowRight': this.onArrowRightKey(event); break; case 'Home': this.onHomeKey(event); break; case 'End': this.onEndKey(event); break; case 'PageDown': this.onPageDownKey(event); break; case 'PageUp': this.onPageUpKey(event); break; case 'Enter': case 'NumpadEnter': this.onEnterKey(event); break; case 'Escape': this.onEscapeKey(event); break; case 'Tab': this.onTabKey(event); break; case 'Backspace': this.onBackspaceKey(event); break; case 'ShiftLeft': case 'ShiftRight': //NOOP break; default: break; } } onArrowDownKey(event) { if (!this.overlayVisible) { return; } const optionIndex = this.focusedOptionIndex() !== -1 ? this.findNextOptionIndex(this.focusedOptionIndex()) : this.findFirstFocusedOptionIndex(); this.changeFocusedOptionIndex(event, optionIndex); event.preventDefault(); event.stopPropagation(); } onArrowUpKey(event) { if (!this.overlayVisible) { return; } if (event.altKey) { if (this.focusedOptionIndex() !== -1) { this.onOptionSelect(event, this.visibleOptions()[this.focusedOptionIndex()]); } this.overlayVisible && this.hide(); event.preventDefault(); } else { const optionIndex = this.focusedOptionIndex() !== -1 ? this.findPrevOptionIndex(this.focusedOptionIndex()) : this.findLastFocusedOptionIndex(); this.changeFocusedOptionIndex(event, optionIndex); event.preventDefault(); event.stopPropagation(); } } get hasFluid() { const nativeElement = this.el.nativeElement; const fluidComponent = nativeElement.closest('p-fluid'); return this.fluid || !!fluidComponent; } onArrowLeftKey(event) { const target = event.currentTarget; this.focusedOptionIndex.set(-1); if (this.multiple) { if (isEmpty(target.value) && this.hasSelectedOption()) { focus(this.multiContainerEL.nativeElement); this.focusedMultipleOptionIndex.set(this.modelValue().length); } else { event.stopPropagation(); // To prevent onArrowLeftKeyOnMultiple method } } } onArrowRightKey(event) { this.focusedOptionIndex.set(-1); this.multiple && event.stopPropagation(); // To prevent onArrowRightKeyOnMultiple method } onHomeKey(event) { const { currentTarget } = event; const len = currentTarget.value.length; currentTarget.setSelectionRange(0, event.shiftKey ? len : 0); this.focusedOptionIndex.set(-1); event.preventDefault(); } onEndKey(event) { const { currentTarget } = event; const len = currentTarget.value.length; currentTarget.setSelectionRange(event.shiftKey ? 0 : len, len); this.focusedOptionIndex.set(-1); event.preventDefault(); } onPageDownKey(event) { this.scrollInView(this.visibleOptions().length - 1); event.preventDefault(); } onPageUpKey(event) { this.scrollInView(0); event.preventDefault(); } onEnterKey(event) { if (!this.typeahead) { if (this.multiple) { this.updateModel([...(this.modelValue() || []), event.target.value]); this.inputEL.nativeElement.value = ''; } } if (!this.overlayVisible) { this.onArrowDownKey(event); } else { if (this.focusedOptionIndex() !== -1) { this.onOptionSelect(event, this.visibleOptions()[this.focusedOptionIndex()]); } this.hide(); } event.preventDefault(); } onEscapeKey(event) { this.overlayVisible && this.hide(true); event.preventDefault(); } onTabKey(event) { if (this.focusedOptionIndex() !== -1) { this.onOptionSelect(event, this.visibleOptions()[this.focusedOptionIndex()]); } this.overlayVisible && this.hide(); } onBackspaceKey(event) { if (this.multiple) { if (isNotEmpty(this.modelValue()) && !this.inputEL.nativeElement.value) { const removedValue = this.modelValue()[this.modelValue().length - 1]; const newValue = this.modelValue().slice(0, -1); this.updateModel(newValue); this.onUnselect.emit({ originalEvent: event, value: removedValue }); } event.stopPropagation(); // To prevent onBackspaceKeyOnMultiple method } if (!this.multiple && this.showClear && this.findSelectedOptionIndex() != -1) { this.clear(); } } onArrowLeftKeyOnMultiple(event) { const optionIndex = this.focusedMultipleOptionIndex() < 1 ? 0 : this.focusedMultipleOptionIndex() - 1; this.focusedMultipleOptionIndex.set(optionIndex); } onArrowRightKeyOnMultiple(event) { let optionIndex