Skip to content

Commit

Permalink
feat(rating): add keyboard support
Browse files Browse the repository at this point in the history
Closes #1015
  • Loading branch information
maxokorokov authored and pkozlowski-opensource committed Nov 9, 2016
1 parent cabf9fe commit da0b6a0
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 3 deletions.
92 changes: 92 additions & 0 deletions src/rating/rating.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@ import {Component} from '@angular/core';
import {NgbRatingModule} from './rating.module';
import {NgbRating} from './rating';
import {NgbRatingConfig} from './rating-config';
import {By} from '@angular/platform-browser';

const createTestComponent = (html: string) =>
createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;

enum Key {
End = 35,
Home = 36,
ArrowLeft = 37,
ArrowUp = 38,
ArrowRight = 39,
ArrowDown = 40
}

function createKeyDownEvent(key: number) {
const event = {which: key, preventDefault: () => {}};
spyOn(event, 'preventDefault');
return event;
}

function getAriaState(compiled) {
const stars = getStars(compiled, '.sr-only');
return stars.map(star => star.textContent === '(*)' && star.textContent !== '( )');
Expand Down Expand Up @@ -69,6 +85,25 @@ describe('ngb-rating', () => {
expect(getState(compiled)).toEqual([true, true, true, false, false]);
});

it('sets stars within 0..max limits', () => {
const fixture = createTestComponent('<ngb-rating [rate]="rate" max="5"></ngb-rating>');

const compiled = fixture.nativeElement;
expect(getState(compiled)).toEqual([true, true, true, false, false]);

fixture.componentInstance.rate = 0;
fixture.detectChanges();
expect(getState(compiled)).toEqual([false, false, false, false, false]);

fixture.componentInstance.rate = -5;
fixture.detectChanges();
expect(getState(compiled)).toEqual([false, false, false, false, false]);

fixture.componentInstance.rate = 20;
fixture.detectChanges();
expect(getState(compiled)).toEqual([true, true, true, true, true]);
});

it('handles correctly the click event', () => {
const fixture = createTestComponent('<ngb-rating rate="3" max="5"></ngb-rating>');

Expand Down Expand Up @@ -212,6 +247,63 @@ describe('ngb-rating', () => {
});
});

describe('Keyboard support', () => {

it('should handle arrow keys', () => {
const fixture = createTestComponent('<ngb-rating [rate]="3" [max]="5"></ngb-rating>');

const element = fixture.debugElement.query(By.directive(NgbRating));

// right -> +1
let event = createKeyDownEvent(Key.ArrowRight);
element.triggerEventHandler('keydown', event);
fixture.detectChanges();
expect(getState(element.nativeElement)).toEqual([true, true, true, true, false]);
expect(event.preventDefault).toHaveBeenCalled();

// up -> +1
event = createKeyDownEvent(Key.ArrowUp);
element.triggerEventHandler('keydown', event);
fixture.detectChanges();
expect(getState(element.nativeElement)).toEqual([true, true, true, true, true]);
expect(event.preventDefault).toHaveBeenCalled();

// left -> -1
event = createKeyDownEvent(Key.ArrowLeft);
element.triggerEventHandler('keydown', event);
fixture.detectChanges();
expect(getState(element.nativeElement)).toEqual([true, true, true, true, false]);
expect(event.preventDefault).toHaveBeenCalled();

// down -> -1
event = createKeyDownEvent(Key.ArrowDown);
element.triggerEventHandler('keydown', event);
fixture.detectChanges();
expect(getState(element.nativeElement)).toEqual([true, true, true, false, false]);
expect(event.preventDefault).toHaveBeenCalled();
});

it('should handle home/end keys', () => {
const fixture = createTestComponent('<ngb-rating [rate]="3" [max]="5"></ngb-rating>');

const element = fixture.debugElement.query(By.directive(NgbRating));

// home -> 0
let event = createKeyDownEvent(Key.Home);
element.triggerEventHandler('keydown', event);
fixture.detectChanges();
expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
expect(event.preventDefault).toHaveBeenCalled();

// end -> max
event = createKeyDownEvent(Key.End);
element.triggerEventHandler('keydown', event);
fixture.detectChanges();
expect(getState(element.nativeElement)).toEqual([true, true, true, true, true]);
expect(event.preventDefault).toHaveBeenCalled();
});
});

describe('Custom config', () => {
let config: NgbRatingConfig;

Expand Down
44 changes: 41 additions & 3 deletions src/rating/rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import {
ContentChild
} from '@angular/core';
import {NgbRatingConfig} from './rating-config';
import {toString, getValueInRange} from '../util/util';

enum Key {
End = 35,
Home = 36,
ArrowLeft = 37,
ArrowUp = 38,
ArrowRight = 39,
ArrowDown = 40
}

/**
* Context for the custom star display template
Expand All @@ -28,6 +38,7 @@ export interface StarTemplateContext {
@Component({
selector: 'ngb-rating',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {'(keydown)': 'handleKeyDown($event)'},
template: `
<template #t let-fill="fill">{{ fill === 100 ? '&#9733;' : '&#9734;' }}</template>
<span tabindex="0" (mouseleave)="reset()" role="slider" aria-valuemin="0"
Expand Down Expand Up @@ -100,6 +111,29 @@ export class NgbRating implements OnInit,
this.hover.emit(value);
}

handleKeyDown(event: KeyboardEvent) {
if (Key[toString(event.which)]) {
event.preventDefault();

switch (event.which) {
case Key.ArrowDown:
case Key.ArrowLeft:
this.update(this.rate - 1);
break;
case Key.ArrowUp:
case Key.ArrowRight:
this.update(this.rate + 1);
break;
case Key.Home:
this.update(0);
break;
case Key.End:
this.update(this.max);
break;
}
}
}

getFillValue(index: number): number {
const diff = this.rate - index;

Expand Down Expand Up @@ -128,9 +162,13 @@ export class NgbRating implements OnInit,

update(value: number): void {
if (!this.readonly) {
this._oldRate = value;
this.rate = value;
this.rateChange.emit(value);
const newRate = getValueInRange(value, this.max, 0);

if (this.rate !== newRate) {
this._oldRate = newRate;
this.rate = newRate;
this.rateChange.emit(newRate);
}
}
}
}

0 comments on commit da0b6a0

Please sign in to comment.