mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(ripple): support animation duration overwrites (#9253)
* Adds a new option to the ripples that allows developers to have a better control of the animation (all ripples, or even individual ripples). * Deprecates the `matRippleSpeedFactor` in favor of the `matRippleAnimation` binding that accepts a `RippleAnimationConfig`. The configuration is more explicit, clean and not confusing as the `speedFactor`. * To provide a more user-friendly `launch()` method API, the passed ripple config will extend the default ripple config from the `MatRipple` instance (removes unnecessary bloat; requested in https://github.com/angular/material2/issues/4179#issuecomment-295889498) * Disables ripples for most of the demo buttons in the ripple demo (allows better debugging; when pressing the buttons)
This commit is contained in:
committed by
Jeremy Elbourn
parent
d5a7ccea25
commit
3bc4cd308d
@@ -35,9 +35,10 @@
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section>
|
||||
<button mat-raised-button (click)="launchRipple()">Launch Ripple</button>
|
||||
<button mat-raised-button (click)="launchRipple(true)">Launch Ripple (Persistent)</button>
|
||||
<button mat-raised-button (click)="fadeOutAll()">Fade Out All</button>
|
||||
<button mat-raised-button (click)="launchRipple()" disableRipple>Launch Ripple</button>
|
||||
<button mat-raised-button (click)="launchRipple(true)" disableRipple>Launch Ripple (Persistent)</button>
|
||||
<button mat-raised-button (click)="launchRipple(true, true)" disableRipple>Launch Ripple (No Animation)</button>
|
||||
<button mat-raised-button (click)="fadeOutAll()" disableRipple>Fade Out All</button>
|
||||
</section>
|
||||
<section>
|
||||
<div class="demo-ripple-container"
|
||||
|
||||
@@ -29,10 +29,18 @@ export class RippleDemo {
|
||||
|
||||
disableButtonRipples = false;
|
||||
|
||||
launchRipple(persistent = false) {
|
||||
if (this.ripple) {
|
||||
this.ripple.launch(0, 0, { centered: true, persistent });
|
||||
launchRipple(persistent = false, disableAnimation = false) {
|
||||
if (!this.ripple) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rippleConfig = {
|
||||
centered: true,
|
||||
persistent: persistent,
|
||||
animation: disableAnimation ? {enterDuration: 0, exitDuration: 0} : undefined
|
||||
};
|
||||
|
||||
this.ripple.launch(0, 0, rippleConfig);
|
||||
}
|
||||
|
||||
fadeOutAll() {
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<div matRipple class="mat-checkbox-ripple"
|
||||
[matRippleTrigger]="label"
|
||||
[matRippleDisabled]="_isRippleDisabled()"
|
||||
[matRippleRadius]="_rippleConfig.radius"
|
||||
[matRippleSpeedFactor]="_rippleConfig.speedFactor"
|
||||
[matRippleCentered]="_rippleConfig.centered">
|
||||
[matRippleRadius]="25"
|
||||
[matRippleCentered]="true"
|
||||
[matRippleAnimation]="{enterDuration: 300}">
|
||||
</div>
|
||||
<div class="mat-checkbox-frame"></div>
|
||||
<div class="mat-checkbox-background">
|
||||
|
||||
@@ -4,7 +4,7 @@ import {Component, DebugElement} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
import {MatCheckbox, MatCheckboxChange, MatCheckboxModule} from './index';
|
||||
import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '@angular/material/core';
|
||||
import {defaultRippleAnimationConfig} from '@angular/material/core';
|
||||
import {MAT_CHECKBOX_CLICK_ACTION} from './checkbox-config';
|
||||
import {MutationObserverFactory} from '@angular/cdk/observers';
|
||||
|
||||
@@ -390,13 +390,13 @@ describe('MatCheckbox', () => {
|
||||
dispatchFakeEvent(inputElement, 'keydown');
|
||||
dispatchFakeEvent(inputElement, 'focus');
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
tick(defaultRippleAnimationConfig.enterDuration);
|
||||
|
||||
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(1, 'Expected ripple after element is focused.');
|
||||
|
||||
dispatchFakeEvent(checkboxInstance._inputElement.nativeElement, 'blur');
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(defaultRippleAnimationConfig.exitDuration);
|
||||
|
||||
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(0, 'Expected no ripple after element is blurred.');
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
mixinDisabled,
|
||||
mixinDisableRipple,
|
||||
mixinTabIndex,
|
||||
RippleConfig,
|
||||
RippleRef,
|
||||
} from '@angular/material/core';
|
||||
import {MAT_CHECKBOX_CLICK_ACTION, MatCheckboxClickAction} from './checkbox-config';
|
||||
@@ -180,9 +179,6 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
|
||||
/** Reference to the ripple instance of the checkbox. */
|
||||
@ViewChild(MatRipple) ripple: MatRipple;
|
||||
|
||||
/** Ripple configuration for the mouse ripples and focus indicators. */
|
||||
_rippleConfig: RippleConfig = {centered: true, radius: 25, speedFactor: 1.5};
|
||||
|
||||
/**
|
||||
* Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor.
|
||||
* @docs-private
|
||||
@@ -341,7 +337,7 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
|
||||
/** Function is called whenever the focus changes for the input element. */
|
||||
private _onInputFocusChange(focusOrigin: FocusOrigin) {
|
||||
if (!this._focusRipple && focusOrigin === 'keyboard') {
|
||||
this._focusRipple = this.ripple.launch(0, 0, {persistent: true, ...this._rippleConfig});
|
||||
this._focusRipple = this.ripple.launch(0, 0, {persistent: true});
|
||||
} else if (!focusOrigin) {
|
||||
this._removeFocusRipple();
|
||||
this.onTouched();
|
||||
|
||||
@@ -9,26 +9,27 @@ import {ElementRef, NgZone} from '@angular/core';
|
||||
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
|
||||
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;
|
||||
|
||||
/** Fade-out duration for the ripples in milliseconds. This can't be modified by the speedFactor. */
|
||||
export const RIPPLE_FADE_OUT_DURATION = 400;
|
||||
|
||||
/**
|
||||
* Timeout for ignoring mouse events. Mouse events will be temporary ignored after touch
|
||||
* events to avoid synthetic mouse events.
|
||||
*/
|
||||
const IGNORE_MOUSE_EVENTS_TIMEOUT = 800;
|
||||
|
||||
export type RippleConfig = {
|
||||
color?: string;
|
||||
centered?: boolean;
|
||||
radius?: number;
|
||||
speedFactor?: number;
|
||||
persistent?: boolean;
|
||||
animation?: RippleAnimationConfig;
|
||||
/** @deprecated Use the animation property instead. */
|
||||
speedFactor?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface that describes the configuration for the animation of a ripple.
|
||||
* There are two animation phases with different durations for the ripples.
|
||||
*/
|
||||
export interface RippleAnimationConfig {
|
||||
/** Duration in milliseconds for the enter animation (expansion from point of contact). */
|
||||
enterDuration?: number;
|
||||
/** Duration in milliseconds for the exit animation (fade-out). */
|
||||
exitDuration?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that describes the target for launching ripples.
|
||||
* It defines the ripple configuration and disabled state for interaction ripples.
|
||||
@@ -37,11 +38,25 @@ export type RippleConfig = {
|
||||
export interface RippleTarget {
|
||||
/** Configuration for ripples that are launched on pointer down. */
|
||||
rippleConfig: RippleConfig;
|
||||
|
||||
/** Whether ripples on pointer down should be disabled. */
|
||||
rippleDisabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default ripple animation configuration for ripples without an explicit
|
||||
* animation config specified.
|
||||
*/
|
||||
export const defaultRippleAnimationConfig = {
|
||||
enterDuration: 450,
|
||||
exitDuration: 400
|
||||
};
|
||||
|
||||
/**
|
||||
* Timeout for ignoring mouse events. Mouse events will be temporary ignored after touch
|
||||
* events to avoid synthetic mouse events.
|
||||
*/
|
||||
const ignoreMouseEventsTimeout = 800;
|
||||
|
||||
/**
|
||||
* Helper service that performs DOM manipulations. Not intended to be used outside this module.
|
||||
* The constructor takes a reference to the ripple directive's host element and a map of DOM
|
||||
@@ -99,6 +114,7 @@ export class RippleRenderer {
|
||||
*/
|
||||
fadeInRipple(x: number, y: number, config: RippleConfig = {}): RippleRef {
|
||||
const containerRect = this._containerElement.getBoundingClientRect();
|
||||
const animationConfig = {...defaultRippleAnimationConfig, ...config.animation};
|
||||
|
||||
if (config.centered) {
|
||||
x = containerRect.left + containerRect.width / 2;
|
||||
@@ -106,9 +122,9 @@ export class RippleRenderer {
|
||||
}
|
||||
|
||||
const radius = config.radius || distanceToFurthestCorner(x, y, containerRect);
|
||||
const duration = RIPPLE_FADE_IN_DURATION / (config.speedFactor || 1);
|
||||
const offsetX = x - containerRect.left;
|
||||
const offsetY = y - containerRect.top;
|
||||
const duration = animationConfig.enterDuration / (config.speedFactor || 1);
|
||||
|
||||
const ripple = document.createElement('div');
|
||||
ripple.classList.add('mat-ripple-element');
|
||||
@@ -159,8 +175,9 @@ export class RippleRenderer {
|
||||
}
|
||||
|
||||
const rippleEl = rippleRef.element;
|
||||
const animationConfig = {...defaultRippleAnimationConfig, ...rippleRef.config.animation};
|
||||
|
||||
rippleEl.style.transitionDuration = `${RIPPLE_FADE_OUT_DURATION}ms`;
|
||||
rippleEl.style.transitionDuration = `${animationConfig.exitDuration}ms`;
|
||||
rippleEl.style.opacity = '0';
|
||||
|
||||
rippleRef.state = RippleState.FADING_OUT;
|
||||
@@ -169,7 +186,7 @@ export class RippleRenderer {
|
||||
this.runTimeoutOutsideZone(() => {
|
||||
rippleRef.state = RippleState.HIDDEN;
|
||||
rippleEl.parentNode!.removeChild(rippleEl);
|
||||
}, RIPPLE_FADE_OUT_DURATION);
|
||||
}, animationConfig.exitDuration);
|
||||
}
|
||||
|
||||
/** Fades out all currently active ripples. */
|
||||
@@ -197,7 +214,7 @@ export class RippleRenderer {
|
||||
/** Function being called whenever the trigger is being pressed using mouse. */
|
||||
private onMousedown = (event: MouseEvent) => {
|
||||
const isSyntheticEvent = this._lastTouchStartEvent &&
|
||||
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;
|
||||
Date.now() < this._lastTouchStartEvent + ignoreMouseEventsTimeout;
|
||||
|
||||
if (!this._target.rippleDisabled && !isSyntheticEvent) {
|
||||
this._isPointerDown = true;
|
||||
|
||||
@@ -75,8 +75,11 @@ Global ripple options can be specified by setting the `MAT_RIPPLE_GLOBAL_OPTIONS
|
||||
```ts
|
||||
const globalRippleConfig: RippleGlobalOptions = {
|
||||
disabled: true,
|
||||
baseSpeedFactor: 1.5 // Ripples will animate 50% faster than before.
|
||||
}
|
||||
animation: {
|
||||
enterDuration: 300,
|
||||
exitDuration: 0
|
||||
}
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
@@ -86,3 +89,23 @@ const globalRippleConfig: RippleGlobalOptions = {
|
||||
```
|
||||
|
||||
All available global options can be seen in the `RippleGlobalOptions` interface.
|
||||
|
||||
### Disabling animation
|
||||
|
||||
The animation of ripples can be disabled by using the `animation` global option. If the
|
||||
`enterDuration` and `exitDuration` is being set to `0`, ripples will just appear without any
|
||||
animation.
|
||||
|
||||
This is specifically useful in combination with the `disabled` global option, because globally
|
||||
disabling ripples won't affect the focus indicator ripples. If someone still wants to disable
|
||||
those ripples for performance reasons, the duration can be set to `0`, to remove the ripple feel.
|
||||
|
||||
```ts
|
||||
const globalRippleConfig: RippleGlobalOptions = {
|
||||
disabled: true,
|
||||
animation: {
|
||||
enterDuration: 0,
|
||||
exitDuration: 0
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -2,11 +2,13 @@ import {TestBed, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {Platform} from '@angular/cdk/platform';
|
||||
import {dispatchMouseEvent, dispatchTouchEvent} from '@angular/cdk/testing';
|
||||
import {RIPPLE_FADE_OUT_DURATION, RIPPLE_FADE_IN_DURATION} from './ripple-renderer';
|
||||
import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer';
|
||||
import {
|
||||
MatRipple, MatRippleModule, MAT_RIPPLE_GLOBAL_OPTIONS, RippleState, RippleGlobalOptions
|
||||
} from './index';
|
||||
|
||||
/** Shorthands for the enter and exit duration of ripples. */
|
||||
const {enterDuration, exitDuration} = defaultRippleAnimationConfig;
|
||||
|
||||
describe('MatRipple', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
@@ -108,12 +110,12 @@ describe('MatRipple', () => {
|
||||
dispatchTouchEvent(rippleTarget, 'touchstart');
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
tick(enterDuration);
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
dispatchTouchEvent(rippleTarget, 'touchend');
|
||||
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
@@ -122,12 +124,12 @@ describe('MatRipple', () => {
|
||||
dispatchTouchEvent(rippleTarget, 'touchstart');
|
||||
dispatchTouchEvent(rippleTarget, 'mousedown');
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
tick(enterDuration);
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
dispatchTouchEvent(rippleTarget, 'touchend');
|
||||
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
@@ -139,7 +141,7 @@ describe('MatRipple', () => {
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
// Calculates the duration for fading-in and fading-out the ripple.
|
||||
tick(RIPPLE_FADE_IN_DURATION + RIPPLE_FADE_OUT_DURATION);
|
||||
tick(enterDuration + exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
@@ -151,19 +153,19 @@ describe('MatRipple', () => {
|
||||
|
||||
// Fakes the duration of fading-in and fading-out normal ripples.
|
||||
// The fade-out duration has been added to ensure that didn't start fading out.
|
||||
tick(RIPPLE_FADE_IN_DURATION + RIPPLE_FADE_OUT_DURATION);
|
||||
tick(enterDuration + exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
dispatchMouseEvent(rippleTarget, 'mouseup');
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not hide ripples while animating.', fakeAsync(() => {
|
||||
// Calculates the duration for fading-in and fading-out the ripple.
|
||||
let hideDuration = RIPPLE_FADE_IN_DURATION + RIPPLE_FADE_OUT_DURATION;
|
||||
let hideDuration = enterDuration + exitDuration;
|
||||
|
||||
dispatchMouseEvent(rippleTarget, 'mousedown');
|
||||
dispatchMouseEvent(rippleTarget, 'mouseup');
|
||||
@@ -323,13 +325,13 @@ describe('MatRipple', () => {
|
||||
|
||||
// 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);
|
||||
tick(enterDuration + exitDuration + 5000);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
rippleRef.fadeOut();
|
||||
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
@@ -341,11 +343,11 @@ describe('MatRipple', () => {
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION / 2);
|
||||
tick(enterDuration / 2);
|
||||
|
||||
rippleDirective.fadeOutAll();
|
||||
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(0, 'Expected no ripples to be active after calling fadeOutAll.');
|
||||
@@ -359,7 +361,7 @@ describe('MatRipple', () => {
|
||||
expect(rippleRef.state).toBe(RippleState.FADING_IN);
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
tick(enterDuration);
|
||||
|
||||
expect(rippleRef.state).toBe(RippleState.VISIBLE);
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
@@ -369,12 +371,25 @@ describe('MatRipple', () => {
|
||||
expect(rippleRef.state).toBe(RippleState.FADING_OUT);
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(exitDuration);
|
||||
|
||||
expect(rippleRef.state).toBe(RippleState.HIDDEN);
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should allow setting a specific animation config for a ripple', fakeAsync(() => {
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
|
||||
rippleDirective.launch(0, 0, {
|
||||
animation: {enterDuration: 120, exitDuration: 0}
|
||||
});
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
tick(120);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('global ripple options', () => {
|
||||
@@ -449,7 +464,7 @@ describe('MatRipple', () => {
|
||||
let fadeInFactor = 1 / 0.5;
|
||||
|
||||
// Calculates the duration for fading-in and fading-out the ripple.
|
||||
tick(RIPPLE_FADE_IN_DURATION * fadeInFactor + RIPPLE_FADE_OUT_DURATION);
|
||||
tick(enterDuration * fadeInFactor + exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
@@ -466,11 +481,25 @@ describe('MatRipple', () => {
|
||||
let fadeInFactor = 1 / (0.5 * 1.5);
|
||||
|
||||
// Calculates the duration for fading-in and fading-out the ripple.
|
||||
tick(RIPPLE_FADE_IN_DURATION * fadeInFactor + RIPPLE_FADE_OUT_DURATION);
|
||||
tick(enterDuration * fadeInFactor + exitDuration);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should support changing the animation duration', fakeAsync(() => {
|
||||
createTestComponent({
|
||||
animation: {enterDuration: 100, exitDuration: 100}
|
||||
});
|
||||
|
||||
rippleDirective.launch(0, 0);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
// Wait the 200ms of the enter duration and exit duration.
|
||||
tick(100 + 100);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('configuring behavior', () => {
|
||||
@@ -487,7 +516,7 @@ describe('MatRipple', () => {
|
||||
});
|
||||
|
||||
it('sets ripple color', () => {
|
||||
let backgroundColor = 'rgba(12, 34, 56, 0.8)';
|
||||
const backgroundColor = 'rgba(12, 34, 56, 0.8)';
|
||||
|
||||
controller.color = backgroundColor;
|
||||
fixture.detectChanges();
|
||||
@@ -583,6 +612,20 @@ describe('MatRipple', () => {
|
||||
expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * customRadius, 1);
|
||||
expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * customRadius, 1);
|
||||
});
|
||||
|
||||
it('should be able to specify animation config through binding', fakeAsync(() => {
|
||||
controller.animationConfig = {enterDuration: 150, exitDuration: 150};
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchMouseEvent(rippleTarget, 'mousedown');
|
||||
dispatchMouseEvent(rippleTarget, 'mouseup');
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
|
||||
tick(150 + 150);
|
||||
|
||||
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
@@ -607,12 +650,14 @@ class BasicRippleContainer {
|
||||
[matRippleCentered]="centered"
|
||||
[matRippleRadius]="radius"
|
||||
[matRippleDisabled]="disabled"
|
||||
[matRippleAnimation]="animationConfig"
|
||||
[matRippleColor]="color">
|
||||
</div>
|
||||
<div class="alternateTrigger"></div>
|
||||
`,
|
||||
})
|
||||
class RippleContainerWithInputBindings {
|
||||
animationConfig: RippleAnimationConfig;
|
||||
trigger: HTMLElement;
|
||||
centered = false;
|
||||
disabled = false;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
Optional,
|
||||
} from '@angular/core';
|
||||
import {RippleRef} from './ripple-ref';
|
||||
import {RippleConfig, RippleRenderer, RippleTarget} from './ripple-renderer';
|
||||
import {RippleAnimationConfig, RippleConfig, RippleRenderer, RippleTarget} from './ripple-renderer';
|
||||
|
||||
/** Configurable options for `matRipple`. */
|
||||
export interface RippleGlobalOptions {
|
||||
@@ -29,10 +29,17 @@ export interface RippleGlobalOptions {
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Configuration for the animation duration of the ripples.
|
||||
* There are two phases with different durations for the ripples.
|
||||
*/
|
||||
animation?: RippleAnimationConfig;
|
||||
|
||||
/**
|
||||
* If set, the default duration of the fade-in animation is divided by this value. For example,
|
||||
* setting it to 0.5 will cause the ripple fade-in animation to take twice as long.
|
||||
* A changed speedFactor will not affect the fade-out duration of the ripples.
|
||||
* @deprecated Use the `animation` global option instead.
|
||||
*/
|
||||
baseSpeedFactor?: number;
|
||||
}
|
||||
@@ -74,9 +81,16 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
|
||||
* If set, the normal duration of ripple animations is divided by this value. For example,
|
||||
* setting it to 0.5 will cause the animations to take twice as long.
|
||||
* A changed speedFactor will not modify the fade-out duration of the ripples.
|
||||
* @deprecated Use the [matRippleAnimation] binding instead.
|
||||
*/
|
||||
@Input('matRippleSpeedFactor') speedFactor: number = 1;
|
||||
|
||||
/**
|
||||
* Configuration for the ripple animation. Allows modifying the enter and exit animation
|
||||
* duration of the ripples.
|
||||
*/
|
||||
@Input('matRippleAnimation') animation: RippleAnimationConfig;
|
||||
|
||||
/**
|
||||
* Whether click events will not trigger the ripple. Ripples can be still launched manually
|
||||
* by using the `launch()` method.
|
||||
@@ -129,8 +143,8 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
|
||||
}
|
||||
|
||||
/** Launches a manual ripple at the specified position. */
|
||||
launch(x: number, y: number, config: RippleConfig = this): RippleRef {
|
||||
return this._rippleRenderer.fadeInRipple(x, y, config);
|
||||
launch(x: number, y: number, config?: RippleConfig): RippleRef {
|
||||
return this._rippleRenderer.fadeInRipple(x, y, {...this.rippleConfig, ...config});
|
||||
}
|
||||
|
||||
/** Fades out all currently showing ripple elements. */
|
||||
@@ -142,13 +156,14 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
|
||||
get rippleConfig(): RippleConfig {
|
||||
return {
|
||||
centered: this.centered,
|
||||
speedFactor: this.speedFactor * (this._globalOptions.baseSpeedFactor || 1),
|
||||
radius: this.radius,
|
||||
color: this.color
|
||||
color: this.color,
|
||||
animation: {...this._globalOptions.animation, ...this.animation},
|
||||
speedFactor: this.speedFactor * (this._globalOptions.baseSpeedFactor || 1),
|
||||
};
|
||||
}
|
||||
|
||||
/** Whether ripples on pointer-down are disabled or not. */
|
||||
/** Whether ripples on pointer-down are disabled or not. */
|
||||
get rippleDisabled(): boolean {
|
||||
return this.disabled || !!this._globalOptions.disabled;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<div mat-ripple class="mat-radio-ripple"
|
||||
[matRippleTrigger]="label"
|
||||
[matRippleDisabled]="_isRippleDisabled()"
|
||||
[matRippleCentered]="_rippleConfig.centered"
|
||||
[matRippleRadius]="_rippleConfig.radius"
|
||||
[matRippleSpeedFactor]="_rippleConfig.speedFactor">
|
||||
[matRippleCentered]="true"
|
||||
[matRippleRadius]="23"
|
||||
[matRippleAnimation]="{enterDuration: 300}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/f
|
||||
import {Component, DebugElement} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '@angular/material/core';
|
||||
import {defaultRippleAnimationConfig} from '@angular/material/core';
|
||||
import {MatRadioButton, MatRadioChange, MatRadioGroup, MatRadioModule} from './index';
|
||||
|
||||
describe('MatRadio', () => {
|
||||
@@ -205,13 +205,13 @@ describe('MatRadio', () => {
|
||||
dispatchFakeEvent(radioInputElements[0], 'keydown');
|
||||
dispatchFakeEvent(radioInputElements[0], 'focus');
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
tick(defaultRippleAnimationConfig.enterDuration);
|
||||
|
||||
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(1, 'Expected one ripple after keyboard focus.');
|
||||
|
||||
dispatchFakeEvent(radioInputElements[0], 'blur');
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(defaultRippleAnimationConfig.exitDuration);
|
||||
|
||||
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(0, 'Expected no ripples on blur.');
|
||||
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
mixinDisabled,
|
||||
mixinDisableRipple,
|
||||
mixinTabIndex,
|
||||
RippleConfig,
|
||||
RippleRef,
|
||||
} from '@angular/material/core';
|
||||
|
||||
@@ -482,9 +481,6 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
|
||||
/** The child ripple instance. */
|
||||
@ViewChild(MatRipple) _ripple: MatRipple;
|
||||
|
||||
/** Ripple configuration for the mouse ripples and focus indicators. */
|
||||
_rippleConfig: RippleConfig = {centered: true, radius: 23, speedFactor: 1.5};
|
||||
|
||||
/** Reference to the current focus ripple. */
|
||||
private _focusRipple: RippleRef | null;
|
||||
|
||||
@@ -598,7 +594,7 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
|
||||
/** Function is called whenever the focus changes for the input element. */
|
||||
private _onInputFocusChange(focusOrigin: FocusOrigin) {
|
||||
if (!this._focusRipple && focusOrigin === 'keyboard') {
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true, ...this._rippleConfig});
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
|
||||
} else if (!focusOrigin) {
|
||||
if (this.radioGroup) {
|
||||
this.radioGroup._touch();
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
<div class="mat-slide-toggle-ripple" mat-ripple
|
||||
[matRippleTrigger]="label"
|
||||
[matRippleDisabled]="disableRipple || disabled"
|
||||
[matRippleCentered]="_rippleConfig.centered"
|
||||
[matRippleRadius]="_rippleConfig.radius"
|
||||
[matRippleSpeedFactor]="_rippleConfig.speedFactor">
|
||||
[matRippleCentered]="true"
|
||||
[matRippleRadius]="23"
|
||||
[matRippleAnimation]="{enterDuration: 300}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/f
|
||||
import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index';
|
||||
import {TestGestureConfig} from '../slider/test-gesture-config';
|
||||
import {dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '@angular/material/core';
|
||||
import {defaultRippleAnimationConfig} from '@angular/material/core';
|
||||
|
||||
describe('MatSlideToggle without forms', () => {
|
||||
let gestureConfig: TestGestureConfig;
|
||||
@@ -263,14 +263,14 @@ describe('MatSlideToggle without forms', () => {
|
||||
dispatchFakeEvent(inputElement, 'keydown');
|
||||
dispatchFakeEvent(inputElement, 'focus');
|
||||
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
tick(defaultRippleAnimationConfig.enterDuration);
|
||||
|
||||
expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(1, 'Expected the focus ripple to be showing up.');
|
||||
|
||||
dispatchFakeEvent(inputElement, 'blur');
|
||||
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
tick(defaultRippleAnimationConfig.exitDuration);
|
||||
|
||||
expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(0, 'Expected focus ripple to be removed.');
|
||||
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
mixinDisabled,
|
||||
mixinDisableRipple,
|
||||
mixinTabIndex,
|
||||
RippleConfig,
|
||||
RippleRef,
|
||||
} from '@angular/material/core';
|
||||
|
||||
@@ -142,9 +141,6 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
/** Reference to the ripple directive on the thumb container. */
|
||||
@ViewChild(MatRipple) _ripple: MatRipple;
|
||||
|
||||
/** Ripple configuration for the mouse ripples and focus indicators. */
|
||||
_rippleConfig: RippleConfig = {centered: true, radius: 23, speedFactor: 1.5};
|
||||
|
||||
constructor(elementRef: ElementRef,
|
||||
private _platform: Platform,
|
||||
private _focusMonitor: FocusMonitor,
|
||||
@@ -237,7 +233,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
private _onInputFocusChange(focusOrigin: FocusOrigin) {
|
||||
if (!this._focusRipple && focusOrigin === 'keyboard') {
|
||||
// For keyboard focus show a persistent ripple as focus indicator.
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true, ...this._rippleConfig});
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
|
||||
} else if (!focusOrigin) {
|
||||
this.onTouched();
|
||||
|
||||
|
||||
@@ -239,7 +239,10 @@ export class MatTabLink extends _MatTabLinkMixinBase
|
||||
this.tabIndex = parseInt(tabIndex) || 0;
|
||||
|
||||
if (globalOptions) {
|
||||
this.rippleConfig = {speedFactor: globalOptions.baseSpeedFactor};
|
||||
this.rippleConfig = {
|
||||
speedFactor: globalOptions.baseSpeedFactor,
|
||||
animation: globalOptions.animation,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user