{
- const distX = Math.max(Math.abs(x - rect.left), Math.abs(x - rect.right));
- const distY = Math.max(Math.abs(y - rect.top), Math.abs(y - rect.bottom));
- return Math.sqrt(distX * distX + distY * distY);
-};
-
export type RippleConfig = {
color?: string;
centered?: boolean;
radius?: number;
speedFactor?: number;
+ persistent?: boolean;
};
/**
@@ -41,12 +34,12 @@ export class RippleRenderer {
/** Whether the mouse is currently down or not. */
private _isMousedown: boolean = false;
- /** Currently active ripples that will be closed on mouseup. */
- private _activeRipples: HTMLElement[] = [];
-
/** Events to be registered on the trigger element. */
private _triggerEvents = new Map();
+ /** Set of currently active ripple references. */
+ private _activeRipples = new Set();
+
/** Ripple config for all ripples created by events. */
rippleConfig: RippleConfig = {};
@@ -66,7 +59,7 @@ export class RippleRenderer {
}
/** Fades in a ripple at the given coordinates. */
- fadeInRipple(pageX: number, pageY: number, config: RippleConfig = {}) {
+ fadeInRipple(pageX: number, pageY: number, config: RippleConfig = {}): RippleRef {
let containerRect = this._containerElement.getBoundingClientRect();
if (config.centered) {
@@ -101,28 +94,46 @@ export class RippleRenderer {
// By default the browser does not recalculate the styles of dynamically created
// ripple elements. This is critical because then the `scale` would not animate properly.
- this._enforceStyleRecalculation(ripple);
+ enforceStyleRecalculation(ripple);
ripple.style.transform = 'scale(1)';
- // Wait for the ripple to be faded in. Once it's faded in, the ripple can be hidden immediately
- // if the mouse is released.
+ // Exposed reference to the ripple that will be returned.
+ let rippleRef = new RippleRef(this, ripple, config);
+
+ // Wait for the ripple element to be completely faded in.
+ // Once it's faded in, the ripple can be hidden immediately if the mouse is released.
this.runTimeoutOutsideZone(() => {
- this._isMousedown ? this._activeRipples.push(ripple) : this.fadeOutRipple(ripple);
+ if (config.persistent || this._isMousedown) {
+ this._activeRipples.add(rippleRef);
+ } else {
+ rippleRef.fadeOut();
+ }
}, duration);
+
+ return rippleRef;
}
- /** Fades out a ripple element. */
- fadeOutRipple(ripple: HTMLElement) {
- ripple.style.transitionDuration = `${RIPPLE_FADE_OUT_DURATION}ms`;
- ripple.style.opacity = '0';
+ /** Fades out a ripple reference. */
+ fadeOutRipple(ripple: RippleRef) {
+ let rippleEl = ripple.element;
+
+ this._activeRipples.delete(ripple);
+
+ rippleEl.style.transitionDuration = `${RIPPLE_FADE_OUT_DURATION}ms`;
+ rippleEl.style.opacity = '0';
// Once the ripple faded out, the ripple can be safely removed from the DOM.
this.runTimeoutOutsideZone(() => {
- ripple.parentNode.removeChild(ripple);
+ rippleEl.parentNode.removeChild(rippleEl);
}, RIPPLE_FADE_OUT_DURATION);
}
+ /** Fades out all currently active ripples. */
+ fadeOutAll() {
+ this._activeRipples.forEach(ripple => ripple.fadeOut());
+ }
+
/** Sets the trigger element and registers the mouse events. */
setTriggerElement(element: HTMLElement) {
// Remove all previously register event listeners from the trigger element.
@@ -151,8 +162,13 @@ export class RippleRenderer {
/** Listener being called on mouseup event. */
private onMouseup() {
this._isMousedown = false;
- this._activeRipples.forEach(ripple => this.fadeOutRipple(ripple));
- this._activeRipples = [];
+
+ // On mouseup, fade-out all ripples that are active and not persistent.
+ this._activeRipples.forEach(ripple => {
+ if (!ripple.config.persistent) {
+ ripple.fadeOut();
+ }
+ });
}
/** Listener being called on mouseleave event. */
@@ -167,13 +183,22 @@ export class RippleRenderer {
this._ngZone.runOutsideAngular(() => setTimeout(fn, delay));
}
- /** Enforces a style recalculation of a DOM element by computing its styles. */
- // TODO(devversion): Move into global utility function.
- private _enforceStyleRecalculation(element: HTMLElement) {
- // Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
- // Calling `getPropertyValue` is important to let optimizers know that this is not a noop.
- // See: https://gist.github.com/paulirish/5d52fb081b3570c81e3a
- window.getComputedStyle(element).getPropertyValue('opacity');
- }
+}
+
+/** Enforces a style recalculation of a DOM element by computing its styles. */
+// TODO(devversion): Move into global utility function.
+function enforceStyleRecalculation(element: HTMLElement) {
+ // Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
+ // Calling `getPropertyValue` is important to let optimizers know that this is not a noop.
+ // See: https://gist.github.com/paulirish/5d52fb081b3570c81e3a
+ window.getComputedStyle(element).getPropertyValue('opacity');
+}
+/**
+ * Returns the distance from the point (x, y) to the furthest corner of a rectangle.
+ */
+function distanceToFurthestCorner(x: number, y: number, rect: ClientRect) {
+ const distX = Math.max(Math.abs(x - rect.left), Math.abs(x - rect.right));
+ const distY = Math.max(Math.abs(y - rect.top), Math.abs(y - rect.bottom));
+ return Math.sqrt(distX * distX + distY * distY);
}
diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts
index 68e2c62238f4..71531a9abbff 100644
--- a/src/lib/core/ripple/ripple.spec.ts
+++ b/src/lib/core/ripple/ripple.spec.ts
@@ -1,6 +1,6 @@
import {TestBed, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/testing';
import {Component, ViewChild} from '@angular/core';
-import {MdRipple, MdRippleModule} from './ripple';
+import {MdRipple, MdRippleModule} from './index';
import {ViewportRuler} from '../overlay/position/viewport-ruler';
import {RIPPLE_FADE_OUT_DURATION, RIPPLE_FADE_IN_DURATION} from './ripple-renderer';
import {dispatchMouseEvent} from '../testing/dispatch-events';
@@ -239,6 +239,39 @@ describe('MdRipple', () => {
});
+ describe('manual ripples', () => {
+ let rippleDirective: MdRipple;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BasicRippleContainer);
+ fixture.detectChanges();
+
+ rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
+ rippleDirective = fixture.componentInstance.ripple;
+ });
+
+ it('should allow persistent ripple elements', fakeAsync(() => {
+ expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
+
+ let rippleRef = rippleDirective.launch(0, 0, { persistent: true });
+
+ expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
+
+ // Calculates the duration for fading-in and fading-out the ripple. Also adds some
+ // extra time to demonstrate that the ripples are persistent.
+ tick(RIPPLE_FADE_IN_DURATION + RIPPLE_FADE_OUT_DURATION + 5000);
+
+ expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
+
+ rippleRef.fadeOut();
+
+ tick(RIPPLE_FADE_OUT_DURATION);
+
+ expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
+ }));
+
+ });
+
describe('configuring behavior', () => {
let controller: RippleContainerWithInputBindings;
let rippleComponent: MdRipple;
diff --git a/src/lib/core/ripple/ripple.ts b/src/lib/core/ripple/ripple.ts
index d2bd9b6cae2c..5218e7e75e94 100644
--- a/src/lib/core/ripple/ripple.ts
+++ b/src/lib/core/ripple/ripple.ts
@@ -1,6 +1,4 @@
import {
- NgModule,
- ModuleWithProviders,
Directive,
ElementRef,
Input,
@@ -10,9 +8,8 @@ import {
OnDestroy,
} from '@angular/core';
import {RippleConfig, RippleRenderer} from './ripple-renderer';
-import {CompatibilityModule} from '../compatibility/compatibility';
-import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from '../overlay/position/viewport-ruler';
-import {SCROLL_DISPATCHER_PROVIDER} from '../overlay/scroll/scroll-dispatcher';
+import {ViewportRuler} from '../overlay/position/viewport-ruler';
+import {RippleRef} from './ripple-ref';
@Directive({
@@ -87,8 +84,13 @@ export class MdRipple implements OnChanges, OnDestroy {
}
/** Launches a manual ripple at the specified position. */
- launch(pageX: number, pageY: number, config = this.rippleConfig) {
- this._rippleRenderer.fadeInRipple(pageX, pageY, config);
+ launch(pageX: number, pageY: number, config = this.rippleConfig): RippleRef {
+ return this._rippleRenderer.fadeInRipple(pageX, pageY, config);
+ }
+
+ /** Fades out all currently showing ripple elements. */
+ fadeOutAll() {
+ this._rippleRenderer.fadeOutAll();
}
/** Ripple configuration from the directive's input values. */
@@ -100,22 +102,4 @@ export class MdRipple implements OnChanges, OnDestroy {
color: this.color
};
}
-
-}
-
-
-@NgModule({
- imports: [CompatibilityModule],
- exports: [MdRipple, CompatibilityModule],
- declarations: [MdRipple],
- providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER],
-})
-export class MdRippleModule {
- /** @deprecated */
- static forRoot(): ModuleWithProviders {
- return {
- ngModule: MdRippleModule,
- providers: []
- };
- }
}
diff --git a/src/lib/menu/menu.ts b/src/lib/menu/menu.ts
index 27d551aca46f..262ca5e7623e 100644
--- a/src/lib/menu/menu.ts
+++ b/src/lib/menu/menu.ts
@@ -4,7 +4,7 @@ import {OverlayModule, CompatibilityModule} from '../core';
import {MdMenu} from './menu-directive';
import {MdMenuItem} from './menu-item';
import {MdMenuTrigger} from './menu-trigger';
-import {MdRippleModule} from '../core/ripple/ripple';
+import {MdRippleModule} from '../core/ripple/index';
export {MdMenu} from './menu-directive';
export {MdMenuItem} from './menu-item';
export {MdMenuTrigger} from './menu-trigger';
diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts
index 0603b311c281..8f71b43c05ee 100644
--- a/src/lib/slide-toggle/slide-toggle.ts
+++ b/src/lib/slide-toggle/slide-toggle.ts
@@ -23,7 +23,7 @@ import {
CompatibilityModule,
} from '../core';
import {Observable} from 'rxjs/Observable';
-import {MdRippleModule} from '../core/ripple/ripple';
+import {MdRippleModule} from '../core/ripple/index';
export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
diff --git a/src/lib/tabs/tab-body.spec.ts b/src/lib/tabs/tab-body.spec.ts
index f0bae7c6a757..4ff41baf642a 100644
--- a/src/lib/tabs/tab-body.spec.ts
+++ b/src/lib/tabs/tab-body.spec.ts
@@ -3,7 +3,7 @@ import {Component, ViewChild, TemplateRef, ViewContainerRef} from '@angular/core
import {LayoutDirection, Dir} from '../core/rtl/dir';
import {TemplatePortal} from '../core/portal/portal';
import {MdTabBody} from './tab-body';
-import {MdRippleModule} from '../core/ripple/ripple';
+import {MdRippleModule} from '../core/ripple/index';
import {CommonModule} from '@angular/common';
import {PortalModule} from '../core';
diff --git a/src/lib/tabs/tab-group.ts b/src/lib/tabs/tab-group.ts
index c40f9fa5a28f..3b7b8d62d639 100644
--- a/src/lib/tabs/tab-group.ts
+++ b/src/lib/tabs/tab-group.ts
@@ -22,7 +22,7 @@ import {MdTabNavBar, MdTabLink, MdTabLinkRipple} from './tab-nav-bar/tab-nav-bar
import {MdInkBar} from './ink-bar';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
-import {MdRippleModule} from '../core/ripple/ripple';
+import {MdRippleModule} from '../core/ripple/index';
import {ObserveContentModule} from '../core/observe-content/observe-content';
import {MdTab} from './tab';
import {MdTabBody} from './tab-body';
diff --git a/src/lib/tabs/tab-header.spec.ts b/src/lib/tabs/tab-header.spec.ts
index ef7bac7e39c4..3c3e068bd06e 100644
--- a/src/lib/tabs/tab-header.spec.ts
+++ b/src/lib/tabs/tab-header.spec.ts
@@ -2,7 +2,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component, ViewChild, ViewContainerRef} from '@angular/core';
import {LayoutDirection, Dir} from '../core/rtl/dir';
import {MdTabHeader} from './tab-header';
-import {MdRippleModule} from '../core/ripple/ripple';
+import {MdRippleModule} from '../core/ripple/index';
import {CommonModule} from '@angular/common';
import {PortalModule} from '../core';
import {MdInkBar} from './ink-bar';
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 995261e91b53..930f8a9a2f1e 100644
--- a/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
+++ b/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
@@ -8,7 +8,7 @@ import {
NgZone,
} from '@angular/core';
import {MdInkBar} from '../ink-bar';
-import {MdRipple} from '../../core/ripple/ripple';
+import {MdRipple} from '../../core/ripple/index';
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
/**