Skip to content

Commit 07992e3

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 19ce1a1 commit 07992e3

File tree

8 files changed

+278
-75
lines changed

8 files changed

+278
-75
lines changed

src/lib/chips/chip.ts

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

236236
if (globalOptions) {
237+
// TODO(paul): Do not copy each option manually. Allow dynamic global option changes: #9729
237238
this._ripplesGloballyDisabled = !!globalOptions.disabled;
238-
// TODO(paul): Once the speedFactor is removed, we no longer need to copy each single option.
239239
this.rippleConfig = {
240-
speedFactor: globalOptions.baseSpeedFactor,
241240
animation: globalOptions.animation,
242241
terminateOnPointerUp: globalOptions.terminateOnPointerUp,
243242
};

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

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

2722
/**
@@ -140,7 +135,7 @@ export class RippleRenderer {
140135
const radius = config.radius || distanceToFurthestCorner(x, y, containerRect);
141136
const offsetX = x - containerRect.left;
142137
const offsetY = y - containerRect.top;
143-
const duration = animationConfig.enterDuration / (config.speedFactor || 1);
138+
const duration = animationConfig.enterDuration;
144139

145140
const ripple = document.createElement('div');
146141
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
@@ -62,7 +62,7 @@ describe('MatRipple', () => {
6262
fixture = TestBed.createComponent(BasicRippleContainer);
6363
fixture.detectChanges();
6464

65-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
65+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
6666
rippleDirective = fixture.componentInstance.ripple;
6767
});
6868

@@ -262,7 +262,7 @@ describe('MatRipple', () => {
262262
fixture = TestBed.createComponent(RippleContainerWithNgIf);
263263
fixture.detectChanges();
264264

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

267267
fixture.componentInstance.isDestroyed = true;
268268
fixture.detectChanges();
@@ -377,7 +377,7 @@ describe('MatRipple', () => {
377377
fixture = TestBed.createComponent(BasicRippleContainer);
378378
fixture.detectChanges();
379379

380-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
380+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
381381
rippleDirective = fixture.componentInstance.ripple;
382382
});
383383

@@ -492,7 +492,7 @@ describe('MatRipple', () => {
492492
fixture = TestBed.createComponent(testComponent);
493493
fixture.detectChanges();
494494

495-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
495+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
496496
rippleDirective = fixture.componentInstance.ripple;
497497
}
498498

@@ -534,41 +534,6 @@ describe('MatRipple', () => {
534534
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
535535
});
536536

537-
it('should support changing the baseSpeedFactor', fakeAsync(() => {
538-
createTestComponent({ baseSpeedFactor: 0.5 });
539-
540-
dispatchMouseEvent(rippleTarget, 'mousedown');
541-
dispatchMouseEvent(rippleTarget, 'mouseup');
542-
543-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
544-
545-
// Calculates the speedFactor for the duration. Those factors needs to be inverted, because
546-
// a lower speed factor, will make the duration longer. For example: 0.5 => 2x duration.
547-
let fadeInFactor = 1 / 0.5;
548-
549-
// Calculates the duration for fading-in and fading-out the ripple.
550-
tick(enterDuration * fadeInFactor + exitDuration);
551-
552-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
553-
}));
554-
555-
it('should combine individual speed factor with baseSpeedFactor', fakeAsync(() => {
556-
createTestComponent({ baseSpeedFactor: 0.5 });
557-
558-
rippleDirective.launch(0, 0, { speedFactor: 1.5 });
559-
560-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
561-
562-
// Calculates the speedFactor for the duration. Those factors needs to be inverted, because
563-
// a lower speed factor, will make the duration longer. For example: 0.5 => 2x duration.
564-
let fadeInFactor = 1 / (0.5 * 1.5);
565-
566-
// Calculates the duration for fading-in and fading-out the ripple.
567-
tick(enterDuration * fadeInFactor + exitDuration);
568-
569-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
570-
}));
571-
572537
it('should support changing the animation duration', fakeAsync(() => {
573538
createTestComponent({
574539
animation: {enterDuration: 100, exitDuration: 100}
@@ -619,7 +584,7 @@ describe('MatRipple', () => {
619584
fixture = TestBed.createComponent(BasicRippleContainer);
620585
fixture.detectChanges();
621586

622-
rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
587+
rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
623588
rippleDirective = fixture.componentInstance.ripple;
624589
});
625590

@@ -637,7 +602,7 @@ describe('MatRipple', () => {
637602
fixture.detectChanges();
638603

639604
controller = fixture.debugElement.componentInstance;
640-
rippleTarget = fixture.debugElement.nativeElement.querySelector('[mat-ripple]');
605+
rippleTarget = fixture.debugElement.nativeElement.querySelector('.mat-ripple');
641606
});
642607

643608
it('sets ripple color', () => {
@@ -757,7 +722,7 @@ describe('MatRipple', () => {
757722

758723
@Component({
759724
template: `
760-
<div id="container" #ripple="matRipple" mat-ripple [matRippleSpeedFactor]="0"
725+
<div id="container" #ripple="matRipple" matRipple
761726
style="position: relative; width:300px; height:200px;">
762727
</div>
763728
`,
@@ -769,8 +734,7 @@ class BasicRippleContainer {
769734
@Component({
770735
template: `
771736
<div id="container" style="position: relative; width:300px; height:200px;"
772-
mat-ripple
773-
[matRippleSpeedFactor]="0"
737+
matRipple
774738
[matRippleTrigger]="trigger"
775739
[matRippleCentered]="centered"
776740
[matRippleRadius]="radius"
@@ -792,11 +756,11 @@ class RippleContainerWithInputBindings {
792756
}
793757

794758
@Component({
795-
template: `<div id="container" #ripple="matRipple" mat-ripple></div>`,
759+
template: `<div id="container" #ripple="matRipple" matRipple></div>`,
796760
})
797761
class RippleContainerWithoutBindings {}
798762

799-
@Component({ template: `<div id="container" mat-ripple [matRippleSpeedFactor]="0"
763+
@Component({ template: `<div id="container" matRipple
800764
*ngIf="!isDestroyed"></div>` })
801765
class RippleContainerWithNgIf {
802766
@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-
* @breaking-change 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-
* @breaking-change 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
@@ -174,7 +156,6 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
174156
color: this.color,
175157
animation: {...this._globalOptions.animation, ...this.animation},
176158
terminateOnPointerUp: this._globalOptions.terminateOnPointerUp,
177-
speedFactor: this.speedFactor * (this._globalOptions.baseSpeedFactor || 1),
178159
};
179160
}
180161

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
}
15+
16+
/**
17+
* Creates a runtime TypeScript expression that can be used in order to calculate the duration
18+
* from the speed factor expression that couldn't be statically analyzed.
19+
*
20+
* @param speedFactorValue Speed factor expression that couldn't be statically analyzed.
21+
*/
22+
export function createSpeedFactorConvertExpression(speedFactorValue: string): string {
23+
// To be sure that the speed factor value expression is calculated properly, we need to add
24+
// the according parenthesis.
25+
return `450 / (${speedFactorValue})`;
26+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 {bold, red} from 'chalk';
10+
import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint';
11+
import * as ts from 'typescript';
12+
import {
13+
convertSpeedFactorToDuration,
14+
createSpeedFactorConvertExpression,
15+
} from './ripple-speed-factor';
16+
17+
/**
18+
* Note that will be added whenever a speed factor expression has been converted to calculate
19+
* the according duration. This note should encourage people to clean up their code by switching
20+
* away from the speed factors to explicit durations.
21+
*/
22+
const removeNote = `TODO: Cleanup duration calculation.`;
23+
24+
/**
25+
* Rule that walks through every property assignment and switches the global `baseSpeedFactor`
26+
* ripple option to the new global animation config. Also updates every class member assignment
27+
* that refers to MatRipple#speedFactor.
28+
*/
29+
export class Rule extends Rules.TypedRule {
30+
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
31+
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
32+
}
33+
}
34+
35+
export class Walker extends ProgramAwareRuleWalker {
36+
37+
/** Switches binary expressions (e.g. myRipple.speedFactor = 0.5) to the new animation config. */
38+
visitBinaryExpression(expression: ts.BinaryExpression) {
39+
if (!ts.isPropertyAccessExpression(expression.left)) {
40+
return;
41+
}
42+
43+
// Left side expression consists of target object and property name (e.g. myInstance.val)
44+
const leftExpression = expression.left as ts.PropertyAccessExpression;
45+
const targetTypeNode = this.getTypeChecker().getTypeAtLocation(leftExpression.expression);
46+
47+
if (!targetTypeNode.symbol) {
48+
return;
49+
}
50+
51+
const targetTypeName = targetTypeNode.symbol.getName();
52+
const propertyName = leftExpression.name.getText();
53+
54+
if (targetTypeName === 'MatRipple' && propertyName === 'speedFactor') {
55+
if (ts.isNumericLiteral(expression.right)) {
56+
const numericValue = parseFloat(expression.right.text);
57+
const newEnterDurationValue = convertSpeedFactorToDuration(numericValue);
58+
59+
// Replace the `speedFactor` property name with `animation`.
60+
const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(),
61+
leftExpression.name.getWidth(), 'animation');
62+
63+
// Replace the value assignment with the new animation config.
64+
const rightExpressionReplacement = this.createReplacement(expression.right.getStart(),
65+
expression.right.getWidth(), `{enterDuration: ${newEnterDurationValue}}`);
66+
67+
this.addFailureAtNode(expression,
68+
`Found deprecated variable assignment for "${bold('MatRipple')}#${red('speedFactor')}"`,
69+
[propertyNameReplacement, rightExpressionReplacement]);
70+
} else {
71+
// Handle the right expression differently if the previous speed factor value can't
72+
// be resolved statically. In that case, we just create a TypeScript expression that
73+
// calculates the explicit duration based on the non-static speed factor expression.
74+
const newExpression = createSpeedFactorConvertExpression(expression.right.getText());
75+
76+
// Replace the `speedFactor` property name with `animation`.
77+
const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(),
78+
leftExpression.name.getWidth(), 'animation');
79+
80+
// Replace the value assignment with the new animation config and remove TODO.
81+
const rightExpressionReplacement = this.createReplacement(expression.right.getStart(),
82+
expression.right.getWidth(), `/** ${removeNote} */ {enterDuration: ${newExpression}}`);
83+
84+
this.addFailureAtNode(expression,
85+
`Found deprecated variable assignment for "${bold('MatRipple')}#${red('speedFactor')}"`,
86+
[propertyNameReplacement, rightExpressionReplacement]);
87+
}
88+
}
89+
}
90+
91+
/**
92+
* Switches a potential global option `baseSpeedFactor` to the new animation config. For this
93+
* we assume that the `baseSpeedFactor` is not used in combination with individual speed factors.
94+
*/
95+
visitPropertyAssignment(assignment: ts.PropertyAssignment) {
96+
// For switching the `baseSpeedFactor` global option we expect the property assignment
97+
// to be inside of a normal object literal. Custom ripple global options cannot be switched
98+
// automatically.
99+
if (!ts.isObjectLiteralExpression(assignment.parent)) {
100+
return;
101+
}
102+
103+
// The assignment consists of a name (key) and initializer (value).
104+
if (assignment.name.getText() !== 'baseSpeedFactor') {
105+
return;
106+
}
107+
108+
// We could technically lazily check for the MAT_RIPPLE_GLOBAL_OPTIONS injection token to
109+
// be present, but it's not right to assume that everyone sets the ripple global options
110+
// immediately in the provider object (e.g. it can happen that someone just imports the
111+
// config from a separate file).
112+
113+
const {initializer, name} = assignment;
114+
115+
if (ts.isNumericLiteral(initializer)) {
116+
const numericValue = parseFloat(initializer.text);
117+
const newEnterDurationValue = convertSpeedFactorToDuration(numericValue);
118+
119+
const keyNameReplacement = this.createReplacement(name.getStart(),
120+
assignment.name.getWidth(), `animation`);
121+
122+
const initializerReplacement = this.createReplacement(initializer.getStart(),
123+
initializer.getWidth(), `{enterDuration: ${newEnterDurationValue}}`);
124+
125+
this.addFailureAtNode(assignment,
126+
`Found deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')}:` +
127+
`${red('baseSpeedFactor')}"`,
128+
[keyNameReplacement, initializerReplacement]);
129+
} else {
130+
// Handle the right expression differently if the previous speed factor value can't
131+
// be resolved statically. In that case, we just create a TypeScript expression that
132+
// calculates the explicit duration based on the non-static speed factor expression.
133+
const newExpression = createSpeedFactorConvertExpression(initializer.getText());
134+
135+
// Replace the `baseSpeedFactor` property name with `animation`.
136+
const propertyNameReplacement = this.createReplacement(name.getStart(),
137+
name.getWidth(), 'animation');
138+
139+
// Replace the value assignment with the new animation config and remove TODO.
140+
const rightExpressionReplacement = this.createReplacement(initializer.getStart(),
141+
initializer.getWidth(), `/** ${removeNote} */ {enterDuration: ${newExpression}}`);
142+
143+
this.addFailureAtNode(assignment,
144+
`Found a deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')}:` +
145+
`${red('baseSpeedFactor')}.`,
146+
[propertyNameReplacement, rightExpressionReplacement]);
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)