From e8bc0e9186b2b2cf9548931b5e17f79b82bc5f7c Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 4 May 2018 21:08:56 +0200 Subject: [PATCH] feat(autocomplete): allow autocomplete panel to be disabled (#11142) --- src/lib/autocomplete/BUILD.bazel | 1 + src/lib/autocomplete/autocomplete-trigger.ts | 22 +++++++++++--- src/lib/autocomplete/autocomplete.spec.ts | 32 +++++++++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/lib/autocomplete/BUILD.bazel b/src/lib/autocomplete/BUILD.bazel index 6fe3521b2b2..4d1f2486b75 100644 --- a/src/lib/autocomplete/BUILD.bazel +++ b/src/lib/autocomplete/BUILD.bazel @@ -17,6 +17,7 @@ ng_module( "//src/cdk/keycodes", "//src/cdk/portal", "//src/cdk/overlay", + "//src/cdk/coercion", ], tsconfig = "//src/lib:tsconfig-build.json", ) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 541d520653c..ca254dd6abf 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -44,6 +44,7 @@ import { import {MatFormField} from '@angular/material/form-field'; import {Subscription, defer, fromEvent, merge, of as observableOf, Subject, Observable} from 'rxjs'; import {MatAutocomplete} from './autocomplete'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; /** @@ -93,12 +94,12 @@ export function getMatAutocompleteMissingPanelError(): Error { @Directive({ selector: `input[matAutocomplete], textarea[matAutocomplete]`, host: { - 'role': 'combobox', 'autocomplete': 'off', - 'aria-autocomplete': 'list', + '[attr.role]': 'autocompleteDisabled ? null : "combobox"', + '[attr.aria-autocomplete]': 'autocompleteDisabled ? null : "list"', '[attr.aria-activedescendant]': 'activeOption?.id', - '[attr.aria-expanded]': 'panelOpen.toString()', - '[attr.aria-owns]': 'autocomplete?.id', + '[attr.aria-expanded]': 'autocompleteDisabled ? null : panelOpen.toString()', + '[attr.aria-owns]': 'autocompleteDisabled ? null : autocomplete?.id', // Note: we use `focusin`, as opposed to `focus`, in order to open the panel // a little earlier. This avoids issues where IE delays the focusing of the input. '(focusin)': '_handleFocus()', @@ -113,6 +114,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { private _overlayRef: OverlayRef | null; private _portal: TemplatePortal; private _componentDestroyed = false; + private _autocompleteDisabled = false; /** Old value of the native input. Used to work around issues with the `input` event on IE. */ private _previousValue: string | number | null; @@ -141,6 +143,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { /** The autocomplete panel to be attached to this trigger. */ @Input('matAutocomplete') autocomplete: MatAutocomplete; + /** + * Whether the autocomplete is disabled. When disabled, the element will + * act as a regular input and the user won't be able to open the panel. + */ + @Input('matAutocompleteDisabled') + get autocompleteDisabled(): boolean { return this._autocompleteDisabled; } + set autocompleteDisabled(value: boolean) { + this._autocompleteDisabled = coerceBooleanProperty(value); + } + constructor(private _element: ElementRef, private _overlay: Overlay, private _viewContainerRef: ViewContainerRef, private _zone: NgZone, @@ -569,7 +581,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { /** Determines whether the panel can be opened. */ private _canOpen(): boolean { const element: HTMLInputElement = this._element.nativeElement; - return !element.readOnly && !element.disabled; + return !element.readOnly && !element.disabled && !this._autocompleteDisabled; } } diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 4b4e9be5d8e..a9b6331fcf0 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -455,6 +455,20 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.closedSpy).not.toHaveBeenCalled(); }); + it('should not be able to open the panel if the autocomplete is disabled', () => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel state to start out closed.`); + + fixture.componentInstance.autocompleteDisabled = true; + fixture.detectChanges(); + + dispatchFakeEvent(input, 'focusin'); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel to remain closed.`); + }); + }); it('should have the correct text direction in RTL', () => { @@ -1300,6 +1314,16 @@ describe('MatAutocomplete', () => { expect(document.activeElement).toBe(input, 'Expected focus to be restored to the input.'); })); + it('should remove autocomplete-specific aria attributes when autocomplete is disabled', () => { + fixture.componentInstance.autocompleteDisabled = true; + fixture.detectChanges(); + + expect(input.getAttribute('role')).toBeFalsy(); + expect(input.getAttribute('aria-autocomplete')).toBeFalsy(); + expect(input.getAttribute('aria-expanded')).toBeFalsy(); + expect(input.getAttribute('aria-owns')).toBeFalsy(); + }); + }); describe('Fallback positions', () => { @@ -1959,7 +1983,12 @@ describe('MatAutocomplete', () => { @Component({ template: ` - +