Skip to content

Commit

Permalink
feat(chips): Add left/right arrow functionality. (#2332)
Browse files Browse the repository at this point in the history
In order to maintain consistentcy with the md1 chips, add the ability
to use the left/right arrow keys to navigate chips.

References #120.
  • Loading branch information
topherfangio authored and jelbourn committed Dec 22, 2016
1 parent 0dd57da commit 3f2db27
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/demo-app/chips/chips-demo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}
}

md-basic-chip {
.md-basic-chip {
margin: auto 10px;
}
}
4 changes: 2 additions & 2 deletions src/lib/chips/_chips-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
$selected-background: if($is-dark-theme, md-color($background, app-bar), #808080);
$selected-foreground: if($is-dark-theme, md-color($foreground, text), $light-selected-foreground);

.md-chip {
.md-chip:not(.md-basic-chip) {
background-color: $unselected-background;
color: $unselected-foreground;
}

.md-chip.md-chip-selected {
.md-chip.md-chip-selected:not(.md-basic-chip) {
background-color: $selected-background;
color: $selected-foreground;

Expand Down
66 changes: 62 additions & 4 deletions src/lib/chips/chip-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import {By} from '@angular/platform-browser';
import {MdChip, MdChipList, MdChipsModule} from './index';
import {ListKeyManager} from '../core/a11y/list-key-manager';
import {FakeEvent} from '../core/a11y/list-key-manager.spec';
import {SPACE} from '../core/keyboard/keycodes';
import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';

class FakeKeyboardEvent extends FakeEvent {
constructor(keyCode: number, protected target: HTMLElement) {
super(keyCode);

this.target = target;
}
}

describe('MdChipList', () => {
let fixture: ComponentFixture<any>;
Expand Down Expand Up @@ -101,6 +109,50 @@ describe('MdChipList', () => {
});

describe('keyboard behavior', () => {
beforeEach(() => {
manager = chipListInstance._keyManager;
});

it('left arrow focuses previous item', () => {
let nativeChips = chipListNativeElement.querySelectorAll('md-chip');
let lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement;

let LEFT_EVENT = new FakeKeyboardEvent(LEFT_ARROW, lastNativeChip) as any;
let array = chips.toArray();
let lastIndex = array.length - 1;
let lastItem = array[lastIndex];

// Focus the last item in the array
lastItem.focus();
expect(manager.focusedItemIndex).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);
});

it('right arrow focuses next item', () => {
let nativeChips = chipListNativeElement.querySelectorAll('md-chip');
let firstNativeChip = nativeChips[0] as HTMLElement;

let RIGHT_EVENT: KeyboardEvent = new FakeKeyboardEvent(RIGHT_ARROW, firstNativeChip) as any;
let array = chips.toArray();
let firstItem = array[0];

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

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

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

describe('when selectable is true', () => {
beforeEach(() => {
Expand All @@ -109,7 +161,10 @@ describe('MdChipList', () => {
});

it('SPACE selects/deselects the currently focused chip', () => {
let SPACE_EVENT: KeyboardEvent = new FakeEvent(SPACE) as KeyboardEvent;
let nativeChips = chipListNativeElement.querySelectorAll('md-chip');
let firstNativeChip = nativeChips[0] as HTMLElement;

let SPACE_EVENT: KeyboardEvent = new FakeKeyboardEvent(SPACE, firstNativeChip) as any;
let firstChip: MdChip = chips.toArray()[0];

spyOn(testComponent, 'chipSelect');
Expand Down Expand Up @@ -181,6 +236,9 @@ class StaticChipList {
selectable: boolean = true;
remove: Number;

chipSelect(index: Number) {}
chipDeselect(index: Number) {}
chipSelect(index: Number) {
}

chipDeselect(index: Number) {
}
}
39 changes: 26 additions & 13 deletions src/lib/chips/chip-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import {MdChip} from './chip';
import {ListKeyManager} from '../core/a11y/list-key-manager';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
import {SPACE} from '../core/keyboard/keycodes';
import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';

/**
* A material design chips component (named ChipList for it's similarity to the List component).
Expand Down Expand Up @@ -98,18 +98,31 @@ export class MdChipList implements AfterContentInit {

/** Passes relevant key presses to our key manager. */
_keydown(event: KeyboardEvent) {
switch (event.keyCode) {
case SPACE:
// If we are selectable, toggle the focused chip
if (this.selectable) {
this._toggleSelectOnFocusedChip();
}

// Always prevent space from scrolling the page since the list has focus
event.preventDefault();
break;
default:
this._keyManager.onKeydown(event);
let target = event.target as HTMLElement;

// If they are on a chip, check for space/left/right, otherwise pass to our key manager
if (target && target.classList.contains('md-chip')) {
switch (event.keyCode) {
case SPACE:
// If we are selectable, toggle the focused chip
if (this.selectable) {
this._toggleSelectOnFocusedChip();
}

// Always prevent space from scrolling the page since the list has focus
event.preventDefault();
break;
case LEFT_ARROW:
this._keyManager.focusPreviousItem();
event.preventDefault();
break;
case RIGHT_ARROW:
this._keyManager.focusNextItem();
event.preventDefault();
break;
default:
this._keyManager.onKeydown(event);
}
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/lib/chips/chip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ describe('Chips', () => {
document.body.removeChild(chipNativeElement);
});

it('does not add the `md-chip` class', () => {
expect(chipNativeElement.classList).not.toContain('md-chip');
it('adds the `md-basic-chip` class', () => {
expect(chipNativeElement.classList).toContain('md-chip');
expect(chipNativeElement.classList).toContain('md-basic-chip');
});
});

Expand Down Expand Up @@ -69,6 +70,10 @@ describe('Chips', () => {
expect(chipNativeElement.classList).toContain('md-chip');
});

it('does not add the `md-basic-chip` class', () => {
expect(chipNativeElement.classList).not.toContain('md-basic-chip');
});

it('emits focus on click', () => {
spyOn(chipInstance, 'focus').and.callThrough();

Expand Down
8 changes: 6 additions & 2 deletions src/lib/chips/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,12 @@ export class MdChip implements Focusable, OnInit, OnDestroy {
private _addDefaultCSSClass() {
let el: HTMLElement = this._elementRef.nativeElement;

if (el.nodeName.toLowerCase() == 'md-chip' || el.hasAttribute('md-chip')) {
el.classList.add('md-chip');
// Always add the `md-chip` class
el.classList.add('md-chip');

// If we are a basic chip, also add the `md-basic-chip` class for :not() targeting
if (el.nodeName.toLowerCase() == 'md-basic-chip' || el.hasAttribute('md-basic-chip')) {
el.classList.add('md-basic-chip');
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/lib/chips/chips.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $md-chips-chip-margin: $md-chip-horizontal-padding / 4;
/*
* Only apply the margins to chips
*/
.md-chip {
.md-chip:not(.md-basic-chip) {
margin: 0 $md-chips-chip-margin 0 $md-chips-chip-margin;

// Remove the margin from the first element (in both LTR and RTL)
Expand Down Expand Up @@ -50,7 +50,7 @@ $md-chips-chip-margin: $md-chip-horizontal-padding / 4;
}
}

.md-chip {
.md-chip:not(.md-basic-chip) {
display: inline-block;
padding: $md-chip-vertical-padding $md-chip-horizontal-padding
$md-chip-vertical-padding $md-chip-horizontal-padding;
Expand All @@ -63,7 +63,7 @@ $md-chips-chip-margin: $md-chip-horizontal-padding / 4;
.md-chip-list-stacked .md-chip-list-wrapper {
display: block;

.md-chip {
.md-chip:not(.md-basic-chip) {
display: block;
margin: 0;
margin-bottom: $md-chip-vertical-padding;
Expand Down

0 comments on commit 3f2db27

Please sign in to comment.