From 124d442c7c9107547debdbcbcfac0e86971b283e Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 25 Feb 2022 10:37:48 +0100 Subject: [PATCH] feat(material/autocomplete): add the ability to auto-select the active option while navigating Adds the `autoSelectActiveOption` input to `mat-autocomplete` which allows the consumer to opt into the behavior where the autocomplete will assign the active option value as the user is navigating through the list. The value is only propagated to the model once the panel is closed. There are a couple of UX differences when the new option is enabled: 1. If the user presses escape while there's a pending auto-selected option, the value is reverted to the last text they typed before they started navigating. 2. If the user clicks away, tabs away or presses enter while there's a pending option, it will be selected. The aforementioned UX differences are based on the Google search autocomplete and one of the examples from the W3C here: https://www.w3.org/TR/wai-aria-practices-1.1/examples/combobox/aria1.1pattern/listbox-combo.html --- .../mdc-autocomplete/autocomplete.spec.ts | 211 ++++++++++++++++++ .../autocomplete/autocomplete-trigger.ts | 102 ++++++--- .../autocomplete/autocomplete.spec.ts | 211 ++++++++++++++++++ src/material/autocomplete/autocomplete.ts | 16 +- .../public_api_guard/material/autocomplete.md | 7 +- 5 files changed, 509 insertions(+), 38 deletions(-) diff --git a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts index 3f111ab66994..3f344cd215e5 100644 --- a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts +++ b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts @@ -2706,6 +2706,217 @@ describe('MDC-based MatAutocomplete', () => { })); }); + describe('automatically selecting the active option', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = createComponent(SimpleAutocomplete); + fixture.detectChanges(); + fixture.componentInstance.trigger.autocomplete.autoSelectActiveOption = true; + }); + + it( + 'should update the input value as the user is navigating, without changing the model ' + + 'value or closing the panel', + fakeAsync(() => { + const {trigger, stateCtrl, closedSpy} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('California'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + }), + ); + + it('should revert back to the last typed value if the user presses escape', fakeAsync(() => { + const {trigger, stateCtrl, closedSpy} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + typeInElement(input, 'al'); + fixture.detectChanges(); + tick(); + + expect(stateCtrl.value).toBe('al'); + expect(input.value).toBe('al'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBe('al'); + expect(input.value).toBe('Alabama'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + fixture.detectChanges(); + + expect(stateCtrl.value).toBe('al'); + expect(input.value).toBe('al'); + expect(trigger.panelOpen).toBe(false); + expect(closedSpy).toHaveBeenCalledTimes(1); + })); + + it( + 'should clear the input if the user presses escape while there was a pending ' + + 'auto selection and there is no previous value', + fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + }), + ); + + it('should propagate the auto-selected value if the user clicks away', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchFakeEvent(document, 'click'); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'}); + expect(input.value).toBe('Alabama'); + })); + + it('should propagate the auto-selected value if the user tabs away', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchKeyboardEvent(input, 'keydown', TAB); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'}); + expect(input.value).toBe('Alabama'); + })); + + it('should propagate the auto-selected value if the user presses enter on it', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchKeyboardEvent(input, 'keydown', ENTER); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'}); + expect(input.value).toBe('Alabama'); + })); + + it('should allow the user to click on an option different from the auto-selected one', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + const options = overlayContainerElement.querySelectorAll( + 'mat-option', + ) as NodeListOf; + options[2].click(); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'FL', name: 'Florida'}); + expect(input.value).toBe('Florida'); + })); + }); + it('should have correct width when opened', () => { const widthFixture = createComponent(SimpleAutocomplete); widthFixture.componentInstance.width = 300; diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index 059665bdaea7..c3cce6e591eb 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -42,7 +42,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import { _countGroupLabelsBeforeOption, _getOptionScrollPosition, - MatOption, + _MatOptionBase, MatOptionSelectionChange, } from '@angular/material/core'; import {MAT_FORM_FIELD, MatFormField} from '@angular/material/form-field'; @@ -128,6 +128,15 @@ export abstract class _MatAutocompleteTriggerBase */ private _canOpenOnNextFocus = true; + /** Value inside the input before we auto-selected an option. */ + private _valueBeforeAutoSelection: string | undefined; + + /** + * Current option that we have auto-selected as the user is navigating, + * but which hasn't been propagated to the model value yet. + */ + private _pendingAutoselectedOption: _MatOptionBase | null; + /** Stream of keyboard events that can close the panel. */ private readonly _closeKeyEventStream = new Subject(); @@ -263,6 +272,7 @@ export abstract class _MatAutocompleteTriggerBase } this.autocomplete._isOpen = this._overlayAttached = false; + this._pendingAutoselectedOption = null; if (this._overlayRef && this._overlayRef.hasAttached()) { this._overlayRef.detach(); @@ -329,7 +339,7 @@ export abstract class _MatAutocompleteTriggerBase }) as Observable; /** The currently active option, coerced to MatOption type. */ - get activeOption(): MatOption | null { + get activeOption(): _MatOptionBase | null { if (this.autocomplete && this.autocomplete._keyManager) { return this.autocomplete._keyManager.activeItem; } @@ -370,7 +380,7 @@ export abstract class _MatAutocompleteTriggerBase // Implemented as part of ControlValueAccessor. writeValue(value: any): void { - Promise.resolve().then(() => this._setTriggerValue(value)); + Promise.resolve(null).then(() => this._assignOptionValue(value)); } // Implemented as part of ControlValueAccessor. @@ -416,6 +426,15 @@ export abstract class _MatAutocompleteTriggerBase if (isArrowKey || this.autocomplete._keyManager.activeItem !== prevActiveItem) { this._scrollToOption(this.autocomplete._keyManager.activeItemIndex || 0); + + if (this.autocomplete.autoSelectActiveOption && this.activeOption) { + if (!this._pendingAutoselectedOption) { + this._valueBeforeAutoSelection = this._element.nativeElement.value; + } + + this._pendingAutoselectedOption = this.activeOption; + this._assignOptionValue(this.activeOption.value); + } } } } @@ -436,6 +455,7 @@ export abstract class _MatAutocompleteTriggerBase // See: https://connect.microsoft.com/IE/feedback/details/885747/ if (this._previousValue !== value) { this._previousValue = value; + this._pendingAutoselectedOption = null; this._onChange(value); if (this._canOpen() && this._document.activeElement === event.target) { @@ -535,7 +555,7 @@ export abstract class _MatAutocompleteTriggerBase } } - private _setTriggerValue(value: any): void { + private _assignOptionValue(value: any): void { const toDisplay = this.autocomplete && this.autocomplete.displayWith ? this.autocomplete.displayWith(value) @@ -543,17 +563,19 @@ export abstract class _MatAutocompleteTriggerBase // Simply falling back to an empty string if the display value is falsy does not work properly. // The display value can also be the number zero and shouldn't fall back to an empty string. - const inputValue = toDisplay != null ? toDisplay : ''; + this._updateNativeInputValue(toDisplay != null ? toDisplay : ''); + } + private _updateNativeInputValue(value: string): void { // If it's used within a `MatFormField`, we should set it through the property so it can go // through change detection. if (this._formField) { - this._formField._control.value = inputValue; + this._formField._control.value = value; } else { - this._element.nativeElement.value = inputValue; + this._element.nativeElement.value = value; } - this._previousValue = inputValue; + this._previousValue = value; } /** @@ -562,13 +584,13 @@ export abstract class _MatAutocompleteTriggerBase * stemmed from the user. */ private _setValueAndClose(event: MatOptionSelectionChange | null): void { - const source = event && event.source; + const toSelect = event ? event.source : this._pendingAutoselectedOption; - if (source) { - this._clearPreviousSelectedOption(source); - this._setTriggerValue(source.value); - this._onChange(source.value); - this.autocomplete._emitSelectEvent(source); + if (toSelect) { + this._clearPreviousSelectedOption(toSelect); + this._assignOptionValue(toSelect.value); + this._onChange(toSelect.value); + this.autocomplete._emitSelectEvent(toSelect); this._element.nativeElement.focus(); } @@ -578,7 +600,7 @@ export abstract class _MatAutocompleteTriggerBase /** * Clear any previous selected option and emit a selection change event for this option */ - private _clearPreviousSelectedOption(skip: MatOption) { + private _clearPreviousSelectedOption(skip: _MatOptionBase) { this.autocomplete.options.forEach(option => { if (option !== skip && option.selected) { option.deselect(); @@ -599,26 +621,7 @@ export abstract class _MatAutocompleteTriggerBase }); overlayRef = this._overlay.create(this._getOverlayConfig()); this._overlayRef = overlayRef; - - // Use the `keydownEvents` in order to take advantage of - // the overlay event targeting provided by the CDK overlay. - overlayRef.keydownEvents().subscribe(event => { - // Close when pressing ESCAPE or ALT + UP_ARROW, based on the a11y guidelines. - // See: https://www.w3.org/TR/wai-aria-practices-1.1/#textbox-keyboard-interaction - if ( - (event.keyCode === ESCAPE && !hasModifierKey(event)) || - (event.keyCode === UP_ARROW && hasModifierKey(event, 'altKey')) - ) { - this._closeKeyEventStream.next(); - this._resetActiveItem(); - - // We need to stop propagation, otherwise the event will eventually - // reach the input itself and cause the overlay to be reopened. - event.stopPropagation(); - event.preventDefault(); - } - }); - + this._handleOverlayEvents(overlayRef); this._viewportSubscription = this._viewportRuler.change().subscribe(() => { if (this.panelOpen && overlayRef) { overlayRef.updateSize({width: this._getPanelWidth()}); @@ -781,6 +784,35 @@ export abstract class _MatAutocompleteTriggerBase } } } + + /** Handles keyboard events coming from the overlay panel. */ + private _handleOverlayEvents(overlayRef: OverlayRef) { + // Use the `keydownEvents` in order to take advantage of + // the overlay event targeting provided by the CDK overlay. + overlayRef.keydownEvents().subscribe(event => { + // Close when pressing ESCAPE or ALT + UP_ARROW, based on the a11y guidelines. + // See: https://www.w3.org/TR/wai-aria-practices-1.1/#textbox-keyboard-interaction + if ( + (event.keyCode === ESCAPE && !hasModifierKey(event)) || + (event.keyCode === UP_ARROW && hasModifierKey(event, 'altKey')) + ) { + // If the user had typed something in before we autoselected an option, and they decided + // to cancel the selection, restore the input value to the one they had typed in. + if (this._pendingAutoselectedOption) { + this._updateNativeInputValue(this._valueBeforeAutoSelection ?? ''); + this._pendingAutoselectedOption = null; + } + + this._closeKeyEventStream.next(); + this._resetActiveItem(); + + // We need to stop propagation, otherwise the event will eventually + // reach the input itself and cause the overlay to be reopened. + event.stopPropagation(); + event.preventDefault(); + } + }); + } } @Directive({ diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index ef3b1f562948..70847a76a6df 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -2708,6 +2708,217 @@ describe('MatAutocomplete', () => { })); }); + describe('automatically selecting the active option', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = createComponent(SimpleAutocomplete); + fixture.detectChanges(); + fixture.componentInstance.trigger.autocomplete.autoSelectActiveOption = true; + }); + + it( + 'should update the input value as the user is navigating, without changing the model ' + + 'value or closing the panel', + fakeAsync(() => { + const {trigger, stateCtrl, closedSpy} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('California'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + }), + ); + + it('should revert back to the last typed value if the user presses escape', fakeAsync(() => { + const {trigger, stateCtrl, closedSpy} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + typeInElement(input, 'al'); + fixture.detectChanges(); + tick(); + + expect(stateCtrl.value).toBe('al'); + expect(input.value).toBe('al'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBe('al'); + expect(input.value).toBe('Alabama'); + expect(trigger.panelOpen).toBe(true); + expect(closedSpy).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + fixture.detectChanges(); + + expect(stateCtrl.value).toBe('al'); + expect(input.value).toBe('al'); + expect(trigger.panelOpen).toBe(false); + expect(closedSpy).toHaveBeenCalledTimes(1); + })); + + it( + 'should clear the input if the user presses escape while there was a pending ' + + 'auto selection and there is no previous value', + fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + }), + ); + + it('should propagate the auto-selected value if the user clicks away', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchFakeEvent(document, 'click'); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'}); + expect(input.value).toBe('Alabama'); + })); + + it('should propagate the auto-selected value if the user tabs away', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchKeyboardEvent(input, 'keydown', TAB); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'}); + expect(input.value).toBe('Alabama'); + })); + + it('should propagate the auto-selected value if the user presses enter on it', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + dispatchKeyboardEvent(input, 'keydown', ENTER); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'}); + expect(input.value).toBe('Alabama'); + })); + + it('should allow the user to click on an option different from the auto-selected one', fakeAsync(() => { + const {trigger, stateCtrl} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBeFalsy(); + + dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(stateCtrl.value).toBeFalsy(); + expect(input.value).toBe('Alabama'); + + const options = overlayContainerElement.querySelectorAll( + 'mat-option', + ) as NodeListOf; + options[2].click(); + fixture.detectChanges(); + + expect(stateCtrl.value).toEqual({code: 'FL', name: 'Florida'}); + expect(input.value).toBe('Florida'); + })); + }); + it('should have correct width when opened', () => { const widthFixture = createComponent(SimpleAutocomplete); widthFixture.componentInstance.width = 300; diff --git a/src/material/autocomplete/autocomplete.ts b/src/material/autocomplete/autocomplete.ts index ad025695f812..17926ec049c0 100644 --- a/src/material/autocomplete/autocomplete.ts +++ b/src/material/autocomplete/autocomplete.ts @@ -74,6 +74,9 @@ export interface MatAutocompleteDefaultOptions { /** Whether the first option should be highlighted when an autocomplete panel is opened. */ autoActiveFirstOption?: boolean; + /** Whether the active option should be selected as the user is navigating. */ + autoSelectActiveOption?: boolean; + /** Class or list of classes to be applied to the autocomplete's overlay panel. */ overlayPanelClass?: string | string[]; } @@ -89,7 +92,7 @@ export const MAT_AUTOCOMPLETE_DEFAULT_OPTIONS = new InjectionToken; protected abstract _visibleClass: string; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration<_MatAutocompleteBase, never, never, { "ariaLabel": "aria-label"; "ariaLabelledby": "aria-labelledby"; "displayWith": "displayWith"; "autoActiveFirstOption": "autoActiveFirstOption"; "panelWidth": "panelWidth"; "classList": "class"; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; "optionActivated": "optionActivated"; }, never>; + static ɵdir: i0.ɵɵDirectiveDeclaration<_MatAutocompleteBase, never, never, { "ariaLabel": "aria-label"; "ariaLabelledby": "aria-labelledby"; "displayWith": "displayWith"; "autoActiveFirstOption": "autoActiveFirstOption"; "autoSelectActiveOption": "autoSelectActiveOption"; "panelWidth": "panelWidth"; "classList": "class"; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; "optionActivated": "optionActivated"; }, never>; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration<_MatAutocompleteBase, never>; } @@ -135,6 +137,7 @@ export abstract class _MatAutocompleteBase extends _MatAutocompleteMixinBase imp // @public export interface MatAutocompleteDefaultOptions { autoActiveFirstOption?: boolean; + autoSelectActiveOption?: boolean; overlayPanelClass?: string | string[]; } @@ -190,7 +193,7 @@ export class MatAutocompleteTrigger extends _MatAutocompleteTriggerBase { export abstract class _MatAutocompleteTriggerBase implements ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy { constructor(_element: ElementRef, _overlay: Overlay, _viewContainerRef: ViewContainerRef, _zone: NgZone, _changeDetectorRef: ChangeDetectorRef, scrollStrategy: any, _dir: Directionality, _formField: MatFormField, _document: any, _viewportRuler: ViewportRuler, _defaults?: MatAutocompleteDefaultOptions | undefined); protected abstract _aboveClass: string; - get activeOption(): MatOption | null; + get activeOption(): _MatOptionBase | null; autocomplete: _MatAutocompleteBase; autocompleteAttribute: string; get autocompleteDisabled(): boolean;