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,352 lines (1,346 loc) 98.1 kB
import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, forwardRef, input, EventEmitter, inject, signal, computed, booleanAttribute, numberAttribute, ContentChildren, HostListener, 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 { BaseInput } from 'primeng/baseinput'; import { Chip } from 'primeng/chip'; import { PrimeNG } from 'primeng/config'; import { TimesCircleIcon, SpinnerIcon, ChevronDownIcon, TimesIcon } from 'primeng/icons'; import { InputText } from 'primeng/inputtext'; import { Overlay } from 'primeng/overlay'; import { Ripple } from 'primeng/ripple'; import { Scroller } from 'primeng/scroller'; import { style } from '@primeuix/styles/autocomplete'; import { BaseStyle } from 'primeng/base'; const theme = /*css*/ ` ${style} /* For PrimeNG */ 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', { 'p-invalid': instance.invalid(), '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-autocomplete-fluid': instance.hasFluid } ], pcInputText: 'p-autocomplete-input', inputMultiple: ({ instance }) => [ 'p-autocomplete-input-multiple', { 'p-disabled': instance.$disabled(), 'p-variant-filled': instance.$variant() === '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: ({ instance }) => ['p-autocomplete-overlay p-component-overlay p-component', { 'p-input-filled': instance.$variant() === 'filled', 'p-ripple-disabled': instance.config.ripple() === false }], listContainer: 'p-autocomplete-list-container', list: 'p-autocomplete-list', optionGroup: 'p-autocomplete-option-group', option: ({ instance, option, i, scrollerOptions }) => ({ 'p-autocomplete-option': true, 'p-autocomplete-option-selected': instance.isSelected(option), 'p-focus': instance.focusedOptionIndex() === instance.getOptionIndex(i, scrollerOptions), 'p-disabled': instance.isOptionDisabled(option) }), emptyMessage: 'p-autocomplete-empty-message', clearIcon: 'p-autocomplete-clear-icon' }; class AutoCompleteStyle extends BaseStyle { name = 'autocomplete'; theme = theme; classes = classes; inlineStyles = inlineStyles; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: AutoCompleteStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: AutoCompleteStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", 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["pcInputText"] = "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"; /** * Class name of the clear icon */ AutoCompleteClasses["clearIcon"] = "p-autocomplete-clear-icon"; })(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 BaseInput { overlayService; zone; /** * Minimum number of characters to initiate a search. * @deprecated since v20.0.0, use `minQueryLength` instead. * @group Props */ minLength = 1; /** * Minimum number of characters to initiate a search. * @group Props */ minQueryLength; /** * Delay between keystrokes to wait before sending a query. * @group Props */ delay = 300; /** * Inline style of the overlay panel element. * @group Props */ panelStyle; /** * Style class of the component. * @deprecated since v20.0.0, use `class` instead. * @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; /** * 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; /** * 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; /** * 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(); } /** * 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; /** * 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). * @defaultValue 'self' * @group Props */ appendTo = input(undefined, ...(ngDevMode ? [{ debugName: "appendTo" }] : [])); /** * 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(); inputEL; multiInputEl; multiContainerEL; dropdownButton; itemsViewChild; scroller; overlayViewChild; 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; onHostClick(event) { this.onContainerClick(event); } primeng = inject(PrimeNG); value; _suggestions = signal(null, ...(ngDevMode ? [{ debugName: "_suggestions" }] : [])); timeout; overlayVisible; suggestionsUpdated; highlightOption; highlightOptionChanged; focused = false; loading; scrollHandler; listId; searchTimeout; dirty = false; _itemTemplate; _groupTemplate; _selectedItemTemplate; _headerTemplate; _emptyTemplate; _footerTemplate; _loaderTemplate; _removeIconTemplate; _loadingIconTemplate; _clearIconTemplate; _dropdownIconTemplate; focusedMultipleOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedMultipleOptionIndex" }] : [])); focusedOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedOptionIndex" }] : [])); _componentStyle = inject(AutoCompleteStyle); $appendTo = computed(() => this.appendTo() || this.config.overlayAppendTo(), ...(ngDevMode ? [{ debugName: "$appendTo" }] : [])); visibleOptions = computed(() => { return this.group ? this.flatOptions(this._suggestions()) : this._suggestions() || []; }, ...(ngDevMode ? [{ debugName: "visibleOptions" }] : [])); 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 ''; } }, ...(ngDevMode ? [{ debugName: "inputValue" }] : [])); 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 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 }); } constructor(overlayService, zone) { super(); this.overlayService = overlayService; this.zone = zone; } 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) { const _minLength = this.minQueryLength || this.minLength; 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 >= _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(); } } 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) { return; } 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 = this.focusedMultipleOptionIndex(); optionIndex++; this.focusedMultipleOptionIndex.set(optionIndex); if (optionIndex > this.modelValue().length - 1) { this.focusedMultipleOptionIndex.set(-1); focus(this.inputEL.nativeElement); } } onBackspaceKeyOnMultiple(event) { if (this.focusedMultipleOptionIndex() !== -1) { this.removeOption(event, this.focusedMultipleOptionIndex()); } } onOptionSelect(event, option, isHide = true) { const value = this.getOptionValue(option); if (this.multiple) { this.inputEL.nativeElement.value = ''; if (!this.isSelected(option)) { this.updateModel([...(this.modelValue() || []), value]); } } else { this.updateModel(value); } this.onSelect.emit({ originalEvent: event, value: option }); isHide && this.hide(true); } onOptionMouseEnter(event, index) { if (this.focusOnHover) { this.changeFocusedOptionIndex(event, index); } } search(event, query, source) { //allow empty string but not undefined or null if (query === undefined || query === null) { return; } //do not search blank values on input change if (source === 'input' && query.trim().length === 0) { return; } this.loading = true; this.completeMethod.emit({ originalEvent: event, query }); } removeOption(event, index) { event.stopPropagation(); const removedOption = this.modelValue()[index]; const value = this.modelValue().filter((_, i) => i !== index); this.updateModel(value); this.onUnselect.emit({ originalEvent: event, value: removedOption }); focus(this.inputEL.nativeElement); } updateModel(value) { this.value = value; this.writeModelValue(value); this.onModelChange(value); this.updateInputValue(); this.cd.markForCheck(); } updateInputValue() { if (this.inputEL && this.inputEL.nativeElement) { if (!this.multiple) { this.inputEL.nativeElement.value = this.inputValue(); } else { this.inputEL.nativeElement.value = ''; } } } autoUpdateModel() { if ((this.selectOnFocus || this.autoHighlight) && this.autoOptionFocus && !this.hasSelectedOption()) { const focusedOptionIndex = this.findFirstFocusedOptionIndex(); this.focusedOptionIndex.set(focusedOptionIndex); this.onOptionSelect(null, this.visibleOptions()[this.focusedOptionIndex()], false); } } scrollInView(index = -1) { const id = index !== -1 ? `${this.id}_${index}` : this.focusedOptionId; if (this.itemsViewChild && this.itemsViewChild.nativeElement) { const element = findSingle(this.itemsViewChild.nativeElement, `li[id="${id}"]`); if (element) { element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } else if (!this.virtualScrollerDisabled) { setTimeout(() => { this.virtualScroll && this.scroller?.scrollToIndex(index !== -1 ? index : this.focusedOptionIndex()); }, 0); } } } changeFocusedOptionIndex(event, index) { if (this.focusedOptionIndex() !== index) { this.focusedOptionIndex.set(index); this.scrollInView(); if (this.selectOnFocus) { this.onOptionSelect(event, this.visibleOptions()[index], false); } } } show(isFocus = false) { this.dirty = true; this.overlayVisible = true; const focusedOptionIndex = this.focusedOptionIndex() !== -1 ? this.focusedOptionIndex() : this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1; this.focusedOptionIndex.set(focusedOptionIndex); isFocus && focus(this.inputEL.nativeElement); if (isFocus) { focus(this.inputEL.nativeElement); } this.onShow.emit(); this.cd.markForCheck(); } hide(isFocus = false) { const _hide = () => { this.dirty = isFocus; this.overlayVisible = false; this.focusedOptionIndex.set(-1); isFocus && focus(this.inputEL.nativeElement); this.onHide.emit(); this.cd.markForCheck(); }; setTimeout(() => { _hide(); }, 0); // For ScreenReaders } clear() { this.updateModel(null); this.inputEL.nativeElement.value = ''; this.onClear.emit(); } hasSelectedOption() { return isNotEmpty(this.modelValue()); } getAriaPosInset(index) { return ((this.optionGroupLabel ? index - this.visibleOptions() .slice(0, index) .filter((option) => this.isOptionGroup(option)).length : index) + 1); } getOptionLabel(option) { return this.optionLabel ? resolveFieldData(option, this.optionLabel) : option && option.label != undefined ? option.label : option; } getOptionValue(option) { return this.optionValue ? resolveFieldData(option, this.optionValue) : option && option.value != undefined ? option.value : option; } getOptionIndex(index, scrollerOptions) { return this.virtualScrollerDisabled ? index : scrollerOptions && scrollerOptions.getItemOptions(index)['index']; } getOptionGroupLabel(optionGroup) { return this.optionGroupLabel ? resolveFieldData(optionGroup, this.optionGroupLabel) : optionGroup && optionGroup.label != undefined ? optionGroup.label : optionGroup; } getOptionGroupChildren(optionGroup) { return this.optionGroupChildren ? resolveFieldData(optionGroup, this.optionGroupChildren) : optionGroup.items; } onOverlayAnimationStart(event) { if (event.toState === 'visible') { this.itemsWrapper = findSingle(this.overlayViewChild.overlayViewChild?.nativeElement, this.virtualScroll ? '.p-scroller' : '.p-autocomplete-panel'); if (this.virtualScroll) { this.scroller?.setContentEl(this.itemsViewChild?.nativeElement); this.scroller.viewInit(); } if (this.visibleOptions() && this.visibleOptions().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-autocomplete-item.p-highlight'); if (selectedListItem) { selectedListItem.scrollIntoView({ block: 'nearest', inline: 'center' }); } } } } } /** * @override * * @see {@link BaseEditableHolder.writeControlValue} * Writes the value to the control. */ writeControlValue(value, setModelValue) { this.value = value; setModelValue(value); this.updateInputValue(); this.cd.markForCheck(); } ngOnDestroy() { if (this.scrollHandler) { this.scrollHandler.destroy(); this.scrollHandler = null; } super.ngOnDestroy(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: AutoComplete, deps: [{ token: i1.OverlayService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.3", type: AutoComplete, isStandalone: true, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: { minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: false, isRequired: false, transformFunction: numberAttribute }, minQueryLength: { classPropertyName: "minQueryLength", publicName: "minQueryLength", isSignal: false, isRequired: false, transformFunction: numberAttribute }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: false, isRequired: false, transformFunction: numberAttribute }, panelStyle: { classPropertyName: "panelStyle", publicName: "panelStyle", isSignal: false, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: false, isRequired: false, transformFunction: null }, panelStyleClass: { classPropertyName: "panelStyleClass", publicName: "panelStyleClass", isSignal: false, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: false, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: false, isRequired: false, transformFunction: null }, inputStyleClass: { classPropertyName: "inputStyleClass", publicName: "inputStyleClass", isSignal: false, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: false, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: false, isRequired: false, transformFunction: booleanAttribute }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: false, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: false, isRequired: false, transformFunction: booleanAttribute }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: false, isRequired: false, transformFunction: booleanAttribute }, virtualScrollItemSize: { classPropertyName: "virtualScrollItemSize", publicName: "virtualScrollItemSize", isSignal: false, isRequired: false, transformFunction: numberAttribute }, virtualScrollOptions: { classPropertyName: "virtualScrollOptions", publicName: "virtualScrollOptions", isSignal: false, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: false, isRequired: false, transformFunction: booleanAttribute }, forceSelection: { classPropertyNam