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..073916dbecce 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 {CanColor, 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)); /** @@ -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, CanColor { /** 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(renderer: Renderer2, + elementRef: ElementRef, + private _platform: Platform, + private _focusOriginMonitor: FocusOriginMonitor) { + 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(renderer, elementRef, platform, focusOriginMonitor); } /** @docs-private */ diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index 9874e7697b47..3f3fc279cbe4 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 {CanColor, 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), 'accent'); /** @@ -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, 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. @@ -183,19 +186,16 @@ 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, + constructor(renderer: Renderer2, + elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, private _focusOriginMonitor: FocusOriginMonitor) { - super(); - this.color = 'accent'; + super(renderer, elementRef); } ngAfterViewInit() { @@ -247,27 +247,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..da89aa89ee47 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 {CanColor, 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, 'primary'); + + /** * 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, CanColor { /** 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,12 @@ 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); + } ngOnInit(): void { this._addDefaultCSSClass(); - this._updateColor(this._color); } ngOnDestroy(): void { @@ -108,15 +115,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 +145,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..580d8fda93de --- /dev/null +++ b/src/lib/core/common-behaviors/color.spec.ts @@ -0,0 +1,84 @@ +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 having no color 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 = null; + + expect(instance.testElement.classList.length) + .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.'); + }); + +}); + +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..82855231726d --- /dev/null +++ b/src/lib/core/common-behaviors/color.ts @@ -0,0 +1,48 @@ +import {Constructor} from './constructor'; +import {ElementRef, Renderer2} from '@angular/core'; + +/** @docs-private */ +export interface CanColor { + color: string; +} + +/** @docs-private */ +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, defaultColor?: ThemePalette) + : Constructor & T { + return class extends base { + private _color: ThemePalette = null; + + get color(): ThemePalette { return this._color; } + set color(value: ThemePalette) { + const colorPalette = value || defaultColor; + + 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/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..ced221138036 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 {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) {} +} +export const _MdPseudoCheckboxBase = mixinColor(MdPseudoCheckboxBase, 'accent'); + + /** * 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,14 @@ export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; '[class.mat-pseudo-checkbox-disabled]': 'disabled', }, }) -export class MdPseudoCheckbox { +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; - /** 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) { - 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 83b1027d1726..666291acfa9a 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 {CanColor, 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); /** @@ -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, + CanColor { /** 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..b9265e2e5079 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 {CanColor, 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, 'primary'); /** * 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, CanColor { + /** 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,11 @@ export class MdProgressSpinner implements OnDestroy { } } - constructor( - private _ngZone: NgZone, - private _elementRef: ElementRef, - private _renderer: Renderer2) { } + constructor(renderer: Renderer2, + elementRef: ElementRef, + private _ngZone: NgZone) { + super(renderer, elementRef); + } /** @@ -274,13 +272,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(renderer, elementRef, ngZone); this.mode = 'indeterminate'; } diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index a99d08220dcb..c972c1a6586d 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 {CanColor, 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, 'primary'); + + @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, CanColor { /** 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,10 +310,14 @@ 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; @@ -327,7 +328,6 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal ngOnInit() { this._selectionModel = new SelectionModel(this.multiple, null, false); - this.color = this.color || 'primary'; } ngAfterContentInit() { @@ -742,7 +742,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..294e16a8e969 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 {CanColor, 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), 'accent'); /** 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, CanColor { 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,11 +122,11 @@ 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(elementRef: ElementRef, + renderer: Renderer2, private _focusOriginMonitor: FocusOriginMonitor, private _changeDetectorRef: ChangeDetectorRef) { - super(); + super(renderer, elementRef); } ngAfterContentInit() { @@ -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..87d0f85be9b8 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 {CanColor, 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 CanColor { - if (isAdd) { - this._renderer.addClass(element, `mat-${color}`); - } else { - this._renderer.removeClass(element, `mat-${color}`); - } - } + constructor(renderer: Renderer2, elementRef: ElementRef) { + super(renderer, elementRef); } }