Skip to content

Commit

Permalink
feat(list-key-manager): active descendant support (#2606)
Browse files Browse the repository at this point in the history
  • Loading branch information
kara authored and mmalerba committed Jan 19, 2017
1 parent 899f190 commit e2ad3a0
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 327 deletions.
20 changes: 10 additions & 10 deletions src/lib/chips/chip-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component, DebugElement, QueryList} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MdChip, MdChipList, MdChipsModule} from './index';
import {ListKeyManager} from '../core/a11y/list-key-manager';
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';

Expand All @@ -21,7 +21,7 @@ describe('MdChipList', () => {
let chipListInstance: MdChipList;
let testComponent: StaticChipList;
let chips: QueryList<MdChip>;
let manager: ListKeyManager;
let manager: FocusKeyManager;

beforeEach(async(() => {
TestBed.configureTestingModule({
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('MdChipList', () => {
chipListInstance.focus();
fixture.detectChanges();

expect(manager.focusedItemIndex).toBe(0);
expect(manager.activeItemIndex).toBe(0);
});

it('watches for chip focus', () => {
Expand All @@ -71,7 +71,7 @@ describe('MdChipList', () => {
lastItem.focus();
fixture.detectChanges();

expect(manager.focusedItemIndex).toBe(lastIndex);
expect(manager.activeItemIndex).toBe(lastIndex);
});

describe('on chip destroy', () => {
Expand All @@ -87,7 +87,7 @@ describe('MdChipList', () => {
fixture.detectChanges();

// It focuses the 4th item (now at index 2)
expect(manager.focusedItemIndex).toEqual(2);
expect(manager.activeItemIndex).toEqual(2);
});

it('focuses the previous item', () => {
Expand All @@ -103,7 +103,7 @@ describe('MdChipList', () => {
fixture.detectChanges();

// It focuses the next-to-last item
expect(manager.focusedItemIndex).toEqual(lastIndex - 1);
expect(manager.activeItemIndex).toEqual(lastIndex - 1);
});
});
});
Expand All @@ -124,14 +124,14 @@ describe('MdChipList', () => {

// Focus the last item in the array
lastItem.focus();
expect(manager.focusedItemIndex).toEqual(lastIndex);
expect(manager.activeItemIndex).toEqual(lastIndex);

// Press the LEFT arrow
chipListInstance._keydown(LEFT_EVENT);
fixture.detectChanges();

// It focuses the next-to-last item
expect(manager.focusedItemIndex).toEqual(lastIndex - 1);
expect(manager.activeItemIndex).toEqual(lastIndex - 1);
});

it('right arrow focuses next item', () => {
Expand All @@ -144,14 +144,14 @@ describe('MdChipList', () => {

// Focus the last item in the array
firstItem.focus();
expect(manager.focusedItemIndex).toEqual(0);
expect(manager.activeItemIndex).toEqual(0);

// Press the RIGHT arrow
chipListInstance._keydown(RIGHT_EVENT);
fixture.detectChanges();

// It focuses the next-to-last item
expect(manager.focusedItemIndex).toEqual(1);
expect(manager.activeItemIndex).toEqual(1);
});

describe('when selectable is true', () => {
Expand Down
22 changes: 11 additions & 11 deletions src/lib/chips/chip-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@angular/core';

import {MdChip} from './chip';
import {ListKeyManager} from '../core/a11y/list-key-manager';
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';

Expand Down Expand Up @@ -55,16 +55,16 @@ export class MdChipList implements AfterContentInit {
/** Whether or not the chip is selectable. */
protected _selectable: boolean = true;

/** The ListKeyManager which handles focus. */
_keyManager: ListKeyManager;
/** The FocusKeyManager which handles focus. */
_keyManager: FocusKeyManager;

/** The chip components contained within this chip list. */
chips: QueryList<MdChip>;

constructor(private _elementRef: ElementRef) { }

ngAfterContentInit(): void {
this._keyManager = new ListKeyManager(this.chips).withFocusWrap();
this._keyManager = new FocusKeyManager(this.chips).withWrap();

// Go ahead and subscribe all of the initial chips
this._subscribeChips(this.chips);
Expand Down Expand Up @@ -93,7 +93,7 @@ export class MdChipList implements AfterContentInit {
*/
focus() {
// TODO: ARIA says this should focus the first `selected` chip.
this._keyManager.focusFirstItem();
this._keyManager.setFirstItemActive();
}

/** Passes relevant key presses to our key manager. */
Expand All @@ -113,11 +113,11 @@ export class MdChipList implements AfterContentInit {
event.preventDefault();
break;
case LEFT_ARROW:
this._keyManager.focusPreviousItem();
this._keyManager.setPreviousItemActive();
event.preventDefault();
break;
case RIGHT_ARROW:
this._keyManager.focusNextItem();
this._keyManager.setNextItemActive();
event.preventDefault();
break;
default:
Expand All @@ -133,7 +133,7 @@ export class MdChipList implements AfterContentInit {
return;
}

let focusedIndex = this._keyManager.focusedItemIndex;
let focusedIndex = this._keyManager.activeItemIndex;

if (this._isValidIndex(focusedIndex)) {
let focusedChip: MdChip = this.chips.toArray()[focusedIndex];
Expand Down Expand Up @@ -173,7 +173,7 @@ export class MdChipList implements AfterContentInit {
let chipIndex: number = this.chips.toArray().indexOf(chip);

if (this._isValidIndex(chipIndex)) {
this._keyManager.updateFocusedItemIndex(chipIndex);
this._keyManager.updateActiveItemIndex(chipIndex);
}
});

Expand All @@ -184,9 +184,9 @@ export class MdChipList implements AfterContentInit {
if (this._isValidIndex(chipIndex)) {
// Check whether the chip is the last item
if (chipIndex < this.chips.length - 1) {
this._keyManager.setFocus(chipIndex);
this._keyManager.setActiveItem(chipIndex);
} else if (chipIndex - 1 >= 0) {
this._keyManager.setFocus(chipIndex - 1);
this._keyManager.setActiveItem(chipIndex - 1);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/chips/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Renderer
} from '@angular/core';

import {Focusable} from '../core/a11y/list-key-manager';
import {Focusable} from '../core/a11y/focus-key-manager';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';

export interface MdChipEvent {
Expand Down
35 changes: 35 additions & 0 deletions src/lib/core/a11y/activedescendant-key-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {QueryList} from '@angular/core';
import {ListKeyManager, CanDisable} from './list-key-manager';

/**
* This is the interface for highlightable items (used by the ActiveDescendantKeyManager).
* Each item must know how to style itself as active or inactive and whether or not it is
* currently disabled.
*/
export interface Highlightable extends CanDisable {
setActiveStyles(): void;
setInactiveStyles(): void;
}

export class ActiveDescendantKeyManager extends ListKeyManager<Highlightable> {

constructor(items: QueryList<Highlightable>) {
super(items);
}

/**
* This method sets the active item to the item at the specified index.
* It also adds active styles to the newly active item and removes active
* styles from the previously active item.
*/
setActiveItem(index: number): void {
if (this.activeItem) {
this.activeItem.setInactiveStyles();
}
super.setActiveItem(index);
if (this.activeItem) {
this.activeItem.setActiveStyles();
}
}

}
29 changes: 29 additions & 0 deletions src/lib/core/a11y/focus-key-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

import {QueryList} from '@angular/core';
import {ListKeyManager, CanDisable} from './list-key-manager';

/**
* This is the interface for focusable items (used by the FocusKeyManager).
* Each item must know how to focus itself and whether or not it is currently disabled.
*/
export interface Focusable extends CanDisable {
focus(): void;
}


export class FocusKeyManager extends ListKeyManager<Focusable> {

constructor(items: QueryList<Focusable>) {
super(items);
}

/**
* This method sets the active item to the item at the specified index.
* It also adds focuses the newly active item.
*/
setActiveItem(index: number): void {
super.setActiveItem(index);
this.activeItem.focus();
}

}
Loading

0 comments on commit e2ad3a0

Please sign in to comment.