Skip to content

Commit 1def6cc

Browse files
committed
update(ripple): remove deprecated speed factor option.
* Removes the deprecated `[matRippleSpeedFactor]` and global `baseSpeedFactor` option. * Adds update rules that can automatically switch to the new API (as much as possible) and even calculate the new durations based on the previously specified speed factor. BREAKING CHANGE: deprecated `[matRippleSpeedFactor]` and `baseSpeedFactor` for the ripples have been removed. Use the new animation config instead.
1 parent f35a314 commit 1def6cc

File tree

9 files changed

+223
-77
lines changed

9 files changed

+223
-77
lines changed

src/lib/chips/chip.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,9 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
229229
this._chipRipple.setupTriggerEvents(_elementRef.nativeElement);
230230

231231
if (globalOptions) {
232+
// TODO(paul): Do not copy each option manually. Allow dynamic global option changes: #9729
232233
this._ripplesGloballyDisabled = !!globalOptions.disabled;
233-
// TODO(paul): Once the speedFactor is removed, we no longer need to copy each single option.
234234
this.rippleConfig = {
235-
speedFactor: globalOptions.baseSpeedFactor,
236235
animation: globalOptions.animation,
237236
terminateOnPointerUp: globalOptions.terminateOnPointerUp,
238237
};

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ export type RippleConfig = {
1616
persistent?: boolean;
1717
animation?: RippleAnimationConfig;
1818
terminateOnPointerUp?: boolean;
19-
/**
20-
* @deprecated Use the `animation` property instead.
21-
* @deletion-target 7.0.0
22-
*/
23-
speedFactor?: number;
2419
};
2520

2621
/**
@@ -137,7 +132,7 @@ export class RippleRenderer {
137132
const radius = config.radius || distanceToFurthestCorner(x, y, containerRect);
138133
const offsetX = x - containerRect.left;
139134
const offsetY = y - containerRect.top;
140-
const duration = animationConfig.enterDuration / (config.speedFactor || 1);
135+
const duration = animationConfig.enterDuration;
141136

142137
const ripple = document.createElement('div');
143138
ripple.classList.add('mat-ripple-element');

src/lib/core/ripple/ripple.spec.ts

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('MatRipple', () => {
5656
fixture = TestBed.createComponent(BasicRippleContainer);
5757
fixture.detectChanges();
5858

59-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
59+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
6060
rippleDirective = fixture.componentInstance.ripple;
6161
});
6262

@@ -210,7 +210,7 @@ describe('MatRipple', () => {
210210
fixture = TestBed.createComponent(RippleContainerWithNgIf);
211211
fixture.detectChanges();
212212

213-
rippleTarget = fixture.debugElement.nativeElement.querySelector('[mat-ripple]');
213+
rippleTarget = fixture.debugElement.nativeElement.querySelector('.mat-ripple');
214214

215215
fixture.componentInstance.isDestroyed = true;
216216
fixture.detectChanges();
@@ -325,7 +325,7 @@ describe('MatRipple', () => {
325325
fixture = TestBed.createComponent(BasicRippleContainer);
326326
fixture.detectChanges();
327327

328-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
328+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
329329
rippleDirective = fixture.componentInstance.ripple;
330330
});
331331

@@ -440,7 +440,7 @@ describe('MatRipple', () => {
440440
fixture = TestBed.createComponent(testComponent);
441441
fixture.detectChanges();
442442

443-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
443+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
444444
rippleDirective = fixture.componentInstance.ripple;
445445
}
446446

@@ -482,41 +482,6 @@ describe('MatRipple', () => {
482482
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
483483
});
484484

485-
it('should support changing the baseSpeedFactor', fakeAsync(() => {
486-
createTestComponent({ baseSpeedFactor: 0.5 });
487-
488-
dispatchMouseEvent(rippleTarget, 'mousedown');
489-
dispatchMouseEvent(rippleTarget, 'mouseup');
490-
491-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
492-
493-
// Calculates the speedFactor for the duration. Those factors needs to be inverted, because
494-
// a lower speed factor, will make the duration longer. For example: 0.5 => 2x duration.
495-
let fadeInFactor = 1 / 0.5;
496-
497-
// Calculates the duration for fading-in and fading-out the ripple.
498-
tick(enterDuration * fadeInFactor + exitDuration);
499-
500-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
501-
}));
502-
503-
it('should combine individual speed factor with baseSpeedFactor', fakeAsync(() => {
504-
createTestComponent({ baseSpeedFactor: 0.5 });
505-
506-
rippleDirective.launch(0, 0, { speedFactor: 1.5 });
507-
508-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
509-
510-
// Calculates the speedFactor for the duration. Those factors needs to be inverted, because
511-
// a lower speed factor, will make the duration longer. For example: 0.5 => 2x duration.
512-
let fadeInFactor = 1 / (0.5 * 1.5);
513-
514-
// Calculates the duration for fading-in and fading-out the ripple.
515-
tick(enterDuration * fadeInFactor + exitDuration);
516-
517-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
518-
}));
519-
520485
it('should support changing the animation duration', fakeAsync(() => {
521486
createTestComponent({
522487
animation: {enterDuration: 100, exitDuration: 100}
@@ -567,7 +532,7 @@ describe('MatRipple', () => {
567532
fixture = TestBed.createComponent(BasicRippleContainer);
568533
fixture.detectChanges();
569534

570-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
535+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
571536
rippleDirective = fixture.componentInstance.ripple;
572537
});
573538

@@ -585,7 +550,7 @@ describe('MatRipple', () => {
585550
fixture.detectChanges();
586551

587552
controller = fixture.debugElement.componentInstance;
588-
rippleTarget = fixture.debugElement.nativeElement.querySelector('[mat-ripple]');
553+
rippleTarget = fixture.debugElement.nativeElement.querySelector('.mat-ripple');
589554
});
590555

591556
it('sets ripple color', () => {
@@ -705,7 +670,7 @@ describe('MatRipple', () => {
705670

706671
@Component({
707672
template: `
708-
<div id="container" #ripple="matRipple" mat-ripple [matRippleSpeedFactor]="0"
673+
<div id="container" #ripple="matRipple" matRipple
709674
style="position: relative; width:300px; height:200px;">
710675
</div>
711676
`,
@@ -717,8 +682,7 @@ class BasicRippleContainer {
717682
@Component({
718683
template: `
719684
<div id="container" style="position: relative; width:300px; height:200px;"
720-
mat-ripple
721-
[matRippleSpeedFactor]="0"
685+
matRipple
722686
[matRippleTrigger]="trigger"
723687
[matRippleCentered]="centered"
724688
[matRippleRadius]="radius"
@@ -740,11 +704,11 @@ class RippleContainerWithInputBindings {
740704
}
741705

742706
@Component({
743-
template: `<div id="container" #ripple="matRipple" mat-ripple></div>`,
707+
template: `<div id="container" #ripple="matRipple" matRipple></div>`,
744708
})
745709
class RippleContainerWithoutBindings {}
746710

747-
@Component({ template: `<div id="container" mat-ripple [matRippleSpeedFactor]="0"
711+
@Component({ template: `<div id="container" matRipple
748712
*ngIf="!isDestroyed"></div>` })
749713
class RippleContainerWithNgIf {
750714
@ViewChild(MatRipple) ripple: MatRipple;

src/lib/core/ripple/ripple.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ export interface RippleGlobalOptions {
3737
*/
3838
animation?: RippleAnimationConfig;
3939

40-
/**
41-
* If set, the default duration of the fade-in animation is divided by this value. For example,
42-
* setting it to 0.5 will cause the ripple fade-in animation to take twice as long.
43-
* A changed speedFactor will not affect the fade-out duration of the ripples.
44-
* @deprecated Use the `animation` global option instead.
45-
* @deletion-target 7.0.0
46-
*/
47-
baseSpeedFactor?: number;
48-
4940
/**
5041
* Whether ripples should start fading out immediately after the mouse our touch is released. By
5142
* default, ripples will wait for the enter animation to complete and for mouse or touch release.
@@ -86,15 +77,6 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
8677
*/
8778
@Input('matRippleRadius') radius: number = 0;
8879

89-
/**
90-
* If set, the normal duration of ripple animations is divided by this value. For example,
91-
* setting it to 0.5 will cause the animations to take twice as long.
92-
* A changed speedFactor will not modify the fade-out duration of the ripples.
93-
* @deprecated Use the [matRippleAnimation] binding instead.
94-
* @deletion-target 7.0.0
95-
*/
96-
@Input('matRippleSpeedFactor') speedFactor: number = 1;
97-
9880
/**
9981
* Configuration for the ripple animation. Allows modifying the enter and exit animation
10082
* duration of the ripples. The animation durations will be overwritten if the
@@ -171,7 +153,6 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
171153
color: this.color,
172154
animation: {...this._globalOptions.animation, ...this.animation},
173155
terminateOnPointerUp: this._globalOptions.terminateOnPointerUp,
174-
speedFactor: this.speedFactor * (this._globalOptions.baseSpeedFactor || 1),
175156
};
176157
}
177158

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/** Converts the specified speed factor into the exact static enter duration. */
10+
export function convertSpeedFactorToDuration(factor: number) {
11+
// Based on the numeric speed factor value that only affected the `enterDuration` we can
12+
// now calculate the exact `enterDuration`. 450ms is the enter duration without factor.
13+
return 450 / (factor || 1);
14+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint';
10+
import * as ts from 'typescript';
11+
import {bold, red, green} from 'chalk';
12+
import {convertSpeedFactorToDuration} from './ripple-speed-factor';
13+
14+
/**
15+
* Rule that walks through property assignment and switches the global `baseSpeedFactor`
16+
* ripple option to the new global animation config.
17+
* Also updates every class member assignment that refers to MatRipple#speedFactor.
18+
*/
19+
export class Rule extends Rules.TypedRule {
20+
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
21+
return this.applyWithWalker(
22+
new SwitchRippleSpeedFactorRule(sourceFile, this.getOptions(), program));
23+
}
24+
}
25+
26+
export class SwitchRippleSpeedFactorRule extends ProgramAwareRuleWalker {
27+
28+
/**
29+
* Switches binary expressions (e.g. myRipple.speedFactor = 0.5) to the new animation config.
30+
*/
31+
visitBinaryExpression(expression: ts.BinaryExpression) {
32+
if (expression.left.kind !== ts.SyntaxKind.PropertyAccessExpression) {
33+
return;
34+
}
35+
36+
// Left side expression consists of target object and property name (e.g. myInstance.val)
37+
const leftExpression = expression.left as ts.PropertyAccessExpression;
38+
const targetTypeNode = this.getTypeChecker().getTypeAtLocation(leftExpression.expression);
39+
40+
if (!targetTypeNode.symbol) {
41+
return;
42+
}
43+
44+
const targetTypeName = targetTypeNode.symbol.getName();
45+
const propertyName = leftExpression.name.getText();
46+
47+
if (targetTypeName === 'MatRipple' && propertyName === 'speedFactor') {
48+
if (expression.right.kind === ts.SyntaxKind.NumericLiteral) {
49+
const numericValue = parseFloat((expression.right as ts.NumericLiteral).text);
50+
const newEnterDurationValue = convertSpeedFactorToDuration(numericValue);
51+
52+
// Replace the `speedFactor` property name with `animation`.
53+
const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(),
54+
leftExpression.name.getWidth(), 'animation');
55+
56+
const rightExpressionReplacement = this.createReplacement(expression.right.getStart(),
57+
expression.right.getWidth(), `{enterDuration: ${newEnterDurationValue}}`);
58+
59+
this.addFailureAtNode(
60+
expression,
61+
`Found deprecated member assignment for "${bold('MatRipple')}#${red('speedFactor')}"`,
62+
[propertyNameReplacement, rightExpressionReplacement]);
63+
} else {
64+
// In case the speed factor is dynamically calculated or passed to the assignment, we just
65+
// print the failure and notify about the breaking change.
66+
this.addFailureAtNode(
67+
expression, `Found deprecated member assignment for "${bold('MatRipple')}#` +
68+
`${red('speedFactor')}. Please manually switch from "${red('speedFactor')}" to ` +
69+
`"${green('animation')}". Note that the animation property only accepts explicit ` +
70+
`durations in milliseconds.`);
71+
}
72+
}
73+
}
74+
75+
/**
76+
* Switches a potential global option `baseSpeedFactor` to the new animation config. For this
77+
* we assume that the `baseSpeedFactor` is not used in combination with individual speed factors.
78+
*/
79+
visitPropertyAssignment(assignment: ts.PropertyAssignment) {
80+
// For switching the `baseSpeedFactor` global option we expect the property assignment
81+
// to be inside of a normal object literal. Custom ripple global options cannot be switched
82+
// automatically.
83+
if (assignment.parent.kind !== ts.SyntaxKind.ObjectLiteralExpression) {
84+
return;
85+
}
86+
87+
// The assignment consists of a name (key) and initializer (value).
88+
if (assignment.name.getText() !== 'baseSpeedFactor') {
89+
return;
90+
}
91+
92+
// We could technically lazily check for the MAT_RIPPLE_GLOBAL_OPTIONS injection token to
93+
// be present, but it's not right to assume that everyone sets the ripple global options
94+
// immediately in the provider object (e.g. it can happen that someone just imports the
95+
// config from a separate file).
96+
97+
if (assignment.initializer.kind === ts.SyntaxKind.NumericLiteral) {
98+
const numericValue = parseFloat((assignment.initializer as ts.NumericLiteral).text);
99+
const newEnterDurationValue = convertSpeedFactorToDuration(numericValue);
100+
101+
const keyNameReplacement = this.createReplacement(assignment.name.getStart(),
102+
assignment.name.getWidth(), `animation`);
103+
104+
const initializerReplacement = this.createReplacement(assignment.initializer.getStart(),
105+
assignment.initializer.getWidth(), `{enterDuration: ${newEnterDurationValue}}`);
106+
107+
this.addFailureAtNode(
108+
assignment,
109+
`Found deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')} -> ` +
110+
`${red('baseSpeedFactor')}"`,
111+
[keyNameReplacement, initializerReplacement]);
112+
} else {
113+
// In case the base speed factor is dynamically calculated and inside of an Angular provider,
114+
// we just print the failure and notify about the breaking change.
115+
this.addFailureAtNode(
116+
assignment, `Found a deprecated property assignment for ` +
117+
`"${bold('MAT_RIPPLE_GLOBAL_OPTIONS')} -> ${red('speedFactor')}. Please manually switch ` +
118+
`from "${red('baseSpeedFactor')}" to "${green('animation')}". Note that the animation ` +
119+
`property only accepts explicit durations in milliseconds.`);
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)