diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts
index c39a39c3e10..99f9d0e04fe 100644
--- a/src/lib/chips/chip.ts
+++ b/src/lib/chips/chip.ts
@@ -234,10 +234,9 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
this._chipRipple.setupTriggerEvents(_elementRef.nativeElement);
if (globalOptions) {
+ // TODO(paul): Do not copy each option manually. Allow dynamic global option changes: #9729
this._ripplesGloballyDisabled = !!globalOptions.disabled;
- // TODO(paul): Once the speedFactor is removed, we no longer need to copy each single option.
this.rippleConfig = {
- speedFactor: globalOptions.baseSpeedFactor,
animation: globalOptions.animation,
terminateOnPointerUp: globalOptions.terminateOnPointerUp,
};
diff --git a/src/lib/core/ripple/ripple-renderer.ts b/src/lib/core/ripple/ripple-renderer.ts
index fcbc6684604..a5f82245cf6 100644
--- a/src/lib/core/ripple/ripple-renderer.ts
+++ b/src/lib/core/ripple/ripple-renderer.ts
@@ -17,11 +17,6 @@ export type RippleConfig = {
persistent?: boolean;
animation?: RippleAnimationConfig;
terminateOnPointerUp?: boolean;
- /**
- * @deprecated Use the `animation` property instead.
- * @breaking-change 7.0.0
- */
- speedFactor?: number;
};
/**
@@ -140,7 +135,7 @@ export class RippleRenderer {
const radius = config.radius || distanceToFurthestCorner(x, y, containerRect);
const offsetX = x - containerRect.left;
const offsetY = y - containerRect.top;
- const duration = animationConfig.enterDuration / (config.speedFactor || 1);
+ const duration = animationConfig.enterDuration;
const ripple = document.createElement('div');
ripple.classList.add('mat-ripple-element');
diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts
index 85bc3fb884e..04a661395b2 100644
--- a/src/lib/core/ripple/ripple.spec.ts
+++ b/src/lib/core/ripple/ripple.spec.ts
@@ -62,7 +62,7 @@ describe('MatRipple', () => {
fixture = TestBed.createComponent(BasicRippleContainer);
fixture.detectChanges();
- rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
rippleDirective = fixture.componentInstance.ripple;
});
@@ -262,7 +262,7 @@ describe('MatRipple', () => {
fixture = TestBed.createComponent(RippleContainerWithNgIf);
fixture.detectChanges();
- rippleTarget = fixture.debugElement.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.debugElement.nativeElement.querySelector('.mat-ripple');
fixture.componentInstance.isDestroyed = true;
fixture.detectChanges();
@@ -377,7 +377,7 @@ describe('MatRipple', () => {
fixture = TestBed.createComponent(BasicRippleContainer);
fixture.detectChanges();
- rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
rippleDirective = fixture.componentInstance.ripple;
});
@@ -492,7 +492,7 @@ describe('MatRipple', () => {
fixture = TestBed.createComponent(testComponent);
fixture.detectChanges();
- rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
rippleDirective = fixture.componentInstance.ripple;
}
@@ -534,41 +534,6 @@ describe('MatRipple', () => {
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
- it('should support changing the baseSpeedFactor', fakeAsync(() => {
- createTestComponent({ baseSpeedFactor: 0.5 });
-
- dispatchMouseEvent(rippleTarget, 'mousedown');
- dispatchMouseEvent(rippleTarget, 'mouseup');
-
- expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
-
- // Calculates the speedFactor for the duration. Those factors needs to be inverted, because
- // a lower speed factor, will make the duration longer. For example: 0.5 => 2x duration.
- let fadeInFactor = 1 / 0.5;
-
- // Calculates the duration for fading-in and fading-out the ripple.
- tick(enterDuration * fadeInFactor + exitDuration);
-
- expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
- }));
-
- it('should combine individual speed factor with baseSpeedFactor', fakeAsync(() => {
- createTestComponent({ baseSpeedFactor: 0.5 });
-
- rippleDirective.launch(0, 0, { speedFactor: 1.5 });
-
- expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
-
- // Calculates the speedFactor for the duration. Those factors needs to be inverted, because
- // a lower speed factor, will make the duration longer. For example: 0.5 => 2x duration.
- let fadeInFactor = 1 / (0.5 * 1.5);
-
- // Calculates the duration for fading-in and fading-out the ripple.
- 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}
@@ -619,7 +584,7 @@ describe('MatRipple', () => {
fixture = TestBed.createComponent(BasicRippleContainer);
fixture.detectChanges();
- rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.nativeElement.querySelector('.mat-ripple');
rippleDirective = fixture.componentInstance.ripple;
});
@@ -637,7 +602,7 @@ describe('MatRipple', () => {
fixture.detectChanges();
controller = fixture.debugElement.componentInstance;
- rippleTarget = fixture.debugElement.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.debugElement.nativeElement.querySelector('.mat-ripple');
});
it('sets ripple color', () => {
@@ -757,7 +722,7 @@ describe('MatRipple', () => {
@Component({
template: `
-
`,
@@ -769,8 +734,7 @@ class BasicRippleContainer {
@Component({
template: `
`,
+ template: ``,
})
class RippleContainerWithoutBindings {}
-@Component({ template: `` })
class RippleContainerWithNgIf {
@ViewChild(MatRipple) ripple: MatRipple;
diff --git a/src/lib/core/ripple/ripple.ts b/src/lib/core/ripple/ripple.ts
index 526529d70a6..2dd3053d5a1 100644
--- a/src/lib/core/ripple/ripple.ts
+++ b/src/lib/core/ripple/ripple.ts
@@ -37,15 +37,6 @@ export interface RippleGlobalOptions {
*/
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.
- * @breaking-change 7.0.0
- */
- baseSpeedFactor?: number;
-
/**
* Whether ripples should start fading out immediately after the mouse our touch is released. By
* 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 {
*/
@Input('matRippleRadius') radius: number = 0;
- /**
- * 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.
- * @breaking-change 7.0.0
- */
- @Input('matRippleSpeedFactor') speedFactor: number = 1;
-
/**
* Configuration for the ripple animation. Allows modifying the enter and exit animation
* duration of the ripples. The animation durations will be overwritten if the
@@ -174,7 +156,6 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
color: this.color,
animation: {...this._globalOptions.animation, ...this.animation},
terminateOnPointerUp: this._globalOptions.terminateOnPointerUp,
- speedFactor: this.speedFactor * (this._globalOptions.baseSpeedFactor || 1),
};
}
diff --git a/src/lib/schematics/update/index.ts b/src/lib/schematics/update/index.ts
index 2fb8858b610..322e3331464 100644
--- a/src/lib/schematics/update/index.ts
+++ b/src/lib/schematics/update/index.ts
@@ -7,14 +7,9 @@
*/
import {Rule} from '@angular-devkit/schematics';
+import {TargetVersion} from './target-version';
import {createUpdateRule} from './update';
-/** Possible versions that can be automatically migrated by `ng update`. */
-export enum TargetVersion {
- V6,
- V7,
-}
-
/** Entry point for the migration schematics with target of Angular Material 6.0.0 */
export function updateToV6(): Rule {
return createUpdateRule(TargetVersion.V6);
diff --git a/src/lib/schematics/update/material/data/attribute-selectors.ts b/src/lib/schematics/update/material/data/attribute-selectors.ts
index 22adcbe4164..dcf48aaf679 100644
--- a/src/lib/schematics/update/material/data/attribute-selectors.ts
+++ b/src/lib/schematics/update/material/data/attribute-selectors.ts
@@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
-import {TargetVersion} from '../../index';
export interface MaterialAttributeSelectorData {
/** The attribute name to replace. */
diff --git a/src/lib/schematics/update/material/data/class-names.ts b/src/lib/schematics/update/material/data/class-names.ts
index 74b9d5690cc..e2c60a79052 100644
--- a/src/lib/schematics/update/material/data/class-names.ts
+++ b/src/lib/schematics/update/material/data/class-names.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialClassNameData {
diff --git a/src/lib/schematics/update/material/data/constructor-checks.ts b/src/lib/schematics/update/material/data/constructor-checks.ts
index 06e4190fa8d..f7e6a4e978e 100644
--- a/src/lib/schematics/update/material/data/constructor-checks.ts
+++ b/src/lib/schematics/update/material/data/constructor-checks.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
/**
diff --git a/src/lib/schematics/update/material/data/css-selectors.ts b/src/lib/schematics/update/material/data/css-selectors.ts
index 600b6262d54..8b22a7e718f 100644
--- a/src/lib/schematics/update/material/data/css-selectors.ts
+++ b/src/lib/schematics/update/material/data/css-selectors.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialCssSelectorData {
diff --git a/src/lib/schematics/update/material/data/element-selectors.ts b/src/lib/schematics/update/material/data/element-selectors.ts
index e2f834d11e5..cb16727419d 100644
--- a/src/lib/schematics/update/material/data/element-selectors.ts
+++ b/src/lib/schematics/update/material/data/element-selectors.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialElementSelectorData {
diff --git a/src/lib/schematics/update/material/data/input-names.ts b/src/lib/schematics/update/material/data/input-names.ts
index 4813c9ef61d..17cf52d49e6 100644
--- a/src/lib/schematics/update/material/data/input-names.ts
+++ b/src/lib/schematics/update/material/data/input-names.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialInputNameData {
diff --git a/src/lib/schematics/update/material/data/method-call-checks.ts b/src/lib/schematics/update/material/data/method-call-checks.ts
index 5322ae05f48..5c2bb9a7ccc 100644
--- a/src/lib/schematics/update/material/data/method-call-checks.ts
+++ b/src/lib/schematics/update/material/data/method-call-checks.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialMethodCallData {
diff --git a/src/lib/schematics/update/material/data/output-names.ts b/src/lib/schematics/update/material/data/output-names.ts
index 7be76533937..3f10e6d4ec0 100644
--- a/src/lib/schematics/update/material/data/output-names.ts
+++ b/src/lib/schematics/update/material/data/output-names.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialOutputNameData {
diff --git a/src/lib/schematics/update/material/data/property-names.ts b/src/lib/schematics/update/material/data/property-names.ts
index 22026d66514..7c8aec7971f 100644
--- a/src/lib/schematics/update/material/data/property-names.ts
+++ b/src/lib/schematics/update/material/data/property-names.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../../index';
+import {TargetVersion} from '../../target-version';
import {VersionChanges} from '../transform-change-data';
export interface MaterialPropertyNameData {
diff --git a/src/lib/schematics/update/material/transform-change-data.ts b/src/lib/schematics/update/material/transform-change-data.ts
index 52f7ab4d8b3..4192aad5f2e 100644
--- a/src/lib/schematics/update/material/transform-change-data.ts
+++ b/src/lib/schematics/update/material/transform-change-data.ts
@@ -5,7 +5,8 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import {TargetVersion} from '../index';
+
+import {TargetVersion} from '../target-version';
export type VersionChanges = {
[target in TargetVersion]?: ReadableChange[];
diff --git a/src/lib/schematics/update/rules/misc-ripples-v7/ripple-speed-factor.ts b/src/lib/schematics/update/rules/misc-ripples-v7/ripple-speed-factor.ts
new file mode 100644
index 00000000000..e72cf48c167
--- /dev/null
+++ b/src/lib/schematics/update/rules/misc-ripples-v7/ripple-speed-factor.ts
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/** Converts the specified speed factor into the exact static enter duration. */
+export function convertSpeedFactorToDuration(factor: number) {
+ // Based on the numeric speed factor value that only affected the `enterDuration` we can
+ // now calculate the exact `enterDuration`. 450ms is the enter duration without factor.
+ return 450 / (factor || 1);
+}
+
+/**
+ * Creates a runtime TypeScript expression that can be used in order to calculate the duration
+ * from the speed factor expression that couldn't be statically analyzed.
+ *
+ * @param speedFactorValue Speed factor expression that couldn't be statically analyzed.
+ */
+export function createSpeedFactorConvertExpression(speedFactorValue: string): string {
+ // To be sure that the speed factor value expression is calculated properly, we need to add
+ // the according parenthesis.
+ return `450 / (${speedFactorValue})`;
+}
diff --git a/src/lib/schematics/update/rules/misc-ripples-v7/rippleSpeedFactorAssignmentRule.ts b/src/lib/schematics/update/rules/misc-ripples-v7/rippleSpeedFactorAssignmentRule.ts
new file mode 100644
index 00000000000..b920d9746e8
--- /dev/null
+++ b/src/lib/schematics/update/rules/misc-ripples-v7/rippleSpeedFactorAssignmentRule.ts
@@ -0,0 +1,149 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {bold, red} from 'chalk';
+import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint';
+import * as ts from 'typescript';
+import {
+ convertSpeedFactorToDuration,
+ createSpeedFactorConvertExpression,
+} from './ripple-speed-factor';
+
+/**
+ * Note that will be added whenever a speed factor expression has been converted to calculate
+ * the according duration. This note should encourage people to clean up their code by switching
+ * away from the speed factors to explicit durations.
+ */
+const removeNote = `TODO: Cleanup duration calculation.`;
+
+/**
+ * Rule that walks through every property assignment and switches the global `baseSpeedFactor`
+ * ripple option to the new global animation config. Also updates every class member assignment
+ * that refers to MatRipple#speedFactor.
+ */
+export class Rule extends Rules.TypedRule {
+ applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
+ return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
+ }
+}
+
+export class Walker extends ProgramAwareRuleWalker {
+
+ /** Switches binary expressions (e.g. myRipple.speedFactor = 0.5) to the new animation config. */
+ visitBinaryExpression(expression: ts.BinaryExpression) {
+ if (!ts.isPropertyAccessExpression(expression.left)) {
+ return;
+ }
+
+ // Left side expression consists of target object and property name (e.g. myInstance.val)
+ const leftExpression = expression.left as ts.PropertyAccessExpression;
+ const targetTypeNode = this.getTypeChecker().getTypeAtLocation(leftExpression.expression);
+
+ if (!targetTypeNode.symbol) {
+ return;
+ }
+
+ const targetTypeName = targetTypeNode.symbol.getName();
+ const propertyName = leftExpression.name.getText();
+
+ if (targetTypeName === 'MatRipple' && propertyName === 'speedFactor') {
+ if (ts.isNumericLiteral(expression.right)) {
+ const numericValue = parseFloat(expression.right.text);
+ const newEnterDurationValue = convertSpeedFactorToDuration(numericValue);
+
+ // Replace the `speedFactor` property name with `animation`.
+ const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(),
+ leftExpression.name.getWidth(), 'animation');
+
+ // Replace the value assignment with the new animation config.
+ const rightExpressionReplacement = this.createReplacement(expression.right.getStart(),
+ expression.right.getWidth(), `{enterDuration: ${newEnterDurationValue}}`);
+
+ this.addFailureAtNode(expression,
+ `Found deprecated variable assignment for "${bold('MatRipple')}#${red('speedFactor')}"`,
+ [propertyNameReplacement, rightExpressionReplacement]);
+ } else {
+ // Handle the right expression differently if the previous speed factor value can't
+ // be resolved statically. In that case, we just create a TypeScript expression that
+ // calculates the explicit duration based on the non-static speed factor expression.
+ const newExpression = createSpeedFactorConvertExpression(expression.right.getText());
+
+ // Replace the `speedFactor` property name with `animation`.
+ const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(),
+ leftExpression.name.getWidth(), 'animation');
+
+ // Replace the value assignment with the new animation config and remove TODO.
+ const rightExpressionReplacement = this.createReplacement(expression.right.getStart(),
+ expression.right.getWidth(), `/** ${removeNote} */ {enterDuration: ${newExpression}}`);
+
+ this.addFailureAtNode(expression,
+ `Found deprecated variable assignment for "${bold('MatRipple')}#${red('speedFactor')}"`,
+ [propertyNameReplacement, rightExpressionReplacement]);
+ }
+ }
+ }
+
+ /**
+ * Switches a potential global option `baseSpeedFactor` to the new animation config. For this
+ * we assume that the `baseSpeedFactor` is not used in combination with individual speed factors.
+ */
+ visitPropertyAssignment(assignment: ts.PropertyAssignment) {
+ // For switching the `baseSpeedFactor` global option we expect the property assignment
+ // to be inside of a normal object literal. Custom ripple global options cannot be switched
+ // automatically.
+ if (!ts.isObjectLiteralExpression(assignment.parent)) {
+ return;
+ }
+
+ // The assignment consists of a name (key) and initializer (value).
+ if (assignment.name.getText() !== 'baseSpeedFactor') {
+ return;
+ }
+
+ // We could technically lazily check for the MAT_RIPPLE_GLOBAL_OPTIONS injection token to
+ // be present, but it's not right to assume that everyone sets the ripple global options
+ // immediately in the provider object (e.g. it can happen that someone just imports the
+ // config from a separate file).
+
+ const {initializer, name} = assignment;
+
+ if (ts.isNumericLiteral(initializer)) {
+ const numericValue = parseFloat(initializer.text);
+ const newEnterDurationValue = convertSpeedFactorToDuration(numericValue);
+
+ const keyNameReplacement = this.createReplacement(name.getStart(),
+ assignment.name.getWidth(), `animation`);
+
+ const initializerReplacement = this.createReplacement(initializer.getStart(),
+ initializer.getWidth(), `{enterDuration: ${newEnterDurationValue}}`);
+
+ this.addFailureAtNode(assignment,
+ `Found deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')}:` +
+ `${red('baseSpeedFactor')}"`,
+ [keyNameReplacement, initializerReplacement]);
+ } else {
+ // Handle the right expression differently if the previous speed factor value can't
+ // be resolved statically. In that case, we just create a TypeScript expression that
+ // calculates the explicit duration based on the non-static speed factor expression.
+ const newExpression = createSpeedFactorConvertExpression(initializer.getText());
+
+ // Replace the `baseSpeedFactor` property name with `animation`.
+ const propertyNameReplacement = this.createReplacement(name.getStart(),
+ name.getWidth(), 'animation');
+
+ // Replace the value assignment with the new animation config and remove TODO.
+ const rightExpressionReplacement = this.createReplacement(initializer.getStart(),
+ initializer.getWidth(), `/** ${removeNote} */ {enterDuration: ${newExpression}}`);
+
+ this.addFailureAtNode(assignment,
+ `Found a deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')}:` +
+ `${red('baseSpeedFactor')}.`,
+ [propertyNameReplacement, rightExpressionReplacement]);
+ }
+ }
+}
diff --git a/src/lib/schematics/update/rules/misc-ripples-v7/rippleSpeedFactorTemplateRule.ts b/src/lib/schematics/update/rules/misc-ripples-v7/rippleSpeedFactorTemplateRule.ts
new file mode 100644
index 00000000000..84a3fa0ddca
--- /dev/null
+++ b/src/lib/schematics/update/rules/misc-ripples-v7/rippleSpeedFactorTemplateRule.ts
@@ -0,0 +1,90 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Replacement, RuleFailure, Rules} from 'tslint';
+import * as ts from 'typescript';
+import {ExternalResource} from '../../tslint/component-file';
+import {ComponentWalker} from '../../tslint/component-walker';
+import {
+ addFailureAtReplacement,
+ createExternalReplacementFailure,
+} from '../../tslint/rule-failures';
+import {
+ convertSpeedFactorToDuration,
+ createSpeedFactorConvertExpression,
+} from './ripple-speed-factor';
+
+/** Regular expression that matches [matRippleSpeedFactor]="$NUMBER" in templates. */
+const speedFactorNumberRegex = /\[matRippleSpeedFactor]="(\d+(?:\.\d+)?)"/g;
+
+/** Regular expression that matches [matRippleSpeedFactor]="$NOT_A_NUMBER" in templates. */
+const speedFactorNotParseable = /\[matRippleSpeedFactor]="(?!\d+(?:\.\d+)?")(.*)"/g;
+
+/** Failure message that will be shown if a speed factor is set to a readable number. */
+const failureMessageReadableNumber =
+ 'Detected deprecated [matRippleSpeedFactor] input binding with readable number.';
+
+/** Failure message that will be shown if a speed factor is set to a non-parseable value. */
+const failureMessageNonParseableValue =
+ 'Detected deprecated [matRippleSpeedFactor] input binding with non-parseable value.';
+
+/**
+ * Rule that walks through every inline or external template and updates the deprecated
+ * [matRippleSpeedFactor] to [matRippleAnimation].
+ */
+export class Rule extends Rules.AbstractRule {
+ apply(sourceFile: ts.SourceFile): RuleFailure[] {
+ return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
+ }
+}
+
+export class Walker extends ComponentWalker {
+
+ visitInlineTemplate(template: ts.StringLiteralLike) {
+ this._createReplacementsForContent(template, template.getText())
+ .forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
+ }
+
+ visitExternalTemplate(template: ExternalResource) {
+ this._createReplacementsForContent(template, template.getFullText())
+ .map(data => createExternalReplacementFailure(template, data.failureMessage,
+ this.getRuleName(), data.replacement))
+ .forEach(failure => this.addFailure(failure));
+ }
+
+ private _createReplacementsForContent(node: ts.Node, templateText: string) {
+ const replacements: {failureMessage: string, replacement: Replacement}[] = [];
+ const startPos = node.getStart();
+
+ let match: RegExpMatchArray | null;
+
+ while ((match = speedFactorNumberRegex.exec(templateText)) !== null) {
+ const newEnterDuration = convertSpeedFactorToDuration(parseFloat(match[1]));
+ const fix = this.createReplacement(startPos + match.index!, match[0].length,
+ `[matRippleAnimation]="{enterDuration: ${newEnterDuration}}"`);
+
+ replacements.push({
+ replacement: fix,
+ failureMessage: failureMessageReadableNumber,
+ });
+ }
+
+ while ((match = speedFactorNotParseable.exec(templateText)) !== null) {
+ const newDurationExpression = createSpeedFactorConvertExpression(match[1]);
+ const fix = this.createReplacement(startPos + match.index!, match[0].length,
+ `[matRippleAnimation]="{enterDuration: (${newDurationExpression})}"`);
+
+ replacements.push({
+ replacement: fix,
+ failureMessage: failureMessageNonParseableValue,
+ });
+ }
+
+ return replacements;
+ }
+}
diff --git a/src/lib/schematics/update/target-version.ts b/src/lib/schematics/update/target-version.ts
new file mode 100644
index 00000000000..9d6b25164f1
--- /dev/null
+++ b/src/lib/schematics/update/target-version.ts
@@ -0,0 +1,13 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/** Possible versions that can be automatically migrated by `ng update`. */
+export enum TargetVersion {
+ V6,
+ V7,
+}
diff --git a/src/lib/schematics/update/test-cases/v7-test-cases.spec.ts b/src/lib/schematics/update/test-cases/v7-test-cases.spec.ts
index 4310bfa1025..7bf50ad6775 100644
--- a/src/lib/schematics/update/test-cases/v7-test-cases.spec.ts
+++ b/src/lib/schematics/update/test-cases/v7-test-cases.spec.ts
@@ -10,7 +10,8 @@ describe('v7 upgrade test cases', () => {
* a developers application.
*/
const testCases = [
- 'v7/property-names'
+ 'v7/property-names',
+ 'v7/ripple-speed-factor',
];
// Iterates through every test case directory and generates a jasmine test block that will
diff --git a/src/lib/schematics/update/test-cases/v7/ripple-speed-factor_expected_output.ts b/src/lib/schematics/update/test-cases/v7/ripple-speed-factor_expected_output.ts
new file mode 100644
index 00000000000..19fb4f81446
--- /dev/null
+++ b/src/lib/schematics/update/test-cases/v7/ripple-speed-factor_expected_output.ts
@@ -0,0 +1,33 @@
+import {Component} from '@angular/core';
+
+class MatRipple {
+ speedFactor: number;
+}
+
+class A {
+ self = {me: this.ripple};
+
+ constructor(protected ripple: MatRipple) {}
+
+ onClick() {
+ this.ripple.animation = {enterDuration: 900};
+ this.self.me.animation = {enterDuration: 300};
+ }
+}
+
+const b = new MatRipple();
+const myConstant = 1;
+
+b.animation = /** TODO: Cleanup duration calculation. */ {enterDuration: 450 / (0.5 + myConstant)};
+
+@Component({
+ template: ``
+})
+class C {}
+
+@Component({
+ template: ``
+})
+class D {
+ myValue = 1.5;
+}
diff --git a/src/lib/schematics/update/test-cases/v7/ripple-speed-factor_input.ts b/src/lib/schematics/update/test-cases/v7/ripple-speed-factor_input.ts
new file mode 100644
index 00000000000..b7f9ae25a40
--- /dev/null
+++ b/src/lib/schematics/update/test-cases/v7/ripple-speed-factor_input.ts
@@ -0,0 +1,33 @@
+import {Component} from '@angular/core';
+
+class MatRipple {
+ speedFactor: number;
+}
+
+class A {
+ self = {me: this.ripple};
+
+ constructor(protected ripple: MatRipple) {}
+
+ onClick() {
+ this.ripple.speedFactor = 0.5;
+ this.self.me.speedFactor = 1.5;
+ }
+}
+
+const b = new MatRipple();
+const myConstant = 1;
+
+b.speedFactor = 0.5 + myConstant;
+
+@Component({
+ template: ``
+})
+class C {}
+
+@Component({
+ template: ``
+})
+class D {
+ myValue = 1.5;
+}
diff --git a/src/lib/schematics/update/tslint-config.ts b/src/lib/schematics/update/tslint-config.ts
index cf93588d672..1dde114423d 100644
--- a/src/lib/schematics/update/tslint-config.ts
+++ b/src/lib/schematics/update/tslint-config.ts
@@ -7,7 +7,7 @@
*/
import {sync as globSync} from 'glob';
-import {TargetVersion} from './index';
+import {TargetVersion} from './target-version';
/** List of rules that need to be enabled when running the TSLint fix task. */
const upgradeRules = [
@@ -52,6 +52,10 @@ const upgradeRules = [
// Additional misc rules.
'check-import-misc',
'check-template-misc',
+
+ // Ripple misc V7
+ ['ripple-speed-factor-assignment', TargetVersion.V7],
+ ['ripple-speed-factor-template', TargetVersion.V7],
];
/** List of absolute paths that refer to directories that contain the upgrade rules. */
@@ -66,8 +70,14 @@ const rulesDirectory = globSync('rules/**/', {cwd: __dirname, absolute: true});
* walker is not able to detect stylesheets which are not referenced by Angular.
*/
export function createTslintConfig(target: TargetVersion, extraStyleFiles: string[]) {
- const rules = upgradeRules.reduce((result, ruleName) => {
- result[ruleName] = [true, target, extraStyleFiles];
+ const rules = upgradeRules.reduce((result, data) => {
+ const ruleName = data instanceof Array ? data[0] : data;
+ const versionConstraints = data instanceof Array ? data.slice(1) : null;
+
+ if (!versionConstraints || versionConstraints.includes(target)) {
+ result[ruleName] = [true, target, extraStyleFiles];
+ }
+
return result;
}, {});
diff --git a/src/lib/schematics/update/update.ts b/src/lib/schematics/update/update.ts
index e9288d846ff..92481591471 100644
--- a/src/lib/schematics/update/update.ts
+++ b/src/lib/schematics/update/update.ts
@@ -9,8 +9,8 @@
import {Rule, SchematicContext, TaskId, Tree} from '@angular-devkit/schematics';
import {RunSchematicTask, TslintFixTask} from '@angular-devkit/schematics/tasks';
import {sync as globSync} from 'glob';
-import {TargetVersion} from './index';
import {getProjectTsConfigPaths} from './project-tsconfig-paths';
+import {TargetVersion} from './target-version';
import {createTslintConfig} from './tslint-config';
/** Entry point for `ng update` from Angular CLI. */
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 449b318248b..c35a0ac6c11 100644
--- a/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
+++ b/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
@@ -239,11 +239,10 @@ export class MatTabLink extends _MatTabLinkMixinBase
this.tabIndex = parseInt(tabIndex) || 0;
if (globalOptions) {
+ // TODO(paul): Do not copy each option manually. Allow dynamic global option changes: #9729
this._ripplesGloballyDisabled = !!globalOptions.disabled;
- // TODO(paul): Once the speedFactor is removed, we no longer need to copy each single option.
this.rippleConfig = {
terminateOnPointerUp: globalOptions.terminateOnPointerUp,
- speedFactor: globalOptions.baseSpeedFactor,
animation: globalOptions.animation,
};
}