Skip to content

Commit 3f2db27

Browse files
topherfangiojelbourn
authored andcommitted
feat(chips): Add left/right arrow functionality. (#2332)
In order to maintain consistentcy with the md1 chips, add the ability to use the left/right arrow keys to navigate chips. References #120.
1 parent 0dd57da commit 3f2db27

File tree

7 files changed

+107
-27
lines changed

7 files changed

+107
-27
lines changed

src/demo-app/chips/chips-demo.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818
}
1919

20-
md-basic-chip {
20+
.md-basic-chip {
2121
margin: auto 10px;
2222
}
2323
}

src/lib/chips/_chips-theme.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
$selected-background: if($is-dark-theme, md-color($background, app-bar), #808080);
2323
$selected-foreground: if($is-dark-theme, md-color($foreground, text), $light-selected-foreground);
2424

25-
.md-chip {
25+
.md-chip:not(.md-basic-chip) {
2626
background-color: $unselected-background;
2727
color: $unselected-foreground;
2828
}
2929

30-
.md-chip.md-chip-selected {
30+
.md-chip.md-chip-selected:not(.md-basic-chip) {
3131
background-color: $selected-background;
3232
color: $selected-foreground;
3333

src/lib/chips/chip-list.spec.ts

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import {By} from '@angular/platform-browser';
44
import {MdChip, MdChipList, MdChipsModule} from './index';
55
import {ListKeyManager} from '../core/a11y/list-key-manager';
66
import {FakeEvent} from '../core/a11y/list-key-manager.spec';
7-
import {SPACE} from '../core/keyboard/keycodes';
7+
import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';
8+
9+
class FakeKeyboardEvent extends FakeEvent {
10+
constructor(keyCode: number, protected target: HTMLElement) {
11+
super(keyCode);
12+
13+
this.target = target;
14+
}
15+
}
816

917
describe('MdChipList', () => {
1018
let fixture: ComponentFixture<any>;
@@ -101,6 +109,50 @@ describe('MdChipList', () => {
101109
});
102110

103111
describe('keyboard behavior', () => {
112+
beforeEach(() => {
113+
manager = chipListInstance._keyManager;
114+
});
115+
116+
it('left arrow focuses previous item', () => {
117+
let nativeChips = chipListNativeElement.querySelectorAll('md-chip');
118+
let lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement;
119+
120+
let LEFT_EVENT = new FakeKeyboardEvent(LEFT_ARROW, lastNativeChip) as any;
121+
let array = chips.toArray();
122+
let lastIndex = array.length - 1;
123+
let lastItem = array[lastIndex];
124+
125+
// Focus the last item in the array
126+
lastItem.focus();
127+
expect(manager.focusedItemIndex).toEqual(lastIndex);
128+
129+
// Press the LEFT arrow
130+
chipListInstance._keydown(LEFT_EVENT);
131+
fixture.detectChanges();
132+
133+
// It focuses the next-to-last item
134+
expect(manager.focusedItemIndex).toEqual(lastIndex - 1);
135+
});
136+
137+
it('right arrow focuses next item', () => {
138+
let nativeChips = chipListNativeElement.querySelectorAll('md-chip');
139+
let firstNativeChip = nativeChips[0] as HTMLElement;
140+
141+
let RIGHT_EVENT: KeyboardEvent = new FakeKeyboardEvent(RIGHT_ARROW, firstNativeChip) as any;
142+
let array = chips.toArray();
143+
let firstItem = array[0];
144+
145+
// Focus the last item in the array
146+
firstItem.focus();
147+
expect(manager.focusedItemIndex).toEqual(0);
148+
149+
// Press the RIGHT arrow
150+
chipListInstance._keydown(RIGHT_EVENT);
151+
fixture.detectChanges();
152+
153+
// It focuses the next-to-last item
154+
expect(manager.focusedItemIndex).toEqual(1);
155+
});
104156

105157
describe('when selectable is true', () => {
106158
beforeEach(() => {
@@ -109,7 +161,10 @@ describe('MdChipList', () => {
109161
});
110162

111163
it('SPACE selects/deselects the currently focused chip', () => {
112-
let SPACE_EVENT: KeyboardEvent = new FakeEvent(SPACE) as KeyboardEvent;
164+
let nativeChips = chipListNativeElement.querySelectorAll('md-chip');
165+
let firstNativeChip = nativeChips[0] as HTMLElement;
166+
167+
let SPACE_EVENT: KeyboardEvent = new FakeKeyboardEvent(SPACE, firstNativeChip) as any;
113168
let firstChip: MdChip = chips.toArray()[0];
114169

115170
spyOn(testComponent, 'chipSelect');
@@ -181,6 +236,9 @@ class StaticChipList {
181236
selectable: boolean = true;
182237
remove: Number;
183238

184-
chipSelect(index: Number) {}
185-
chipDeselect(index: Number) {}
239+
chipSelect(index: Number) {
240+
}
241+
242+
chipDeselect(index: Number) {
243+
}
186244
}

src/lib/chips/chip-list.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import {MdChip} from './chip';
1515
import {ListKeyManager} from '../core/a11y/list-key-manager';
1616
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
17-
import {SPACE} from '../core/keyboard/keycodes';
17+
import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes';
1818

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

9999
/** Passes relevant key presses to our key manager. */
100100
_keydown(event: KeyboardEvent) {
101-
switch (event.keyCode) {
102-
case SPACE:
103-
// If we are selectable, toggle the focused chip
104-
if (this.selectable) {
105-
this._toggleSelectOnFocusedChip();
106-
}
107-
108-
// Always prevent space from scrolling the page since the list has focus
109-
event.preventDefault();
110-
break;
111-
default:
112-
this._keyManager.onKeydown(event);
101+
let target = event.target as HTMLElement;
102+
103+
// If they are on a chip, check for space/left/right, otherwise pass to our key manager
104+
if (target && target.classList.contains('md-chip')) {
105+
switch (event.keyCode) {
106+
case SPACE:
107+
// If we are selectable, toggle the focused chip
108+
if (this.selectable) {
109+
this._toggleSelectOnFocusedChip();
110+
}
111+
112+
// Always prevent space from scrolling the page since the list has focus
113+
event.preventDefault();
114+
break;
115+
case LEFT_ARROW:
116+
this._keyManager.focusPreviousItem();
117+
event.preventDefault();
118+
break;
119+
case RIGHT_ARROW:
120+
this._keyManager.focusNextItem();
121+
event.preventDefault();
122+
break;
123+
default:
124+
this._keyManager.onKeydown(event);
125+
}
113126
}
114127
}
115128

src/lib/chips/chip.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ describe('Chips', () => {
3838
document.body.removeChild(chipNativeElement);
3939
});
4040

41-
it('does not add the `md-chip` class', () => {
42-
expect(chipNativeElement.classList).not.toContain('md-chip');
41+
it('adds the `md-basic-chip` class', () => {
42+
expect(chipNativeElement.classList).toContain('md-chip');
43+
expect(chipNativeElement.classList).toContain('md-basic-chip');
4344
});
4445
});
4546

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

73+
it('does not add the `md-basic-chip` class', () => {
74+
expect(chipNativeElement.classList).not.toContain('md-basic-chip');
75+
});
76+
7277
it('emits focus on click', () => {
7378
spyOn(chipInstance, 'focus').and.callThrough();
7479

src/lib/chips/chip.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,12 @@ export class MdChip implements Focusable, OnInit, OnDestroy {
136136
private _addDefaultCSSClass() {
137137
let el: HTMLElement = this._elementRef.nativeElement;
138138

139-
if (el.nodeName.toLowerCase() == 'md-chip' || el.hasAttribute('md-chip')) {
140-
el.classList.add('md-chip');
139+
// Always add the `md-chip` class
140+
el.classList.add('md-chip');
141+
142+
// If we are a basic chip, also add the `md-basic-chip` class for :not() targeting
143+
if (el.nodeName.toLowerCase() == 'md-basic-chip' || el.hasAttribute('md-basic-chip')) {
144+
el.classList.add('md-basic-chip');
141145
}
142146
}
143147

src/lib/chips/chips.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ $md-chips-chip-margin: $md-chip-horizontal-padding / 4;
1515
/*
1616
* Only apply the margins to chips
1717
*/
18-
.md-chip {
18+
.md-chip:not(.md-basic-chip) {
1919
margin: 0 $md-chips-chip-margin 0 $md-chips-chip-margin;
2020

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

53-
.md-chip {
53+
.md-chip:not(.md-basic-chip) {
5454
display: inline-block;
5555
padding: $md-chip-vertical-padding $md-chip-horizontal-padding
5656
$md-chip-vertical-padding $md-chip-horizontal-padding;
@@ -63,7 +63,7 @@ $md-chips-chip-margin: $md-chip-horizontal-padding / 4;
6363
.md-chip-list-stacked .md-chip-list-wrapper {
6464
display: block;
6565

66-
.md-chip {
66+
.md-chip:not(.md-basic-chip) {
6767
display: block;
6868
margin: 0;
6969
margin-bottom: $md-chip-vertical-padding;

0 commit comments

Comments
 (0)