Skip to content

Commit

Permalink
fix(input): update aria-describedby to also include errors (#6239)
Browse files Browse the repository at this point in the history
* fix(input): update aria-describedby to also include errors

* comments addressed

* check for null errorChildren
  • Loading branch information
mmalerba authored and tinayuangao committed Aug 4, 2017
1 parent 73c6d8d commit 2af284c
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 20 deletions.
18 changes: 18 additions & 0 deletions src/lib/input/input-container.spec.ts
Expand Up @@ -813,6 +813,24 @@ describe('MdInputContainer with forms', () => {
.toBe(1, 'Expected one hint to still be shown.');
});
}));

it('sets the aria-describedby to reference errors when in error state', () => {
let hintId = fixture.debugElement.query(By.css('.mat-hint')).nativeElement.getAttribute('id');
let describedBy = inputEl.getAttribute('aria-describedby');

expect(hintId).toBeTruthy('hint should be shown');
expect(describedBy).toBe(hintId);

fixture.componentInstance.formControl.markAsTouched();
fixture.detectChanges();

let errorIds = fixture.debugElement.queryAll(By.css('.mat-input-error'))
.map(el => el.nativeElement.getAttribute('id')).join(' ');
describedBy = inputEl.getAttribute('aria-describedby');

expect(errorIds).toBeTruthy('errors should be shown');
expect(describedBy).toBe(errorIds);
});
});

describe('custom error behavior', () => {
Expand Down
58 changes: 38 additions & 20 deletions src/lib/input/input-container.ts
Expand Up @@ -51,6 +51,7 @@ import {
MD_ERROR_GLOBAL_OPTIONS
} from '../core/error/error-options';
import {Subject} from 'rxjs/Subject';
import {startWith} from '@angular/cdk/rxjs';

// Invalid input type. Using one of these will throw an MdInputContainerUnsupportedTypeError.
const MD_INPUT_INVALID_TYPES = [
Expand Down Expand Up @@ -100,10 +101,13 @@ export class MdHint {
@Directive({
selector: 'md-error, mat-error',
host: {
'class': 'mat-input-error'
'class': 'mat-input-error',
'[attr.id]': 'id',
}
})
export class MdErrorDirective { }
export class MdErrorDirective {
@Input() id: string = `md-input-error-${nextUniqueId++}`;
}

/** Prefix to be placed the the front of the input. */
@Directive({
Expand Down Expand Up @@ -474,12 +478,11 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC

ngAfterContentInit() {
this._validateInputChild();
this._processHints();
this._validatePlaceholders();

// Subscribe to changes in the child input state in order to update the container UI.
this._mdInputChild._stateChanges.subscribe(() => {
startWith.call(this._mdInputChild._stateChanges, null).subscribe(() => {
this._validatePlaceholders();
this._syncAriaDescribedby();
this._changeDetectorRef.markForCheck();
});

Expand All @@ -489,8 +492,17 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
});
}

// Re-validate when the amount of hints changes.
this._hintChildren.changes.subscribe(() => this._processHints());
// Re-validate when the number of hints changes.
startWith.call(this._hintChildren.changes, null).subscribe(() => {
this._processHints();
this._changeDetectorRef.markForCheck();
});

// Update the aria-described by when the number of errors changes.
startWith.call(this._errorChildren.changes, null).subscribe(() => {
this._syncAriaDescribedby();
this._changeDetectorRef.markForCheck();
});
}

ngAfterContentChecked() {
Expand Down Expand Up @@ -522,7 +534,8 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
/** Determines whether to display hints or errors. */
_getDisplayedMessages(): 'error' | 'hint' {
let input = this._mdInputChild;
return (this._errorChildren.length > 0 && input._isErrorState) ? 'error' : 'hint';
return (this._errorChildren && this._errorChildren.length > 0 && input._isErrorState) ?
'error' : 'hint';
}

/**
Expand Down Expand Up @@ -574,19 +587,24 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
private _syncAriaDescribedby() {
if (this._mdInputChild) {
let ids: string[] = [];
let startHint = this._hintChildren ?
this._hintChildren.find(hint => hint.align === 'start') : null;
let endHint = this._hintChildren ?
this._hintChildren.find(hint => hint.align === 'end') : null;

if (startHint) {
ids.push(startHint.id);
} else if (this._hintLabel) {
ids.push(this._hintLabelId);
}

if (endHint) {
ids.push(endHint.id);
if (this._getDisplayedMessages() === 'hint') {
let startHint = this._hintChildren ?
this._hintChildren.find(hint => hint.align === 'start') : null;
let endHint = this._hintChildren ?
this._hintChildren.find(hint => hint.align === 'end') : null;

if (startHint) {
ids.push(startHint.id);
} else if (this._hintLabel) {
ids.push(this._hintLabelId);
}

if (endHint) {
ids.push(endHint.id);
}
} else if (this._errorChildren) {
ids = this._errorChildren.map(mdError => mdError.id);
}

this._mdInputChild.ariaDescribedby = ids.join(' ');
Expand Down

0 comments on commit 2af284c

Please sign in to comment.