diff --git a/package.json b/package.json index 1eeac95d1840..13e704dac5eb 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,13 @@ "@angular/platform-browser-dynamic": "^2.2.0", "@angular/router": "^3.2.0", "core-js": "^2.4.1", - "hammerjs": "^2.0.8", "rxjs": "5.0.0-beta.12", "systemjs": "0.19.38", "zone.js": "^0.6.23" }, + "optionalDependencies": { + "hammerjs": "^2.0.8" + }, "devDependencies": { "@angular/compiler-cli": "^2.2.0", "@angular/platform-server": "^2.2.0", diff --git a/src/demo-app/tsconfig.json b/src/demo-app/tsconfig.json index 3a220f91e278..f8acc9304ae2 100644 --- a/src/demo-app/tsconfig.json +++ b/src/demo-app/tsconfig.json @@ -21,10 +21,7 @@ "@angular/material": [ "../../dist/@angular/material" ] - }, - "types": [ - "hammerjs" - ] + } }, "angularCompilerOptions": { "genDir": "../../dist", diff --git a/src/e2e-app/tsconfig.json b/src/e2e-app/tsconfig.json index 678e1786cccc..898fe4d4aceb 100644 --- a/src/e2e-app/tsconfig.json +++ b/src/e2e-app/tsconfig.json @@ -22,10 +22,7 @@ "@angular/material": [ "../../dist/@angular/material" ] - }, - "types": [ - "hammerjs" - ] + } }, "angularCompilerOptions": { "genDir": "../../dist", diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 2a314300e8d7..c9b0a6a5f20d 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -50,6 +50,7 @@ export * from './overlay/position/connected-position'; // Gestures export {GestureConfig} from './gestures/gesture-config'; +export * from './gestures/gesture-annotations'; // Ripple export {MdRipple, MdRippleModule} from './ripple/ripple'; diff --git a/src/lib/core/gestures/gesture-annotations.ts b/src/lib/core/gestures/gesture-annotations.ts new file mode 100644 index 000000000000..480b0a0f4136 --- /dev/null +++ b/src/lib/core/gestures/gesture-annotations.ts @@ -0,0 +1,48 @@ +/** + * Stripped-down HammerJS annotations to be used within Material, which are necessary, + * because HammerJS is an optional dependency. For the full annotations see: + * https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/hammerjs + */ + +/** @docs-private */ +export interface HammerInput { + preventDefault: () => {}; + deltaX: number; + deltaY: number; + center: { x: number; y: number; }; +} + +/** @docs-private */ +export interface HammerStatic { + new(element: HTMLElement | SVGElement, options?: any): HammerManager; + + Pan: Recognizer; + Swipe: Recognizer; + Press: Recognizer; +} + +/** @docs-private */ +export interface Recognizer { + new(options?: any): Recognizer; + recognizeWith(otherRecognizer: Recognizer | string): Recognizer; +} + +/** @docs-private */ +export interface RecognizerStatic { + new(options?: any): Recognizer; +} + +/** @docs-private */ +export interface HammerInstance { + on(eventName: string, callback: Function): void; + off(eventName: string, callback: Function): void; +} + +/** @docs-private */ +export interface HammerManager { + add(recogniser: Recognizer | Recognizer[]): Recognizer; + set(options: any): HammerManager; + emit(event: string, data: any): void; + off(events: string, handler?: Function): void; + on(events: string, handler: Function): void; +} diff --git a/src/lib/core/gestures/gesture-config.ts b/src/lib/core/gestures/gesture-config.ts index 63ad3fcd11d0..3ab4ec804ee5 100644 --- a/src/lib/core/gestures/gesture-config.ts +++ b/src/lib/core/gestures/gesture-config.ts @@ -1,19 +1,32 @@ -import {Injectable} from '@angular/core'; +import {Injectable, isDevMode} from '@angular/core'; import {HammerGestureConfig} from '@angular/platform-browser'; +import {HammerStatic, HammerInstance, Recognizer, RecognizerStatic} from './gesture-annotations'; /* Adjusts configuration of our gesture library, Hammer. */ @Injectable() export class GestureConfig extends HammerGestureConfig { + private _hammer: HammerStatic = typeof window !== 'undefined' ? (window as any).Hammer : null; /* List of new event names to add to the gesture support list */ - events: string[] = [ + events: string[] = this._hammer ? [ 'longpress', 'slide', 'slidestart', 'slideend', 'slideright', 'slideleft' - ]; + ] : []; + + constructor() { + super(); + + if (!this._hammer && isDevMode()) { + console.warn( + 'Could not find HammerJS. Certain Angular Material ' + + 'components may not work correctly.' + ); + } + } /* * Builds Hammer instance manually to add custom recognizers that match the Material Design spec. @@ -28,12 +41,12 @@ export class GestureConfig extends HammerGestureConfig { * TODO: Confirm threshold numbers with Material Design UX Team * */ buildHammer(element: HTMLElement) { - const mc = new Hammer(element); + const mc = new this._hammer(element); // Default Hammer Recognizers. - let pan = new Hammer.Pan(); - let swipe = new Hammer.Swipe(); - let press = new Hammer.Press(); + let pan = new this._hammer.Pan(); + let swipe = new this._hammer.Swipe(); + let press = new this._hammer.Press(); // Notice that a HammerJS recognizer can only depend on one other recognizer once. // Otherwise the previous `recognizeWith` will be dropped. @@ -46,12 +59,12 @@ export class GestureConfig extends HammerGestureConfig { // Add customized gestures to Hammer manager mc.add([swipe, press, pan, slide, longpress]); - return mc; + return mc as HammerInstance; } /** Creates a new recognizer, without affecting the default recognizers of HammerJS */ private _createRecognizer(base: Recognizer, options: any, ...inheritances: Recognizer[]) { - let recognizer = new ( base.constructor)(options); + let recognizer = new (base.constructor as RecognizerStatic)(options); inheritances.push(base); inheritances.forEach(item => recognizer.recognizeWith(item)); diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 09674d2cfc6a..d015590fd92f 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -19,6 +19,7 @@ import { applyCssTransform, coerceBooleanProperty, GestureConfig, + HammerInput, DefaultStyleCompatibilityModeModule, } from '../core'; import {Observable} from 'rxjs/Observable'; diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 3e539b71ad9c..e685e2ac8eea 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -14,11 +14,11 @@ import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/for import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import { GestureConfig, + HammerInput, coerceBooleanProperty, coerceNumberProperty, DefaultStyleCompatibilityModeModule, } from '../core'; -import {Input as HammerInput} from 'hammerjs'; import {Dir} from '../core/rtl/dir'; import {CommonModule} from '@angular/common'; import { @@ -32,7 +32,6 @@ import { DOWN_ARROW, } from '../core/keyboard/keycodes'; - /** * Visually, a 30px separation between tick marks looks best. This is very subjective but it is * the default separation we chose. diff --git a/src/lib/slider/test-gesture-config.ts b/src/lib/slider/test-gesture-config.ts index f4573b002035..4efd37318381 100644 --- a/src/lib/slider/test-gesture-config.ts +++ b/src/lib/slider/test-gesture-config.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {GestureConfig} from '../core'; +import {GestureConfig, HammerManager} from '../core'; /** * An extension of GestureConfig that exposes the underlying HammerManager instances. @@ -17,7 +17,7 @@ export class TestGestureConfig extends GestureConfig { * Create a mapping of Hammer instances to element so that events can be emitted during testing. */ buildHammer(element: HTMLElement) { - let mc = super.buildHammer(element); + let mc = super.buildHammer(element) as HammerManager; if (this.hammerInstances.get(element)) { this.hammerInstances.get(element).push(mc); diff --git a/src/lib/tsconfig-srcs.json b/src/lib/tsconfig-srcs.json index 948f8c15377e..b9f9eb382263 100644 --- a/src/lib/tsconfig-srcs.json +++ b/src/lib/tsconfig-srcs.json @@ -17,8 +17,6 @@ "stripInternal": false, "typeRoots": [ "../../node_modules/@types" - ], - "types": [ ] }, "exclude": [