mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(input): update aria-describedby to also include errors (#6239)
* fix(input): update aria-describedby to also include errors * comments addressed * check for null errorChildren
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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 = [
|
||||
@@ -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({
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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() {
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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 (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 (endHint) {
|
||||
ids.push(endHint.id);
|
||||
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(' ');
|
||||
|
||||
Reference in New Issue
Block a user