mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Jeremy Elbourn
parent
45e087936b
commit
07a82eda06
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user