mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
@@ -35,7 +35,9 @@
|
||||
</md-input-container>
|
||||
</section>
|
||||
<section>
|
||||
<button md-raised-button (click)="doManualRipple()">Manual ripple</button>
|
||||
<button md-raised-button (click)="launchRipple()">Launch Ripple</button>
|
||||
<button md-raised-button (click)="launchRipple(true)">Launch Ripple (Persistent)</button>
|
||||
<button md-raised-button (click)="fadeOutAll()">Fade Out All</button>
|
||||
</section>
|
||||
<section>
|
||||
<div class="demo-ripple-container"
|
||||
|
||||
@@ -21,9 +21,15 @@ export class RippleDemo {
|
||||
|
||||
disableButtonRipples = false;
|
||||
|
||||
doManualRipple() {
|
||||
launchRipple(persistent = false) {
|
||||
if (this.ripple) {
|
||||
this.ripple.launch(0, 0, { centered: true });
|
||||
this.ripple.launch(0, 0, { centered: true, persistent });
|
||||
}
|
||||
}
|
||||
|
||||
fadeOutAll() {
|
||||
if (this.ripple) {
|
||||
this.ripple.fadeOutAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import {MdLineModule} from './line/line';
|
||||
import {RtlModule} from './rtl/dir';
|
||||
import {ObserveContentModule} from './observe-content/observe-content';
|
||||
import {MdOptionModule} from './option/option';
|
||||
import {MdRippleModule} from './ripple/ripple';
|
||||
import {PortalModule} from './portal/portal-directives';
|
||||
import {OverlayModule} from './overlay/overlay-directives';
|
||||
import {A11yModule} from './a11y/index';
|
||||
import {MdSelectionModule} from './selection/index';
|
||||
import {MdRippleModule} from './ripple/index';
|
||||
|
||||
|
||||
// RTL
|
||||
@@ -64,7 +64,7 @@ export {GestureConfig} from './gestures/gesture-config';
|
||||
export {HammerInput, HammerManager} from './gestures/gesture-annotations';
|
||||
|
||||
// Ripple
|
||||
export {MdRipple, MdRippleModule} from './ripple/ripple';
|
||||
export * from './ripple/index';
|
||||
|
||||
// a11y
|
||||
export {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ENTER, SPACE} from '../keyboard/keycodes';
|
||||
import {coerceBooleanProperty} from '../coercion/boolean-property';
|
||||
import {MdRippleModule} from '../ripple/ripple';
|
||||
import {MdRippleModule} from '../ripple/index';
|
||||
|
||||
/**
|
||||
* Option IDs need to be unique across components, so this counter exists outside of
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import {ModuleWithProviders, NgModule} from '@angular/core';
|
||||
import {MdRipple} from './ripple';
|
||||
import {CompatibilityModule} from '../compatibility/compatibility';
|
||||
import {VIEWPORT_RULER_PROVIDER} from '../overlay/position/viewport-ruler';
|
||||
import {SCROLL_DISPATCHER_PROVIDER} from '../overlay/scroll/scroll-dispatcher';
|
||||
|
||||
export {MdRipple} from './ripple';
|
||||
export {RippleRef} from './ripple-ref';
|
||||
export {RippleConfig} from './ripple-renderer';
|
||||
|
||||
@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: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {RippleConfig, RippleRenderer} from './ripple-renderer';
|
||||
|
||||
/**
|
||||
* Reference to a previously launched ripple element.
|
||||
*/
|
||||
export class RippleRef {
|
||||
|
||||
constructor(
|
||||
private _renderer: RippleRenderer,
|
||||
public element: HTMLElement,
|
||||
public config: RippleConfig) {
|
||||
}
|
||||
|
||||
/** Fades out the ripple element. */
|
||||
fadeOut() {
|
||||
this._renderer.fadeOutRipple(this);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import {ElementRef, NgZone} from '@angular/core';
|
||||
import {ViewportRuler} from '../overlay/position/viewport-ruler';
|
||||
import {RippleRef} from './ripple-ref';
|
||||
|
||||
/** Fade-in duration for the ripples. Can be modified with the speedFactor option. */
|
||||
export const RIPPLE_FADE_IN_DURATION = 450;
|
||||
@@ -7,20 +8,12 @@ export const RIPPLE_FADE_IN_DURATION = 450;
|
||||
/** Fade-out duration for the ripples in milliseconds. This can't be modified by the speedFactor. */
|
||||
export const RIPPLE_FADE_OUT_DURATION = 400;
|
||||
|
||||
/**
|
||||
* Returns the distance from the point (x, y) to the furthest corner of a rectangle.
|
||||
*/
|
||||
const 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);
|
||||
};
|
||||
|
||||
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<string, any>();
|
||||
|
||||
/** Set of currently active ripple references. */
|
||||
private _activeRipples = new Set<RippleRef>();
|
||||
|
||||
/** 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user