diff --git a/src/cdk/accordion/accordion-item.ts b/src/cdk/accordion/accordion-item.ts index 1323bf00cca3..f058b3e5d4bb 100644 --- a/src/cdk/accordion/accordion-item.ts +++ b/src/cdk/accordion/accordion-item.ts @@ -17,6 +17,7 @@ import { } from '@angular/core'; import {UniqueSelectionDispatcher} from '@angular/cdk/collections'; import {CdkAccordion} from './accordion'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; /** Used to generate unique ID for each accordion item. */ let nextId = 0; @@ -41,8 +42,10 @@ export class CdkAccordionItem implements OnDestroy { /** Whether the AccordionItem is expanded. */ @Input() - get expanded(): boolean { return this._expanded; } - set expanded(expanded: boolean) { + get expanded(): any { return this._expanded; } + set expanded(expanded: any) { + expanded = coerceBooleanProperty(expanded); + // Only emit events and update the internal value if the value changes. if (this._expanded !== expanded) { this._expanded = expanded; diff --git a/src/cdk/coercion/number-property.ts b/src/cdk/coercion/number-property.ts index d10568e2700f..38d783974a6b 100644 --- a/src/cdk/coercion/number-property.ts +++ b/src/cdk/coercion/number-property.ts @@ -7,6 +7,8 @@ */ /** Coerces a data-bound value (typically a string) to a number. */ +export function coerceNumberProperty(value: any): number; +export function coerceNumberProperty(value: any, fallback: D): number | D; export function coerceNumberProperty(value: any, fallbackValue = 0) { // parseFloat(value) handles most of the cases we're interested in (it treats null, empty string, // and other non-number values as NaN, where Number just uses 0) but it considers the string diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 4bb98d541480..11dc501b0932 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -143,15 +143,22 @@ export class MatDatepicker implements OnDestroy { * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather * than a popup and elements have more padding to allow for bigger touch targets. */ - @Input() touchUi = false; + @Input() + get touchUi(): boolean { + return this._touchUi; + } + set touchUi(value: boolean) { + this._touchUi = coerceBooleanProperty(value); + } + private _touchUi = false; /** Whether the datepicker pop-up should be disabled. */ @Input() - get disabled() { + get disabled(): boolean { return this._disabled === undefined && this._datepickerInput ? this._datepickerInput.disabled : !!this._disabled; } - set disabled(value: any) { + set disabled(value: boolean) { const newValue = coerceBooleanProperty(value); if (newValue !== this._disabled) { diff --git a/src/lib/expansion/expansion-panel.ts b/src/lib/expansion/expansion-panel.ts index 87684803b50b..5aa4ac4a85be 100644 --- a/src/lib/expansion/expansion-panel.ts +++ b/src/lib/expansion/expansion-panel.ts @@ -26,6 +26,7 @@ import {UniqueSelectionDispatcher} from '@angular/cdk/collections'; import {CanDisable, mixinDisabled} from '@angular/material/core'; import {Subject} from 'rxjs/Subject'; import {MatAccordion} from './accordion'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; /** Workaround for https://github.com/angular/angular/issues/17849 */ export const _CdkAccordionItem = CdkAccordionItem; @@ -90,8 +91,16 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2, }) export class MatExpansionPanel extends _MatExpansionPanelMixinBase implements CanDisable, OnChanges, OnDestroy { + /** Whether the toggle indicator should be hidden. */ - @Input() hideToggle: boolean = false; + @Input() + get hideToggle(): boolean { + return this._hideToggle; + } + set hideToggle(value: boolean) { + this._hideToggle = coerceBooleanProperty(value); + } + private _hideToggle = false; /** Stream that emits for changes in `@Input` properties. */ _inputChanges = new Subject(); diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts index b7d2fbd29b65..5d6b2f72428b 100644 --- a/src/lib/menu/menu-directive.ts +++ b/src/lib/menu/menu-directive.ts @@ -37,6 +37,7 @@ import {throwMatMenuInvalidPositionX, throwMatMenuInvalidPositionY} from './menu import {MatMenuItem} from './menu-item'; import {MatMenuPanel} from './menu-panel'; import {MenuPositionX, MenuPositionY} from './menu-positions'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; /** Default `mat-menu` options that can be overridden. */ @@ -120,7 +121,14 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy { @ContentChildren(MatMenuItem) items: QueryList; /** Whether the menu should overlap its trigger. */ - @Input() overlapTrigger = this._defaultOptions.overlapTrigger; + @Input() + set overlapTrigger(value: boolean) { + this._overlapTrigger = coerceBooleanProperty(value); + } + get overlapTrigger(): boolean { + return this._overlapTrigger; + } + private _overlapTrigger: boolean = this._defaultOptions.overlapTrigger; /** * This method takes classes set on the host mat-menu element and applies them on the diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index ef3d48ec991a..08ac069c1b9b 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -124,7 +124,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro @Input() get checked(): boolean { return this._checked; } set checked(value) { - this._checked = !!value; + this._checked = coerceBooleanProperty(value); this._changeDetectorRef.markForCheck(); } /** An event will be dispatched each time the slide-toggle changes its value. */ diff --git a/src/lib/tabs/tab-group.ts b/src/lib/tabs/tab-group.ts index a41bd2225867..da581c60b5bb 100644 --- a/src/lib/tabs/tab-group.ts +++ b/src/lib/tabs/tab-group.ts @@ -23,7 +23,7 @@ import { ViewChild, ViewEncapsulation, } from '@angular/core'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion'; import {Subscription} from 'rxjs/Subscription'; import {MatTab} from './tab'; import {MatTabHeader} from './tab-header'; @@ -109,7 +109,9 @@ export class MatTabGroup extends _MatTabGroupMixinBase implements AfterContentIn /** The index of the active tab. */ @Input() - set selectedIndex(value: number | null) { this._indexToSelect = value; } + set selectedIndex(value: number | null) { + this._indexToSelect = coerceNumberProperty(value, null); + } get selectedIndex(): number | null { return this._selectedIndex; } private _selectedIndex: number | null = null; diff --git a/src/lib/tabs/tab-header.ts b/src/lib/tabs/tab-header.ts index 20b9b133ef50..29977549a6a3 100644 --- a/src/lib/tabs/tab-header.ts +++ b/src/lib/tabs/tab-header.ts @@ -31,6 +31,7 @@ import {CanDisableRipple, mixinDisableRipple} from '@angular/material/core'; import {merge} from 'rxjs/observable/merge'; import {of as observableOf} from 'rxjs/observable/of'; import {Subscription} from 'rxjs/Subscription'; +import {coerceNumberProperty} from '@angular/cdk/coercion'; import {MatInkBar} from './ink-bar'; import {MatTabLabelWrapper} from './tab-label-wrapper'; import {ViewportRuler} from '@angular/cdk/scrolling'; @@ -120,6 +121,7 @@ export class MatTabHeader extends _MatTabHeaderMixinBase @Input() get selectedIndex(): number { return this._selectedIndex; } set selectedIndex(value: number) { + value = coerceNumberProperty(value); this._selectedIndexChanged = this._selectedIndex != value; this._selectedIndex = value; this._focusIndex = value;