From 566087161ea1a4948f4e84510f755778dda2ccce Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 5 Jun 2017 18:12:52 +0200 Subject: [PATCH 1/3] refactor: replace color getters and setters with mixin * Replaces the color getters and setters with a Mixin function (similar as for the disabled property) * This should reduce payload and also should make it way easier to maintain the color functionality. --- src/lib/button/button.spec.ts | 7 +- src/lib/button/button.ts | 55 +++++---------- src/lib/checkbox/checkbox.ts | 46 ++++-------- src/lib/chips/chip.ts | 49 +++++-------- src/lib/core/common-behaviors/color.spec.ts | 70 +++++++++++++++++++ src/lib/core/common-behaviors/color.ts | 47 +++++++++++++ src/lib/core/common-behaviors/constructor.ts | 2 + src/lib/core/common-behaviors/disabled.ts | 5 +- .../pseudo-checkbox/pseudo-checkbox.ts | 30 ++++---- src/lib/icon/icon.ts | 41 ++++------- src/lib/progress-spinner/progress-spinner.ts | 38 +++++----- src/lib/select/select.ts | 41 +++++------ src/lib/slide-toggle/slide-toggle.ts | 39 +++-------- src/lib/toolbar/toolbar.ts | 44 ++++-------- 14 files changed, 263 insertions(+), 251 deletions(-) create mode 100644 src/lib/core/common-behaviors/color.spec.ts create mode 100644 src/lib/core/common-behaviors/color.ts create mode 100644 src/lib/core/common-behaviors/constructor.ts diff --git a/src/lib/button/button.spec.ts b/src/lib/button/button.spec.ts index a685b2fb31b9..3204769b8fa3 100644 --- a/src/lib/button/button.spec.ts +++ b/src/lib/button/button.spec.ts @@ -38,6 +38,12 @@ describe('MdButton', () => { fixture.detectChanges(); expect(buttonDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true); expect(aDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true); + + testComponent.buttonColor = null; + fixture.detectChanges(); + + expect(buttonDebugElement.nativeElement.classList).not.toContain('mat-accent'); + expect(aDebugElement.nativeElement.classList).not.toContain('mat-accent'); }); it('should should not clear previous defined classes', () => { @@ -59,7 +65,6 @@ describe('MdButton', () => { expect(buttonDebugElement.nativeElement.classList.contains('mat-primary')).toBe(false); expect(buttonDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true); expect(buttonDebugElement.nativeElement.classList.contains('custom-class')).toBe(true); - }); // Regular button tests diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index 8efc0341250a..2a23e0786df9 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -11,6 +11,7 @@ import { } from '@angular/core'; import {coerceBooleanProperty, FocusOriginMonitor, Platform} from '../core'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; // TODO(kara): Convert attribute selectors to classes when attr maps become available @@ -71,8 +72,10 @@ export class MdMiniFabCssMatStyler {} // Boilerplate for applying mixins to MdButton. -export class MdButtonBase { } -export const _MdButtonMixinBase = mixinDisabled(MdButtonBase); +export class MdButtonBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdButtonMixinBase = mixinColor(mixinDisabled(MdButtonBase), true); /** @@ -89,13 +92,11 @@ export const _MdButtonMixinBase = mixinDisabled(MdButtonBase); }, templateUrl: 'button.html', styleUrls: ['button.css'], - inputs: ['disabled'], + inputs: ['disabled', 'color'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisable { - private _color: string; - +export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisable, IsColorable { /** Whether the button is round. */ _isRoundButton: boolean = this._hasAttributeWithPrefix('fab', 'mini-fab'); @@ -110,12 +111,11 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl get disableRipple() { return this._disableRipple; } set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); } - constructor( - private _elementRef: ElementRef, - private _renderer: Renderer2, - private _platform: Platform, - private _focusOriginMonitor: FocusOriginMonitor) { - super(); + constructor(private _platform: Platform, + private _focusOriginMonitor: FocusOriginMonitor, + renderer: Renderer2, + elementRef: ElementRef) { + super(renderer, elementRef); this._focusOriginMonitor.monitor(this._elementRef.nativeElement, this._renderer, true); } @@ -123,27 +123,6 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl this._focusOriginMonitor.stopMonitoring(this._elementRef.nativeElement); } - /** The color of the button. Can be `primary`, `accent`, or `warn`. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { this._updateColor(value); } - - _updateColor(newColor: string) { - this._setElementColor(this._color, false); - this._setElementColor(newColor, true); - this._color = newColor; - } - - _setElementColor(color: string, isAdd: boolean) { - if (color != null && color != '') { - if (isAdd) { - this._renderer.addClass(this._getHostElement(), `mat-${color}`); - } else { - this._renderer.removeClass(this._getHostElement(), `mat-${color}`); - } - } - } - /** Focuses the button. */ focus(): void { this._getHostElement().focus(); @@ -189,18 +168,18 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl '[attr.aria-disabled]': '_isAriaDisabled', '(click)': '_haltDisabledEvents($event)', }, - inputs: ['disabled'], + inputs: ['disabled', 'color'], templateUrl: 'button.html', styleUrls: ['button.css'], encapsulation: ViewEncapsulation.None }) export class MdAnchor extends MdButton { constructor( - elementRef: ElementRef, - renderer: Renderer2, platform: Platform, - focusOriginMonitor: FocusOriginMonitor) { - super(elementRef, renderer, platform, focusOriginMonitor); + focusOriginMonitor: FocusOriginMonitor, + elementRef: ElementRef, + renderer: Renderer2) { + super(platform, focusOriginMonitor, renderer, elementRef); } /** @docs-private */ diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index 9874e7697b47..48d88d702ce4 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -17,6 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; import {FocusOrigin, FocusOriginMonitor, MdRipple, RippleRef} from '../core'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; /** Monotonically increasing integer used to auto-generate unique ids for checkbox components. */ @@ -57,8 +58,10 @@ export class MdCheckboxChange { } // Boilerplate for applying mixins to MdCheckbox. -export class MdCheckboxBase { } -export const _MdCheckboxMixinBase = mixinDisabled(MdCheckboxBase); +export class MdCheckboxBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdCheckboxMixinBase = mixinColor(mixinDisabled(MdCheckboxBase)); /** @@ -82,12 +85,12 @@ export const _MdCheckboxMixinBase = mixinDisabled(MdCheckboxBase); '[class.mat-checkbox-label-before]': 'labelPosition == "before"', }, providers: [MD_CHECKBOX_CONTROL_VALUE_ACCESSOR], - inputs: ['disabled'], + inputs: ['disabled', 'color'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) export class MdCheckbox extends _MdCheckboxMixinBase - implements ControlValueAccessor, AfterViewInit, OnDestroy, CanDisable { + implements ControlValueAccessor, AfterViewInit, OnDestroy, IsColorable, CanDisable { /** * Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will * take precedence so this may be omitted. @@ -183,18 +186,18 @@ export class MdCheckbox extends _MdCheckboxMixinBase private _indeterminate: boolean = false; - private _color: string; - private _controlValueAccessorChangeFn: (value: any) => void = (value) => {}; /** Reference to the focused state ripple. */ private _focusRipple: RippleRef; - constructor(private _renderer: Renderer2, - private _elementRef: ElementRef, - private _changeDetectorRef: ChangeDetectorRef, - private _focusOriginMonitor: FocusOriginMonitor) { - super(); + constructor(private _changeDetectorRef: ChangeDetectorRef, + private _focusOriginMonitor: FocusOriginMonitor, + _renderer: Renderer2, + _elementRef: ElementRef) { + super(_renderer, _elementRef); + + // By default the checkbox uses the accent color for styling. this.color = 'accent'; } @@ -247,27 +250,6 @@ export class MdCheckbox extends _MdCheckboxMixinBase } } - /** The color of the button. Can be `primary`, `accent`, or `warn`. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { this._updateColor(value); } - - _updateColor(newColor: string) { - this._setElementColor(this._color, false); - this._setElementColor(newColor, true); - this._color = newColor; - } - - _setElementColor(color: string, isAdd: boolean) { - if (color != null && color != '') { - if (isAdd) { - this._renderer.addClass(this._elementRef.nativeElement, `mat-${color}`); - } else { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-${color}`); - } - } - } - _isRippleDisabled() { return this.disableRipple || this.disabled; } diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 5e06c3839994..86b548bbfc69 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -11,11 +11,19 @@ import { import {Focusable} from '../core/a11y/focus-key-manager'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; export interface MdChipEvent { chip: MdChip; } +// Boilerplate for applying mixins to MdChip. +export class MdChipBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdChipMixinBase = mixinColor(MdChipBase); + + /** * Material design styled Chip component. Used inside the MdChipList component. */ @@ -23,6 +31,7 @@ export interface MdChipEvent { selector: `md-basic-chip, [md-basic-chip], md-chip, [md-chip], mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]`, template: ``, + inputs: ['color'], host: { '[class.mat-chip]': 'true', 'tabindex': '-1', @@ -35,7 +44,7 @@ export interface MdChipEvent { '(click)': '_handleClick($event)' } }) -export class MdChip implements Focusable, OnInit, OnDestroy { +export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDestroy, IsColorable { /** Whether or not the chip is disabled. Disabled chips cannot be focused. */ protected _disabled: boolean = null; @@ -43,9 +52,6 @@ export class MdChip implements Focusable, OnInit, OnDestroy { /** Whether or not the chip is selected. */ protected _selected: boolean = false; - /** The palette color of selected chips. */ - protected _color: string = 'primary'; - /** Emitted when the chip is focused. */ onFocus = new EventEmitter(); @@ -58,11 +64,15 @@ export class MdChip implements Focusable, OnInit, OnDestroy { /** Emitted when the chip is destroyed. */ @Output() destroy = new EventEmitter(); - constructor(protected _renderer: Renderer2, protected _elementRef: ElementRef) { } + constructor(renderer: Renderer2, elementRef: ElementRef) { + super(renderer, elementRef); + + // By default the chip component uses the primary color palette. + this.color = 'primary'; + } ngOnInit(): void { this._addDefaultCSSClass(); - this._updateColor(this._color); } ngOnDestroy(): void { @@ -108,15 +118,6 @@ export class MdChip implements Focusable, OnInit, OnDestroy { return this.selected; } - /** The color of the chip. Can be `primary`, `accent`, or `warn`. */ - @Input() get color(): string { - return this._color; - } - - set color(value: string) { - this._updateColor(value); - } - /** Allows for programmatic focusing of the chip. */ focus(): void { this._elementRef.nativeElement.focus(); @@ -147,22 +148,4 @@ export class MdChip implements Focusable, OnInit, OnDestroy { this._renderer.addClass(el, 'mat-basic-chip'); } } - - /** Updates the private _color variable and the native element. */ - private _updateColor(newColor: string) { - this._setElementColor(this._color, false); - this._setElementColor(newColor, true); - this._color = newColor; - } - - /** Sets the mat-color on the native element. */ - private _setElementColor(color: string, isAdd: boolean) { - if (color != null && color != '') { - if (isAdd) { - this._renderer.addClass(this._elementRef.nativeElement, `mat-${color}`); - } else { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-${color}`); - } - } - } } diff --git a/src/lib/core/common-behaviors/color.spec.ts b/src/lib/core/common-behaviors/color.spec.ts new file mode 100644 index 000000000000..3dce4d49cb0c --- /dev/null +++ b/src/lib/core/common-behaviors/color.spec.ts @@ -0,0 +1,70 @@ +import {mixinColor} from './color'; +import {ElementRef, Renderer2} from '@angular/core'; + +describe('MixinColor', () => { + + it('should augment an existing class with a color property', () => { + const classWithColor = mixinColor(TestClass); + const instance = new classWithColor(); + + expect(instance.color) + .toBeFalsy('Expected the mixed-into class to have a color property'); + + instance.color = 'accent'; + + expect(instance.color) + .toBe('accent', 'Expected the mixed-into class to have an updated color property'); + }); + + it('should remove old color classes if new color is set', () => { + const classWithColor = mixinColor(TestClass); + const instance = new classWithColor(); + + expect(instance.testElement.classList.length) + .toBe(0, 'Expected the element to not have any classes at initialization'); + + instance.color = 'primary'; + + expect(instance.testElement.classList) + .toContain('mat-primary', 'Expected the element to have the "mat-primary" class set'); + + instance.color = 'accent'; + + expect(instance.testElement.classList) + .not.toContain('mat-primary', 'Expected the element to no longer have "mat-primary" set.'); + expect(instance.testElement.classList) + .toContain('mat-accent', 'Expected the element to have the "mat-accent" class set'); + }); + + it('should allow updating the color to an empty value', () => { + const classWithColor = mixinColor(TestClass, true); + const instance = new classWithColor(); + + expect(instance.testElement.classList.length) + .toBe(0, 'Expected the element to not have any classes at initialization'); + + instance.color = 'primary'; + + expect(instance.testElement.classList) + .toContain('mat-primary', 'Expected the element to have the "mat-primary" class set'); + + instance.color = null; + + expect(instance.testElement.classList.length) + .toBe(0, 'Expected the element to not have any classes after the color has been set to null'); + }); + +}); + +class TestClass { + testElement: HTMLElement = document.createElement('div'); + + /** Mock of a RendererV2 for the color mixin. */ + _renderer: Renderer2 = { + addClass: (element: HTMLElement, className: string) => element.classList.add(className), + removeClass: (element: HTMLElement, className: string) => element.classList.remove(className) + } as any; + + /** Fake instance of an ElementRef. */ + _elementRef = new ElementRef(this.testElement); +} diff --git a/src/lib/core/common-behaviors/color.ts b/src/lib/core/common-behaviors/color.ts new file mode 100644 index 000000000000..2a004bd6d98d --- /dev/null +++ b/src/lib/core/common-behaviors/color.ts @@ -0,0 +1,47 @@ +import {Constructor} from './constructor'; +import {ElementRef, Renderer2} from '@angular/core'; + +/** List of possible color values that can be set. */ +const AVAILABLE_COLOR_VALUES = ['primary', 'accent', 'warn']; + +/** @docs-private */ +export interface IsColorable { + color: string; +} + +/** @docs-private */ +export interface ColorableBase { + _renderer: Renderer2; + _elementRef: ElementRef; +} + +/** Mixin to augment a directive with a `color` property. */ +export function mixinColor>(base: T, allowNoColor = false) + : Constructor & T { + return class extends base { + private _color: string = null; + + constructor(...args: any[]) { super(...args); } + + get color(): string { return this._color; } + set color(value: string) { + if (AVAILABLE_COLOR_VALUES.indexOf(value) !== -1 || (allowNoColor && !value)) { + this._setColorClass(this._color, false); + this._setColorClass(value, true); + this._color = value; + } + } + + /** Method that changes the color classes on the host element. */ + private _setColorClass(colorName: string, isAdd: boolean) { + if (colorName) { + if (isAdd) { + this._renderer.addClass(this._elementRef.nativeElement, `mat-${colorName}`); + } else { + this._renderer.removeClass(this._elementRef.nativeElement, `mat-${colorName}`); + } + } + } + }; +} + diff --git a/src/lib/core/common-behaviors/constructor.ts b/src/lib/core/common-behaviors/constructor.ts new file mode 100644 index 000000000000..5a695a1b4d23 --- /dev/null +++ b/src/lib/core/common-behaviors/constructor.ts @@ -0,0 +1,2 @@ +/** @docs-private */ +export type Constructor = new(...args: any[]) => T; diff --git a/src/lib/core/common-behaviors/disabled.ts b/src/lib/core/common-behaviors/disabled.ts index 8c290d1e8fdd..5db4d2968f2d 100644 --- a/src/lib/core/common-behaviors/disabled.ts +++ b/src/lib/core/common-behaviors/disabled.ts @@ -1,8 +1,5 @@ import {coerceBooleanProperty} from '../coercion/boolean-property'; - - -/** @docs-private */ -export type Constructor = new(...args: any[]) => T; +import {Constructor} from './constructor'; /** @docs-private */ export interface CanDisable { diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts index 8b17c2e3fd48..7391ee29a389 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -5,9 +5,18 @@ import { ElementRef, Renderer2, } from '@angular/core'; +import {IsColorable, mixinColor} from '../../common-behaviors/color'; export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; + +// Boilerplate for applying mixins to MdChip. +export class MdPseudoCheckboxBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdPseudoCheckboxBase = mixinColor(MdPseudoCheckboxBase); + + /** * Component that shows a simplified checkbox without including any kind of "real" checkbox. * Meant to be used when the checkbox is purely decorative and a large number of them will be @@ -24,6 +33,7 @@ export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; encapsulation: ViewEncapsulation.None, selector: 'md-pseudo-checkbox, mat-pseudo-checkbox', styleUrls: ['pseudo-checkbox.css'], + inputs: ['color'], template: '', host: { '[class.mat-pseudo-checkbox]': 'true', @@ -32,29 +42,15 @@ export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; '[class.mat-pseudo-checkbox-disabled]': 'disabled', }, }) -export class MdPseudoCheckbox { +export class MdPseudoCheckbox extends _MdPseudoCheckboxBase implements IsColorable { /** Display state of the checkbox. */ @Input() state: MdPseudoCheckboxState = 'unchecked'; /** Whether the checkbox is disabled. */ @Input() disabled: boolean = false; - /** Color of the checkbox. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { - if (value) { - let nativeElement = this._elementRef.nativeElement; - - this._renderer.removeClass(nativeElement, `mat-${this.color}`); - this._renderer.addClass(nativeElement, `mat-${value}`); - this._color = value; - } - } - - private _color: string; - - constructor(private _elementRef: ElementRef, private _renderer: Renderer2) { + constructor(elementRef: ElementRef, renderer: Renderer2) { + super(renderer, elementRef); this.color = 'accent'; } } diff --git a/src/lib/icon/icon.ts b/src/lib/icon/icon.ts index 83b1027d1726..a8b235b88102 100644 --- a/src/lib/icon/icon.ts +++ b/src/lib/icon/icon.ts @@ -11,6 +11,14 @@ import { AfterViewChecked, } from '@angular/core'; import {MdIconRegistry} from './icon-registry'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; + + +// Boilerplate for applying mixins to MdIcon. +export class MdIconBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdIconMixinBase = mixinColor(MdIconBase, true); /** @@ -51,6 +59,7 @@ import {MdIconRegistry} from './icon-registry'; template: '', selector: 'md-icon, mat-icon', styleUrls: ['icon.css'], + inputs: ['color'], host: { 'role': 'img', '[class.mat-icon]': 'true', @@ -58,8 +67,8 @@ import {MdIconRegistry} from './icon-registry'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdIcon implements OnChanges, OnInit, AfterViewChecked { - private _color: string; +export class MdIcon extends _MdIconMixinBase implements OnChanges, OnInit, AfterViewChecked, + IsColorable { /** Name of the icon in the SVG icon set. */ @Input() svgIcon: string; @@ -76,34 +85,14 @@ export class MdIcon implements OnChanges, OnInit, AfterViewChecked { /** Screenreader label for the icon. */ @Input('aria-label') hostAriaLabel: string = ''; - /** Color of the icon. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { this._updateColor(value); } - private _previousFontSetClass: string; private _previousFontIconClass: string; private _previousAriaLabel: string; - constructor( - private _elementRef: ElementRef, - private _renderer: Renderer2, - private _mdIconRegistry: MdIconRegistry) { } - - _updateColor(newColor: string) { - this._setElementColor(this._color, false); - this._setElementColor(newColor, true); - this._color = newColor; - } - - _setElementColor(color: string, isAdd: boolean) { - if (color != null && color != '') { - if (isAdd) { - this._renderer.addClass(this._elementRef.nativeElement, `mat-${color}`); - } else { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-${color}`); - } - } + constructor(private _mdIconRegistry: MdIconRegistry, + renderer: Renderer2, + elementRef: ElementRef) { + super(renderer, elementRef); } /** diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index a88fef129c72..ccb2715b1ddb 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -10,6 +10,7 @@ import { Directive, ViewChild, } from '@angular/core'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; // TODO(josephperrott): Benchpress tests. @@ -49,6 +50,11 @@ type EasingFn = (currentTime: number, startValue: number, }) export class MdProgressSpinnerCssMatStyler {} +// Boilerplate for applying mixins to MdProgressSpinner. +export class MdProgressSpinnerBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase); /** * component. @@ -61,11 +67,14 @@ export class MdProgressSpinnerCssMatStyler {} '[attr.aria-valuemin]': '_ariaValueMin', '[attr.aria-valuemax]': '_ariaValueMax' }, + inputs: ['color'], templateUrl: 'progress-spinner.html', styleUrls: ['progress-spinner.css'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdProgressSpinner implements OnDestroy { +export class MdProgressSpinner extends _MdProgressSpinnerMixinBase + implements OnDestroy, IsColorable { + /** The id of the last requested animation. */ private _lastAnimationId: number = 0; @@ -77,7 +86,6 @@ export class MdProgressSpinner implements OnDestroy { private _mode: ProgressSpinnerMode = 'determinate'; private _value: number; - private _color: string = 'primary'; /** Stroke width of the progress spinner. By default uses 10px as stroke width. */ @Input() strokeWidth: number = PROGRESS_SPINNER_STROKE_WIDTH; @@ -112,17 +120,6 @@ export class MdProgressSpinner implements OnDestroy { this._cleanupIndeterminateAnimation(); } - /** The color of the progress-spinner. Can be primary, accent, or warn. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { - if (value) { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-${this._color}`); - this._renderer.addClass(this._elementRef.nativeElement, `mat-${value}`); - this._color = value; - } - } - /** Value of the progress circle. It is bound to the host as the attribute aria-valuenow. */ @Input() @HostBinding('attr.aria-valuenow') @@ -162,10 +159,14 @@ export class MdProgressSpinner implements OnDestroy { } } - constructor( - private _ngZone: NgZone, - private _elementRef: ElementRef, - private _renderer: Renderer2) { } + constructor(private _ngZone: NgZone, + renderer: Renderer2, + elementRef: ElementRef) { + super(renderer, elementRef); + + // By default the progress-spinner component uses the primary color palette. + this.color = 'primary'; + } /** @@ -274,13 +275,14 @@ export class MdProgressSpinner implements OnDestroy { 'mode': 'indeterminate', '[class.mat-spinner]': 'true', }, + inputs: ['color'], templateUrl: 'progress-spinner.html', styleUrls: ['progress-spinner.css'], }) export class MdSpinner extends MdProgressSpinner implements OnDestroy { constructor(elementRef: ElementRef, ngZone: NgZone, renderer: Renderer2) { - super(ngZone, elementRef, renderer); + super(ngZone, renderer, elementRef); this.mode = 'indeterminate'; } diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index a99d08220dcb..b114b08e25dc 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -34,6 +34,8 @@ import {getMdSelectDynamicMultipleError, getMdSelectNonArrayValueError} from './ import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/filter'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanDisable} from '../core/common-behaviors/disabled'; /** @@ -95,11 +97,19 @@ export class MdSelectChange { /** Allowed values for the floatPlaceholder option. */ export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto'; +// Boilerplate for applying mixins to MdSelect. +export class MdSelectBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdSelectMixinBase = mixinColor(MdSelectBase); + + @Component({ moduleId: module.id, selector: 'md-select, mat-select', templateUrl: 'select.html', styleUrls: ['select.css'], + inputs: ['color'], encapsulation: ViewEncapsulation.None, host: { 'role': 'listbox', @@ -122,7 +132,8 @@ export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto'; ], exportAs: 'mdSelect', }) -export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlValueAccessor { +export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, OnDestroy, OnInit, + ControlValueAccessor, IsColorable { /** Whether or not the overlay panel is open. */ private _panelOpen = false; @@ -159,9 +170,6 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Tab index for the element. */ private _tabIndex: number; - /** Theme color for the component. */ - private _color: string; - /** * The width of the trigger. Must be saved to set the min width of the overlay panel * and the width of the selected value. @@ -288,17 +296,6 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Input that can be used to specify the `aria-labelledby` attribute. */ @Input('aria-labelledby') ariaLabelledby: string = ''; - /** Theme color for the component. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { - if (value && value !== this._color) { - this._renderer.removeClass(this._element.nativeElement, `mat-${this._color}`); - this._renderer.addClass(this._element.nativeElement, `mat-${value}`); - this._color = value; - } - } - /** Combined stream of all of the child options' change events. */ get optionSelectionChanges(): Observable { return Observable.merge(...this.options.map(option => option.onSelectionChange)); @@ -313,21 +310,25 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Event emitted when the selected value has been changed by the user. */ @Output() change: EventEmitter = new EventEmitter(); - constructor(private _element: ElementRef, private _renderer: Renderer2, - private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, - @Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl, + constructor(private _viewportRuler: ViewportRuler, + private _changeDetectorRef: ChangeDetectorRef, + renderer: Renderer2, + elementRef: ElementRef, + @Optional() private _dir: Dir, + @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string) { + super(renderer, elementRef); if (this._control) { this._control.valueAccessor = this; } this._tabIndex = parseInt(tabIndex) || 0; + this.color = 'primary'; } ngOnInit() { this._selectionModel = new SelectionModel(this.multiple, null, false); - this.color = this.color || 'primary'; } ngAfterContentInit() { @@ -742,7 +743,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Focuses the host element when the panel closes. */ private _focusHost(): void { - this._element.nativeElement.focus(); + this._elementRef.nativeElement.focus(); } /** Gets the index of the provided option in the option list. */ diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index b4e6415bf7b4..83aa9b5de160 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -24,6 +24,7 @@ import { } from '../core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = { @@ -44,8 +45,10 @@ let nextId = 0; // Boilerplate for applying mixins to MdSlideToggle. -export class MdSlideToggleBase { } -export const _MdSlideToggleMixinBase = mixinDisabled(MdSlideToggleBase); +export class MdSlideToggleBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdSlideToggleMixinBase = mixinColor(mixinDisabled(MdSlideToggleBase)); /** Represents a slidable "switch" toggle that can be moved between on and off. */ @Component({ @@ -60,19 +63,18 @@ export const _MdSlideToggleMixinBase = mixinDisabled(MdSlideToggleBase); templateUrl: 'slide-toggle.html', styleUrls: ['slide-toggle.css'], providers: [MD_SLIDE_TOGGLE_VALUE_ACCESSOR], - inputs: ['disabled'], + inputs: ['disabled', 'color'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) export class MdSlideToggle extends _MdSlideToggleMixinBase - implements OnDestroy, AfterContentInit, ControlValueAccessor, CanDisable { + implements OnDestroy, AfterContentInit, ControlValueAccessor, CanDisable, IsColorable { private onChange = (_: any) => {}; private onTouched = () => {}; // A unique id for the slide-toggle. By default the id is auto-generated. private _uniqueId = `md-slide-toggle-${++nextId}`; private _checked: boolean = false; - private _color: string; private _slideRenderer: SlideToggleRenderer = null; private _required: boolean = false; private _disableRipple: boolean = false; @@ -120,8 +122,8 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase /** Reference to the ripple directive on the thumb container. */ @ViewChild(MdRipple) _ripple: MdRipple; - constructor(private _elementRef: ElementRef, - private _renderer: Renderer2, + constructor(public _elementRef: ElementRef, + public _renderer: Renderer2, private _focusOriginMonitor: FocusOriginMonitor, private _changeDetectorRef: ChangeDetectorRef) { super(); @@ -210,13 +212,6 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase } } - /** The color of the slide-toggle. Can be primary, accent, or warn. */ - @Input() - get color(): string { return this._color; } - set color(value: string) { - this._updateColor(value); - } - /** Toggles the checked state of the slide-toggle. */ toggle() { this.checked = !this.checked; @@ -238,22 +233,6 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase } } - private _updateColor(newColor: string) { - this._setElementColor(this._color, false); - this._setElementColor(newColor, true); - this._color = newColor; - } - - private _setElementColor(color: string, isAdd: boolean) { - if (color != null && color != '') { - if (isAdd) { - this._renderer.addClass(this._elementRef.nativeElement, `mat-${color}`); - } else { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-${color}`); - } - } - } - /** Emits the change event to the `change` output EventEmitter */ private _emitChangeEvent() { let event = new MdSlideToggleChange(); diff --git a/src/lib/toolbar/toolbar.ts b/src/lib/toolbar/toolbar.ts index fedf302fa950..41738ddee677 100644 --- a/src/lib/toolbar/toolbar.ts +++ b/src/lib/toolbar/toolbar.ts @@ -1,12 +1,12 @@ import { Component, ChangeDetectionStrategy, - Input, ViewEncapsulation, Directive, ElementRef, Renderer2, } from '@angular/core'; +import {IsColorable, mixinColor} from '../core/common-behaviors/color'; @Directive({ @@ -17,11 +17,19 @@ import { }) export class MdToolbarRow {} +// Boilerplate for applying mixins to MdToolbar. +export class MdToolbarBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); + + @Component({ moduleId: module.id, selector: 'md-toolbar, mat-toolbar', templateUrl: 'toolbar.html', styleUrls: ['toolbar.css'], + inputs: ['color'], host: { '[class.mat-toolbar]': 'true', 'role': 'toolbar' @@ -29,38 +37,10 @@ export class MdToolbarRow {} changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }) -export class MdToolbar { - - private _color: string; - - constructor(private _elementRef: ElementRef, private _renderer: Renderer2) { } - - /** The color of the toolbar. Can be primary, accent, or warn. */ - @Input() - get color(): string { - return this._color; - } - - set color(value: string) { - this._updateColor(value); - } - - private _updateColor(newColor: string) { - this._setElementColor(this._color, false); - this._setElementColor(newColor, true); - this._color = newColor; - } - - private _setElementColor(color: string, isAdd: boolean) { - if (color != null && color != '') { - let element = this._elementRef.nativeElement; +export class MdToolbar extends _MdToolbarMixinBase implements IsColorable { - if (isAdd) { - this._renderer.addClass(element, `mat-${color}`); - } else { - this._renderer.removeClass(element, `mat-${color}`); - } - } + constructor(renderer: Renderer2, elementRef: ElementRef) { + super(renderer, elementRef); } } From 2f6c16d1758f51216cda0d72bcaf13cb51767459 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 5 Jun 2017 20:31:55 +0200 Subject: [PATCH 2/3] Address comments --- src/lib/button/button.ts | 19 ++++++++-------- src/lib/checkbox/checkbox.ts | 17 +++++++------- src/lib/chips/chip.ts | 12 +++++----- src/lib/core/common-behaviors/color.ts | 22 +++++++++---------- .../pseudo-checkbox/pseudo-checkbox.ts | 11 +++++----- src/lib/icon/icon.ts | 13 ++++++----- src/lib/progress-spinner/progress-spinner.ts | 17 +++++++------- src/lib/select/select.ts | 13 ++++++----- src/lib/slide-toggle/slide-toggle.ts | 7 +++--- src/lib/toolbar/toolbar.ts | 11 +++++----- 10 files changed, 75 insertions(+), 67 deletions(-) diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index 2a23e0786df9..bb50a0ac49e7 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -11,7 +11,7 @@ import { } from '@angular/core'; import {coerceBooleanProperty, FocusOriginMonitor, Platform} from '../core'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; // TODO(kara): Convert attribute selectors to classes when attr maps become available @@ -73,7 +73,8 @@ export class MdMiniFabCssMatStyler {} // Boilerplate for applying mixins to MdButton. export class MdButtonBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdButtonMixinBase = mixinColor(mixinDisabled(MdButtonBase), true); @@ -96,7 +97,7 @@ export const _MdButtonMixinBase = mixinColor(mixinDisabled(MdButtonBase), true); encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisable, IsColorable { +export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisable, CanColor { /** Whether the button is round. */ _isRoundButton: boolean = this._hasAttributeWithPrefix('fab', 'mini-fab'); @@ -111,11 +112,11 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl get disableRipple() { return this._disableRipple; } set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); } - constructor(private _platform: Platform, - private _focusOriginMonitor: FocusOriginMonitor, - renderer: Renderer2, - elementRef: ElementRef) { - super(renderer, elementRef); + constructor(public _renderer: Renderer2, + public _elementRef: ElementRef, + private _platform: Platform, + private _focusOriginMonitor: FocusOriginMonitor) { + super(); this._focusOriginMonitor.monitor(this._elementRef.nativeElement, this._renderer, true); } @@ -179,7 +180,7 @@ export class MdAnchor extends MdButton { focusOriginMonitor: FocusOriginMonitor, elementRef: ElementRef, renderer: Renderer2) { - super(platform, focusOriginMonitor, renderer, elementRef); + super(renderer, elementRef, platform, focusOriginMonitor); } /** @docs-private */ diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index 48d88d702ce4..4c7628debaf0 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -17,7 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; import {FocusOrigin, FocusOriginMonitor, MdRipple, RippleRef} from '../core'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; /** Monotonically increasing integer used to auto-generate unique ids for checkbox components. */ @@ -59,7 +59,8 @@ export class MdCheckboxChange { // Boilerplate for applying mixins to MdCheckbox. export class MdCheckboxBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdCheckboxMixinBase = mixinColor(mixinDisabled(MdCheckboxBase)); @@ -90,7 +91,7 @@ export const _MdCheckboxMixinBase = mixinColor(mixinDisabled(MdCheckboxBase)); changeDetection: ChangeDetectionStrategy.OnPush }) export class MdCheckbox extends _MdCheckboxMixinBase - implements ControlValueAccessor, AfterViewInit, OnDestroy, IsColorable, CanDisable { + implements ControlValueAccessor, AfterViewInit, OnDestroy, CanColor, CanDisable { /** * Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will * take precedence so this may be omitted. @@ -191,11 +192,11 @@ export class MdCheckbox extends _MdCheckboxMixinBase /** Reference to the focused state ripple. */ private _focusRipple: RippleRef; - constructor(private _changeDetectorRef: ChangeDetectorRef, - private _focusOriginMonitor: FocusOriginMonitor, - _renderer: Renderer2, - _elementRef: ElementRef) { - super(_renderer, _elementRef); + constructor(public _renderer: Renderer2, + public _elementRef: ElementRef, + private _changeDetectorRef: ChangeDetectorRef, + private _focusOriginMonitor: FocusOriginMonitor) { + super(); // By default the checkbox uses the accent color for styling. this.color = 'accent'; diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 86b548bbfc69..652c261150ec 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -11,7 +11,7 @@ import { import {Focusable} from '../core/a11y/focus-key-manager'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; export interface MdChipEvent { chip: MdChip; @@ -19,7 +19,8 @@ export interface MdChipEvent { // Boilerplate for applying mixins to MdChip. export class MdChipBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdChipMixinBase = mixinColor(MdChipBase); @@ -44,7 +45,7 @@ export const _MdChipMixinBase = mixinColor(MdChipBase); '(click)': '_handleClick($event)' } }) -export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDestroy, IsColorable { +export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDestroy, CanColor { /** Whether or not the chip is disabled. Disabled chips cannot be focused. */ protected _disabled: boolean = null; @@ -64,9 +65,8 @@ export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDes /** Emitted when the chip is destroyed. */ @Output() destroy = new EventEmitter(); - constructor(renderer: Renderer2, elementRef: ElementRef) { - super(renderer, elementRef); - + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) { + super(); // By default the chip component uses the primary color palette. this.color = 'primary'; } diff --git a/src/lib/core/common-behaviors/color.ts b/src/lib/core/common-behaviors/color.ts index 2a004bd6d98d..52130a050ae4 100644 --- a/src/lib/core/common-behaviors/color.ts +++ b/src/lib/core/common-behaviors/color.ts @@ -1,31 +1,31 @@ import {Constructor} from './constructor'; import {ElementRef, Renderer2} from '@angular/core'; -/** List of possible color values that can be set. */ -const AVAILABLE_COLOR_VALUES = ['primary', 'accent', 'warn']; - /** @docs-private */ -export interface IsColorable { +export interface CanColor { color: string; } /** @docs-private */ -export interface ColorableBase { +export interface HasRenderer { _renderer: Renderer2; _elementRef: ElementRef; } +/** Possible color palette values. */ +export type ThemePalette = 'primary' | 'accent' | 'warn' | null; + /** Mixin to augment a directive with a `color` property. */ -export function mixinColor>(base: T, allowNoColor = false) - : Constructor & T { +export function mixinColor>(base: T, allowNoColor = false) + : Constructor & T { return class extends base { - private _color: string = null; + private _color: ThemePalette = undefined; constructor(...args: any[]) { super(...args); } - get color(): string { return this._color; } - set color(value: string) { - if (AVAILABLE_COLOR_VALUES.indexOf(value) !== -1 || (allowNoColor && !value)) { + get color(): ThemePalette { return this._color; } + set color(value: ThemePalette) { + if (value || allowNoColor && !value) { this._setColorClass(this._color, false); this._setColorClass(value, true); this._color = value; diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts index 7391ee29a389..d595e63598c6 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -5,14 +5,15 @@ import { ElementRef, Renderer2, } from '@angular/core'; -import {IsColorable, mixinColor} from '../../common-behaviors/color'; +import {CanColor, mixinColor} from '../../common-behaviors/color'; export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; // Boilerplate for applying mixins to MdChip. export class MdPseudoCheckboxBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdPseudoCheckboxBase = mixinColor(MdPseudoCheckboxBase); @@ -42,15 +43,15 @@ export const _MdPseudoCheckboxBase = mixinColor(MdPseudoCheckboxBase); '[class.mat-pseudo-checkbox-disabled]': 'disabled', }, }) -export class MdPseudoCheckbox extends _MdPseudoCheckboxBase implements IsColorable { +export class MdPseudoCheckbox extends _MdPseudoCheckboxBase implements CanColor { /** Display state of the checkbox. */ @Input() state: MdPseudoCheckboxState = 'unchecked'; /** Whether the checkbox is disabled. */ @Input() disabled: boolean = false; - constructor(elementRef: ElementRef, renderer: Renderer2) { - super(renderer, elementRef); + constructor(public _elementRef: ElementRef, public _renderer: Renderer2) { + super(); this.color = 'accent'; } } diff --git a/src/lib/icon/icon.ts b/src/lib/icon/icon.ts index a8b235b88102..422ace6d8a8d 100644 --- a/src/lib/icon/icon.ts +++ b/src/lib/icon/icon.ts @@ -11,12 +11,13 @@ import { AfterViewChecked, } from '@angular/core'; import {MdIconRegistry} from './icon-registry'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; // Boilerplate for applying mixins to MdIcon. export class MdIconBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdIconMixinBase = mixinColor(MdIconBase, true); @@ -68,7 +69,7 @@ export const _MdIconMixinBase = mixinColor(MdIconBase, true); changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdIcon extends _MdIconMixinBase implements OnChanges, OnInit, AfterViewChecked, - IsColorable { + CanColor { /** Name of the icon in the SVG icon set. */ @Input() svgIcon: string; @@ -90,9 +91,9 @@ export class MdIcon extends _MdIconMixinBase implements OnChanges, OnInit, After private _previousAriaLabel: string; constructor(private _mdIconRegistry: MdIconRegistry, - renderer: Renderer2, - elementRef: ElementRef) { - super(renderer, elementRef); + public _renderer: Renderer2, + public _elementRef: ElementRef) { + super(); } /** diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index ccb2715b1ddb..f5c030c5201a 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -10,7 +10,7 @@ import { Directive, ViewChild, } from '@angular/core'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; // TODO(josephperrott): Benchpress tests. @@ -52,7 +52,8 @@ export class MdProgressSpinnerCssMatStyler {} // Boilerplate for applying mixins to MdProgressSpinner. export class MdProgressSpinnerBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase); @@ -73,7 +74,7 @@ export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase); changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdProgressSpinner extends _MdProgressSpinnerMixinBase - implements OnDestroy, IsColorable { + implements OnDestroy, CanColor { /** The id of the last requested animation. */ private _lastAnimationId: number = 0; @@ -159,10 +160,10 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase } } - constructor(private _ngZone: NgZone, - renderer: Renderer2, - elementRef: ElementRef) { - super(renderer, elementRef); + constructor(public _renderer: Renderer2, + public _elementRef: ElementRef, + private _ngZone: NgZone) { + super(); // By default the progress-spinner component uses the primary color palette. this.color = 'primary'; @@ -282,7 +283,7 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase export class MdSpinner extends MdProgressSpinner implements OnDestroy { constructor(elementRef: ElementRef, ngZone: NgZone, renderer: Renderer2) { - super(ngZone, renderer, elementRef); + super(renderer, elementRef, ngZone); this.mode = 'indeterminate'; } diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index b114b08e25dc..58382033dc5a 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -34,7 +34,7 @@ import {getMdSelectDynamicMultipleError, getMdSelectNonArrayValueError} from './ import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/filter'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; import {CanDisable} from '../core/common-behaviors/disabled'; @@ -99,7 +99,8 @@ export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto'; // Boilerplate for applying mixins to MdSelect. export class MdSelectBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdSelectMixinBase = mixinColor(MdSelectBase); @@ -133,7 +134,7 @@ export const _MdSelectMixinBase = mixinColor(MdSelectBase); exportAs: 'mdSelect', }) export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, OnDestroy, OnInit, - ControlValueAccessor, IsColorable { + ControlValueAccessor, CanColor { /** Whether or not the overlay panel is open. */ private _panelOpen = false; @@ -312,12 +313,12 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On constructor(private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, - renderer: Renderer2, - elementRef: ElementRef, + public _renderer: Renderer2, + public _elementRef: ElementRef, @Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string) { - super(renderer, elementRef); + super(); if (this._control) { this._control.valueAccessor = this; diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 83aa9b5de160..15061025194b 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -24,7 +24,7 @@ import { } from '../core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = { @@ -46,7 +46,8 @@ let nextId = 0; // Boilerplate for applying mixins to MdSlideToggle. export class MdSlideToggleBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdSlideToggleMixinBase = mixinColor(mixinDisabled(MdSlideToggleBase)); @@ -68,7 +69,7 @@ export const _MdSlideToggleMixinBase = mixinColor(mixinDisabled(MdSlideToggleBas changeDetection: ChangeDetectionStrategy.OnPush }) export class MdSlideToggle extends _MdSlideToggleMixinBase - implements OnDestroy, AfterContentInit, ControlValueAccessor, CanDisable, IsColorable { + implements OnDestroy, AfterContentInit, ControlValueAccessor, CanDisable, CanColor { private onChange = (_: any) => {}; private onTouched = () => {}; diff --git a/src/lib/toolbar/toolbar.ts b/src/lib/toolbar/toolbar.ts index 41738ddee677..5a9b1c572fe7 100644 --- a/src/lib/toolbar/toolbar.ts +++ b/src/lib/toolbar/toolbar.ts @@ -6,7 +6,7 @@ import { ElementRef, Renderer2, } from '@angular/core'; -import {IsColorable, mixinColor} from '../core/common-behaviors/color'; +import {CanColor, mixinColor} from '../core/common-behaviors/color'; @Directive({ @@ -19,7 +19,8 @@ export class MdToolbarRow {} // Boilerplate for applying mixins to MdToolbar. export class MdToolbarBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + _renderer: Renderer2; + _elementRef: ElementRef; } export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); @@ -37,10 +38,10 @@ export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }) -export class MdToolbar extends _MdToolbarMixinBase implements IsColorable { +export class MdToolbar extends _MdToolbarMixinBase implements CanColor { - constructor(renderer: Renderer2, elementRef: ElementRef) { - super(renderer, elementRef); + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) { + super(); } } From 9c5465b49ed0d312d5a7daaba28ac5a53f2b3568 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 6 Jun 2017 14:18:33 +0200 Subject: [PATCH 3/3] Updates --- src/lib/button/button.ts | 11 +++--- src/lib/checkbox/checkbox.ts | 14 +++----- src/lib/chips/chip.ts | 11 +++--- src/lib/core/common-behaviors/color.spec.ts | 20 +++++++++-- src/lib/core/common-behaviors/color.ts | 35 ++++++++++--------- .../pseudo-checkbox/pseudo-checkbox.ts | 10 +++--- src/lib/icon/icon.ts | 11 +++--- src/lib/progress-spinner/progress-spinner.ts | 14 +++----- src/lib/select/select.ts | 12 +++---- src/lib/slide-toggle/slide-toggle.ts | 11 +++--- src/lib/toolbar/toolbar.ts | 7 ++-- 11 files changed, 76 insertions(+), 80 deletions(-) diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index bb50a0ac49e7..073916dbecce 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -73,10 +73,9 @@ export class MdMiniFabCssMatStyler {} // Boilerplate for applying mixins to MdButton. export class MdButtonBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdButtonMixinBase = mixinColor(mixinDisabled(MdButtonBase), true); +export const _MdButtonMixinBase = mixinColor(mixinDisabled(MdButtonBase)); /** @@ -112,11 +111,11 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl get disableRipple() { return this._disableRipple; } set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); } - constructor(public _renderer: Renderer2, - public _elementRef: ElementRef, + constructor(renderer: Renderer2, + elementRef: ElementRef, private _platform: Platform, private _focusOriginMonitor: FocusOriginMonitor) { - super(); + super(renderer, elementRef); this._focusOriginMonitor.monitor(this._elementRef.nativeElement, this._renderer, true); } diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index 4c7628debaf0..3f3fc279cbe4 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -59,10 +59,9 @@ export class MdCheckboxChange { // Boilerplate for applying mixins to MdCheckbox. export class MdCheckboxBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdCheckboxMixinBase = mixinColor(mixinDisabled(MdCheckboxBase)); +export const _MdCheckboxMixinBase = mixinColor(mixinDisabled(MdCheckboxBase), 'accent'); /** @@ -192,14 +191,11 @@ export class MdCheckbox extends _MdCheckboxMixinBase /** Reference to the focused state ripple. */ private _focusRipple: RippleRef; - constructor(public _renderer: Renderer2, - public _elementRef: ElementRef, + constructor(renderer: Renderer2, + elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, private _focusOriginMonitor: FocusOriginMonitor) { - super(); - - // By default the checkbox uses the accent color for styling. - this.color = 'accent'; + super(renderer, elementRef); } ngAfterViewInit() { diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 652c261150ec..da89aa89ee47 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -19,10 +19,9 @@ export interface MdChipEvent { // Boilerplate for applying mixins to MdChip. export class MdChipBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdChipMixinBase = mixinColor(MdChipBase); +export const _MdChipMixinBase = mixinColor(MdChipBase, 'primary'); /** @@ -65,10 +64,8 @@ export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDes /** Emitted when the chip is destroyed. */ @Output() destroy = new EventEmitter(); - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) { - super(); - // By default the chip component uses the primary color palette. - this.color = 'primary'; + constructor(renderer: Renderer2, elementRef: ElementRef) { + super(renderer, elementRef); } ngOnInit(): void { diff --git a/src/lib/core/common-behaviors/color.spec.ts b/src/lib/core/common-behaviors/color.spec.ts index 3dce4d49cb0c..580d8fda93de 100644 --- a/src/lib/core/common-behaviors/color.spec.ts +++ b/src/lib/core/common-behaviors/color.spec.ts @@ -36,8 +36,8 @@ describe('MixinColor', () => { .toContain('mat-accent', 'Expected the element to have the "mat-accent" class set'); }); - it('should allow updating the color to an empty value', () => { - const classWithColor = mixinColor(TestClass, true); + it('should allow having no color set', () => { + const classWithColor = mixinColor(TestClass); const instance = new classWithColor(); expect(instance.testElement.classList.length) @@ -51,7 +51,21 @@ describe('MixinColor', () => { instance.color = null; expect(instance.testElement.classList.length) - .toBe(0, 'Expected the element to not have any classes after the color has been set to null'); + .toBe(0, 'Expected the element to have no color class set.'); + }); + + it('should allow having a default color if specified', () => { + const classWithColor = mixinColor(TestClass, 'accent'); + const instance = new classWithColor(); + + + expect(instance.testElement.classList) + .toContain('mat-accent', 'Expected the element to have the "mat-accent" class by default.'); + + instance.color = null; + + expect(instance.testElement.classList) + .toContain('mat-accent', 'Expected the default color "mat-accent" to be set.'); }); }); diff --git a/src/lib/core/common-behaviors/color.ts b/src/lib/core/common-behaviors/color.ts index 52130a050ae4..82855231726d 100644 --- a/src/lib/core/common-behaviors/color.ts +++ b/src/lib/core/common-behaviors/color.ts @@ -16,32 +16,33 @@ export interface HasRenderer { export type ThemePalette = 'primary' | 'accent' | 'warn' | null; /** Mixin to augment a directive with a `color` property. */ -export function mixinColor>(base: T, allowNoColor = false) +export function mixinColor>(base: T, defaultColor?: ThemePalette) : Constructor & T { return class extends base { - private _color: ThemePalette = undefined; - - constructor(...args: any[]) { super(...args); } + private _color: ThemePalette = null; get color(): ThemePalette { return this._color; } set color(value: ThemePalette) { - if (value || allowNoColor && !value) { - this._setColorClass(this._color, false); - this._setColorClass(value, true); - this._color = value; - } - } + const colorPalette = value || defaultColor; - /** Method that changes the color classes on the host element. */ - private _setColorClass(colorName: string, isAdd: boolean) { - if (colorName) { - if (isAdd) { - this._renderer.addClass(this._elementRef.nativeElement, `mat-${colorName}`); - } else { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-${colorName}`); + if (colorPalette !== this._color) { + if (this._color) { + this._renderer.removeClass(this._elementRef.nativeElement, `mat-${this._color}`); + } + if (colorPalette) { + this._renderer.addClass(this._elementRef.nativeElement, `mat-${colorPalette}`); } + + this._color = colorPalette; } } + + constructor(...args: any[]) { + super(...args); + + // Set the default color that can be specified from the mixin. + this.color = defaultColor; + } }; } diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts index d595e63598c6..ced221138036 100644 --- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts +++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -12,10 +12,9 @@ export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; // Boilerplate for applying mixins to MdChip. export class MdPseudoCheckboxBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdPseudoCheckboxBase = mixinColor(MdPseudoCheckboxBase); +export const _MdPseudoCheckboxBase = mixinColor(MdPseudoCheckboxBase, 'accent'); /** @@ -50,8 +49,7 @@ export class MdPseudoCheckbox extends _MdPseudoCheckboxBase implements CanColor /** Whether the checkbox is disabled. */ @Input() disabled: boolean = false; - constructor(public _elementRef: ElementRef, public _renderer: Renderer2) { - super(); - this.color = 'accent'; + constructor(elementRef: ElementRef, renderer: Renderer2) { + super(renderer, elementRef); } } diff --git a/src/lib/icon/icon.ts b/src/lib/icon/icon.ts index 422ace6d8a8d..666291acfa9a 100644 --- a/src/lib/icon/icon.ts +++ b/src/lib/icon/icon.ts @@ -16,10 +16,9 @@ import {CanColor, mixinColor} from '../core/common-behaviors/color'; // Boilerplate for applying mixins to MdIcon. export class MdIconBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdIconMixinBase = mixinColor(MdIconBase, true); +export const _MdIconMixinBase = mixinColor(MdIconBase); /** @@ -91,9 +90,9 @@ export class MdIcon extends _MdIconMixinBase implements OnChanges, OnInit, After private _previousAriaLabel: string; constructor(private _mdIconRegistry: MdIconRegistry, - public _renderer: Renderer2, - public _elementRef: ElementRef) { - super(); + renderer: Renderer2, + elementRef: ElementRef) { + super(renderer, elementRef); } /** diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index f5c030c5201a..b9265e2e5079 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -52,10 +52,9 @@ export class MdProgressSpinnerCssMatStyler {} // Boilerplate for applying mixins to MdProgressSpinner. export class MdProgressSpinnerBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase); +export const _MdProgressSpinnerMixinBase = mixinColor(MdProgressSpinnerBase, 'primary'); /** * component. @@ -160,13 +159,10 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase } } - constructor(public _renderer: Renderer2, - public _elementRef: ElementRef, + constructor(renderer: Renderer2, + elementRef: ElementRef, private _ngZone: NgZone) { - super(); - - // By default the progress-spinner component uses the primary color palette. - this.color = 'primary'; + super(renderer, elementRef); } diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 58382033dc5a..c972c1a6586d 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -99,10 +99,9 @@ export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto'; // Boilerplate for applying mixins to MdSelect. export class MdSelectBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdSelectMixinBase = mixinColor(MdSelectBase); +export const _MdSelectMixinBase = mixinColor(MdSelectBase, 'primary'); @Component({ @@ -313,19 +312,18 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On constructor(private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, - public _renderer: Renderer2, - public _elementRef: ElementRef, + renderer: Renderer2, + elementRef: ElementRef, @Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string) { - super(); + super(renderer, elementRef); if (this._control) { this._control.valueAccessor = this; } this._tabIndex = parseInt(tabIndex) || 0; - this.color = 'primary'; } ngOnInit() { diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 15061025194b..294e16a8e969 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -46,10 +46,9 @@ let nextId = 0; // Boilerplate for applying mixins to MdSlideToggle. export class MdSlideToggleBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } -export const _MdSlideToggleMixinBase = mixinColor(mixinDisabled(MdSlideToggleBase)); +export const _MdSlideToggleMixinBase = mixinColor(mixinDisabled(MdSlideToggleBase), 'accent'); /** Represents a slidable "switch" toggle that can be moved between on and off. */ @Component({ @@ -123,11 +122,11 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase /** Reference to the ripple directive on the thumb container. */ @ViewChild(MdRipple) _ripple: MdRipple; - constructor(public _elementRef: ElementRef, - public _renderer: Renderer2, + constructor(elementRef: ElementRef, + renderer: Renderer2, private _focusOriginMonitor: FocusOriginMonitor, private _changeDetectorRef: ChangeDetectorRef) { - super(); + super(renderer, elementRef); } ngAfterContentInit() { diff --git a/src/lib/toolbar/toolbar.ts b/src/lib/toolbar/toolbar.ts index 5a9b1c572fe7..87d0f85be9b8 100644 --- a/src/lib/toolbar/toolbar.ts +++ b/src/lib/toolbar/toolbar.ts @@ -19,8 +19,7 @@ export class MdToolbarRow {} // Boilerplate for applying mixins to MdToolbar. export class MdToolbarBase { - _renderer: Renderer2; - _elementRef: ElementRef; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} } export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); @@ -40,8 +39,8 @@ export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); }) export class MdToolbar extends _MdToolbarMixinBase implements CanColor { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) { - super(); + constructor(renderer: Renderer2, elementRef: ElementRef) { + super(renderer, elementRef); } }