Skip to content

Commit 7ed5f6e

Browse files
committed
fix(autocomplete): up arrow should set last item active
1 parent 71fb0a6 commit 7ed5f6e

File tree

4 files changed

+64
-6
lines changed

4 files changed

+64
-6
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
6262
@Optional() private _controlDir: NgControl, @Optional() private _dir: Dir) {}
6363

6464
ngAfterContentInit() {
65-
this._keyManager = new ActiveDescendantKeyManager(this.autocomplete.options);
65+
this._keyManager = new ActiveDescendantKeyManager(this.autocomplete.options).withWrap();
6666
}
6767

6868
ngOnDestroy() {
@@ -235,9 +235,9 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
235235
return this._element.nativeElement.getBoundingClientRect().width;
236236
}
237237

238-
/** Reset active item to -1 so DOWN_ARROW event will activate the first option.*/
238+
/** Reset active item to null so arrow events will activate the correct options.*/
239239
private _resetActiveItem(): void {
240-
this._keyManager.setActiveItem(-1);
240+
this._keyManager.setActiveItem(null);
241241
}
242242

243243
/**

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {MdInputModule} from '../input/index';
77
import {Dir, LayoutDirection} from '../core/rtl/dir';
88
import {FormControl, ReactiveFormsModule} from '@angular/forms';
99
import {Subscription} from 'rxjs/Subscription';
10-
import {ENTER, DOWN_ARROW, SPACE} from '../core/keyboard/keycodes';
10+
import {ENTER, DOWN_ARROW, SPACE, UP_ARROW} from '../core/keyboard/keycodes';
1111
import {MdOption} from '../core/option/option';
1212
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
1313

@@ -294,6 +294,36 @@ describe('MdAutocomplete', () => {
294294
});
295295
}));
296296

297+
it('should set the active item to the last option when UP key is pressed', async(() => {
298+
fixture.componentInstance.trigger.openPanel();
299+
fixture.detectChanges();
300+
301+
const optionEls =
302+
overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
303+
304+
const UP_ARROW_EVENT = new FakeKeyboardEvent(UP_ARROW) as KeyboardEvent;
305+
fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT);
306+
307+
fixture.whenStable().then(() => {
308+
fixture.detectChanges();
309+
expect(fixture.componentInstance.trigger.activeOption)
310+
.toBe(fixture.componentInstance.options.last, 'Expected last option to be active.');
311+
expect(optionEls[10].classList).toContain('md-active');
312+
expect(optionEls[0].classList).not.toContain('md-active');
313+
314+
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
315+
316+
fixture.whenStable().then(() => {
317+
fixture.detectChanges();
318+
expect(fixture.componentInstance.trigger.activeOption)
319+
.toBe(fixture.componentInstance.options.first,
320+
'Expected first option to be active.');
321+
expect(optionEls[0].classList).toContain('md-active');
322+
expect(optionEls[10].classList).not.toContain('md-active');
323+
});
324+
});
325+
}));
326+
297327
it('should set the active item properly after filtering', async(() => {
298328
fixture.componentInstance.trigger.openPanel();
299329
fixture.detectChanges();

src/lib/core/a11y/list-key-manager.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ describe('Key managers', () => {
8787
expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(0);
8888
});
8989

90+
it('should set first item active when down arrow pressed if no active item', () => {
91+
keyManager.setActiveItem(null);
92+
keyManager.onKeydown(DOWN_ARROW_EVENT);
93+
94+
expect(keyManager.activeItemIndex)
95+
.toBe(0, 'Expected active item to be 0 after down key if active item was null.');
96+
expect(keyManager.setActiveItem).toHaveBeenCalledWith(0);
97+
expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(1);
98+
expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(2);
99+
});
100+
90101
it('should set previous items as active when up arrow is pressed', () => {
91102
keyManager.onKeydown(DOWN_ARROW_EVENT);
92103

@@ -347,6 +358,22 @@ describe('Key managers', () => {
347358
expect(keyManager.activeItemIndex).toBe(2, 'Expected active item to wrap to end.');
348359
});
349360

361+
it('should set last item active when up arrow is pressed if no active item', () => {
362+
keyManager.withWrap();
363+
keyManager.setActiveItem(null);
364+
keyManager.onKeydown(UP_ARROW_EVENT);
365+
366+
expect(keyManager.activeItemIndex)
367+
.toBe(2, 'Expected last item to be active on up arrow if no active item.');
368+
expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(0);
369+
expect(keyManager.setActiveItem).toHaveBeenCalledWith(2);
370+
371+
keyManager.onKeydown(DOWN_ARROW_EVENT);
372+
expect(keyManager.activeItemIndex)
373+
.toBe(0, 'Expected active item to be 0 after wrapping back to beginning.');
374+
expect(keyManager.setActiveItem).toHaveBeenCalledWith(0);
375+
});
376+
350377
});
351378

352379
});

src/lib/core/a11y/list-key-manager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,13 @@ export class ListKeyManager<T extends CanDisable> {
9696

9797
/** Sets the active item to the next enabled item in the list. */
9898
setNextItemActive(): void {
99-
this._setActiveItemByDelta(1);
99+
this._activeItemIndex === null ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
100100
}
101101

102102
/** Sets the active item to a previous enabled item in the list. */
103103
setPreviousItemActive(): void {
104-
this._setActiveItemByDelta(-1);
104+
this._activeItemIndex === null && this._wrap ? this.setLastItemActive()
105+
: this._setActiveItemByDelta(-1);
105106
}
106107

107108
/**

0 commit comments

Comments
 (0)