diff --git a/src/core/interaction/interaction.spec.ts b/src/core/interaction/interaction.spec.ts new file mode 100644 index 000000000000..7dbcdc9f56d6 --- /dev/null +++ b/src/core/interaction/interaction.spec.ts @@ -0,0 +1,51 @@ +import { + it, + describe, + expect, + beforeEach, + inject, + beforeEachProviders, +} from 'angular2/testing'; +import {MdInteraction, MdInteractionType} from './interaction'; + +export function main() { + describe('MdInteraction', () => { + let interaction: MdInteraction; + + beforeEachProviders(() => [MdInteraction]); + + beforeEach(inject([MdInteraction], (_interaction: MdInteraction) => { + interaction = _interaction; + })); + + it('should correctly detect the keyboard interaction', () => { + + let event = document.createEvent('Event'); + + event.keyCode = 37; + event.initEvent('keydown', false, true); + + document.body.dispatchEvent(event); + + expect(interaction.getLastInteractionType()).toBe(MdInteractionType.KEYBOARD); + }); + + it('should correctly detect the mouse interaction', () => { + let eventType = 'PointerEvent' in window ? 'pointerdown' : 'mousedown'; + let event = document.createEvent('MouseEvent'); + + event.initMouseEvent(eventType, true, true, window, null, 0, 0, 0, 0, + false, false, false, false, 0, null); + + if (eventType === 'pointerdown') { + // https://msdn.microsoft.com/library/windows/apps/hh466130.aspx + ( event).pointerType = 4; + } + + document.body.dispatchEvent(event); + + expect(interaction.getLastInteractionType()).toBe(MdInteractionType.MOUSE); + }); + + }); +} diff --git a/src/core/interaction/interaction.ts b/src/core/interaction/interaction.ts new file mode 100644 index 000000000000..39ed48d5c6c5 --- /dev/null +++ b/src/core/interaction/interaction.ts @@ -0,0 +1,92 @@ +import {Injectable} from 'angular2/core'; + +export enum MdInteractionType { + KEYBOARD, + MOUSE, + TOUCH +} + +// Interface for the interaction mappings, which holds an index definition +// to avoid implicitly warnings. +interface MdInteractionMap { + [index: string]: any; +} + +@Injectable() +export class MdInteraction { + + private _isBuffering: boolean = false; + private _bufferTimeout: number; + private _lastInteractionType: MdInteractionType; + + private _inputTypeMap: MdInteractionMap = { + 'keydown': MdInteractionType.KEYBOARD, + 'mousedown': MdInteractionType.MOUSE, + 'mouseenter': MdInteractionType.MOUSE, + 'touchstart': MdInteractionType.TOUCH, + 'pointerdown': 'pointer', + 'MSPointerDown': 'pointer', +}; + + // IE dispatches `pointerdown` events, which need to be validated separately. + // Index numbers referenced here: https://msdn.microsoft.com/library/windows/apps/hh466130.aspx + private _iePointerMap: MdInteractionMap = { + 2: MdInteractionType.TOUCH, + 3: MdInteractionType.TOUCH, + 4: MdInteractionType.MOUSE + }; + + constructor() { + let mouseEvent = 'PointerEvent' in window ? 'pointerdown' : 'mousedown'; + + document.body.addEventListener('keydown', (e: Event) => this._onInputEvent(e)); + document.body.addEventListener(mouseEvent, (e: Event) => this._onInputEvent(e)); + document.body.addEventListener('mouseenter', (e: Event) => this._onInputEvent(e)); + + if ('ontouchstart' in document.documentElement) { + document.body.addEventListener('touchstart', (e: TouchEvent) => this._onTouchInputEvent(e)); + } + } + + private _onInputEvent(event: Event) { + if (this._isBuffering) { + return; + } + + let type = this._inputTypeMap[event.type]; + + if (type === 'pointer') { + let pointerType = ( event)['pointerType']; + type = (typeof pointerType === 'number') ? this._iePointerMap[pointerType] + : this._parsePointerType(pointerType); + } + + if (type !== undefined) { + this._lastInteractionType = type; + } + } + + private _onTouchInputEvent(event: TouchEvent) { + clearTimeout(this._bufferTimeout); + + this._onInputEvent(event); + this._isBuffering = true; + + // The timeout of 650ms is needed to delay the touchstart, because otherwise the touch will call + // the `onInput` function multiple times. + this._bufferTimeout = setTimeout(() => this._isBuffering = false, 650); + } + + private _parsePointerType(pointerType: string) { + switch (pointerType) { + case 'mouse': + return MdInteractionType.MOUSE; + case 'touch': + return MdInteractionType.TOUCH; + } + } + + getLastInteractionType(): MdInteractionType { + return this._lastInteractionType; + } +} diff --git a/src/demo-app/demo-app.html b/src/demo-app/demo-app.html index 76a1d72b49e1..3ae4b67aa3aa 100644 --- a/src/demo-app/demo-app.html +++ b/src/demo-app/demo-app.html @@ -15,6 +15,7 @@

Angular Material2 Demos

  • Live Announcer demo
  • Sidenav demo
  • Gestures demo
  • +
  • Interaction demo
  • + + \ No newline at end of file diff --git a/src/demo-app/interaction/interaction-demo.scss b/src/demo-app/interaction/interaction-demo.scss new file mode 100644 index 000000000000..250426e2ebd3 --- /dev/null +++ b/src/demo-app/interaction/interaction-demo.scss @@ -0,0 +1,3 @@ +.demo-interaction { + padding: 8px; +} \ No newline at end of file diff --git a/src/demo-app/interaction/interaction-demo.ts b/src/demo-app/interaction/interaction-demo.ts new file mode 100644 index 000000000000..c46996f4fa70 --- /dev/null +++ b/src/demo-app/interaction/interaction-demo.ts @@ -0,0 +1,31 @@ +import {Component} from 'angular2/core'; +import {MdInteraction, MdInteractionType} from '../../core/interaction/interaction'; +import {MdButton} from '../../components/button/button'; + +@Component({ + selector: 'interaction-demo', + templateUrl: 'demo-app/interaction/interaction-demo.html', + styleUrls: ['demo-app/interaction/interaction-demo.css'], + directives: [MdButton] +}) +export class InteractionDemo { + + constructor(private interaction: MdInteraction) {}; + + alertLastInteraction() { + let lastInteraction = this.interaction.getLastInteractionType(); + + switch (lastInteraction) { + case MdInteractionType.KEYBOARD: + alert('Keyboards are sometimes very loud - Try switching to a mouse.'); + break; + case MdInteractionType.MOUSE: + alert('Using a mouse is cool! What sensitivity are you using?'); + break; + case MdInteractionType.TOUCH: + alert('Material also runs well on touch devices.'); + break; + } + } + +} diff --git a/src/main.ts b/src/main.ts index c0ace470e4df..284001f10bf8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,10 +7,12 @@ import {MdLiveAnnouncer} from './core/live-announcer/live-announcer'; import {provide} from 'angular2/core'; import {createOverlayContainer} from './core/overlay/overlay-container'; import {MdGestureConfig} from './core/gestures/MdGestureConfig'; +import {MdInteraction} from './core/interaction/interaction'; bootstrap(DemoApp, [ ROUTER_PROVIDERS, MdLiveAnnouncer, + MdInteraction, provide(OVERLAY_CONTAINER_TOKEN, {useValue: createOverlayContainer()}), provide(HAMMER_GESTURE_CONFIG, {useClass: MdGestureConfig}) ]);