fix(chips): unable to tab out of chip list (#4605)

* Fixes not being able to escape focus from an `md-chip-list` backwards via shift+tab on all browsers.
* Fixes not being able to tab out of an `md-chip-list` at all on Firefox.

Fixes #4593.
This commit is contained in:
Kristiyan Kostadinov
2017-05-19 02:05:38 +02:00
committed by Jeremy Elbourn
parent 45e087936b
commit 07a82eda06
2 changed files with 42 additions and 15 deletions
+16 -5
View File
@@ -1,10 +1,12 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {Component, DebugElement, QueryList} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MdChip, MdChipList, MdChipsModule} from './index';
import {FocusKeyManager} from '../core/a11y/focus-key-manager';
import {FakeEvent} from '../core/a11y/list-key-manager.spec';
import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';
import {SPACE, LEFT_ARROW, RIGHT_ARROW, TAB} from '../core/keyboard/keycodes';
import {createKeyboardEvent} from '../core/testing/event-objects';
class FakeKeyboardEvent extends FakeEvent {
constructor(keyCode: number, protected target: HTMLElement) {
@@ -26,9 +28,7 @@ describe('MdChipList', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdChipsModule],
declarations: [
StaticChipList
]
declarations: [StaticChipList]
});
TestBed.compileComponents();
@@ -189,6 +189,17 @@ describe('MdChipList', () => {
expect(testComponent.chipDeselect).toHaveBeenCalledTimes(1);
expect(testComponent.chipDeselect).toHaveBeenCalledWith(0);
});
it('allow focus to escape when tabbing away', fakeAsync(() => {
chipListInstance._keyManager.onKeydown(createKeyboardEvent('keydown', TAB));
expect(chipListInstance._tabIndex)
.toBe(-1, 'Expected tabIndex to be set to -1 temporarily.');
tick();
expect(chipListInstance._tabIndex).toBe(0, 'Expected tabIndex to be reset back to 0');
}));
});
describe('when selectable is false', () => {
+26 -10
View File
@@ -3,16 +3,17 @@ import {
ChangeDetectionStrategy,
Component,
ContentChildren,
ElementRef,
Input,
QueryList,
ViewEncapsulation
ViewEncapsulation,
OnDestroy,
} from '@angular/core';
import {MdChip} from './chip';
import {FocusKeyManager} from '../core/a11y/focus-key-manager';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';
import {SPACE, LEFT_ARROW, RIGHT_ARROW, TAB} from '../core/keyboard/keycodes';
import {Subscription} from 'rxjs/Subscription';
/**
* A material design chips component (named ChipList for it's similarity to the List component).
@@ -30,7 +31,7 @@ import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';
template: `<div class="mat-chip-list-wrapper"><ng-content></ng-content></div>`,
host: {
// Properties
'tabindex': '0',
'[attr.tabindex]': '_tabIndex',
'role': 'listbox',
'[class.mat-chip-list]': 'true',
@@ -45,11 +46,14 @@ import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MdChipList implements AfterContentInit {
export class MdChipList implements AfterContentInit, OnDestroy {
/** Track which chips we're listening to for focus/destruction. */
private _subscribed: WeakMap<MdChip, boolean> = new WeakMap();
/** Subscription to tabbing out from the chip list. */
private _tabOutSubscription: Subscription;
/** Whether or not the chip is selectable. */
protected _selectable: boolean = true;
@@ -59,11 +63,19 @@ export class MdChipList implements AfterContentInit {
/** The chip components contained within this chip list. */
chips: QueryList<MdChip>;
constructor(private _elementRef: ElementRef) { }
/** Tab index for the chip list. */
_tabIndex = 0;
ngAfterContentInit(): void {
this._keyManager = new FocusKeyManager(this.chips).withWrap();
// Prevents the chip list from capturing focus and redirecting
// it back to the first chip when the user tabs out.
this._tabOutSubscription = this._keyManager.tabOut.subscribe(() => {
this._tabIndex = -1;
setTimeout(() => this._tabIndex = 0);
});
// Go ahead and subscribe all of the initial chips
this._subscribeChips(this.chips);
@@ -73,14 +85,18 @@ export class MdChipList implements AfterContentInit {
});
}
ngOnDestroy(): void {
if (this._tabOutSubscription) {
this._tabOutSubscription.unsubscribe();
}
}
/**
* Whether or not this chip is selectable. When a chip is not selectable,
* it's selected state is always ignored.
*/
@Input() get selectable(): boolean {
return this._selectable;
}
@Input()
get selectable(): boolean { return this._selectable; }
set selectable(value: boolean) {
this._selectable = coerceBooleanProperty(value);
}