Skip to content

feat(autocomplete): allow autocomplete panel to be disabled #11142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/autocomplete/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ng_module(
"//src/cdk/keycodes",
"//src/cdk/portal",
"//src/cdk/overlay",
"//src/cdk/coercion",
],
tsconfig = "//src/lib:tsconfig-build.json",
)
Expand Down
22 changes: 17 additions & 5 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';


/**
Expand Down Expand Up @@ -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()',
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

}
32 changes: 31 additions & 1 deletion src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -1959,7 +1983,12 @@ describe('MatAutocomplete', () => {
@Component({
template: `
<mat-form-field [floatLabel]="floatLabel" [style.width.px]="width">
<input matInput placeholder="State" [matAutocomplete]="auto" [formControl]="stateCtrl">
<input
matInput
placeholder="State"
[matAutocomplete]="auto"
[matAutocompleteDisabled]="autocompleteDisabled"
[formControl]="stateCtrl">
</mat-form-field>

<mat-autocomplete class="class-one class-two" #auto="matAutocomplete" [displayWith]="displayFn"
Expand All @@ -1977,6 +2006,7 @@ class SimpleAutocomplete implements OnDestroy {
floatLabel = 'auto';
width: number;
disableRipple = false;
autocompleteDisabled = false;
openedSpy = jasmine.createSpy('autocomplete opened spy');
closedSpy = jasmine.createSpy('autocomplete closed spy');

Expand Down