From 2fc6a4c7821e38866e53af5796e44dafd5081a72 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 23 Aug 2017 02:44:18 +0200 Subject: [PATCH] refactor: turn off view encapsulation for all components (#6563) Fixes #6037. --- src/cdk/table/row.ts | 5 +- src/lib/datepicker/datepicker.ts | 1 + src/lib/grid-list/grid-tile.ts | 1 + src/lib/menu/menu-item.ts | 9 +++- src/lib/progress-bar/progress-bar.scss | 13 ++--- src/lib/progress-bar/progress-bar.ts | 3 +- .../progress-spinner/progress-spinner.scss | 2 +- src/lib/progress-spinner/progress-spinner.ts | 6 ++- src/lib/table/row.ts | 4 +- src/lib/tabs/tab-group.scss | 8 ++-- src/lib/tabs/tab-group.ts | 2 + src/lib/tabs/tab.ts | 15 +++++- tools/tslint-rules/noViewEncapsulationRule.js | 48 +++++++++++++++++++ tslint.json | 4 ++ 14 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 tools/tslint-rules/noViewEncapsulationRule.js diff --git a/src/cdk/table/row.ts b/src/cdk/table/row.ts index 91331a49f9e..1c521e64846 100644 --- a/src/cdk/table/row.ts +++ b/src/cdk/table/row.ts @@ -15,7 +15,8 @@ import { IterableDiffers, SimpleChanges, TemplateRef, - ViewContainerRef + ViewContainerRef, + ViewEncapsulation, } from '@angular/core'; import {CdkCellDef} from './cell'; @@ -147,6 +148,7 @@ export class CdkCellOutlet { 'role': 'row', }, changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class CdkHeaderRow { } @@ -159,5 +161,6 @@ export class CdkHeaderRow { } 'role': 'row', }, changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class CdkRow { } diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 29ab0c40afa..b1e9e355583 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -122,6 +122,7 @@ export class MdDatepickerContent implements AfterContentInit { selector: 'md-datepicker, mat-datepicker', template: '', changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdDatepicker implements OnDestroy { /** The date to open the calendar to initially. */ diff --git a/src/lib/grid-list/grid-tile.ts b/src/lib/grid-list/grid-tile.ts index 77b24e64b1b..24a9dc21c78 100644 --- a/src/lib/grid-list/grid-tile.ts +++ b/src/lib/grid-list/grid-tile.ts @@ -62,6 +62,7 @@ export class MdGridTile { selector: 'md-grid-tile-header, mat-grid-tile-header, md-grid-tile-footer, mat-grid-tile-footer', templateUrl: 'grid-tile-text.html', changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdGridTileText implements AfterContentInit { /** diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index cabe77a97da..860a6216643 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -6,7 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ElementRef, OnDestroy, ChangeDetectionStrategy} from '@angular/core'; +import { + Component, + ElementRef, + OnDestroy, + ChangeDetectionStrategy, + ViewEncapsulation, +} from '@angular/core'; import {FocusableOption} from '../core/a11y/focus-key-manager'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; import {Subject} from 'rxjs/Subject'; @@ -36,6 +42,7 @@ export const _MdMenuItemMixinBase = mixinDisabled(MdMenuItemBase); '(mouseenter)': '_emitHoverEvent()', }, changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, templateUrl: 'menu-item.html', exportAs: 'mdMenuItem', }) diff --git a/src/lib/progress-bar/progress-bar.scss b/src/lib/progress-bar/progress-bar.scss index bb7a7a13e47..5542ce4142f 100644 --- a/src/lib/progress-bar/progress-bar.scss +++ b/src/lib/progress-bar/progress-bar.scss @@ -5,7 +5,7 @@ $mat-progress-bar-full-animation-duration: 2000ms !default; $mat-progress-bar-piece-animation-duration: 250ms !default; -:host { +.mat-progress-bar { display: block; // Height is provided for md-progress-bar to act as a default. height: $mat-progress-bar-height; @@ -59,6 +59,12 @@ $mat-progress-bar-piece-animation-duration: 250ms !default; left: 0; } + // Reverse the apparent directionality of progress vars for rtl. + &[dir='rtl'], + [dir='rtl'] & { + transform: rotateY(180deg); + } + &[mode='query'] { transform: rotateZ(180deg); } @@ -99,11 +105,6 @@ $mat-progress-bar-piece-animation-duration: 250ms !default; } -// Reverse the apparent directionality of progress vars for rtl. -:host-context([dir='rtl']) { - transform: rotateY(180deg); -} - // The values used for animations in md-progress-bar, both timing and transformation, can be // considered magic values. They are sourced from the Material Design example spec and duplicate // the values of the original designers definitions. diff --git a/src/lib/progress-bar/progress-bar.ts b/src/lib/progress-bar/progress-bar.ts index 490af933e81..83e3a4ead8f 100644 --- a/src/lib/progress-bar/progress-bar.ts +++ b/src/lib/progress-bar/progress-bar.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ChangeDetectionStrategy, Input} from '@angular/core'; +import {Component, ChangeDetectionStrategy, Input, ViewEncapsulation} from '@angular/core'; // TODO(josephperrott): Benchpress tests. // TODO(josephperrott): Add ARIA attributes for progressbar "for". @@ -32,6 +32,7 @@ import {Component, ChangeDetectionStrategy, Input} from '@angular/core'; templateUrl: 'progress-bar.html', styleUrls: ['progress-bar.css'], changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdProgressBar { /** Color of the progress bar. */ diff --git a/src/lib/progress-spinner/progress-spinner.scss b/src/lib/progress-spinner/progress-spinner.scss index 5477374eb6e..d213c518e15 100644 --- a/src/lib/progress-spinner/progress-spinner.scss +++ b/src/lib/progress-spinner/progress-spinner.scss @@ -12,7 +12,7 @@ $mat-progress-spinner-stroke-width: 10px !default; $mat-progress-spinner-viewport-size: 100px !default; -:host { +.mat-progress-spinner { display: block; // Height and width are provided for mat-progress-spinner to act as a default. // The height and width are expected to be overwritten by application css. diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index 5623900437b..a4fe0ee2d98 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -16,6 +16,7 @@ import { Renderer2, Directive, ViewChild, + ViewEncapsulation, } from '@angular/core'; import {CanColor, mixinColor} from '../core/common-behaviors/color'; @@ -70,6 +71,7 @@ export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase, 'pr selector: 'md-progress-spinner, mat-progress-spinner', host: { 'role': 'progressbar', + 'class': 'mat-progress-spinner', '[attr.aria-valuemin]': '_ariaValueMin', '[attr.aria-valuemax]': '_ariaValueMax', '[attr.aria-valuenow]': 'value', @@ -79,6 +81,7 @@ export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase, 'pr templateUrl: 'progress-spinner.html', styleUrls: ['progress-spinner.css'], changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdProgressSpinner extends _MdProgressSpinnerMixinBase implements OnDestroy, CanColor { @@ -279,12 +282,13 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase host: { 'role': 'progressbar', 'mode': 'indeterminate', - 'class': 'mat-spinner', + 'class': 'mat-spinner mat-progress-spinner', }, inputs: ['color'], templateUrl: 'progress-spinner.html', styleUrls: ['progress-spinner.css'], changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdSpinner extends MdProgressSpinner { constructor(elementRef: ElementRef, ngZone: NgZone, renderer: Renderer2) { diff --git a/src/lib/table/row.ts b/src/lib/table/row.ts index e16313248dd..1dbdfeb3243 100644 --- a/src/lib/table/row.ts +++ b/src/lib/table/row.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, Component, Directive} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Directive, ViewEncapsulation} from '@angular/core'; import { CdkHeaderRow, CdkRow, @@ -52,6 +52,7 @@ export class MdRowDef extends _MdCdkRowDef { } 'role': 'row', }, changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdHeaderRow extends _MdHeaderRow { } @@ -64,5 +65,6 @@ export class MdHeaderRow extends _MdHeaderRow { } 'role': 'row', }, changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MdRow extends _MdRow { } diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index fb09e2b3aee..ab3e059cebd 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -2,7 +2,7 @@ @import '../core/style/layout-common'; @import 'tabs-common'; -:host { +.mat-tab-group { display: flex; flex-direction: column; @@ -23,8 +23,8 @@ } } -:host[md-stretch-tabs] .mat-tab-label, -:host[mat-stretch-tabs] .mat-tab-label { +.mat-tab-group[md-stretch-tabs] .mat-tab-label, +.mat-tab-group[mat-stretch-tabs] .mat-tab-label { flex-basis: 0; flex-grow: 1; } @@ -51,7 +51,7 @@ flex-grow: 1; } - :host.mat-tab-group-dynamic-height &.mat-tab-body-active { + .mat-tab-group.mat-tab-group-dynamic-height &.mat-tab-body-active { overflow-y: hidden; } } diff --git a/src/lib/tabs/tab-group.ts b/src/lib/tabs/tab-group.ts index e002500f288..8205638fcde 100644 --- a/src/lib/tabs/tab-group.ts +++ b/src/lib/tabs/tab-group.ts @@ -22,6 +22,7 @@ import { AfterContentInit, AfterContentChecked, OnDestroy, + ViewEncapsulation, } from '@angular/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {map} from '@angular/cdk/rxjs'; @@ -62,6 +63,7 @@ export const _MdTabGroupMixinBase = mixinColor(mixinDisableRipple(MdTabGroupBase selector: 'md-tab-group, mat-tab-group', templateUrl: 'tab-group.html', styleUrls: ['tab-group.css'], + encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, inputs: ['color', 'disableRipple'], host: { diff --git a/src/lib/tabs/tab.ts b/src/lib/tabs/tab.ts index 6af56be136c..37778064e09 100644 --- a/src/lib/tabs/tab.ts +++ b/src/lib/tabs/tab.ts @@ -8,8 +8,18 @@ import {TemplatePortal} from '../core/portal/portal'; import { - ViewContainerRef, Input, TemplateRef, ViewChild, OnInit, ContentChild, - Component, ChangeDetectionStrategy, OnDestroy, OnChanges, SimpleChanges, + ChangeDetectionStrategy, + Component, + ContentChild, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + TemplateRef, + ViewChild, + ViewContainerRef, + ViewEncapsulation, } from '@angular/core'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; import {MdTabLabel} from './tab-label'; @@ -26,6 +36,7 @@ export const _MdTabMixinBase = mixinDisabled(MdTabBase); templateUrl: 'tab.html', inputs: ['disabled'], changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, exportAs: 'mdTab', }) export class MdTab extends _MdTabMixinBase implements OnInit, CanDisable, OnChanges, OnDestroy { diff --git a/tools/tslint-rules/noViewEncapsulationRule.js b/tools/tslint-rules/noViewEncapsulationRule.js new file mode 100644 index 00000000000..23e85e17774 --- /dev/null +++ b/tools/tslint-rules/noViewEncapsulationRule.js @@ -0,0 +1,48 @@ +const Lint = require('tslint'); +const ERROR_MESSAGE = 'Components must turn off view encapsulation.'; + +// TODO(crisbeto): combine this with the OnPush rule when it gets in. + +/** + * Rule that enforces that view encapsulation is turned off on all components. + * Files can be whitelisted via `"no-view-encapsulation": [true, "\.spec\.ts$"]`. + */ +class Rule extends Lint.Rules.AbstractRule { + apply(file) { + return this.applyWithWalker(new Walker(file, this.getOptions())); + } +} + +class Walker extends Lint.RuleWalker { + constructor(file, options) { + super(...arguments); + + // Whitelist with regular expressions to use when determining which files to lint. + const whitelist = options.ruleArguments; + + // Whether the file should be checked at all. + this._enabled = !whitelist.length || whitelist.some(p => new RegExp(p).test(file.fileName)); + } + + visitClassDeclaration(node) { + if (!this._enabled || !node.decorators) return; + + node.decorators + .map(decorator => decorator.expression) + .filter(expression => expression.expression.getText() === 'Component') + .filter(expression => expression.arguments.length && expression.arguments[0].properties) + .forEach(expression => { + const hasTurnedOffEncapsulation = expression.arguments[0].properties.some(prop => { + const value = prop.initializer.getText(); + return prop.name.getText() === 'encapsulation' && value.endsWith('.None'); + }); + + if (!hasTurnedOffEncapsulation) { + this.addFailureAtNode(expression.parent, ERROR_MESSAGE); + } + }); + } + +} + +exports.Rule = Rule; diff --git a/tslint.json b/tslint.json index 8088da464e7..547dbb6da31 100644 --- a/tslint.json +++ b/tslint.json @@ -79,6 +79,10 @@ "check-type", "check-preblock" ], + "no-view-encapsulation": [ + true, + "(lib|cdk)\/((?!spec.ts).)*.ts$" + ], // Bans jasmine helper functions that will prevent the CI from properly running tests. "ban": [ true,