diff --git a/src/lib/button-toggle/index.ts b/src/lib/button-toggle/index.ts index f9ea9ef40935..cfdfeb590665 100644 --- a/src/lib/button-toggle/index.ts +++ b/src/lib/button-toggle/index.ts @@ -4,12 +4,12 @@ import {MdButtonToggleGroup, MdButtonToggleGroupMultiple, MdButtonToggle} from ' import { UNIQUE_SELECTION_DISPATCHER_PROVIDER, MdCommonModule, - FocusOriginMonitor, + StyleModule, } from '../core'; @NgModule({ - imports: [FormsModule, MdCommonModule], + imports: [FormsModule, MdCommonModule, StyleModule], exports: [ MdButtonToggleGroup, MdButtonToggleGroupMultiple, @@ -17,7 +17,7 @@ import { MdCommonModule, ], declarations: [MdButtonToggleGroup, MdButtonToggleGroupMultiple, MdButtonToggle], - providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER, FocusOriginMonitor] + providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER] }) export class MdButtonToggleModule {} diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index ab29c24704dd..8efc0341250a 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -9,7 +9,7 @@ import { Renderer2, ViewEncapsulation } from '@angular/core'; -import {coerceBooleanProperty, FocusOriginMonitor} from '../core'; +import {coerceBooleanProperty, FocusOriginMonitor, Platform} from '../core'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; @@ -22,9 +22,7 @@ import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; */ @Directive({ selector: 'button[md-button], button[mat-button], a[md-button], a[mat-button]', - host: { - '[class.mat-button]': 'true' - } + host: {'class': 'mat-button'} }) export class MdButtonCssMatStyler {} @@ -36,9 +34,7 @@ export class MdButtonCssMatStyler {} selector: 'button[md-raised-button], button[mat-raised-button], ' + 'a[md-raised-button], a[mat-raised-button]', - host: { - '[class.mat-raised-button]': 'true' - } + host: {'class': 'mat-raised-button'} }) export class MdRaisedButtonCssMatStyler {} @@ -49,9 +45,7 @@ export class MdRaisedButtonCssMatStyler {} @Directive({ selector: 'button[md-icon-button], button[mat-icon-button], a[md-icon-button], a[mat-icon-button]', - host: { - '[class.mat-icon-button]': 'true', - } + host: {'class': 'mat-icon-button'} }) export class MdIconButtonCssMatStyler {} @@ -61,9 +55,7 @@ export class MdIconButtonCssMatStyler {} */ @Directive({ selector: 'button[md-fab], button[mat-fab], a[md-fab], a[mat-fab]', - host: { - '[class.mat-fab]': 'true' - } + host: {'class': 'mat-fab'} }) export class MdFabCssMatStyler {} @@ -73,9 +65,7 @@ export class MdFabCssMatStyler {} */ @Directive({ selector: 'button[md-mini-fab], button[mat-mini-fab], a[md-mini-fab], a[mat-mini-fab]', - host: { - '[class.mat-mini-fab]': 'true' - } + host: {'class': 'mat-mini-fab'} }) export class MdMiniFabCssMatStyler {} @@ -120,8 +110,11 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl get disableRipple() { return this._disableRipple; } set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); } - constructor(private _elementRef: ElementRef, private _renderer: Renderer2, - private _focusOriginMonitor: FocusOriginMonitor) { + constructor( + private _elementRef: ElementRef, + private _renderer: Renderer2, + private _platform: Platform, + private _focusOriginMonitor: FocusOriginMonitor) { super(); this._focusOriginMonitor.monitor(this._elementRef.nativeElement, this._renderer, true); } @@ -169,6 +162,13 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl * with either an 'md-' or 'mat-' prefix. */ _hasAttributeWithPrefix(...unprefixedAttributeNames: string[]) { + // If not on the browser, say that there are none of the attributes present. + // Since these only affect how the ripple displays (and ripples only happen on the client), + // detecting these attributes isn't necessary when not on the browser. + if (!this._platform.isBrowser) { + return false; + } + return unprefixedAttributeNames.some(suffix => { const el = this._getHostElement(); @@ -195,8 +195,12 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl encapsulation: ViewEncapsulation.None }) export class MdAnchor extends MdButton { - constructor(elementRef: ElementRef, renderer: Renderer2, focusOriginMonitor: FocusOriginMonitor) { - super(elementRef, renderer, focusOriginMonitor); + constructor( + elementRef: ElementRef, + renderer: Renderer2, + platform: Platform, + focusOriginMonitor: FocusOriginMonitor) { + super(elementRef, renderer, platform, focusOriginMonitor); } /** @docs-private */ diff --git a/src/lib/core/a11y/live-announcer.spec.ts b/src/lib/core/a11y/live-announcer.spec.ts index b8cf745e6128..a540d8e9e9a8 100644 --- a/src/lib/core/a11y/live-announcer.spec.ts +++ b/src/lib/core/a11y/live-announcer.spec.ts @@ -2,6 +2,7 @@ import {inject, fakeAsync, tick, ComponentFixture, TestBed} from '@angular/core/ import {Component} from '@angular/core'; import {By} from '@angular/platform-browser'; import {LiveAnnouncer, LIVE_ANNOUNCER_ELEMENT_TOKEN} from './live-announcer'; +import {A11yModule} from '../index'; describe('LiveAnnouncer', () => { @@ -11,8 +12,8 @@ describe('LiveAnnouncer', () => { describe('with default element', () => { beforeEach(() => TestBed.configureTestingModule({ + imports: [A11yModule], declarations: [TestApp], - providers: [LiveAnnouncer] })); beforeEach(fakeAsync(inject([LiveAnnouncer], (la: LiveAnnouncer) => { @@ -77,11 +78,9 @@ describe('LiveAnnouncer', () => { customLiveElement = document.createElement('div'); return TestBed.configureTestingModule({ + imports: [A11yModule], declarations: [TestApp], - providers: [ - {provide: LIVE_ANNOUNCER_ELEMENT_TOKEN, useValue: customLiveElement}, - LiveAnnouncer, - ], + providers: [{provide: LIVE_ANNOUNCER_ELEMENT_TOKEN, useValue: customLiveElement}], }); }); diff --git a/src/lib/core/a11y/live-announcer.ts b/src/lib/core/a11y/live-announcer.ts index 4186b362ece9..bbfecc9733eb 100644 --- a/src/lib/core/a11y/live-announcer.ts +++ b/src/lib/core/a11y/live-announcer.ts @@ -5,6 +5,8 @@ import { Inject, SkipSelf, } from '@angular/core'; +import {Platform} from '../platform/platform'; + export const LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken('liveAnnouncerElement'); @@ -16,12 +18,16 @@ export class LiveAnnouncer { private _liveElement: Element; - constructor(@Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any) { - - // We inject the live element as `any` because the constructor signature cannot reference - // browser globals (HTMLElement) on non-browser environments, since having a class decorator - // causes TypeScript to preserve the constructor signature types. - this._liveElement = elementToken || this._createLiveElement(); + constructor( + @Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any, + platform: Platform) { + // Only do anything if we're on the browser platform. + if (platform.isBrowser) { + // We inject the live element as `any` because the constructor signature cannot reference + // browser globals (HTMLElement) on non-browser environments, since having a class decorator + // causes TypeScript to preserve the constructor signature types. + this._liveElement = elementToken || this._createLiveElement(); + } } /** @@ -64,8 +70,9 @@ export class LiveAnnouncer { } -export function LIVE_ANNOUNCER_PROVIDER_FACTORY(parentDispatcher: LiveAnnouncer, liveElement: any) { - return parentDispatcher || new LiveAnnouncer(liveElement); +export function LIVE_ANNOUNCER_PROVIDER_FACTORY( + parentDispatcher: LiveAnnouncer, liveElement: any, platform: Platform) { + return parentDispatcher || new LiveAnnouncer(liveElement, platform); } export const LIVE_ANNOUNCER_PROVIDER = { @@ -73,7 +80,8 @@ export const LIVE_ANNOUNCER_PROVIDER = { provide: LiveAnnouncer, deps: [ [new Optional(), new SkipSelf(), LiveAnnouncer], - [new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)] + [new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)], + Platform, ], useFactory: LIVE_ANNOUNCER_PROVIDER_FACTORY }; diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index b3ab073e02e6..2e5562b30458 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -29,6 +29,7 @@ import {coerceBooleanProperty} from '../coercion/boolean-property'; import {ESCAPE} from '../keyboard/keycodes'; import {ScrollDispatcher} from './scroll/scroll-dispatcher'; import {Subscription} from 'rxjs/Subscription'; +import {ScrollDispatchModule} from './scroll/index'; /** Default set of positions for the overlay. Follows the behavior of a dropdown. */ @@ -323,9 +324,9 @@ export class ConnectedOverlayDirective implements OnDestroy { @NgModule({ - imports: [PortalModule], - exports: [ConnectedOverlayDirective, OverlayOrigin, Scrollable], - declarations: [ConnectedOverlayDirective, OverlayOrigin, Scrollable], + imports: [PortalModule, ScrollDispatchModule], + exports: [ConnectedOverlayDirective, OverlayOrigin, ScrollDispatchModule], + declarations: [ConnectedOverlayDirective, OverlayOrigin], providers: [OVERLAY_PROVIDERS], }) export class OverlayModule {} diff --git a/src/lib/core/overlay/overlay.ts b/src/lib/core/overlay/overlay.ts index 827e2879836d..1693a830eaa1 100644 --- a/src/lib/core/overlay/overlay.ts +++ b/src/lib/core/overlay/overlay.ts @@ -12,7 +12,6 @@ import {OverlayRef} from './overlay-ref'; import {OverlayPositionBuilder} from './position/overlay-position-builder'; import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler'; import {OverlayContainer, OVERLAY_CONTAINER_PROVIDER} from './overlay-container'; -import {SCROLL_DISPATCHER_PROVIDER} from './scroll/scroll-dispatcher'; /** Next overlay unique ID. */ @@ -94,6 +93,5 @@ export const OVERLAY_PROVIDERS: Provider[] = [ Overlay, OverlayPositionBuilder, VIEWPORT_RULER_PROVIDER, - SCROLL_DISPATCHER_PROVIDER, OVERLAY_CONTAINER_PROVIDER, ]; diff --git a/src/lib/core/overlay/position/connected-position-strategy.spec.ts b/src/lib/core/overlay/position/connected-position-strategy.spec.ts index a6a8473f7880..a6efaeaf25d1 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.spec.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.spec.ts @@ -7,7 +7,7 @@ import {Scrollable} from '../scroll/scrollable'; import {Subscription} from 'rxjs/Subscription'; import {TestBed, inject} from '@angular/core/testing'; import Spy = jasmine.Spy; -import {SCROLL_DISPATCHER_PROVIDER} from '../scroll/scroll-dispatcher'; +import {ScrollDispatchModule} from '../scroll/index'; // Default width and height of the overlay and origin panels throughout these tests. @@ -23,7 +23,8 @@ describe('ConnectedPositionStrategy', () => { let viewportRuler: ViewportRuler; beforeEach(() => TestBed.configureTestingModule({ - providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER] + imports: [ScrollDispatchModule], + providers: [VIEWPORT_RULER_PROVIDER] })); beforeEach(inject([ViewportRuler], (_ruler: ViewportRuler) => { diff --git a/src/lib/core/overlay/position/viewport-ruler.spec.ts b/src/lib/core/overlay/position/viewport-ruler.spec.ts index 999b9db1f8cb..376c50397282 100644 --- a/src/lib/core/overlay/position/viewport-ruler.spec.ts +++ b/src/lib/core/overlay/position/viewport-ruler.spec.ts @@ -1,6 +1,7 @@ import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from './viewport-ruler'; import {TestBed, inject} from '@angular/core/testing'; -import {SCROLL_DISPATCHER_PROVIDER} from '../scroll/scroll-dispatcher'; +import {ScrollDispatchModule} from '../scroll/index'; + // For all tests, we assume the browser window is 1024x786 (outerWidth x outerHeight). // The karma config has been set to this for local tests, and it is the default size @@ -22,7 +23,8 @@ describe('ViewportRuler', () => { veryLargeElement.style.height = '6000px'; beforeEach(() => TestBed.configureTestingModule({ - providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER] + imports: [ScrollDispatchModule], + providers: [VIEWPORT_RULER_PROVIDER] })); beforeEach(inject([ViewportRuler], (viewportRuler: ViewportRuler) => { diff --git a/src/lib/core/overlay/position/viewport-ruler.ts b/src/lib/core/overlay/position/viewport-ruler.ts index 2df3fe3ed5b0..8fb7b87e769f 100644 --- a/src/lib/core/overlay/position/viewport-ruler.ts +++ b/src/lib/core/overlay/position/viewport-ruler.ts @@ -13,15 +13,18 @@ export class ViewportRuler { private _documentRect?: ClientRect; constructor(scrollDispatcher: ScrollDispatcher) { - // Initially cache the document rectangle. - this._cacheViewportGeometry(); - // Subscribe to scroll and resize events and update the document rectangle on changes. scrollDispatcher.scrolled(null, () => this._cacheViewportGeometry()); } /** Gets a ClientRect for the viewport's bounds. */ getViewportRect(documentRect = this._documentRect): ClientRect { + // Cache the document bounding rect so that we don't recompute it for multiple calls. + if (!documentRect) { + this._cacheViewportGeometry(); + documentRect = this._documentRect; + } + // Use the document element's bounding rect rather than the window scroll properties // (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll // properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different @@ -51,6 +54,12 @@ export class ViewportRuler { * @param documentRect */ getViewportScrollPosition(documentRect = this._documentRect) { + // Cache the document bounding rect so that we don't recompute it for multiple calls. + if (!documentRect) { + this._cacheViewportGeometry(); + documentRect = this._documentRect; + } + // The top-left-corner of the viewport is determined by the scroll position of the document // body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about // whether `document.body` or `document.documentElement` is the scrolled element, so reading diff --git a/src/lib/core/overlay/scroll/index.ts b/src/lib/core/overlay/scroll/index.ts new file mode 100644 index 000000000000..44802ecd4e3b --- /dev/null +++ b/src/lib/core/overlay/scroll/index.ts @@ -0,0 +1,15 @@ +import {NgModule} from '@angular/core'; +import {SCROLL_DISPATCHER_PROVIDER} from './scroll-dispatcher'; +import {Scrollable} from './scrollable'; +import {PlatformModule} from '../../platform/index'; + +export {Scrollable} from './scrollable'; +export {ScrollDispatcher} from './scroll-dispatcher'; + +@NgModule({ + imports: [PlatformModule], + exports: [Scrollable], + declarations: [Scrollable], + providers: [SCROLL_DISPATCHER_PROVIDER], +}) +export class ScrollDispatchModule { } diff --git a/src/lib/core/overlay/scroll/scroll-dispatcher.ts b/src/lib/core/overlay/scroll/scroll-dispatcher.ts index 2315d7cc9332..60718bf02e8a 100644 --- a/src/lib/core/overlay/scroll/scroll-dispatcher.ts +++ b/src/lib/core/overlay/scroll/scroll-dispatcher.ts @@ -1,4 +1,5 @@ -import {Injectable, ElementRef, Optional, SkipSelf, NgZone} from '@angular/core'; +import {ElementRef, Injectable, NgZone, Optional, SkipSelf} from '@angular/core'; +import {Platform} from '../../platform/index'; import {Scrollable} from './scrollable'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; @@ -17,7 +18,7 @@ export const DEFAULT_SCROLL_TIME = 20; */ @Injectable() export class ScrollDispatcher { - constructor(private _ngZone: NgZone) { } + constructor(private _ngZone: NgZone, private _platform: Platform) { } /** Subject for notifying that a registered scrollable reference element has been scrolled. */ _scrolled: Subject = new Subject(); @@ -62,6 +63,11 @@ export class ScrollDispatcher { * to override the default "throttle" time. */ scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME, callback: () => any): Subscription { + // Scroll events can only happen on the browser, so do nothing if we're not on the browser. + if (!this._platform.isBrowser) { + return Subscription.EMPTY; + } + // In the case of a 0ms delay, use an observable without auditTime // since it does add a perceptible delay in processing overhead. let observable = auditTimeInMs > 0 ? @@ -126,14 +132,14 @@ export class ScrollDispatcher { } } -export function SCROLL_DISPATCHER_PROVIDER_FACTORY(parentDispatcher: ScrollDispatcher, - ngZone: NgZone) { - return parentDispatcher || new ScrollDispatcher(ngZone); +export function SCROLL_DISPATCHER_PROVIDER_FACTORY( + parentDispatcher: ScrollDispatcher, ngZone: NgZone, platform: Platform) { + return parentDispatcher || new ScrollDispatcher(ngZone, platform); } export const SCROLL_DISPATCHER_PROVIDER = { // If there is already a ScrollDispatcher available, use that. Otherwise, provide a new one. provide: ScrollDispatcher, - deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone], + deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone, Platform], useFactory: SCROLL_DISPATCHER_PROVIDER_FACTORY }; diff --git a/src/lib/core/platform/platform.ts b/src/lib/core/platform/platform.ts index df0a7b5ac90b..3437ea6d94a9 100755 --- a/src/lib/core/platform/platform.ts +++ b/src/lib/core/platform/platform.ts @@ -15,6 +15,8 @@ const hasV8BreakIterator = typeof(window) !== 'undefined' ? */ @Injectable() export class Platform { + isBrowser: boolean = typeof document === 'object' && !!document; + /** Layout Engines */ EDGE = /(edge)/i.test(navigator.userAgent); TRIDENT = /(msie|trident)/i.test(navigator.userAgent); diff --git a/src/lib/core/ripple/index.ts b/src/lib/core/ripple/index.ts index 91f2c845da4a..c16b39d08958 100644 --- a/src/lib/core/ripple/index.ts +++ b/src/lib/core/ripple/index.ts @@ -2,16 +2,17 @@ import {NgModule} from '@angular/core'; import {MdRipple} from './ripple'; import {MdCommonModule} from '../common-behaviors/common-module'; import {VIEWPORT_RULER_PROVIDER} from '../overlay/position/viewport-ruler'; -import {SCROLL_DISPATCHER_PROVIDER} from '../overlay/scroll/scroll-dispatcher'; +import {ScrollDispatchModule} from '../overlay/scroll/index'; +import {PlatformModule} from '../platform/index'; export {MdRipple, RippleGlobalOptions, MD_RIPPLE_GLOBAL_OPTIONS} from './ripple'; export {RippleRef, RippleState} from './ripple-ref'; export {RippleConfig, RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from './ripple-renderer'; @NgModule({ - imports: [MdCommonModule], + imports: [MdCommonModule, PlatformModule, ScrollDispatchModule], exports: [MdRipple, MdCommonModule], declarations: [MdRipple], - providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER], + providers: [VIEWPORT_RULER_PROVIDER], }) export class MdRippleModule {} diff --git a/src/lib/core/ripple/ripple-renderer.ts b/src/lib/core/ripple/ripple-renderer.ts index f4e15d869213..bd93b5c54854 100644 --- a/src/lib/core/ripple/ripple-renderer.ts +++ b/src/lib/core/ripple/ripple-renderer.ts @@ -1,7 +1,9 @@ import {ElementRef, NgZone} from '@angular/core'; +import {Platform} from '../platform/platform'; import {ViewportRuler} from '../overlay/position/viewport-ruler'; import {RippleRef, RippleState} from './ripple-ref'; + /** Fade-in duration for the ripples. Can be modified with the speedFactor option. */ export const RIPPLE_FADE_IN_DURATION = 450; @@ -46,16 +48,23 @@ export class RippleRenderer { /** Whether mouse ripples should be created or not. */ rippleDisabled: boolean = false; - constructor(_elementRef: ElementRef, private _ngZone: NgZone, private _ruler: ViewportRuler) { - this._containerElement = _elementRef.nativeElement; - - // Specify events which need to be registered on the trigger. - this._triggerEvents.set('mousedown', this.onMousedown.bind(this)); - this._triggerEvents.set('mouseup', this.onMouseup.bind(this)); - this._triggerEvents.set('mouseleave', this.onMouseLeave.bind(this)); - - // By default use the host element as trigger element. - this.setTriggerElement(this._containerElement); + constructor( + elementRef: ElementRef, + private _ngZone: NgZone, + private _ruler: ViewportRuler, + platform: Platform) { + // Only do anything if we're on the browser. + if (platform.isBrowser) { + this._containerElement = elementRef.nativeElement; + + // Specify events which need to be registered on the trigger. + this._triggerEvents.set('mousedown', this.onMousedown.bind(this)); + this._triggerEvents.set('mouseup', this.onMouseup.bind(this)); + this._triggerEvents.set('mouseleave', this.onMouseLeave.bind(this)); + + // By default use the host element as trigger element. + this.setTriggerElement(this._containerElement); + } } /** Fades in a ripple at the given coordinates. */ diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts index 8efe51205949..ef2330601ad4 100644 --- a/src/lib/core/ripple/ripple.spec.ts +++ b/src/lib/core/ripple/ripple.spec.ts @@ -56,6 +56,34 @@ describe('MdRipple', () => { rippleDirective = fixture.componentInstance.ripple; }); + it('sizes ripple to cover element', () => { + let elementRect = rippleTarget.getBoundingClientRect(); + + // Dispatch a ripple at the following relative coordinates (X: 50| Y: 75) + dispatchMouseEvent(rippleTarget, 'mousedown', 50, 75); + dispatchMouseEvent(rippleTarget, 'mouseup'); + + // Calculate distance from the click to farthest edge of the ripple target. + let maxDistanceX = TARGET_WIDTH - 50; + let maxDistanceY = TARGET_HEIGHT - 75; + + // At this point the foreground ripple should be created with a div centered at the click + // location, and large enough to reach the furthest corner, which is 250px to the right + // and 125px down relative to the click position. + let expectedRadius = Math.sqrt(maxDistanceX * maxDistanceX + maxDistanceY * maxDistanceY); + let expectedLeft = elementRect.left + 50 - expectedRadius; + let expectedTop = elementRect.top + 75 - expectedRadius; + + let ripple = rippleTarget.querySelector('.mat-ripple-element') as HTMLElement; + + // Note: getBoundingClientRect won't work because there's a transform applied to make the + // ripple start out tiny. + expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); + expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); + expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); + expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1); + }); + it('creates ripple on mousedown', () => { dispatchMouseEvent(rippleTarget, 'mousedown'); dispatchMouseEvent(rippleTarget, 'mouseup'); @@ -137,35 +165,6 @@ describe('MdRipple', () => { expect(parseFloat(rippleElement.style.top)).toBeCloseTo(TARGET_HEIGHT / 2 - radius, 1); }); - it('sizes ripple to cover element', () => { - let elementRect = rippleTarget.getBoundingClientRect(); - - // Dispatch a ripple at the following relative coordinates (X: 50| Y: 75) - dispatchMouseEvent(rippleTarget, 'mousedown', 50, 75); - dispatchMouseEvent(rippleTarget, 'mouseup'); - - // Calculate distance from the click to farthest edge of the ripple target. - let maxDistanceX = TARGET_WIDTH - 50; - let maxDistanceY = TARGET_HEIGHT - 75; - - // At this point the foreground ripple should be created with a div centered at the click - // location, and large enough to reach the furthest corner, which is 250px to the right - // and 125px down relative to the click position. - let expectedRadius = Math.sqrt(maxDistanceX * maxDistanceX + maxDistanceY * maxDistanceY); - let expectedLeft = elementRect.left + 50 - expectedRadius; - let expectedTop = elementRect.top + 75 - expectedRadius; - - let ripple = rippleTarget.querySelector('.mat-ripple-element') as HTMLElement; - - // Note: getBoundingClientRect won't work because there's a transform applied to make the - // ripple start out tiny. - expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); - expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); - expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); - expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1); - }); - - it('cleans up the event handlers when the container gets destroyed', () => { fixture = TestBed.createComponent(RippleContainerWithNgIf); fixture.detectChanges(); diff --git a/src/lib/core/ripple/ripple.ts b/src/lib/core/ripple/ripple.ts index 73e2835c8925..3cf0f097cfee 100644 --- a/src/lib/core/ripple/ripple.ts +++ b/src/lib/core/ripple/ripple.ts @@ -11,8 +11,9 @@ import { Optional, } from '@angular/core'; import {RippleConfig, RippleRenderer} from './ripple-renderer'; -import {ViewportRuler} from '../overlay/position/viewport-ruler'; import {RippleRef} from './ripple-ref'; +import {ViewportRuler} from '../overlay/position/viewport-ruler'; +import {Platform} from '../platform/platform'; export interface RippleGlobalOptions { disabled?: boolean; @@ -27,7 +28,7 @@ export const MD_RIPPLE_GLOBAL_OPTIONS = selector: '[md-ripple], [mat-ripple], [mdRipple], [matRipple]', exportAs: 'mdRipple', host: { - '[class.mat-ripple]': 'true', + 'class': 'mat-ripple', '[class.mat-ripple-unbounded]': 'unbounded' } }) @@ -83,9 +84,10 @@ export class MdRipple implements OnChanges, OnDestroy { elementRef: ElementRef, ngZone: NgZone, ruler: ViewportRuler, + platform: Platform, @Optional() @Inject(MD_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions ) { - this._rippleRenderer = new RippleRenderer(elementRef, ngZone, ruler); + this._rippleRenderer = new RippleRenderer(elementRef, ngZone, ruler, platform); this._globalOptions = globalOptions ? globalOptions : {}; this._updateRippleRenderer(); diff --git a/src/lib/core/style/focus-origin-monitor.ts b/src/lib/core/style/focus-origin-monitor.ts index 151f05da20ca..e76d90269626 100644 --- a/src/lib/core/style/focus-origin-monitor.ts +++ b/src/lib/core/style/focus-origin-monitor.ts @@ -12,6 +12,7 @@ import { } from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; +import {Platform} from '../platform/platform'; // This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found @@ -51,7 +52,7 @@ export class FocusOriginMonitor { /** Weak map of elements being monitored to their info. */ private _elementInfo = new WeakMap(); - constructor(private _ngZone: NgZone) { + constructor(private _ngZone: NgZone, private _platform: Platform) { this._ngZone.runOutsideAngular(() => this._registerDocumentEvents()); } @@ -63,9 +64,14 @@ export class FocusOriginMonitor { * @returns An observable that emits when the focus state of the element changes. * When the element is blurred, null will be emitted. */ - monitor(element: HTMLElement, renderer: Renderer2, checkChildren: boolean): - Observable { - + monitor( + element: HTMLElement, + renderer: Renderer2, + checkChildren: boolean): Observable { + // Do nothing if we're not on the browser platform. + if (!this._platform.isBrowser) { + return Observable.of(); + } // Check if we're already monitoring this element. if (this._elementInfo.has(element)) { let info = this._elementInfo.get(element); @@ -128,6 +134,11 @@ export class FocusOriginMonitor { /** Register necessary event listeners on the document and window. */ private _registerDocumentEvents() { + // Do nothing if we're not on the browser platform. + if (!this._platform.isBrowser) { + return; + } + // Note: we listen to events in the capture phase so we can detect them even if the user stops // propagation. @@ -306,15 +317,15 @@ export class CdkMonitorFocus implements OnDestroy { } -export function FOCUS_ORIGIN_MONITOR_PROVIDER_FACTORY(parentDispatcher: FocusOriginMonitor, - ngZone: NgZone) { - return parentDispatcher || new FocusOriginMonitor(ngZone); +export function FOCUS_ORIGIN_MONITOR_PROVIDER_FACTORY( + parentDispatcher: FocusOriginMonitor, ngZone: NgZone, platform: Platform) { + return parentDispatcher || new FocusOriginMonitor(ngZone, platform); } export const FOCUS_ORIGIN_MONITOR_PROVIDER = { // If there is already a FocusOriginMonitor available, use that. Otherwise, provide a new one. provide: FocusOriginMonitor, - deps: [[new Optional(), new SkipSelf(), FocusOriginMonitor], NgZone], + deps: [[new Optional(), new SkipSelf(), FocusOriginMonitor], NgZone, Platform], useFactory: FOCUS_ORIGIN_MONITOR_PROVIDER_FACTORY }; diff --git a/src/lib/core/style/index.ts b/src/lib/core/style/index.ts index a61a25dbb43a..52da7eded97f 100644 --- a/src/lib/core/style/index.ts +++ b/src/lib/core/style/index.ts @@ -1,8 +1,10 @@ import {NgModule} from '@angular/core'; import {CdkMonitorFocus, FOCUS_ORIGIN_MONITOR_PROVIDER} from './focus-origin-monitor'; +import {PlatformModule} from '../platform/index'; @NgModule({ + imports: [PlatformModule], declarations: [CdkMonitorFocus], exports: [CdkMonitorFocus], providers: [FOCUS_ORIGIN_MONITOR_PROVIDER], diff --git a/src/lib/tabs/index.ts b/src/lib/tabs/index.ts index e523c6826f70..68b85419a5e3 100644 --- a/src/lib/tabs/index.ts +++ b/src/lib/tabs/index.ts @@ -12,7 +12,7 @@ import {MdInkBar} from './ink-bar'; import {MdTabBody} from './tab-body'; import {VIEWPORT_RULER_PROVIDER} from '../core/overlay/position/viewport-ruler'; import {MdTabHeader} from './tab-header'; -import {SCROLL_DISPATCHER_PROVIDER} from '../core/overlay/scroll/scroll-dispatcher'; +import {ScrollDispatchModule} from '../core/overlay/scroll/index'; @NgModule({ @@ -21,6 +21,7 @@ import {SCROLL_DISPATCHER_PROVIDER} from '../core/overlay/scroll/scroll-dispatch PortalModule, MdRippleModule, ObserveContentModule, + ScrollDispatchModule, ], // Don't export all components because some are only to be used internally. exports: [ @@ -43,7 +44,7 @@ import {SCROLL_DISPATCHER_PROVIDER} from '../core/overlay/scroll/scroll-dispatch MdTabLinkRipple, MdTabHeader ], - providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER], + providers: [VIEWPORT_RULER_PROVIDER], }) export class MdTabsModule {} diff --git a/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts b/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts index 76c363d108b9..3ca54f6f46d9 100644 --- a/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts @@ -14,7 +14,7 @@ import { import {MdInkBar} from '../ink-bar'; import {MdRipple} from '../../core/ripple/index'; import {ViewportRuler} from '../../core/overlay/position/viewport-ruler'; -import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, Dir} from '../../core'; +import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, Dir, Platform} from '../../core'; import {Observable} from 'rxjs/Observable'; import {Subscription} from 'rxjs/Subscription'; import 'rxjs/add/operator/auditTime'; @@ -30,9 +30,7 @@ import 'rxjs/add/observable/merge'; selector: '[md-tab-nav-bar], [mat-tab-nav-bar]', templateUrl: 'tab-nav-bar.html', styleUrls: ['tab-nav-bar.css'], - host: { - '[class.mat-tab-nav-bar]': 'true', - }, + host: {'class': 'mat-tab-nav-bar'}, encapsulation: ViewEncapsulation.None, }) export class MdTabNavBar implements AfterContentInit, OnDestroy { @@ -91,9 +89,7 @@ export class MdTabNavBar implements AfterContentInit, OnDestroy { */ @Directive({ selector: '[md-tab-link], [mat-tab-link]', - host: { - '[class.mat-tab-link]': 'true', - } + host: {'class': 'mat-tab-link'} }) export class MdTabLink { private _isActive: boolean = false; @@ -117,13 +113,15 @@ export class MdTabLink { */ @Directive({ selector: '[md-tab-link], [mat-tab-link]', - host: { - '[class.mat-tab-link]': 'true', - }, + host: {'class': 'mat-tab-link'}, }) export class MdTabLinkRipple extends MdRipple { - constructor(elementRef: ElementRef, ngZone: NgZone, ruler: ViewportRuler, - @Optional() @Inject(MD_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) { - super(elementRef, ngZone, ruler, globalOptions); + constructor( + elementRef: ElementRef, + ngZone: NgZone, + ruler: ViewportRuler, + platform: Platform, + @Optional() @Inject(MD_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) { + super(elementRef, ngZone, ruler, platform, globalOptions); } } diff --git a/src/lib/tooltip/tooltip.spec.ts b/src/lib/tooltip/tooltip.spec.ts index f9915cff266f..1b9f0d18fef0 100644 --- a/src/lib/tooltip/tooltip.spec.ts +++ b/src/lib/tooltip/tooltip.spec.ts @@ -35,7 +35,7 @@ describe('MdTooltip', () => { imports: [MdTooltipModule, OverlayModule, NoopAnimationsModule], declarations: [BasicTooltipDemo, ScrollableTooltipDemo, OnPushTooltipDemo], providers: [ - {provide: Platform, useValue: {IOS: false}}, + {provide: Platform, useValue: {IOS: false, isBrowser: true}}, {provide: OverlayContainer, useFactory: () => { overlayContainerElement = document.createElement('div'); document.body.appendChild(overlayContainerElement); diff --git a/tools/gulp/util/rollup-helper.ts b/tools/gulp/util/rollup-helper.ts index fe56b6932610..e1b7c158dc65 100644 --- a/tools/gulp/util/rollup-helper.ts +++ b/tools/gulp/util/rollup-helper.ts @@ -37,7 +37,8 @@ const ROLLUP_GLOBALS = { 'rxjs/add/operator/startWith': 'Rx.Observable.prototype', 'rxjs/add/operator/switchMap': 'Rx.Observable.prototype', 'rxjs/add/operator/debounceTime': 'Rx.Observable.prototype', - 'rxjs/Observable': 'Rx' + 'rxjs/Observable': 'Rx', + 'rxjs/Subscription': 'Rx', }; export type BundleConfig = {