import * as i2 from '@angular/common'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { forwardRef, EventEmitter, signal, computed, effect, numberAttribute, booleanAttribute, Component, ChangeDetectionStrategy, ViewEncapsulation, Inject, Input, Output, ViewChild, ContentChildren, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import * as i1 from 'primeng/api'; import { TranslationKeys, PrimeTemplate, SharedModule } from 'primeng/api'; import * as i7 from 'primeng/autofocus'; import { AutoFocusModule } from 'primeng/autofocus'; import * as i4 from 'primeng/button'; import { ButtonModule } from 'primeng/button'; import { DomHandler } from 'primeng/dom'; import { InputTextModule } from 'primeng/inputtext'; import * as i3 from 'primeng/overlay'; import { OverlayModule } from 'primeng/overlay'; import * as i5 from 'primeng/ripple'; import { RippleModule } from 'primeng/ripple'; import * as i6 from 'primeng/scroller'; import { ScrollerModule } from 'primeng/scroller'; import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; import { TimesCircleIcon } from 'primeng/icons/timescircle'; import { SpinnerIcon } from 'primeng/icons/spinner'; import { TimesIcon } from 'primeng/icons/times'; import { ChevronDownIcon } from 'primeng/icons/chevrondown'; 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 { document; el; renderer; cd; config; 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; /** * Size of the input field. * @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.warn('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; /** * Specifies the input variant of the component. * @group Props */ variant = 'outlined'; /** * 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; templates; _itemSize; itemsWrapper; itemTemplate; emptyTemplate; headerTemplate; footerTemplate; selectedItemTemplate; groupTemplate; loaderTemplate; removeIconTemplate; loadingIconTemplate; clearIconTemplate; dropdownIconTemplate; 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; modelValue = signal(null); focusedMultipleOptionIndex = signal(-1); focusedOptionIndex = signal(-1); 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) => ObjectUtils.resolveFieldData(item, this.optionValue) === modelValue) : modelValue; if (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 containerClass() { return { 'p-autocomplete p-component p-inputwrapper': true, 'p-disabled': this.disabled, 'p-focus': this.focused, 'p-autocomplete-dd': this.dropdown, 'p-autocomplete-multiple': this.multiple, 'p-inputwrapper-focus': this.focused, 'p-overlay-open': this.overlayVisible }; } get multiContainerClass() { return { 'p-autocomplete-multiple-container p-component p-inputtext': true, 'p-variant-filled': this.variant === 'filled' || this.config.inputStyle() === 'filled' }; } get panelClass() { return { 'p-autocomplete-panel p-component': true, 'p-input-filled': this.config.inputStyle() === 'filled', 'p-ripple-disabled': this.config.ripple === false }; } get inputClass() { return { 'p-autocomplete-input p-inputtext p-component': !this.multiple, 'p-autocomplete-dd-input': this.dropdown, 'p-variant-filled': this.variant === 'filled' || this.config.inputStyle() === 'filled' }; } get searchResultMessageText() { return ObjectUtils.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; } constructor(document, el, renderer, cd, config, overlayService, zone) { this.document = document; this.el = el; this.renderer = renderer; this.cd = cd; this.config = config; this.overlayService = overlayService; this.zone = zone; effect(() => { this.filled = ObjectUtils.isNotEmpty(this.modelValue()); }); } ngOnInit() { this.id = this.id || UniqueComponentId(); this.cd.detectChanges(); } 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; }); } } 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 '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; } }); } handleSuggestionsChange() { if (this.loading) { this._suggestions() ? this.show() : !!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 ObjectUtils.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 ? ObjectUtils.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 ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false; } isSelected(option) { if (this.multiple) { return this.unique ? this.modelValue()?.find((model) => ObjectUtils.equals(model, this.getOptionValue(option), this.equalityKey())) : false; } return ObjectUtils.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)) { DomHandler.focus(this.inputEL.nativeElement); } } handleDropdownClick(event) { let query = undefined; if (this.overlayVisible) { this.hide(true); } else { DomHandler.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.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(); } } onArrowLeftKey(event) { const target = event.currentTarget; this.focusedOptionIndex.set(-1); if (this.multiple) { if (ObjectUtils.isEmpty(target.value) && this.hasSelectedOption()) { DomHandler.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.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 (ObjectUtils.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); DomHandler.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) .map((option) => this.getOptionValue(option)); this.updateModel(value); this.onUnselect.emit({ originalEvent: event, value: removedOption }); DomHandler.focus(this.inputEL.nativeElement); } updateModel(value) { this.value = value; this.modelValue.set(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 = DomHandler.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 && DomHandler.focus(this.inputEL.nativeElement); if (isFocus) { DomHandler.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 && DomHandler.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(); } writeValue(value) { this.value = value; this.modelValue.set(value); this.updateInputValue(); this.cd.markForCheck(); } hasSelectedOption() { return ObjectUtils.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.field || this.optionLabel ? ObjectUtils.resolveFieldData(option, this.field || this.optionLabel) : option && option.label != undefined ? option.label : option; } getOptionValue(option) { return this.optionValue ? ObjectUtils.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 ? ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel) : optionGroup && optionGroup.label != undefined ? optionGroup.label : optionGroup; } getOptionGroupChildren(optionGroup) { return this.optionGroupChildren ? ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren) : optionGroup.items; } registerOnChange(fn) { this.onModelChange = fn; } registerOnTouched(fn) { this.onModelTouched = fn; } setDisabledState(val) { this.disabled = val; this.cd.markForCheck(); } onOverlayAnimationStart(event) { if (event.toState === 'visible') { this.itemsWrapper = DomHandler.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 = DomHandler.findSingle(this.itemsWrapper, '.p-autocomplete-item.p-highlight'); if (selectedListItem) { selectedListItem.scrollIntoView({ block: 'nearest', inline: 'center' }); } } } } } ngOnDestroy() { if (this.scrollHandler) { this.scrollHandler.destroy(); this.scrollHandler = null; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: AutoComplete, deps: [{ token: DOCUMENT }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i1.PrimeNGConfig }, { token: i1.OverlayService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "17.3.7", type: AutoComplete, selector: "p-autoComplete", inputs: { minLength: ["minLength", "minLength", numberAttribute], delay: ["delay", "delay", numberAttribute], style: "style", panelStyle: "panelStyle", styleClass: "styleClass", panelStyleClass: "panelStyleClass", inputStyle: "inputStyle", inputId: "inputId", inputStyleClass: "inputStyleClass", placeholder: "placeholder", readonly: ["readonly", "readonly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], scrollHeight: "scrollHeight", lazy: ["lazy", "lazy", booleanAttribute], virtualScroll: ["virtualScroll", "virtualScroll", booleanAttribute], virtualScrollItemSize: ["virtualScrollItemSize", "virtualScrollItemSize", numberAttribute], virtualScrollOptions: "virtualScrollOptions", maxlength: ["maxlength", "maxlength", (value) => numberAttribute(value, null)], name: "name", required: ["required", "required", booleanAttribute], size: ["size", "size", numberAttribute], appendTo: "appendTo", autoHighlight: ["autoHighlight", "autoHighlight", booleanAttribute], forceSelection: ["forceSelection", "forceSelection", booleanAttribute], type: "type", autoZIndex: ["autoZIndex", "autoZIndex", booleanAttribute], baseZIndex: ["baseZIndex", "baseZIndex", numberAttribute], ariaLabel: "ariaLabel", dropdownAriaLabel: "dropdownAriaLabel", ariaLabelledBy: "ariaLabelledBy", dropdownIcon: "dropdownIcon", unique: ["unique", "unique", booleanAttribute], group: ["group", "group", booleanAttribute], completeOnFocus: ["completeOnFocus", "completeOnFocus", booleanAttribute], showClear: ["showClear", "showClear", booleanAttribute], field: "field", dropdown: ["dropdown", "dropdown", booleanAttribute], showEmptyMessage: ["showEmptyMessage", "showEmptyMessage", booleanAttribute], dropdownMode: "dropdownMode", multiple: ["multiple", "multiple", booleanAttribute], tabindex: ["tabindex", "tabindex", numberAttribute], dataKey: "dataKey", emptyMessage: "emptyMessage", showTransitionOptions: "showTransitionOptions", hideTransitionOptions: "hideTransitionOptions", autofocus: ["autofocus", "autofocus", booleanAttribute], autocomplete: "autocomplete", optionGroupChildren: "optionGroupChildren", optionGroupLabel: "optionGroupLabel", overlayOptions: "overlayOptions", suggestions: "suggestions", itemSize: "itemSize", optionLabel: "optionLabel", optionValue: "optionValue", id: "id", searchMessage: "searchMessage", emptySelectionMessage: "emptySelectionMessage", selectionMessage: "selectionMessage", autoOptionFocus: ["autoOptionFocus", "autoOptionFocus", booleanAttribute], selectOnFocus: ["selectOnFocus", "selectOnFocus", booleanAttribute], searchLocale: ["searchLocale", "searchLocale", booleanAttribute], optionDisabled: "optionDisabled", focusOnHover: ["focusOnHover", "focusOnHover", booleanAttribute], variant: "variant" }, outputs: { completeMethod: "completeMethod", onSelect: "onSelect", onUnselect: "onUnselect", onFocus: "onFocus", onBlur: "onBlur", onDropdownClick: "onDropdownClick", onClear: "onClear", onKeyUp: "onKeyUp", onShow: "onShow", onHide: "onHide", onLazyLoad: "onLazyLoad" }, host: { properties: { "class.p-inputwrapper-filled": "filled", "class.p-inputwrapper-focus": "((focused && !disabled) || autofocus) || overlayVisible", "class.p-autocomplete-clearable": "showClear && !disabled" }, classAttribute: "p-element p-inputwrapper" }, providers: [AUTOCOMPLETE_VALUE_ACCESSOR], queries: [{ propertyName: "templates", predicate: PrimeTemplate }], viewQueries: [{ propertyName: "containerEL", first: true, predicate: ["container"], descendants: true }, { propertyName: "inputEL", first: true, predicate: ["focusInput"], descendants: true }, { propertyName: "multiInputEl", first: true, predicate: ["multiIn"], descendants: true }, { propertyName: "multiContainerEL", first: true, predicate: ["multiContainer"], descendants: true }, { propertyName: "dropdownButton", first: true, predicate: ["ddBtn"], descendants: true }, { propertyName: "itemsViewChild", first: true, predicate: ["items"], descendants: true }, { propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true }, { propertyName: "overlayViewChild", first: true, predicate: ["overlay"], descendants: true }], ngImport: i0, template: `