diff --git a/src/cdk/a11y/live-announcer.spec.ts b/src/cdk/a11y/live-announcer.spec.ts index c871ba53a851..e88c30ebe7ac 100644 --- a/src/cdk/a11y/live-announcer.spec.ts +++ b/src/cdk/a11y/live-announcer.spec.ts @@ -23,9 +23,9 @@ describe('LiveAnnouncer', () => { }))); afterEach(() => { - // In our tests we always remove the current live element, because otherwise we would have - // multiple live elements due multiple service instantiations. - announcer._removeLiveElement(); + // In our tests we always remove the current live element, in + // order to avoid having multiple announcer elements in the DOM. + announcer.ngOnDestroy(); }); it('should correctly update the announce text', fakeAsync(() => { @@ -58,13 +58,14 @@ describe('LiveAnnouncer', () => { expect(ariaLiveElement.getAttribute('aria-live')).toBe('polite'); })); - it('should remove the aria-live element from the DOM', fakeAsync(() => { + it('should remove the aria-live element from the DOM on destroy', fakeAsync(() => { announcer.announce('Hey Google'); // This flushes our 100ms timeout for the screenreaders. tick(100); - announcer._removeLiveElement(); + // Call the lifecycle hook manually since Angular won't do it in tests. + announcer.ngOnDestroy(); expect(document.body.querySelector('[aria-live]')) .toBeFalsy('Expected that the aria-live element was remove from the DOM.'); @@ -85,10 +86,9 @@ describe('LiveAnnouncer', () => { }); beforeEach(inject([LiveAnnouncer], (la: LiveAnnouncer) => { - announcer = la; - ariaLiveElement = getLiveElement(); - })); - + announcer = la; + ariaLiveElement = getLiveElement(); + })); it('should allow to use a custom live element', fakeAsync(() => { announcer.announce('Custom Element'); diff --git a/src/cdk/a11y/live-announcer.ts b/src/cdk/a11y/live-announcer.ts index a827c47238b9..48d6d3b06e28 100644 --- a/src/cdk/a11y/live-announcer.ts +++ b/src/cdk/a11y/live-announcer.ts @@ -12,6 +12,7 @@ import { Optional, Inject, SkipSelf, + OnDestroy, } from '@angular/core'; import {Platform} from '../platform/platform'; @@ -22,8 +23,7 @@ export const LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken('liv export type AriaLivePoliteness = 'off' | 'polite' | 'assertive'; @Injectable() -export class LiveAnnouncer { - +export class LiveAnnouncer implements OnDestroy { private _liveElement: Element; constructor( @@ -57,8 +57,7 @@ export class LiveAnnouncer { setTimeout(() => this._liveElement.textContent = message, 100); } - /** Removes the aria-live element from the DOM. */ - _removeLiveElement() { + ngOnDestroy() { if (this._liveElement && this._liveElement.parentNode) { this._liveElement.parentNode.removeChild(this._liveElement); } diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html index 1f6f5a882802..0aab603d2ba2 100644 --- a/src/demo-app/datepicker/datepicker-demo.html +++ b/src/demo-app/datepicker/datepicker-demo.html @@ -3,25 +3,26 @@

Options

Use touch UI Filter odd months and dates Start in year view + Disable datepicker

- + - +

- +

Result

@@ -44,12 +45,14 @@

Result

- Result

+ +

Input disabled datepicker

+

+ + + + + +

+ +

Input disabled, datepicker popup enabled

+

+ + + + + +

diff --git a/src/demo-app/datepicker/datepicker-demo.ts b/src/demo-app/datepicker/datepicker-demo.ts index 0b2264e380dd..b80c11c6f830 100644 --- a/src/demo-app/datepicker/datepicker-demo.ts +++ b/src/demo-app/datepicker/datepicker-demo.ts @@ -11,6 +11,8 @@ export class DatepickerDemo { touch: boolean; filterOdd: boolean; yearView: boolean; + inputDisabled: boolean; + datepickerDisabled: boolean; minDate: Date; maxDate: Date; startAt: Date; diff --git a/src/demo-app/progress-spinner/progress-spinner-demo.html b/src/demo-app/progress-spinner/progress-spinner-demo.html index 2e2ba59b85ee..07a04390dc8c 100644 --- a/src/demo-app/progress-spinner/progress-spinner-demo.html +++ b/src/demo-app/progress-spinner/progress-spinner-demo.html @@ -4,13 +4,13 @@

Determinate

Value: {{progressValue}}

- Is determinate + Is determinate
- -
diff --git a/src/demo-app/progress-spinner/progress-spinner-demo.ts b/src/demo-app/progress-spinner/progress-spinner-demo.ts index b67781631c5e..9677e78e50d3 100644 --- a/src/demo-app/progress-spinner/progress-spinner-demo.ts +++ b/src/demo-app/progress-spinner/progress-spinner-demo.ts @@ -8,9 +8,9 @@ import {Component} from '@angular/core'; styleUrls: ['progress-spinner-demo.css'], }) export class ProgressSpinnerDemo { - progressValue: number = 60; - color: string = 'primary'; - modeToggle: boolean = false; + progressValue = 60; + color = 'primary'; + isDeterminate = true; step(val: number) { this.progressValue = Math.max(0, Math.min(100, val + this.progressValue)); diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index ba175e64dd56..475a45fa5b0c 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -170,6 +170,10 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { /** Closes the autocomplete suggestion panel. */ closePanel(): void { + if (!this.panelOpen) { + return; + } + if (this._overlayRef && this._overlayRef.hasAttached()) { this._overlayRef.detach(); this._closingActionsSubscription.unsubscribe(); diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 6ad3319b624c..de12a3fb799a 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -221,17 +221,31 @@ describe('MdAutocomplete', () => { }); })); - it('should close the panel programmatically', () => { + it('should close the panel programmatically', async(() => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - fixture.componentInstance.trigger.closePanel(); + fixture.whenStable().then(() => { + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected closing programmatically to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected closing programmatically to close the panel.`); + }); + }); + })); + + it('should not throw when attempting to close the panel of a destroyed autocomplete', () => { + const trigger = fixture.componentInstance.trigger; + + trigger.openPanel(); fixture.detectChanges(); + fixture.destroy(); - expect(fixture.componentInstance.trigger.panelOpen) - .toBe(false, `Expected closing programmatically to set the panel state to closed.`); - expect(overlayContainerElement.textContent) - .toEqual('', `Expected closing programmatically to close the panel.`); + expect(() => trigger.closePanel()).not.toThrow(); }); it('should hide the panel when the options list is empty', async(() => { @@ -763,17 +777,19 @@ describe('MdAutocomplete', () => { .toEqual(32, `Expected panel to reveal the sixth option.`); })); - it('should scroll to active options on UP arrow', fakeAsync(() => { - tick(); - const scrollContainer = - document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!; + it('should scroll to active options on UP arrow', async(() => { + fixture.whenStable().then(() => { + const scrollContainer = + document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!; - fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); - tick(); - fixture.detectChanges(); + fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); + fixture.detectChanges(); - // Expect option bottom minus the panel height (528 - 256 = 272) - expect(scrollContainer.scrollTop).toEqual(272, `Expected panel to reveal last option.`); + fixture.whenStable().then(() => { + // Expect option bottom minus the panel height (528 - 256 = 272) + expect(scrollContainer.scrollTop).toEqual(272, `Expected panel to reveal last option.`); + }); + }); })); it('should not scroll to active options that are fully in the panel', fakeAsync(() => { diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index 03939f6b7b53..0937d1ee2f99 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -17,6 +17,7 @@ import { ViewChild, ViewEncapsulation, ChangeDetectorRef, + ChangeDetectionStrategy, } from '@angular/core'; import {MdOption} from '../core'; import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-manager'; @@ -35,6 +36,7 @@ export type AutocompletePositionY = 'above' | 'below'; templateUrl: 'autocomplete.html', styleUrls: ['autocomplete.css'], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, exportAs: 'mdAutocomplete', host: { 'class': 'mat-autocomplete' diff --git a/src/lib/button-toggle/button-toggle.spec.ts b/src/lib/button-toggle/button-toggle.spec.ts index a1dcbfdb9916..0b45163c4728 100644 --- a/src/lib/button-toggle/button-toggle.spec.ts +++ b/src/lib/button-toggle/button-toggle.spec.ts @@ -17,22 +17,210 @@ import { } from './index'; -describe('MdButtonToggle', () => { +describe('MdButtonToggle with forms', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MdButtonToggleModule, FormsModule, ReactiveFormsModule], declarations: [ - ButtonTogglesInsideButtonToggleGroup, ButtonToggleGroupWithNgModel, + ButtonToggleGroupWithFormControl, + ], + }); + + TestBed.compileComponents(); + })); + + describe('using FormControl', () => { + let fixture: ComponentFixture; + let groupDebugElement: DebugElement; + let groupInstance: MdButtonToggleGroup; + let testComponent: ButtonToggleGroupWithFormControl; + + beforeEach(async(() => { + fixture = TestBed.createComponent(ButtonToggleGroupWithFormControl); + fixture.detectChanges(); + + testComponent = fixture.debugElement.componentInstance; + + groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup)); + groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup); + })); + + it('should toggle the disabled state', () => { + testComponent.control.disable(); + + expect(groupInstance.disabled).toBe(true); + + testComponent.control.enable(); + + expect(groupInstance.disabled).toBe(false); + }); + + it('should set the value', () => { + testComponent.control.setValue('green'); + + expect(groupInstance.value).toBe('green'); + + testComponent.control.setValue('red'); + + expect(groupInstance.value).toBe('red'); + }); + + it('should register the on change callback', () => { + let spy = jasmine.createSpy('onChange callback'); + + testComponent.control.registerOnChange(spy); + testComponent.control.setValue('blue'); + + expect(spy).toHaveBeenCalled(); + }); + }); + + describe('button toggle group with ngModel and change event', () => { + let fixture: ComponentFixture; + let groupDebugElement: DebugElement; + let groupNativeElement: HTMLElement; + let buttonToggleDebugElements: DebugElement[]; + let buttonToggleNativeElements: HTMLElement[]; + let groupInstance: MdButtonToggleGroup; + let buttonToggleInstances: MdButtonToggle[]; + let testComponent: ButtonToggleGroupWithNgModel; + let groupNgModel: NgModel; + + beforeEach(async(() => { + fixture = TestBed.createComponent(ButtonToggleGroupWithNgModel); + + testComponent = fixture.debugElement.componentInstance; + + groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup)); + groupNativeElement = groupDebugElement.nativeElement; + groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup); + groupNgModel = groupDebugElement.injector.get(NgModel); + + buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle)); + buttonToggleNativeElements = + buttonToggleDebugElements.map(debugEl => debugEl.nativeElement); + buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); + + fixture.detectChanges(); + })); + + it('should update the model before firing change event', fakeAsync(() => { + expect(testComponent.modelValue).toBeUndefined(); + expect(testComponent.lastEvent).toBeUndefined(); + + groupInstance.value = 'red'; + fixture.detectChanges(); + + tick(); + expect(testComponent.modelValue).toBe('red'); + expect(testComponent.lastEvent.value).toBe('red'); + })); + }); + + describe('button toggle group with ngModel', () => { + let fixture: ComponentFixture; + let groupDebugElement: DebugElement; + let groupNativeElement: HTMLElement; + let buttonToggleDebugElements: DebugElement[]; + let buttonToggleNativeElements: HTMLElement[]; + let groupInstance: MdButtonToggleGroup; + let buttonToggleInstances: MdButtonToggle[]; + let testComponent: ButtonToggleGroupWithNgModel; + let groupNgModel: NgModel; + + beforeEach(async(() => { + fixture = TestBed.createComponent(ButtonToggleGroupWithNgModel); + fixture.detectChanges(); + + testComponent = fixture.debugElement.componentInstance; + + groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup)); + groupNativeElement = groupDebugElement.nativeElement; + groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup); + groupNgModel = groupDebugElement.injector.get(NgModel); + + buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle)); + buttonToggleNativeElements = + buttonToggleDebugElements.map(debugEl => debugEl.nativeElement); + buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); + })); + + it('should set individual radio names based on the group name', () => { + expect(groupInstance.name).toBeTruthy(); + for (let buttonToggle of buttonToggleInstances) { + expect(buttonToggle.name).toBe(groupInstance.name); + } + + groupInstance.name = 'new name'; + for (let buttonToggle of buttonToggleInstances) { + expect(buttonToggle.name).toBe(groupInstance.name); + } + }); + + it('should check the corresponding button toggle on a group value change', () => { + expect(groupInstance.value).toBeFalsy(); + for (let buttonToggle of buttonToggleInstances) { + expect(buttonToggle.checked).toBeFalsy(); + } + + groupInstance.value = 'red'; + for (let buttonToggle of buttonToggleInstances) { + expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value); + } + expect(groupInstance.selected!.value).toBe(groupInstance.value); + }); + + it('should have the correct FormControl state initially and after interaction', + fakeAsync(() => { + expect(groupNgModel.valid).toBe(true); + expect(groupNgModel.pristine).toBe(true); + expect(groupNgModel.touched).toBe(false); + + buttonToggleInstances[1].checked = true; + fixture.detectChanges(); + tick(); + + expect(groupNgModel.valid).toBe(true); + expect(groupNgModel.pristine).toBe(false); + expect(groupNgModel.touched).toBe(false); + + let nativeRadioLabel = buttonToggleDebugElements[2].query(By.css('label')).nativeElement; + nativeRadioLabel.click(); + fixture.detectChanges(); + tick(); + + expect(groupNgModel.valid).toBe(true); + expect(groupNgModel.pristine).toBe(false); + expect(groupNgModel.touched).toBe(true); + })); + + it('should update the ngModel value when selecting a button toggle', fakeAsync(() => { + buttonToggleInstances[1].checked = true; + fixture.detectChanges(); + + tick(); + + expect(testComponent.modelValue).toBe('green'); + })); + }); + +}); + +describe('MdButtonToggle without forms', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdButtonToggleModule], + declarations: [ + ButtonTogglesInsideButtonToggleGroup, ButtonTogglesInsideButtonToggleGroupMultiple, ButtonToggleGroupWithInitialValue, - ButtonToggleGroupWithFormControl, StandaloneButtonToggle, ], }); - TestBed.compileComponents(); })); @@ -206,136 +394,6 @@ describe('MdButtonToggle', () => { }); }); - describe('button toggle group with ngModel', () => { - let fixture: ComponentFixture; - let groupDebugElement: DebugElement; - let groupNativeElement: HTMLElement; - let buttonToggleDebugElements: DebugElement[]; - let buttonToggleNativeElements: HTMLElement[]; - let groupInstance: MdButtonToggleGroup; - let buttonToggleInstances: MdButtonToggle[]; - let testComponent: ButtonToggleGroupWithNgModel; - let groupNgModel: NgModel; - - beforeEach(async(() => { - fixture = TestBed.createComponent(ButtonToggleGroupWithNgModel); - fixture.detectChanges(); - - testComponent = fixture.debugElement.componentInstance; - - groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup)); - groupNativeElement = groupDebugElement.nativeElement; - groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup); - groupNgModel = groupDebugElement.injector.get(NgModel); - - buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle)); - buttonToggleNativeElements = - buttonToggleDebugElements.map(debugEl => debugEl.nativeElement); - buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); - })); - - it('should set individual radio names based on the group name', () => { - expect(groupInstance.name).toBeTruthy(); - for (let buttonToggle of buttonToggleInstances) { - expect(buttonToggle.name).toBe(groupInstance.name); - } - - groupInstance.name = 'new name'; - for (let buttonToggle of buttonToggleInstances) { - expect(buttonToggle.name).toBe(groupInstance.name); - } - }); - - it('should check the corresponding button toggle on a group value change', () => { - expect(groupInstance.value).toBeFalsy(); - for (let buttonToggle of buttonToggleInstances) { - expect(buttonToggle.checked).toBeFalsy(); - } - - groupInstance.value = 'red'; - for (let buttonToggle of buttonToggleInstances) { - expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value); - } - expect(groupInstance.selected!.value).toBe(groupInstance.value); - }); - - it('should have the correct FormControl state initially and after interaction', - fakeAsync(() => { - expect(groupNgModel.valid).toBe(true); - expect(groupNgModel.pristine).toBe(true); - expect(groupNgModel.touched).toBe(false); - - buttonToggleInstances[1].checked = true; - fixture.detectChanges(); - tick(); - - expect(groupNgModel.valid).toBe(true); - expect(groupNgModel.pristine).toBe(false); - expect(groupNgModel.touched).toBe(false); - - let nativeRadioLabel = buttonToggleDebugElements[2].query(By.css('label')).nativeElement; - nativeRadioLabel.click(); - fixture.detectChanges(); - tick(); - - expect(groupNgModel.valid).toBe(true); - expect(groupNgModel.pristine).toBe(false); - expect(groupNgModel.touched).toBe(true); - })); - - it('should update the ngModel value when selecting a button toggle', fakeAsync(() => { - buttonToggleInstances[1].checked = true; - fixture.detectChanges(); - - tick(); - - expect(testComponent.modelValue).toBe('green'); - })); - }); - - describe('button toggle group with ngModel and change event', () => { - let fixture: ComponentFixture; - let groupDebugElement: DebugElement; - let groupNativeElement: HTMLElement; - let buttonToggleDebugElements: DebugElement[]; - let buttonToggleNativeElements: HTMLElement[]; - let groupInstance: MdButtonToggleGroup; - let buttonToggleInstances: MdButtonToggle[]; - let testComponent: ButtonToggleGroupWithNgModel; - let groupNgModel: NgModel; - - beforeEach(async(() => { - fixture = TestBed.createComponent(ButtonToggleGroupWithNgModel); - - testComponent = fixture.debugElement.componentInstance; - - groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup)); - groupNativeElement = groupDebugElement.nativeElement; - groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup); - groupNgModel = groupDebugElement.injector.get(NgModel); - - buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle)); - buttonToggleNativeElements = - buttonToggleDebugElements.map(debugEl => debugEl.nativeElement); - buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); - - fixture.detectChanges(); - })); - - it('should update the model before firing change event', fakeAsync(() => { - expect(testComponent.modelValue).toBeUndefined(); - expect(testComponent.lastEvent).toBeUndefined(); - - groupInstance.value = 'red'; - fixture.detectChanges(); - - tick(); - expect(testComponent.modelValue).toBe('red'); - expect(testComponent.lastEvent.value).toBe('red'); - })); - - }); - describe('with initial value and change event', () => { it('should not fire an initial change event', () => { @@ -469,52 +527,6 @@ describe('MdButtonToggle', () => { }); - describe('using FormControl', () => { - let fixture: ComponentFixture; - let groupDebugElement: DebugElement; - let groupInstance: MdButtonToggleGroup; - let testComponent: ButtonToggleGroupWithFormControl; - - beforeEach(async(() => { - fixture = TestBed.createComponent(ButtonToggleGroupWithFormControl); - fixture.detectChanges(); - - testComponent = fixture.debugElement.componentInstance; - - groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup)); - groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup); - })); - - it('should toggle the disabled state', () => { - testComponent.control.disable(); - - expect(groupInstance.disabled).toBe(true); - - testComponent.control.enable(); - - expect(groupInstance.disabled).toBe(false); - }); - - it('should set the value', () => { - testComponent.control.setValue('green'); - - expect(groupInstance.value).toBe('green'); - - testComponent.control.setValue('red'); - - expect(groupInstance.value).toBe('red'); - }); - - it('should register the on change callback', () => { - let spy = jasmine.createSpy('onChange callback'); - - testComponent.control.registerOnChange(spy); - testComponent.control.setValue('blue'); - - expect(spy).toHaveBeenCalled(); - }); - }); - describe('as standalone', () => { let fixture: ComponentFixture; let buttonToggleDebugElement: DebugElement; diff --git a/src/lib/button-toggle/index.ts b/src/lib/button-toggle/index.ts index e7d0553297f1..adf987daf8b0 100644 --- a/src/lib/button-toggle/index.ts +++ b/src/lib/button-toggle/index.ts @@ -7,7 +7,6 @@ */ import {NgModule} from '@angular/core'; -import {FormsModule} from '@angular/forms'; import {MdButtonToggleGroup, MdButtonToggleGroupMultiple, MdButtonToggle} from './button-toggle'; import { UNIQUE_SELECTION_DISPATCHER_PROVIDER, @@ -17,7 +16,7 @@ import { @NgModule({ - imports: [FormsModule, MdCommonModule, StyleModule], + imports: [MdCommonModule, StyleModule], exports: [ MdButtonToggleGroup, MdButtonToggleGroupMultiple, diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index e62a36ede756..fb74796e41cd 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -12,14 +12,13 @@ import { Directive, ElementRef, forwardRef, - HostBinding, Input, OnDestroy, Optional, Renderer2, Self, ViewEncapsulation, - Inject + Inject, } from '@angular/core'; import {coerceBooleanProperty, FocusOriginMonitor, Platform} from '../core'; import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; @@ -193,6 +192,7 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl selector: `a[md-button], a[md-raised-button], a[md-icon-button], a[md-fab], a[md-mini-fab], a[mat-button], a[mat-raised-button], a[mat-icon-button], a[mat-fab], a[mat-mini-fab]`, host: { + '[attr.tabindex]': 'disabled ? -1 : 0', '[attr.disabled]': 'disabled || null', '[attr.aria-disabled]': 'disabled.toString()', '(click)': '_haltDisabledEvents($event)', @@ -200,7 +200,8 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl inputs: ['disabled', 'color'], templateUrl: 'button.html', styleUrls: ['button.css'], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdAnchor extends MdButton { constructor( @@ -211,12 +212,6 @@ export class MdAnchor extends MdButton { super(renderer, elementRef, platform, focusOriginMonitor); } - /** @docs-private */ - @HostBinding('tabIndex') - get tabIndex(): number { - return this.disabled ? -1 : 0; - } - _haltDisabledEvents(event: Event) { // A disabled button shouldn't apply any actions if (this.disabled) { diff --git a/src/lib/core/datetime/native-date-adapter.spec.ts b/src/lib/core/datetime/native-date-adapter.spec.ts index 0e55b872aad2..fcde1c3bcea0 100644 --- a/src/lib/core/datetime/native-date-adapter.spec.ts +++ b/src/lib/core/datetime/native-date-adapter.spec.ts @@ -1,17 +1,24 @@ -import {NativeDateAdapter} from './native-date-adapter'; +import {TestBed, async, inject} from '@angular/core/testing'; +import {LOCALE_ID} from '@angular/core'; +import {NativeDateAdapter, NativeDateModule, DateAdapter} from './index'; import {Platform} from '../platform/index'; import {DEC, FEB, JAN, MAR} from '../testing/month-constants'; const SUPPORTS_INTL = typeof Intl != 'undefined'; describe('NativeDateAdapter', () => { - let adapter; - let platform; + const platform = new Platform(); + let adapter: NativeDateAdapter; - beforeEach(() => { - adapter = new NativeDateAdapter(); - platform = new Platform(); - }); + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NativeDateModule] + }).compileComponents(); + })); + + beforeEach(inject([DateAdapter], (d: NativeDateAdapter) => { + adapter = d; + })); it('should get year', () => { expect(adapter.getYear(new Date(2017, JAN, 1))).toBe(2017); @@ -195,9 +202,9 @@ describe('NativeDateAdapter', () => { it('should format', () => { if (SUPPORTS_INTL) { - expect(adapter.format(new Date(2017, JAN, 1))).toEqual('1/1/2017'); + expect(adapter.format(new Date(2017, JAN, 1), {})).toEqual('1/1/2017'); } else { - expect(adapter.format(new Date(2017, JAN, 1))).toEqual('Sun Jan 01 2017'); + expect(adapter.format(new Date(2017, JAN, 1), {})).toEqual('Sun Jan 01 2017'); } }); @@ -222,12 +229,12 @@ describe('NativeDateAdapter', () => { if (SUPPORTS_INTL) { // Edge & IE use a different format in Japanese. if (platform.EDGE || platform.TRIDENT) { - expect(adapter.format(new Date(2017, JAN, 1))).toEqual('2017年1月1日'); + expect(adapter.format(new Date(2017, JAN, 1), {})).toEqual('2017年1月1日'); } else { - expect(adapter.format(new Date(2017, JAN, 1))).toEqual('2017/1/1'); + expect(adapter.format(new Date(2017, JAN, 1), {})).toEqual('2017/1/1'); } } else { - expect(adapter.format(new Date(2017, JAN, 1))).toEqual('Sun Jan 01 2017'); + expect(adapter.format(new Date(2017, JAN, 1), {})).toEqual('Sun Jan 01 2017'); } }); @@ -290,3 +297,28 @@ describe('NativeDateAdapter', () => { .toEqual(new Date(2018, FEB, 1)); }); }); + + +describe('NativeDateAdapter with LOCALE_ID override', () => { + let adapter: NativeDateAdapter; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NativeDateModule], + providers: [{provide: LOCALE_ID, useValue: 'da-DK'}] + }).compileComponents(); + })); + + beforeEach(inject([DateAdapter], (d: NativeDateAdapter) => { + adapter = d; + })); + + it('should take the default locale id from the LOCALE_ID injection token', () => { + const expectedValue = SUPPORTS_INTL ? + ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'] : + ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + + expect(adapter.getDayOfWeekNames('long')).toEqual(expectedValue); + }); + +}); diff --git a/src/lib/core/datetime/native-date-adapter.ts b/src/lib/core/datetime/native-date-adapter.ts index 7a371c6af61c..e6d27bc418ac 100644 --- a/src/lib/core/datetime/native-date-adapter.ts +++ b/src/lib/core/datetime/native-date-adapter.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {Inject, Injectable, Optional, LOCALE_ID} from '@angular/core'; import {DateAdapter} from './date-adapter'; @@ -48,7 +49,13 @@ function range(length: number, valueFunction: (index: number) => T): T[] { /** Adapts the native JS Date for use with cdk-based components that work with dates. */ +@Injectable() export class NativeDateAdapter extends DateAdapter { + constructor(@Optional() @Inject(LOCALE_ID) localeId: any) { + super(); + super.setLocale(localeId); + } + getYear(date: Date): number { return date.getFullYear(); } diff --git a/src/lib/core/overlay/scroll/block-scroll-strategy.spec.ts b/src/lib/core/overlay/scroll/block-scroll-strategy.spec.ts index 434280144ec7..3612a9328bc8 100644 --- a/src/lib/core/overlay/scroll/block-scroll-strategy.spec.ts +++ b/src/lib/core/overlay/scroll/block-scroll-strategy.spec.ts @@ -9,6 +9,7 @@ import { OverlayState, Overlay, OverlayRef, + OverlayContainer, } from '../../core'; @@ -26,24 +27,26 @@ describe('BlockScrollStrategy', () => { })); beforeEach(inject([Overlay, ViewportRuler], (overlay: Overlay, viewportRuler: ViewportRuler) => { - let overlayState = new OverlayState(); + let overlayState = new OverlayState(); - overlayState.scrollStrategy = overlay.scrollStrategies.block(); - overlayRef = overlay.create(overlayState); - componentPortal = new ComponentPortal(FocacciaMsg); + overlayState.scrollStrategy = overlay.scrollStrategies.block(); + overlayRef = overlay.create(overlayState); + componentPortal = new ComponentPortal(FocacciaMsg); - viewport = viewportRuler; - forceScrollElement = document.createElement('div'); - document.body.appendChild(forceScrollElement); - forceScrollElement.style.width = '100px'; - forceScrollElement.style.height = '3000px'; - })); + viewport = viewportRuler; + forceScrollElement = document.createElement('div'); + document.body.appendChild(forceScrollElement); + forceScrollElement.style.width = '100px'; + forceScrollElement.style.height = '3000px'; + forceScrollElement.style.background = 'rebeccapurple'; + })); - afterEach(() => { + afterEach(inject([OverlayContainer], (container: OverlayContainer) => { overlayRef.dispose(); document.body.removeChild(forceScrollElement); setScrollPosition(0, 0); - }); + container.getContainerElement().parentNode!.removeChild(container.getContainerElement()); + })); it('should toggle scroll blocking along the y axis', skipIOS(() => { setScrollPosition(0, 100); diff --git a/src/lib/core/overlay/scroll/close-scroll-strategy.spec.ts b/src/lib/core/overlay/scroll/close-scroll-strategy.spec.ts index d2966336c380..f5d0b2ad5851 100644 --- a/src/lib/core/overlay/scroll/close-scroll-strategy.spec.ts +++ b/src/lib/core/overlay/scroll/close-scroll-strategy.spec.ts @@ -9,6 +9,7 @@ import { OverlayRef, OverlayModule, ScrollDispatcher, + OverlayContainer, } from '../../core'; @@ -39,9 +40,10 @@ describe('CloseScrollStrategy', () => { componentPortal = new ComponentPortal(MozarellaMsg); })); - afterEach(() => { + afterEach(inject([OverlayContainer], (container: OverlayContainer) => { overlayRef.dispose(); - }); + container.getContainerElement().parentNode!.removeChild(container.getContainerElement()); + })); it('should detach the overlay as soon as the user scrolls', () => { overlayRef.attach(componentPortal); diff --git a/src/lib/core/overlay/scroll/reposition-scroll-strategy.spec.ts b/src/lib/core/overlay/scroll/reposition-scroll-strategy.spec.ts index d1be6efbb42d..af6a82553237 100644 --- a/src/lib/core/overlay/scroll/reposition-scroll-strategy.spec.ts +++ b/src/lib/core/overlay/scroll/reposition-scroll-strategy.spec.ts @@ -8,6 +8,7 @@ import { OverlayState, OverlayRef, OverlayModule, + OverlayContainer, ScrollDispatcher, } from '../../core'; @@ -39,9 +40,10 @@ describe('RepositionScrollStrategy', () => { componentPortal = new ComponentPortal(PastaMsg); })); - afterEach(() => { + afterEach(inject([OverlayContainer], (container: OverlayContainer) => { overlayRef.dispose(); - }); + container.getContainerElement().parentNode!.removeChild(container.getContainerElement()); + })); it('should update the overlay position when the page is scrolled', () => { overlayRef.attach(componentPortal); diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index 59bd785d6bf5..524ca4dea34b 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -35,6 +35,7 @@ import {DOWN_ARROW} from '../core/keyboard/keycodes'; import {DateAdapter} from '../core/datetime/index'; import {createMissingDateImplError} from './datepicker-errors'; import {MD_DATE_FORMATS, MdDateFormats} from '../core/datetime/date-formats'; +import {coerceBooleanProperty} from '@angular/cdk'; export const MD_DATEPICKER_VALUE_ACCESSOR: any = { @@ -61,6 +62,7 @@ export const MD_DATEPICKER_VALIDATORS: any = { '[attr.aria-owns]': '_datepicker?.id', '[attr.min]': 'min ? _dateAdapter.getISODateString(min) : null', '[attr.max]': 'max ? _dateAdapter.getISODateString(max) : null', + '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(blur)': '_onTouched()', '(keydown)': '_onKeydown($event)', @@ -124,6 +126,14 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces } private _max: D; + /** Whether the datepicker-input is disabled. */ + @Input() + get disabled() { return this._disabled; } + set disabled(value: any) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + /** Emits when the value changes (either due to user input or programmatic change). */ _valueChange = new EventEmitter(); diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts index c3f4a5944540..9f7591e015eb 100644 --- a/src/lib/datepicker/datepicker-toggle.ts +++ b/src/lib/datepicker/datepicker-toggle.ts @@ -9,6 +9,7 @@ import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core'; import {MdDatepicker} from './datepicker'; import {MdDatepickerIntl} from './datepicker-intl'; +import {coerceBooleanProperty} from '@angular/cdk'; @Component({ @@ -20,6 +21,7 @@ import {MdDatepickerIntl} from './datepicker-intl'; 'type': 'button', 'class': 'mat-datepicker-toggle', '[attr.aria-label]': '_intl.openCalendarLabel', + '[disabled]': 'disabled', '(click)': '_open($event)', }, encapsulation: ViewEncapsulation.None, @@ -33,10 +35,20 @@ export class MdDatepickerToggle { get _datepicker() { return this.datepicker; } set _datepicker(v: MdDatepicker) { this.datepicker = v; } + /** Whether the toggle button is disabled. */ + @Input() + get disabled() { + return this._disabled === undefined ? this.datepicker.disabled : this._disabled; + } + set disabled(value) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + constructor(public _intl: MdDatepickerIntl) {} _open(event: Event): void { - if (this.datepicker) { + if (this.datepicker && !this.disabled) { this.datepicker.open(); event.stopPropagation(); } diff --git a/src/lib/datepicker/datepicker.md b/src/lib/datepicker/datepicker.md index 8d1feb401717..16939027d70d 100644 --- a/src/lib/datepicker/datepicker.md +++ b/src/lib/datepicker/datepicker.md @@ -104,6 +104,19 @@ three pieces via injection: 2. The display and parse formats used by the datepicker. 3. The message strings used in the datepicker's UI. +#### Setting the locale code +By default the datepicker will use the locale code from the `LOCALE_ID` injection token from +`@angular/core`. If you want to override it, you can provide a new value for the token: + +```ts +@NgModule({ + providers: [ + {provide: LOCALE_ID, useValue: 'en-GB'}, + ], +}) +export class MyApp {} +``` + #### Choosing a date implementation and date format settings The datepicker was built to be date implementation agnostic. This means that it can be made to work with a variety of different date implementations. However it also means that developers need to make diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index ea7f05288c33..ceb1c6b11b3e 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -1,5 +1,5 @@ import {Component, ViewChild} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; @@ -8,11 +8,15 @@ import {MdDatepicker} from './datepicker'; import {MdDatepickerInput} from './datepicker-input'; import {MdInputModule} from '../input/index'; import {MdNativeDateModule, DateAdapter, NativeDateAdapter} from '../core/datetime/index'; -import {ESCAPE} from '../core'; +import {ESCAPE, OverlayContainer} from '../core'; import {dispatchFakeEvent, dispatchMouseEvent, dispatchKeyboardEvent} from '@angular/cdk/testing'; import {DEC, JAN} from '../core/testing/month-constants'; describe('MdDatepicker', () => { + afterEach(inject([OverlayContainer], (container: OverlayContainer) => { + container.getContainerElement().parentNode!.removeChild(container.getContainerElement()); + })); + describe('with MdNativeDateModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -24,13 +28,6 @@ describe('MdDatepicker', () => { NoopAnimationsModule, ReactiveFormsModule, ], - providers: [ - {provide: DateAdapter, useFactory: () => { - let adapter = new NativeDateAdapter(); - adapter.setLocale('en-US'); - return adapter; - }}, - ], declarations: [ DatepickerWithFilterAndValidation, DatepickerWithFormControl, @@ -65,16 +62,16 @@ describe('MdDatepicker', () => { fixture.detectChanges(); })); - it('open non-touch should open popup', async(() => { + it('open non-touch should open popup', () => { expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); testComponent.datepicker.open(); fixture.detectChanges(); expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull(); - })); + }); - it('open touch should open dialog', async(() => { + it('open touch should open dialog', () => { testComponent.touch = true; fixture.detectChanges(); @@ -84,9 +81,36 @@ describe('MdDatepicker', () => { fixture.detectChanges(); expect(document.querySelector('md-dialog-container')).not.toBeNull(); - })); + }); + + it('open in disabled mode should not open the calendar', () => { + testComponent.disabled = true; + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); + expect(document.querySelector('md-dialog-container')).toBeNull(); - it('close should close popup', async(() => { + testComponent.datepicker.open(); + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); + expect(document.querySelector('md-dialog-container')).toBeNull(); + }); + + it('disabled datepicker input should open the calendar if datepicker is enabled', () => { + testComponent.datepicker.disabled = false; + testComponent.datepickerInput.disabled = true; + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); + + testComponent.datepicker.open(); + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull(); + }); + + it('close should close popup', () => { testComponent.datepicker.open(); fixture.detectChanges(); @@ -100,7 +124,7 @@ describe('MdDatepicker', () => { fixture.whenStable().then(() => { expect(parseInt(getComputedStyle(popup).height as string)).toBe(0); }); - })); + }); it('should close the popup when pressing ESCAPE', () => { testComponent.datepicker.open(); @@ -119,7 +143,7 @@ describe('MdDatepicker', () => { .toBe(true, 'Expected default ESCAPE action to be prevented.'); }); - it('close should close dialog', async(() => { + it('close should close dialog', () => { testComponent.touch = true; fixture.detectChanges(); @@ -134,9 +158,9 @@ describe('MdDatepicker', () => { fixture.whenStable().then(() => { expect(document.querySelector('md-dialog-container')).toBeNull(); }); - })); + }); - it('setting selected should update input and close calendar', async(() => { + it('setting selected should update input and close calendar', () => { testComponent.touch = true; fixture.detectChanges(); @@ -154,7 +178,7 @@ describe('MdDatepicker', () => { expect(document.querySelector('md-dialog-container')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); }); - })); + }); it('startAt should fallback to input value', () => { expect(testComponent.datepicker.startAt).toEqual(new Date(2020, JAN, 1)); @@ -433,6 +457,19 @@ describe('MdDatepicker', () => { expect(document.querySelector('md-dialog-container')).not.toBeNull(); }); + it('should not open calendar when toggle clicked if datepicker is disabled', () => { + testComponent.datepicker.disabled = true; + fixture.detectChanges(); + + expect(document.querySelector('md-dialog-container')).toBeNull(); + + let toggle = fixture.debugElement.query(By.css('button')); + dispatchMouseEvent(toggle.nativeElement, 'click'); + fixture.detectChanges(); + + expect(document.querySelector('md-dialog-container')).toBeNull(); + }); + it('should set the `button` type on the trigger to prevent form submissions', () => { let toggle = fixture.debugElement.query(By.css('button')).nativeElement; expect(toggle.getAttribute('type')).toBe('button'); @@ -724,11 +761,12 @@ describe('MdDatepicker', () => { @Component({ template: ` - + `, }) class StandardDatepicker { touch = false; + disabled = false; date = new Date(2020, JAN, 1); @ViewChild('d') datepicker: MdDatepicker; @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 7d4bedf51d4d..0f68427a6200 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -38,6 +38,7 @@ import {createMissingDateImplError} from './datepicker-errors'; import {ESCAPE} from '../core/keyboard/keycodes'; import {MdCalendar} from './calendar'; import {first} from '../core/rxjs/index'; +import {coerceBooleanProperty} from '@angular/cdk'; /** Used to generate a unique ID for each datepicker instance. */ @@ -94,6 +95,7 @@ export class MdDatepickerContent implements AfterContentInit { moduleId: module.id, selector: 'md-datepicker, mat-datepicker', template: '', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdDatepicker implements OnDestroy { /** The date to open the calendar to initially. */ @@ -115,6 +117,16 @@ export class MdDatepicker implements OnDestroy { */ @Input() touchUi = false; + /** Whether the datepicker pop-up should be disabled. */ + @Input() + get disabled() { + return this._disabled === undefined ? this._datepickerInput.disabled : this._disabled; + } + set disabled(value: any) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + /** Emits new selected date when selected date changes. */ @Output() selectedChanged = new EventEmitter(); @@ -205,7 +217,7 @@ export class MdDatepicker implements OnDestroy { /** Open the calendar. */ open(): void { - if (this.opened) { + if (this.opened || this.disabled) { return; } if (!this._datepickerInput) { diff --git a/src/lib/datepicker/month-view.spec.ts b/src/lib/datepicker/month-view.spec.ts index aae098c596d5..220d81903bf0 100644 --- a/src/lib/datepicker/month-view.spec.ts +++ b/src/lib/datepicker/month-view.spec.ts @@ -12,13 +12,6 @@ describe('MdMonthView', () => { imports: [ MdNativeDateModule, ], - providers: [ - {provide: DateAdapter, useFactory: () => { - let adapter = new NativeDateAdapter(); - adapter.setLocale('en-US'); - return adapter; - }} - ], declarations: [ MdCalendarBody, MdMonthView, diff --git a/src/lib/datepicker/year-view.spec.ts b/src/lib/datepicker/year-view.spec.ts index d81a1978ce11..dfd8e8b71d7c 100644 --- a/src/lib/datepicker/year-view.spec.ts +++ b/src/lib/datepicker/year-view.spec.ts @@ -12,13 +12,6 @@ describe('MdYearView', () => { imports: [ MdNativeDateModule, ], - providers: [ - {provide: DateAdapter, useFactory: () => { - let adapter = new NativeDateAdapter(); - adapter.setLocale('en-US'); - return adapter; - }} - ], declarations: [ MdCalendarBody, MdYearView, diff --git a/src/lib/expansion/accordion-item.ts b/src/lib/expansion/accordion-item.ts index 52ec10681bc8..db2ce5261d54 100644 --- a/src/lib/expansion/accordion-item.ts +++ b/src/lib/expansion/accordion-item.ts @@ -6,7 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import {Output, EventEmitter, Input, Injectable, OnDestroy, Optional} from '@angular/core'; +import { + Output, + EventEmitter, + Input, + Injectable, + OnDestroy, + Optional, + ChangeDetectorRef, +} from '@angular/core'; import {UniqueSelectionDispatcher} from '../core'; import {CdkAccordion} from './accordion'; @@ -44,6 +52,10 @@ export class AccordionItem implements OnDestroy { } else { this.closed.emit(); } + + // Ensures that the animation will run when the value is set outside of an `@Input`. + // This includes cases like the open, close and toggle methods. + this._changeDetectorRef.markForCheck(); } } private _expanded: boolean; @@ -52,6 +64,7 @@ export class AccordionItem implements OnDestroy { private _removeUniqueSelectionListener: () => void = () => {}; constructor(@Optional() public accordion: CdkAccordion, + private _changeDetectorRef: ChangeDetectorRef, protected _expansionDispatcher: UniqueSelectionDispatcher) { this._removeUniqueSelectionListener = _expansionDispatcher.listen((id: string, accordionId: string) => { diff --git a/src/lib/expansion/expansion-panel-header.ts b/src/lib/expansion/expansion-panel-header.ts index d2ba9541fb50..4221e4166583 100644 --- a/src/lib/expansion/expansion-panel-header.ts +++ b/src/lib/expansion/expansion-panel-header.ts @@ -11,6 +11,7 @@ import { Directive, Host, ViewEncapsulation, + ChangeDetectionStrategy, } from '@angular/core'; import { trigger, @@ -36,6 +37,7 @@ import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-pa styleUrls: ['./expansion-panel-header.css'], templateUrl: './expansion-panel-header.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, host: { 'class': 'mat-expansion-panel-header', 'role': 'button', diff --git a/src/lib/expansion/expansion-panel.ts b/src/lib/expansion/expansion-panel.ts index d7d56b21fa35..8bdc8c1758ba 100644 --- a/src/lib/expansion/expansion-panel.ts +++ b/src/lib/expansion/expansion-panel.ts @@ -14,6 +14,8 @@ import { ViewEncapsulation, Optional, forwardRef, + ChangeDetectionStrategy, + ChangeDetectorRef, } from '@angular/core'; import { trigger, @@ -47,6 +49,7 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2, selector: 'md-expansion-panel, mat-expansion-panel', templateUrl: './expansion-panel.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, host: { 'class': 'mat-expansion-panel', '[class.mat-expanded]': 'expanded', @@ -57,8 +60,8 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2, ], animations: [ trigger('bodyExpansion', [ - state('collapsed', style({height: '0px'})), - state('expanded', style({height: '*'})), + state('collapsed', style({height: '0px', visibility: 'hidden'})), + state('expanded', style({height: '*', visibility: 'visible'})), transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)), ]), trigger('displayMode', [ @@ -75,8 +78,9 @@ export class MdExpansionPanel extends AccordionItem { @Input() hideToggle: boolean = false; constructor(@Optional() @Host() accordion: MdAccordion, + _changeDetectorRef: ChangeDetectorRef, _uniqueSelectionDispatcher: UniqueSelectionDispatcher) { - super(accordion, _uniqueSelectionDispatcher); + super(accordion, _changeDetectorRef, _uniqueSelectionDispatcher); this.accordion = accordion; } diff --git a/src/lib/expansion/expansion.spec.ts b/src/lib/expansion/expansion.spec.ts index e6f53a7d6437..abb7495c16e0 100644 --- a/src/lib/expansion/expansion.spec.ts +++ b/src/lib/expansion/expansion.spec.ts @@ -1,7 +1,7 @@ -import {async, TestBed} from '@angular/core/testing'; +import {async, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {Component} from '@angular/core'; import {By} from '@angular/platform-browser'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {MdExpansionModule} from './index'; @@ -9,7 +9,7 @@ describe('MdExpansionPanel', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - BrowserAnimationsModule, + NoopAnimationsModule, MdExpansionModule ], declarations: [ @@ -20,9 +20,9 @@ describe('MdExpansionPanel', () => { })); it('should expanded and collapse the panel', () => { - let fixture = TestBed.createComponent(PanelWithContent); - let contentEl = fixture.debugElement.query(By.css('.mat-expansion-panel-content')); - let headerEl = fixture.debugElement.query(By.css('.mat-expansion-panel-header')); + const fixture = TestBed.createComponent(PanelWithContent); + const contentEl = fixture.debugElement.query(By.css('.mat-expansion-panel-content')); + const headerEl = fixture.debugElement.query(By.css('.mat-expansion-panel-header')); fixture.detectChanges(); expect(headerEl.classes['mat-expanded']).toBeFalsy(); expect(contentEl.classes['mat-expanded']).toBeFalsy(); @@ -34,7 +34,7 @@ describe('MdExpansionPanel', () => { }); it('emit correct events for change in panel expanded state', () => { - let fixture = TestBed.createComponent(PanelWithContent); + const fixture = TestBed.createComponent(PanelWithContent); fixture.componentInstance.expanded = true; fixture.detectChanges(); expect(fixture.componentInstance.openCallback).toHaveBeenCalled(); @@ -45,17 +45,37 @@ describe('MdExpansionPanel', () => { }); it('creates a unique panel id for each panel', () => { - let fixtureOne = TestBed.createComponent(PanelWithContent); - let headerElOne = fixtureOne.nativeElement.querySelector('.mat-expansion-panel-header'); - let fixtureTwo = TestBed.createComponent(PanelWithContent); - let headerElTwo = fixtureTwo.nativeElement.querySelector('.mat-expansion-panel-header'); + const fixtureOne = TestBed.createComponent(PanelWithContent); + const headerElOne = fixtureOne.nativeElement.querySelector('.mat-expansion-panel-header'); + const fixtureTwo = TestBed.createComponent(PanelWithContent); + const headerElTwo = fixtureTwo.nativeElement.querySelector('.mat-expansion-panel-header'); fixtureOne.detectChanges(); fixtureTwo.detectChanges(); - let panelIdOne = headerElOne.getAttribute('aria-controls'); - let panelIdTwo = headerElTwo.getAttribute('aria-controls'); + const panelIdOne = headerElOne.getAttribute('aria-controls'); + const panelIdTwo = headerElTwo.getAttribute('aria-controls'); expect(panelIdOne).not.toBe(panelIdTwo); }); + + it('should not be able to focus content while closed', fakeAsync(() => { + const fixture = TestBed.createComponent(PanelWithContent); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + + fixture.componentInstance.expanded = true; + fixture.detectChanges(); + tick(250); + + button.focus(); + expect(document.activeElement).toBe(button, 'Expected button to start off focusable.'); + + button.blur(); + fixture.componentInstance.expanded = false; + fixture.detectChanges(); + tick(250); + + button.focus(); + expect(document.activeElement).not.toBe(button, 'Expected button to no longer be focusable.'); + })); }); @@ -65,6 +85,7 @@ describe('MdExpansionPanel', () => { (closed)="closeCallback()"> Panel Title

Some content

+ `}) class PanelWithContent { expanded: boolean = false; diff --git a/src/lib/grid-list/grid-list.ts b/src/lib/grid-list/grid-list.ts index 64f7aade1644..4a40b27e3cc1 100644 --- a/src/lib/grid-list/grid-list.ts +++ b/src/lib/grid-list/grid-list.ts @@ -17,6 +17,7 @@ import { Renderer2, ElementRef, Optional, + ChangeDetectionStrategy, } from '@angular/core'; import {MdGridTile} from './grid-tile'; import {TileCoordinator} from './tile-coordinator'; @@ -43,6 +44,7 @@ const MD_FIT_MODE = 'fit'; 'role': 'list', 'class': 'mat-grid-list', }, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MdGridList implements OnInit, AfterContentChecked { diff --git a/src/lib/grid-list/grid-tile.ts b/src/lib/grid-list/grid-tile.ts index 5e22cc7d2d21..2f969be9a986 100644 --- a/src/lib/grid-list/grid-tile.ts +++ b/src/lib/grid-list/grid-tile.ts @@ -14,7 +14,9 @@ import { Input, ContentChildren, QueryList, - AfterContentInit, Directive + AfterContentInit, + Directive, + ChangeDetectionStrategy, } from '@angular/core'; import {MdLine, MdLineSetter} from '../core'; import {coerceToNumber} from './grid-list-measure'; @@ -29,6 +31,7 @@ import {coerceToNumber} from './grid-list-measure'; templateUrl: 'grid-tile.html', styleUrls: ['grid-list.css'], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdGridTile { _rowspan: number = 1; @@ -58,7 +61,8 @@ export class MdGridTile { @Component({ moduleId: module.id, selector: 'md-grid-tile-header, mat-grid-tile-header, md-grid-tile-footer, mat-grid-tile-footer', - templateUrl: 'grid-tile-text.html' + templateUrl: 'grid-tile-text.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdGridTileText implements AfterContentInit { /** diff --git a/src/lib/input/index.ts b/src/lib/input/index.ts index b15a025e2b38..b616a558408d 100644 --- a/src/lib/input/index.ts +++ b/src/lib/input/index.ts @@ -18,7 +18,6 @@ import { } from './input-container'; import {MdTextareaAutosize} from './autosize'; import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; import {PlatformModule} from '../core/platform/index'; @@ -35,7 +34,6 @@ import {PlatformModule} from '../core/platform/index'; ], imports: [ CommonModule, - FormsModule, PlatformModule, ], exports: [ diff --git a/src/lib/input/input-container.spec.ts b/src/lib/input/input-container.spec.ts index c8f70cbe91f7..ebf4fd8f851a 100644 --- a/src/lib/input/input-container.spec.ts +++ b/src/lib/input/input-container.spec.ts @@ -24,7 +24,7 @@ import { import {MD_PLACEHOLDER_GLOBAL_OPTIONS} from '../core/placeholder/placeholder-options'; import {MD_ERROR_GLOBAL_OPTIONS, showOnDirtyErrorStateMatcher} from '../core/error/error-options'; -describe('MdInputContainer', function () { +describe('MdInputContainer without forms', function () { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ @@ -35,7 +35,6 @@ describe('MdInputContainer', function () { ReactiveFormsModule, ], declarations: [ - MdInputContainerBaseTestController, MdInputContainerDateTestController, MdInputContainerHintLabel2TestController, MdInputContainerHintLabelTestController, @@ -54,17 +53,12 @@ describe('MdInputContainer', function () { MdInputContainerTextTestController, MdInputContainerWithDisabled, MdInputContainerWithDynamicPlaceholder, - MdInputContainerWithFormControl, - MdInputContainerWithFormErrorMessages, - MdInputContainerWithCustomErrorStateMatcher, - MdInputContainerWithFormGroupErrorMessages, MdInputContainerWithId, MdInputContainerWithPrefixAndSuffix, MdInputContainerWithRequired, MdInputContainerWithStaticPlaceholder, MdInputContainerWithType, MdInputContainerWithValueBinding, - MdInputContainerZeroTestController, MdTextareaWithBindings, MdInputContainerWithNgIf, ], @@ -74,7 +68,7 @@ describe('MdInputContainer', function () { })); it('should default to floating placeholders', () => { - let fixture = TestBed.createComponent(MdInputContainerBaseTestController); + let fixture = TestBed.createComponent(MdInputContainerWithId); fixture.detectChanges(); let inputContainer = fixture.debugElement.query(By.directive(MdInputContainer)) @@ -92,12 +86,12 @@ describe('MdInputContainer', function () { NoopAnimationsModule ], declarations: [ - MdInputContainerBaseTestController + MdInputContainerWithId ], providers: [{ provide: MD_PLACEHOLDER_GLOBAL_OPTIONS, useValue: { float: 'always' } }] }); - let fixture = TestBed.createComponent(MdInputContainerBaseTestController); + let fixture = TestBed.createComponent(MdInputContainerWithId); fixture.detectChanges(); let inputContainer = fixture.debugElement.query(By.directive(MdInputContainer)) @@ -210,33 +204,6 @@ describe('MdInputContainer', function () { expect(placeholderEl.classList).toContain('mat-empty'); })); - it('should not treat the number 0 as empty', async(() => { - let fixture = TestBed.createComponent(MdInputContainerZeroTestController); - fixture.detectChanges(); - - fixture.whenStable().then(() => { - fixture.detectChanges(); - - let el = fixture.debugElement.query(By.css('label')).nativeElement; - expect(el).not.toBeNull(); - expect(el.classList.contains('mat-empty')).toBe(false); - }); - })); - - it('should update the value when using FormControl.setValue', () => { - let fixture = TestBed.createComponent(MdInputContainerWithFormControl); - fixture.detectChanges(); - - let input = fixture.debugElement.query(By.directive(MdInputDirective)) - .injector.get(MdInputDirective); - - expect(input.value).toBeFalsy(); - - fixture.componentInstance.formControl.setValue('something'); - - expect(input.value).toBe('something'); - }); - it('should add id', () => { let fixture = TestBed.createComponent(MdInputContainerTextTestController); fixture.detectChanges(); @@ -441,25 +408,6 @@ describe('MdInputContainer', function () { expect(inputEl.disabled).toBe(true); })); - it('should display disabled styles when using FormControl.disable()', () => { - const fixture = TestBed.createComponent(MdInputContainerWithFormControl); - fixture.detectChanges(); - - const underlineEl = fixture.debugElement.query(By.css('.mat-input-underline')).nativeElement; - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - - expect(underlineEl.classList) - .not.toContain('mat-disabled', `Expected underline not to start out disabled.`); - expect(inputEl.disabled).toBe(false); - - fixture.componentInstance.formControl.disable(); - fixture.detectChanges(); - - expect(underlineEl.classList).toContain('mat-disabled', - `Expected underline to look disabled after disable() is called.`); - expect(inputEl.disabled).toBe(true); - }); - it('supports the required attribute as binding', async(() => { let fixture = TestBed.createComponent(MdInputContainerWithRequired); fixture.detectChanges(); @@ -616,6 +564,54 @@ describe('MdInputContainer', function () { expect(labelEl.classList).not.toContain('mat-float'); }); + it('should not have prefix and suffix elements when none are specified', () => { + let fixture = TestBed.createComponent(MdInputContainerWithId); + fixture.detectChanges(); + + let prefixEl = fixture.debugElement.query(By.css('.mat-input-prefix')); + let suffixEl = fixture.debugElement.query(By.css('.mat-input-suffix')); + + expect(prefixEl).toBeNull(); + expect(suffixEl).toBeNull(); + }); + + it('should add prefix and suffix elements when specified', () => { + let fixture = TestBed.createComponent(MdInputContainerWithPrefixAndSuffix); + fixture.detectChanges(); + + let prefixEl = fixture.debugElement.query(By.css('.mat-input-prefix')); + let suffixEl = fixture.debugElement.query(By.css('.mat-input-suffix')); + + expect(prefixEl).not.toBeNull(); + expect(suffixEl).not.toBeNull(); + expect(prefixEl.nativeElement.innerText.trim()).toEqual('Prefix'); + expect(suffixEl.nativeElement.innerText.trim()).toEqual('Suffix'); + }); +}); + +describe('MdInputContainer with forms', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + MdInputModule, + NoopAnimationsModule, + PlatformModule, + ReactiveFormsModule, + ], + declarations: [ + MdInputContainerWithFormControl, + MdInputContainerWithFormErrorMessages, + MdInputContainerWithCustomErrorStateMatcher, + MdInputContainerWithFormGroupErrorMessages, + MdInputContainerZeroTestController, + ], + }); + + TestBed.compileComponents(); + })); + describe('error messages', () => { let fixture: ComponentFixture; let testComponent: MdInputContainerWithFormErrorMessages; @@ -634,7 +630,7 @@ describe('MdInputContainer', function () { expect(testComponent.formControl.untouched).toBe(true, 'Expected untouched form control'); expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages'); expect(inputEl.getAttribute('aria-invalid')) - .toBe('false', 'Expected aria-invalid to be set to "false".'); + .toBe('false', 'Expected aria-invalid to be set to "false".'); }); it('should display an error message when the input is touched and invalid', async(() => { @@ -646,11 +642,11 @@ describe('MdInputContainer', function () { fixture.whenStable().then(() => { expect(containerEl.classList) - .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); + .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error message to have been rendered.'); + .toBe(1, 'Expected one error message to have been rendered.'); expect(inputEl.getAttribute('aria-invalid')) - .toBe('true', 'Expected aria-invalid to be set to "true".'); + .toBe('true', 'Expected aria-invalid to be set to "true".'); }); })); @@ -665,11 +661,11 @@ describe('MdInputContainer', function () { fixture.whenStable().then(() => { expect(testComponent.form.submitted).toBe(true, 'Expected form to have been submitted'); expect(containerEl.classList) - .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); + .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error message to have been rendered.'); + .toBe(1, 'Expected one error message to have been rendered.'); expect(inputEl.getAttribute('aria-invalid')) - .toBe('true', 'Expected aria-invalid to be set to "true".'); + .toBe('true', 'Expected aria-invalid to be set to "true".'); }); })); @@ -687,22 +683,22 @@ describe('MdInputContainer', function () { expect(component.formGroup.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages'); expect(inputEl.getAttribute('aria-invalid')) - .toBe('false', 'Expected aria-invalid to be set to "false".'); + .toBe('false', 'Expected aria-invalid to be set to "false".'); expect(component.formGroupDirective.submitted) - .toBe(false, 'Expected form not to have been submitted'); + .toBe(false, 'Expected form not to have been submitted'); dispatchFakeEvent(groupFixture.debugElement.query(By.css('form')).nativeElement, 'submit'); groupFixture.detectChanges(); groupFixture.whenStable().then(() => { expect(component.formGroupDirective.submitted) - .toBe(true, 'Expected form to have been submitted'); + .toBe(true, 'Expected form to have been submitted'); expect(containerEl.classList) - .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); + .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error message to have been rendered.'); + .toBe(1, 'Expected one error message to have been rendered.'); expect(inputEl.getAttribute('aria-invalid')) - .toBe('true', 'Expected aria-invalid to be set to "true".'); + .toBe('true', 'Expected aria-invalid to be set to "true".'); }); })); @@ -712,22 +708,22 @@ describe('MdInputContainer', function () { fixture.whenStable().then(() => { expect(containerEl.classList) - .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); + .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error message to have been rendered.'); + .toBe(1, 'Expected one error message to have been rendered.'); expect(containerEl.querySelectorAll('md-hint').length) - .toBe(0, 'Expected no hints to be shown.'); + .toBe(0, 'Expected no hints to be shown.'); testComponent.formControl.setValue('something'); fixture.detectChanges(); fixture.whenStable().then(() => { expect(containerEl.classList).not.toContain('mat-input-invalid', - 'Expected container not to have the invalid class when valid.'); + 'Expected container not to have the invalid class when valid.'); expect(containerEl.querySelectorAll('md-error').length) - .toBe(0, 'Expected no error messages when the input is valid.'); + .toBe(0, 'Expected no error messages when the input is valid.'); expect(containerEl.querySelectorAll('md-hint').length) - .toBe(1, 'Expected one hint to be shown once the input is valid.'); + .toBe(1, 'Expected one hint to be shown once the input is valid.'); }); }); })); @@ -737,20 +733,20 @@ describe('MdInputContainer', function () { fixture.detectChanges(); expect(containerEl.querySelectorAll('md-hint').length) - .toBe(1, 'Expected one hint to be shown on load.'); + .toBe(1, 'Expected one hint to be shown on load.'); testComponent.formControl.markAsTouched(); fixture.detectChanges(); fixture.whenStable().then(() => { expect(containerEl.querySelectorAll('md-hint').length) - .toBe(1, 'Expected one hint to still be shown.'); + .toBe(1, 'Expected one hint to still be shown.'); }); })); - }); describe('custom error behavior', () => { + it('should display an error message when a custom error matcher returns true', () => { let fixture = TestBed.createComponent(MdInputContainerWithCustomErrorStateMatcher); fixture.detectChanges(); @@ -762,19 +758,19 @@ describe('MdInputContainer', function () { expect(control.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('md-error').length) - .toBe(0, 'Expected no error messages'); + .toBe(0, 'Expected no error messages'); control.markAsTouched(); fixture.detectChanges(); expect(containerEl.querySelectorAll('md-error').length) - .toBe(0, 'Expected no error messages after being touched.'); + .toBe(0, 'Expected no error messages after being touched.'); component.errorState = true; fixture.detectChanges(); expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error messages to have been rendered.'); + .toBe(1, 'Expected one error messages to have been rendered.'); }); it('should display an error message when global error matcher returns true', () => { @@ -847,39 +843,61 @@ describe('MdInputContainer', function () { fixture.detectChanges(); expect(containerEl.querySelectorAll('md-error').length) - .toBe(0, 'Expected no error messages when touched'); + .toBe(0, 'Expected no error messages when touched'); testComponent.formControl.markAsDirty(); fixture.detectChanges(); expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error message when dirty'); + .toBe(1, 'Expected one error message when dirty'); })); }); - it('should not have prefix and suffix elements when none are specified', () => { - let fixture = TestBed.createComponent(MdInputContainerWithId); + it('should update the value when using FormControl.setValue', () => { + let fixture = TestBed.createComponent(MdInputContainerWithFormControl); fixture.detectChanges(); - let prefixEl = fixture.debugElement.query(By.css('.mat-input-prefix')); - let suffixEl = fixture.debugElement.query(By.css('.mat-input-suffix')); + let input = fixture.debugElement.query(By.directive(MdInputDirective)) + .injector.get(MdInputDirective); - expect(prefixEl).toBeNull(); - expect(suffixEl).toBeNull(); + expect(input.value).toBeFalsy(); + + fixture.componentInstance.formControl.setValue('something'); + + expect(input.value).toBe('something'); }); - it('should add prefix and suffix elements when specified', () => { - let fixture = TestBed.createComponent(MdInputContainerWithPrefixAndSuffix); + it('should display disabled styles when using FormControl.disable()', () => { + const fixture = TestBed.createComponent(MdInputContainerWithFormControl); fixture.detectChanges(); - let prefixEl = fixture.debugElement.query(By.css('.mat-input-prefix')); - let suffixEl = fixture.debugElement.query(By.css('.mat-input-suffix')); + const underlineEl = fixture.debugElement.query(By.css('.mat-input-underline')).nativeElement; + const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - expect(prefixEl).not.toBeNull(); - expect(suffixEl).not.toBeNull(); - expect(prefixEl.nativeElement.innerText.trim()).toEqual('Prefix'); - expect(suffixEl.nativeElement.innerText.trim()).toEqual('Suffix'); + expect(underlineEl.classList) + .not.toContain('mat-disabled', `Expected underline not to start out disabled.`); + expect(inputEl.disabled).toBe(false); + + fixture.componentInstance.formControl.disable(); + fixture.detectChanges(); + + expect(underlineEl.classList).toContain('mat-disabled', + `Expected underline to look disabled after disable() is called.`); + expect(inputEl.disabled).toBe(true); }); + + it('should not treat the number 0 as empty', async(() => { + let fixture = TestBed.createComponent(MdInputContainerZeroTestController); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let el = fixture.debugElement.query(By.css('label')).nativeElement; + expect(el).not.toBeNull(); + expect(el.classList.contains('mat-empty')).toBe(false); + }); + })); }); @Component({ @@ -1014,13 +1032,6 @@ class MdInputContainerMultipleHintTestController { }) class MdInputContainerMultipleHintMixedTestController {} -@Component({ - template: `` -}) -class MdInputContainerBaseTestController { - model: any = ''; -} - @Component({ template: ` diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts index 52222e503d19..0720e14e30ea 100644 --- a/src/lib/menu/menu-directive.ts +++ b/src/lib/menu/menu-directive.ts @@ -19,6 +19,7 @@ import { ViewChild, ViewEncapsulation, ElementRef, + ChangeDetectionStrategy, } from '@angular/core'; import {MenuPositionX, MenuPositionY} from './menu-positions'; import {throwMdMenuInvalidPositionX, throwMdMenuInvalidPositionY} from './menu-errors'; @@ -35,6 +36,7 @@ import {ESCAPE} from '../core/keyboard/keycodes'; selector: 'md-menu, mat-menu', templateUrl: 'menu.html', styleUrls: ['menu.css'], + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, animations: [ transformMenu, @@ -116,6 +118,9 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy { if (this._tabSubscription) { this._tabSubscription.unsubscribe(); } + + this._emitCloseEvent(); + this.close.complete(); } /** Handle a keyboard event from the menu, delegating to the appropriate action. */ diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index d471dd3d42ad..21a64397d2e7 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ElementRef} from '@angular/core'; +import {Component, ElementRef, ChangeDetectionStrategy} from '@angular/core'; import {Focusable} from '../core/a11y/focus-key-manager'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; @@ -31,8 +31,9 @@ export const _MdMenuItemMixinBase = mixinDisabled(MdMenuItemBase); '[attr.disabled]': 'disabled || null', '(click)': '_checkDisabled($event)', }, + changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: 'menu-item.html', - exportAs: 'mdMenuItem' + exportAs: 'mdMenuItem', }) export class MdMenuItem extends _MdMenuItemMixinBase implements Focusable, CanDisable { diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 9ec4f7684d75..c029c56154bb 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -15,7 +15,8 @@ import { MdMenuTrigger, MdMenuPanel, MenuPositionX, - MenuPositionY + MenuPositionY, + MdMenu } from './index'; import {OverlayContainer} from '../core/overlay/overlay-container'; import {Directionality, Direction} from '../core/bidi/index'; @@ -477,6 +478,17 @@ describe('MdMenu', () => { expect(fixture.componentInstance.closeCallback).toHaveBeenCalled(); }); + + it('should complete the callback when the menu is destroyed', () => { + let emitCallback = jasmine.createSpy('emit callback'); + let completeCallback = jasmine.createSpy('complete callback'); + + fixture.componentInstance.menu.close.subscribe(emitCallback, null, completeCallback); + fixture.destroy(); + + expect(emitCallback).toHaveBeenCalled(); + expect(completeCallback).toHaveBeenCalled(); + }); }); describe('destroy', () => { @@ -499,6 +511,7 @@ describe('MdMenu', () => { class SimpleMenu { @ViewChild(MdMenuTrigger) trigger: MdMenuTrigger; @ViewChild('triggerEl') triggerEl: ElementRef; + @ViewChild(MdMenu) menu: MdMenu; closeCallback = jasmine.createSpy('menu closed callback'); } diff --git a/src/lib/progress-spinner/progress-spinner.spec.ts b/src/lib/progress-spinner/progress-spinner.spec.ts index 76bc536fd046..c23ba1bde66e 100644 --- a/src/lib/progress-spinner/progress-spinner.spec.ts +++ b/src/lib/progress-spinner/progress-spinner.spec.ts @@ -27,10 +27,10 @@ describe('MdProgressSpinner', () => { it('should apply a mode of "determinate" if no mode is provided.', () => { let fixture = TestBed.createComponent(BasicProgressSpinner); - fixture.detectChanges(); + fixture.detectChanges(); - let progressElement = fixture.debugElement.query(By.css('md-progress-spinner')); - expect(progressElement.componentInstance.mode).toBe('determinate'); + let progressElement = fixture.debugElement.query(By.css('md-progress-spinner')); + expect(progressElement.componentInstance.mode).toBe('determinate'); }); it('should not modify the mode if a valid mode is provided.', () => { diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index aaa74d42e058..7320c54ab7cf 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -287,19 +287,13 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase inputs: ['color'], templateUrl: 'progress-spinner.html', styleUrls: ['progress-spinner.css'], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdSpinner extends MdProgressSpinner implements OnDestroy { - +export class MdSpinner extends MdProgressSpinner { constructor(elementRef: ElementRef, ngZone: NgZone, renderer: Renderer2) { super(renderer, elementRef, ngZone); this.mode = 'indeterminate'; } - - ngOnDestroy() { - // The `ngOnDestroy` from `MdProgressSpinner` should be called explicitly, because - // in certain cases Angular won't call it (e.g. when using AoT and in unit tests). - super.ngOnDestroy(); - } } diff --git a/src/lib/select/_select-theme.scss b/src/lib/select/_select-theme.scss index 4033a4716b97..f886af4bb45c 100644 --- a/src/lib/select/_select-theme.scss +++ b/src/lib/select/_select-theme.scss @@ -52,6 +52,10 @@ &.mat-accent { @include _mat-select-inner-content-theme($accent); } + + &.mat-select-required .mat-select-placeholder::after { + color: mat-color($warn); + } } .mat-select:focus:not(.mat-select-disabled).mat-warn, diff --git a/src/lib/select/select.html b/src/lib/select/select.html index 870e7883606d..6549ea92568b 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -7,11 +7,11 @@ #trigger> {{ placeholder }} - + {{ triggerValue }} diff --git a/src/lib/select/select.scss b/src/lib/select/select.scss index d7ca055905d9..5fd2e0b8d9be 100644 --- a/src/lib/select/select.scss +++ b/src/lib/select/select.scss @@ -72,7 +72,7 @@ $mat-select-panel-max-height: 256px !default; } // TODO: Double-check accessibility of this style - [aria-required=true] &::after { + .mat-select-required &::after { content: '*'; } } diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index a8a7726c435a..01c09e97ca10 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -62,7 +62,8 @@ describe('MdSelect', () => { BasicSelectWithTheming, ResetValuesSelect, FalsyValueSelect, - SelectWithGroups + SelectWithGroups, + InvalidSelectInForm ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1572,6 +1573,17 @@ describe('MdSelect', () => { .toEqual('true', `Expected aria-required attr to be true for required selects.`); }); + it('should set the mat-select-required class for required selects', () => { + expect(select.classList).not.toContain( + 'mat-select-required', `Expected the mat-select-required class not to be set.`); + + fixture.componentInstance.isRequired = true; + fixture.detectChanges(); + + expect(select.classList).toContain( + 'mat-select-required', `Expected the mat-select-required class to be set.`); + }); + it('should set aria-invalid for selects that are invalid', () => { expect(select.getAttribute('aria-invalid')) .toEqual('false', `Expected aria-invalid attr to be false for valid selects.`); @@ -1961,6 +1973,17 @@ describe('MdSelect', () => { }).not.toThrow(); })); + it('should not throw selection model-related errors in addition to the errors from ngModel', + async(() => { + const fixture = TestBed.createComponent(InvalidSelectInForm); + + // The first change detection run will throw the "ngModel is missing a name" error. + expect(() => fixture.detectChanges()).toThrowError(/the name attribute must be set/g); + + // The second run shouldn't throw selection-model related errors. + expect(() => fixture.detectChanges()).not.toThrow(); + })); + }); describe('change event', () => { @@ -2861,3 +2884,11 @@ class SelectWithGroups { @ViewChild(MdSelect) select: MdSelect; @ViewChildren(MdOption) options: QueryList; } + + +@Component({ + template: `
` +}) +class InvalidSelectInForm { + value: any; +} diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 493f5522b1a2..9cca65662c84 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -132,6 +132,7 @@ export const _MdSelectMixinBase = mixinColor(mixinDisabled(MdSelectBase), 'prima '[attr.aria-invalid]': '_control?.invalid || "false"', '[attr.aria-owns]': '_optionIds', '[class.mat-select-disabled]': 'disabled', + '[class.mat-select-required]': 'required', 'class': 'mat-select', '(keydown)': '_handleClosedKeydown($event)', '(blur)': '_onBlur()', @@ -547,6 +548,11 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On this._setScrollTop(); } + /** Whether the select has a value. */ + _hasValue(): boolean { + return this._selectionModel && this._selectionModel.hasValue(); + } + /** * Sets the scroll position of the scroll container. This must be called after * the overlay pane is attached or the scroll container element will not yet be @@ -771,7 +777,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On // The farthest the panel can be scrolled before it hits the bottom const maxScroll = scrollContainerHeight - panelHeight; - if (this._selectionModel.hasValue()) { + if (this._hasValue()) { let selectedOptionOffset = this._getOptionIndex(this._selectionModel.selected[0])!; selectedOptionOffset += this._getLabelCountBeforeOption(selectedOptionOffset); diff --git a/src/lib/slide-toggle/index.ts b/src/lib/slide-toggle/index.ts index a7454d797f80..51412f905a57 100644 --- a/src/lib/slide-toggle/index.ts +++ b/src/lib/slide-toggle/index.ts @@ -7,7 +7,6 @@ */ import {NgModule} from '@angular/core'; -import {FormsModule} from '@angular/forms'; import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdSlideToggle} from './slide-toggle'; import { @@ -19,7 +18,7 @@ import { } from '../core'; @NgModule({ - imports: [FormsModule, MdRippleModule, MdCommonModule, PlatformModule], + imports: [MdRippleModule, MdCommonModule, PlatformModule], exports: [MdSlideToggle, MdCommonModule], declarations: [MdSlideToggle], providers: [ diff --git a/src/lib/slide-toggle/slide-toggle.spec.ts b/src/lib/slide-toggle/slide-toggle.spec.ts index c0d52461ca65..ef67755d5a17 100644 --- a/src/lib/slide-toggle/slide-toggle.spec.ts +++ b/src/lib/slide-toggle/slide-toggle.spec.ts @@ -26,6 +26,53 @@ describe('MdSlideToggle', () => { TestBed.compileComponents(); })); + describe('without form modules', () => { + let fixture: ComponentFixture; + let slideToggleInstance: MdSlideToggle; + let labelElement: HTMLLabelElement; + + beforeEach(async(() => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [MdSlideToggleModule], + declarations: [SlideToggleWithoutForms] + }); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SlideToggleWithoutForms); + fixture.detectChanges(); + + const slideToggleDebug = fixture.debugElement.query(By.directive(MdSlideToggle)); + + slideToggleInstance = slideToggleDebug.componentInstance; + labelElement = fixture.debugElement.query(By.css('label')).nativeElement; + }); + + it('should update the checked state on click', () => { + expect(slideToggleInstance.checked) + .toBe(false, 'Expected the slide-toggle not to be checked initially.'); + + labelElement.click(); + fixture.detectChanges(); + + expect(slideToggleInstance.checked) + .toBe(true, 'Expected the slide-toggle to be checked after click.'); + }); + + it('should update the checked state from binding', () => { + expect(slideToggleInstance.checked) + .toBe(false, 'Expected the slide-toggle not to be checked initially.'); + + fixture.componentInstance.isChecked = true; + fixture.detectChanges(); + + expect(slideToggleInstance.checked) + .toBe(true, 'Expected the slide-toggle to be checked after click.'); + }); + + }); + describe('basic behavior', () => { let fixture: ComponentFixture; @@ -720,3 +767,10 @@ class SlideToggleFormsTestApp { class SlideToggleWithFormControl { formControl = new FormControl(); } + +@Component({ + template: `` +}) +class SlideToggleWithoutForms { + isChecked = false; +} diff --git a/src/lib/slider/index.ts b/src/lib/slider/index.ts index 834456716c50..508cd636e6c3 100644 --- a/src/lib/slider/index.ts +++ b/src/lib/slider/index.ts @@ -9,14 +9,13 @@ import {NgModule} from '@angular/core'; import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; import {MdCommonModule, GestureConfig, StyleModule} from '../core'; import {MdSlider} from './slider'; import {BidiModule} from '../core/bidi/index'; @NgModule({ - imports: [CommonModule, FormsModule, MdCommonModule, StyleModule, BidiModule], + imports: [CommonModule, MdCommonModule, StyleModule, BidiModule], exports: [MdSlider, MdCommonModule], declarations: [MdSlider], providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}] diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index f77a85367d46..ff37c85ae162 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -13,12 +13,12 @@ import { PAGE_DOWN, PAGE_UP, RIGHT_ARROW, - UP_ARROW + UP_ARROW, + BACKSPACE } from '../core/keyboard/keycodes'; import {dispatchKeyboardEvent, dispatchMouseEvent} from '@angular/cdk/testing'; - -describe('MdSlider', () => { +describe('MdSlider without forms', () => { let gestureConfig: TestGestureConfig; beforeEach(async(() => { @@ -34,8 +34,6 @@ describe('MdSlider', () => { SliderWithSetTickInterval, SliderWithThumbLabel, SliderWithOneWayBinding, - SliderWithFormControl, - SliderWithNgModel, SliderWithValueSmallerThanMin, SliderWithValueGreaterThanMax, SliderWithChangeHandler, @@ -551,156 +549,6 @@ describe('MdSlider', () => { }); }); - describe('slider as a custom form control', () => { - let fixture: ComponentFixture; - let sliderDebugElement: DebugElement; - let sliderNativeElement: HTMLElement; - let sliderInstance: MdSlider; - let sliderWrapperElement: HTMLElement; - let testComponent: SliderWithFormControl; - - beforeEach(() => { - fixture = TestBed.createComponent(SliderWithFormControl); - fixture.detectChanges(); - - testComponent = fixture.debugElement.componentInstance; - - sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider)); - sliderNativeElement = sliderDebugElement.nativeElement; - sliderInstance = sliderDebugElement.injector.get(MdSlider); - sliderWrapperElement = sliderNativeElement.querySelector('.mat-slider-wrapper'); - }); - - it('should not update the control when the value is updated', () => { - expect(testComponent.control.value).toBe(0); - - sliderInstance.value = 11; - fixture.detectChanges(); - - expect(testComponent.control.value).toBe(0); - }); - - it('should update the control on click', () => { - expect(testComponent.control.value).toBe(0); - - dispatchClickEventSequence(sliderNativeElement, 0.76); - fixture.detectChanges(); - - expect(testComponent.control.value).toBe(76); - }); - - it('should update the control on slide', () => { - expect(testComponent.control.value).toBe(0); - - dispatchSlideEventSequence(sliderNativeElement, 0, 0.19, gestureConfig); - fixture.detectChanges(); - - expect(testComponent.control.value).toBe(19); - }); - - it('should update the value when the control is set', () => { - expect(sliderInstance.value).toBe(0); - - testComponent.control.setValue(7); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(7); - }); - - it('should update the disabled state when control is disabled', () => { - expect(sliderInstance.disabled).toBe(false); - - testComponent.control.disable(); - fixture.detectChanges(); - - expect(sliderInstance.disabled).toBe(true); - }); - - it('should update the disabled state when the control is enabled', () => { - sliderInstance.disabled = true; - - testComponent.control.enable(); - fixture.detectChanges(); - - expect(sliderInstance.disabled).toBe(false); - }); - - it('should have the correct control state initially and after interaction', () => { - let sliderControl = testComponent.control; - - // The control should start off valid, pristine, and untouched. - expect(sliderControl.valid).toBe(true); - expect(sliderControl.pristine).toBe(true); - expect(sliderControl.touched).toBe(false); - - // After changing the value, the control should become dirty (not pristine), - // but remain untouched. - dispatchClickEventSequence(sliderNativeElement, 0.5); - fixture.detectChanges(); - - expect(sliderControl.valid).toBe(true); - expect(sliderControl.pristine).toBe(false); - expect(sliderControl.touched).toBe(false); - - // If the control has been visited due to interaction, the control should remain - // dirty and now also be touched. - sliderInstance._onBlur(); - fixture.detectChanges(); - - expect(sliderControl.valid).toBe(true); - expect(sliderControl.pristine).toBe(false); - expect(sliderControl.touched).toBe(true); - }); - }); - - describe('slider with ngModel', () => { - let fixture: ComponentFixture; - let sliderDebugElement: DebugElement; - let sliderNativeElement: HTMLElement; - let sliderInstance: MdSlider; - let sliderWrapperElement: HTMLElement; - let testComponent: SliderWithNgModel; - - beforeEach(() => { - fixture = TestBed.createComponent(SliderWithNgModel); - fixture.detectChanges(); - - testComponent = fixture.debugElement.componentInstance; - - sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider)); - sliderNativeElement = sliderDebugElement.nativeElement; - sliderInstance = sliderDebugElement.injector.get(MdSlider); - sliderWrapperElement = sliderNativeElement.querySelector('.mat-slider-wrapper'); - }); - - it('should update the model on click', () => { - expect(testComponent.val).toBe(0); - - dispatchClickEventSequence(sliderNativeElement, 0.76); - fixture.detectChanges(); - - expect(testComponent.val).toBe(76); - }); - - it('should update the model on slide', () => { - expect(testComponent.val).toBe(0); - - dispatchSlideEventSequence(sliderNativeElement, 0, 0.19, gestureConfig); - fixture.detectChanges(); - - expect(testComponent.val).toBe(19); - }); - - it('should update the model on keydown', () => { - expect(testComponent.val).toBe(0); - - dispatchKeyboardEvent(sliderNativeElement, 'keydown', UP_ARROW); - fixture.detectChanges(); - - expect(testComponent.val).toBe(1); - }); - }); - describe('slider with value property binding', () => { let fixture: ComponentFixture; let sliderDebugElement: DebugElement; @@ -895,18 +743,21 @@ describe('MdSlider', () => { }); describe('keyboard support', () => { - let fixture: ComponentFixture; + let fixture: ComponentFixture; let sliderDebugElement: DebugElement; let sliderNativeElement: HTMLElement; let sliderWrapperElement: HTMLElement; - let testComponent: StandardSlider; + let testComponent: SliderWithChangeHandler; let sliderInstance: MdSlider; beforeEach(() => { - fixture = TestBed.createComponent(StandardSlider); + fixture = TestBed.createComponent(SliderWithChangeHandler); fixture.detectChanges(); testComponent = fixture.debugElement.componentInstance; + spyOn(testComponent, 'onInput'); + spyOn(testComponent, 'onChange'); + sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider)); sliderNativeElement = sliderDebugElement.nativeElement; sliderWrapperElement = sliderNativeElement.querySelector('.mat-slider-wrapper'); @@ -914,68 +765,122 @@ describe('MdSlider', () => { }); it('should increment slider by 1 on up arrow pressed', () => { + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', UP_ARROW); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(1); }); it('should increment slider by 1 on right arrow pressed', () => { + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', RIGHT_ARROW); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(1); }); it('should decrement slider by 1 on down arrow pressed', () => { sliderInstance.value = 100; + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', DOWN_ARROW); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(99); }); it('should decrement slider by 1 on left arrow pressed', () => { sliderInstance.value = 100; + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', LEFT_ARROW); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(99); }); it('should increment slider by 10 on page up pressed', () => { + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', PAGE_UP); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(10); }); it('should decrement slider by 10 on page down pressed', () => { sliderInstance.value = 100; + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', PAGE_DOWN); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(90); }); it('should set slider to max on end pressed', () => { + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', END); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(100); }); it('should set slider to min on home pressed', () => { sliderInstance.value = 100; + expect(testComponent.onChange).not.toHaveBeenCalled(); + dispatchKeyboardEvent(sliderNativeElement, 'keydown', HOME); fixture.detectChanges(); + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).toHaveBeenCalledTimes(1); + expect(testComponent.onChange).toHaveBeenCalledTimes(1); expect(sliderInstance.value).toBe(0); }); + + it(`should take not action for presses of keys it doesn't care about`, () => { + sliderInstance.value = 50; + + expect(testComponent.onChange).not.toHaveBeenCalled(); + + dispatchKeyboardEvent(sliderNativeElement, 'keydown', BACKSPACE); + fixture.detectChanges(); + + // The `onInput` event should be emitted once due to a single keyboard press. + expect(testComponent.onInput).not.toHaveBeenCalled(); + expect(testComponent.onChange).not.toHaveBeenCalled(); + expect(sliderInstance.value).toBe(50); + }); }); describe('slider with direction and invert', () => { @@ -1167,6 +1072,179 @@ describe('MdSlider', () => { }); }); +describe('MdSlider with forms module', () => { + let gestureConfig: TestGestureConfig; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdSliderModule, ReactiveFormsModule, FormsModule, BidiModule], + declarations: [ + SliderWithFormControl, + SliderWithNgModel, + ], + providers: [ + {provide: HAMMER_GESTURE_CONFIG, useFactory: () => { + gestureConfig = new TestGestureConfig(); + return gestureConfig; + }} + ], + }); + + TestBed.compileComponents(); + })); + + describe('slider with ngModel', () => { + let fixture: ComponentFixture; + let sliderDebugElement: DebugElement; + let sliderNativeElement: HTMLElement; + let sliderInstance: MdSlider; + let sliderWrapperElement: HTMLElement; + let testComponent: SliderWithNgModel; + + beforeEach(() => { + fixture = TestBed.createComponent(SliderWithNgModel); + fixture.detectChanges(); + + testComponent = fixture.debugElement.componentInstance; + + sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider)); + sliderNativeElement = sliderDebugElement.nativeElement; + sliderInstance = sliderDebugElement.injector.get(MdSlider); + sliderWrapperElement = sliderNativeElement.querySelector('.mat-slider-wrapper'); + }); + + it('should update the model on click', () => { + expect(testComponent.val).toBe(0); + + dispatchClickEventSequence(sliderNativeElement, 0.76); + fixture.detectChanges(); + + expect(testComponent.val).toBe(76); + }); + + it('should update the model on slide', () => { + expect(testComponent.val).toBe(0); + + dispatchSlideEventSequence(sliderNativeElement, 0, 0.19, gestureConfig); + fixture.detectChanges(); + + expect(testComponent.val).toBe(19); + }); + + it('should update the model on keydown', () => { + expect(testComponent.val).toBe(0); + + dispatchKeyboardEvent(sliderNativeElement, 'keydown', UP_ARROW); + fixture.detectChanges(); + + expect(testComponent.val).toBe(1); + }); + }); + + describe('slider as a custom form control', () => { + let fixture: ComponentFixture; + let sliderDebugElement: DebugElement; + let sliderNativeElement: HTMLElement; + let sliderInstance: MdSlider; + let sliderWrapperElement: HTMLElement; + let testComponent: SliderWithFormControl; + + beforeEach(() => { + fixture = TestBed.createComponent(SliderWithFormControl); + fixture.detectChanges(); + + testComponent = fixture.debugElement.componentInstance; + + sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider)); + sliderNativeElement = sliderDebugElement.nativeElement; + sliderInstance = sliderDebugElement.injector.get(MdSlider); + sliderWrapperElement = sliderNativeElement.querySelector('.mat-slider-wrapper'); + }); + + it('should not update the control when the value is updated', () => { + expect(testComponent.control.value).toBe(0); + + sliderInstance.value = 11; + fixture.detectChanges(); + + expect(testComponent.control.value).toBe(0); + }); + + it('should update the control on click', () => { + expect(testComponent.control.value).toBe(0); + + dispatchClickEventSequence(sliderNativeElement, 0.76); + fixture.detectChanges(); + + expect(testComponent.control.value).toBe(76); + }); + + it('should update the control on slide', () => { + expect(testComponent.control.value).toBe(0); + + dispatchSlideEventSequence(sliderNativeElement, 0, 0.19, gestureConfig); + fixture.detectChanges(); + + expect(testComponent.control.value).toBe(19); + }); + + it('should update the value when the control is set', () => { + expect(sliderInstance.value).toBe(0); + + testComponent.control.setValue(7); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(7); + }); + + it('should update the disabled state when control is disabled', () => { + expect(sliderInstance.disabled).toBe(false); + + testComponent.control.disable(); + fixture.detectChanges(); + + expect(sliderInstance.disabled).toBe(true); + }); + + it('should update the disabled state when the control is enabled', () => { + sliderInstance.disabled = true; + + testComponent.control.enable(); + fixture.detectChanges(); + + expect(sliderInstance.disabled).toBe(false); + }); + + it('should have the correct control state initially and after interaction', () => { + let sliderControl = testComponent.control; + + // The control should start off valid, pristine, and untouched. + expect(sliderControl.valid).toBe(true); + expect(sliderControl.pristine).toBe(true); + expect(sliderControl.touched).toBe(false); + + // After changing the value, the control should become dirty (not pristine), + // but remain untouched. + dispatchClickEventSequence(sliderNativeElement, 0.5); + fixture.detectChanges(); + + expect(sliderControl.valid).toBe(true); + expect(sliderControl.pristine).toBe(false); + expect(sliderControl.touched).toBe(false); + + // If the control has been visited due to interaction, the control should remain + // dirty and now also be touched. + sliderInstance._onBlur(); + fixture.detectChanges(); + + expect(sliderControl.valid).toBe(true); + expect(sliderControl.pristine).toBe(false); + expect(sliderControl.touched).toBe(true); + }); + }); + +}); + // Disable animations and make the slider an even 100px (+ 8px padding on either side) // so we get nice round values in tests. const styles = ` diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index c34ef52c00e2..a75fecd3c9b7 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -16,7 +16,9 @@ import { Optional, Output, Renderer2, - ViewEncapsulation + ViewEncapsulation, + ChangeDetectionStrategy, + ChangeDetectorRef, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {coerceBooleanProperty, coerceNumberProperty, HammerInput} from '../core'; @@ -118,31 +120,33 @@ export const _MdSliderMixinBase = mixinDisabled(MdSliderBase); styleUrls: ['slider.css'], inputs: ['disabled'], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdSlider extends _MdSliderMixinBase implements ControlValueAccessor, OnDestroy, CanDisable { /** Whether the slider is inverted. */ @Input() get invert() { return this._invert; } - set invert(value: any) { this._invert = coerceBooleanProperty(value); } + set invert(value: any) { + this._invert = coerceBooleanProperty(value); + } private _invert = false; /** The maximum value that the slider can have. */ @Input() - get max() { - return this._max; - } + get max() { return this._max; } set max(v: number) { this._max = coerceNumberProperty(v, this._max); this._percent = this._calculatePercentage(this._value); + + // Since this also modifies the percentage, we need to let the change detection know. + this._changeDetectorRef.markForCheck(); } private _max: number = 100; /** The minimum value that the slider can have. */ @Input() - get min() { - return this._min; - } + get min() { return this._min; } set min(v: number) { this._min = coerceNumberProperty(v, this._min); @@ -151,6 +155,9 @@ export class MdSlider extends _MdSliderMixinBase this.value = this._min; } this._percent = this._calculatePercentage(this._value); + + // Since this also modifies the percentage, we need to let the change detection know. + this._changeDetectorRef.markForCheck(); } private _min: number = 0; @@ -163,6 +170,9 @@ export class MdSlider extends _MdSliderMixinBase if (this._step % 1 !== 0) { this._roundLabelTo = this._step.toString().split('.').pop()!.length; } + + // Since this could modify the label, we need to notify the change detection. + this._changeDetectorRef.markForCheck(); } private _step: number = 1; @@ -209,15 +219,22 @@ export class MdSlider extends _MdSliderMixinBase return this._value; } set value(v: number | null) { - this._value = coerceNumberProperty(v, this._value || 0); - this._percent = this._calculatePercentage(this._value); + if (v !== this._value) { + this._value = coerceNumberProperty(v, this._value || 0); + this._percent = this._calculatePercentage(this._value); + + // Since this also modifies the percentage, we need to let the change detection know. + this._changeDetectorRef.markForCheck(); + } } private _value: number | null = null; /** Whether the slider is vertical. */ @Input() get vertical() { return this._vertical; } - set vertical(value: any) { this._vertical = coerceBooleanProperty(value); } + set vertical(value: any) { + this._vertical = coerceBooleanProperty(value); + } private _vertical = false; @Input() color: 'primary' | 'accent' | 'warn' = 'accent'; @@ -392,9 +409,11 @@ export class MdSlider extends _MdSliderMixinBase constructor(renderer: Renderer2, private _elementRef: ElementRef, private _focusOriginMonitor: FocusOriginMonitor, + private _changeDetectorRef: ChangeDetectorRef, @Optional() private _dir: Directionality) { super(); - this._focusOriginMonitor.monitor(this._elementRef.nativeElement, renderer, true) + this._focusOriginMonitor + .monitor(this._elementRef.nativeElement, renderer, true) .subscribe((origin: FocusOrigin) => this._isActive = !!origin && origin !== 'keyboard'); this._renderer = new SliderRenderer(this._elementRef); } @@ -512,6 +531,8 @@ export class MdSlider extends _MdSliderMixinBase // it. return; } + this._emitInputEvent(); + this._emitValueIfChanged(); this._isSliding = true; event.preventDefault(); @@ -524,8 +545,6 @@ export class MdSlider extends _MdSliderMixinBase /** Increments the slider by the given number of steps (negative number decrements). */ private _increment(numSteps: number) { this.value = this._clamp((this.value || 0) + this.step * numSteps, this.min, this.max); - this._emitInputEvent(); - this._emitValueIfChanged(); } /** Calculate the new value from the new physical location. The value will always be snapped. */ diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index 882a82191ab1..ff3232a5ca0e 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -47,7 +47,7 @@ describe('MdSnackBar', () => { afterEach(() => { overlayContainerElement.innerHTML = ''; - liveAnnouncer._removeLiveElement(); + liveAnnouncer.ngOnDestroy(); }); beforeEach(() => { @@ -396,7 +396,7 @@ describe('MdSnackBar with parent MdSnackBar', () => { afterEach(() => { overlayContainerElement.innerHTML = ''; - liveAnnouncer._removeLiveElement(); + liveAnnouncer.ngOnDestroy(); }); it('should close snackBars opened by parent when opening from child MdSnackBar', fakeAsync(() => { diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts index a2692b157c12..88d0297e3b18 100644 --- a/src/lib/tooltip/tooltip.ts +++ b/src/lib/tooltip/tooltip.ts @@ -17,6 +17,7 @@ import { OnDestroy, Renderer2, ChangeDetectorRef, + ChangeDetectionStrategy, ViewEncapsulation, } from '@angular/core'; import { @@ -378,6 +379,7 @@ export type TooltipVisibility = 'initial' | 'visible' | 'hidden'; templateUrl: 'tooltip.html', styleUrls: ['tooltip.css'], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, animations: [ trigger('state', [ state('void', style({transform: 'scale(0)'})), diff --git a/src/material-examples/autocomplete-overview/autocomplete-overview-example.ts b/src/material-examples/autocomplete-overview/autocomplete-overview-example.ts index 73d2e3cb208f..60c3092451a5 100644 --- a/src/material-examples/autocomplete-overview/autocomplete-overview-example.ts +++ b/src/material-examples/autocomplete-overview/autocomplete-overview-example.ts @@ -5,6 +5,9 @@ import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/map'; +/** + * @title Basic autocomplete + */ @Component({ selector: 'autocomplete-overview-example', templateUrl: 'autocomplete-overview-example.html', diff --git a/src/material-examples/button-overview/button-overview-example.ts b/src/material-examples/button-overview/button-overview-example.ts index d7839b7a7051..9dbde6873eba 100644 --- a/src/material-examples/button-overview/button-overview-example.ts +++ b/src/material-examples/button-overview/button-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic buttons + */ @Component({ selector: 'button-overview-example', templateUrl: 'button-overview-example.html', diff --git a/src/material-examples/button-toggle-exclusive/button-toggle-exclusive-example.ts b/src/material-examples/button-toggle-exclusive/button-toggle-exclusive-example.ts index 9a505de5c3ce..efe1c7ccc4a0 100644 --- a/src/material-examples/button-toggle-exclusive/button-toggle-exclusive-example.ts +++ b/src/material-examples/button-toggle-exclusive/button-toggle-exclusive-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Exclusive selection + */ @Component({ selector: 'button-toggle-exclusive-example', templateUrl: 'button-toggle-exclusive-example.html', diff --git a/src/material-examples/button-toggle-overview/button-toggle-overview-example.ts b/src/material-examples/button-toggle-overview/button-toggle-overview-example.ts index 55fc47168564..c8afb4373a25 100644 --- a/src/material-examples/button-toggle-overview/button-toggle-overview-example.ts +++ b/src/material-examples/button-toggle-overview/button-toggle-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic button-toggles + */ @Component({ selector: 'button-toggle-overview-example', templateUrl: 'button-toggle-overview-example.html', diff --git a/src/material-examples/button-types/button-types-example.ts b/src/material-examples/button-types/button-types-example.ts index e0becac38f6c..525729b2cc08 100644 --- a/src/material-examples/button-types/button-types-example.ts +++ b/src/material-examples/button-types/button-types-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Button varieties + */ @Component({ selector: 'button-types-example', templateUrl: 'button-types-example.html', diff --git a/src/material-examples/card-fancy/card-fancy-example.ts b/src/material-examples/card-fancy/card-fancy-example.ts index 0b0458c089c8..92826ad08569 100644 --- a/src/material-examples/card-fancy/card-fancy-example.ts +++ b/src/material-examples/card-fancy/card-fancy-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Card with multiple sections + */ @Component({ selector: 'card-fancy-example', templateUrl: 'card-fancy-example.html', diff --git a/src/material-examples/card-overview/card-overview-example.ts b/src/material-examples/card-overview/card-overview-example.ts index 09ea32a3a50b..f87f9401fe72 100644 --- a/src/material-examples/card-overview/card-overview-example.ts +++ b/src/material-examples/card-overview/card-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic cards + */ @Component({ selector: 'card-overview-example', templateUrl: 'card-overview-example.html', diff --git a/src/material-examples/cdk-table-basic/cdk-table-basic-example.ts b/src/material-examples/cdk-table-basic/cdk-table-basic-example.ts index f9b183f9e348..26104dc1f951 100644 --- a/src/material-examples/cdk-table-basic/cdk-table-basic-example.ts +++ b/src/material-examples/cdk-table-basic/cdk-table-basic-example.ts @@ -6,6 +6,9 @@ import 'rxjs/add/operator/startWith'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/map'; +/** + * @title Basic CDK data-table + */ @Component({ selector: 'cdk-table-basic-example', styleUrls: ['cdk-table-basic-example.css'], diff --git a/src/material-examples/checkbox-configurable/checkbox-configurable-example.ts b/src/material-examples/checkbox-configurable/checkbox-configurable-example.ts index eefb5db799e2..f4bdf570a31f 100644 --- a/src/material-examples/checkbox-configurable/checkbox-configurable-example.ts +++ b/src/material-examples/checkbox-configurable/checkbox-configurable-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Configurable checkbox + */ @Component({ selector: 'checkbox-configurable-example', templateUrl: 'checkbox-configurable-example.html', diff --git a/src/material-examples/checkbox-overview/checkbox-overview-example.ts b/src/material-examples/checkbox-overview/checkbox-overview-example.ts index 6df17b220d42..2c8225824eba 100644 --- a/src/material-examples/checkbox-overview/checkbox-overview-example.ts +++ b/src/material-examples/checkbox-overview/checkbox-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic checkboxes + */ @Component({ selector: 'checkbox-overview-example', templateUrl: 'checkbox-overview-example.html', diff --git a/src/material-examples/chips-overview/chips-overview-example.ts b/src/material-examples/chips-overview/chips-overview-example.ts index 83e0fd4da4ce..5db3ceb1ed66 100644 --- a/src/material-examples/chips-overview/chips-overview-example.ts +++ b/src/material-examples/chips-overview/chips-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic chips + */ @Component({ selector: 'chips-overview-example', templateUrl: 'chips-overview-example.html', diff --git a/src/material-examples/chips-stacked/chips-stacked-example.ts b/src/material-examples/chips-stacked/chips-stacked-example.ts index 278332901591..816c226494b9 100644 --- a/src/material-examples/chips-stacked/chips-stacked-example.ts +++ b/src/material-examples/chips-stacked/chips-stacked-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Stacked chips + */ @Component({ selector: 'chips-stacked-example', templateUrl: 'chips-stacked-example.html', diff --git a/src/material-examples/datepicker-api/datepicker-api-example.ts b/src/material-examples/datepicker-api/datepicker-api-example.ts index 6eeea21efb16..cf4ce8ad6778 100644 --- a/src/material-examples/datepicker-api/datepicker-api-example.ts +++ b/src/material-examples/datepicker-api/datepicker-api-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Datepicker API + */ @Component({ selector: 'datepicker-api-example', templateUrl: 'datepicker-api-example.html', diff --git a/src/material-examples/datepicker-filter/datepicker-filter-example.ts b/src/material-examples/datepicker-filter/datepicker-filter-example.ts index 94c72d000d8b..a2fe6d13f86b 100644 --- a/src/material-examples/datepicker-filter/datepicker-filter-example.ts +++ b/src/material-examples/datepicker-filter/datepicker-filter-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Datepicker Filter + */ @Component({ selector: 'datepicker-filter-example', templateUrl: 'datepicker-filter-example.html', diff --git a/src/material-examples/datepicker-min-max/datepicker-min-max-example.ts b/src/material-examples/datepicker-min-max/datepicker-min-max-example.ts index cee80389f85e..fea707bb1fcb 100644 --- a/src/material-examples/datepicker-min-max/datepicker-min-max-example.ts +++ b/src/material-examples/datepicker-min-max/datepicker-min-max-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Datepicker Min Max + */ @Component({ selector: 'datepicker-min-max-example', templateUrl: 'datepicker-min-max-example.html', diff --git a/src/material-examples/datepicker-overview/datepicker-overview-example.ts b/src/material-examples/datepicker-overview/datepicker-overview-example.ts index ffc7464af1d1..b9dde7ca1d3b 100644 --- a/src/material-examples/datepicker-overview/datepicker-overview-example.ts +++ b/src/material-examples/datepicker-overview/datepicker-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic datepicker + */ @Component({ selector: 'datepicker-overview-example', templateUrl: 'datepicker-overview-example.html', diff --git a/src/material-examples/datepicker-start-view/datepicker-start-view-example.ts b/src/material-examples/datepicker-start-view/datepicker-start-view-example.ts index 89054330b155..cb76dba2a7a2 100644 --- a/src/material-examples/datepicker-start-view/datepicker-start-view-example.ts +++ b/src/material-examples/datepicker-start-view/datepicker-start-view-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Datepicker start date + */ @Component({ selector: 'datepicker-start-view-example', templateUrl: 'datepicker-start-view-example.html', diff --git a/src/material-examples/datepicker-touch/datepicker-touch-example.ts b/src/material-examples/datepicker-touch/datepicker-touch-example.ts index 22d2206baee0..df5daa92f54e 100644 --- a/src/material-examples/datepicker-touch/datepicker-touch-example.ts +++ b/src/material-examples/datepicker-touch/datepicker-touch-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Datepicker Touch + */ @Component({ selector: 'datepicker-touch-example', templateUrl: 'datepicker-touch-example.html', diff --git a/src/material-examples/dialog-elements/dialog-elements-example.ts b/src/material-examples/dialog-elements/dialog-elements-example.ts index 733a56f07191..37f3703e3aa4 100644 --- a/src/material-examples/dialog-elements/dialog-elements-example.ts +++ b/src/material-examples/dialog-elements/dialog-elements-example.ts @@ -1,7 +1,9 @@ import {Component} from '@angular/core'; import {MdDialog} from '@angular/material'; - +/** + * @title Dialog elements + */ @Component({ selector: 'dialog-elements-example', templateUrl: 'dialog-elements-example.html', diff --git a/src/material-examples/dialog-overview/dialog-overview-example.ts b/src/material-examples/dialog-overview/dialog-overview-example.ts index 0613b400e7c6..6e88f61cf480 100644 --- a/src/material-examples/dialog-overview/dialog-overview-example.ts +++ b/src/material-examples/dialog-overview/dialog-overview-example.ts @@ -1,7 +1,9 @@ import {Component} from '@angular/core'; import {MdDialog} from '@angular/material'; - +/** + * @title Dialog Overview + */ @Component({ selector: 'dialog-overview-example', templateUrl: 'dialog-overview-example.html', diff --git a/src/material-examples/dialog-result/dialog-result-example.ts b/src/material-examples/dialog-result/dialog-result-example.ts index 19ac72220241..8f2cc8acac17 100644 --- a/src/material-examples/dialog-result/dialog-result-example.ts +++ b/src/material-examples/dialog-result/dialog-result-example.ts @@ -1,7 +1,9 @@ import {Component} from '@angular/core'; import {MdDialog, MdDialogRef} from '@angular/material'; - +/** + * @title Dialog with a result + */ @Component({ selector: 'dialog-result-example', templateUrl: 'dialog-result-example.html', diff --git a/src/material-examples/example-module.ts b/src/material-examples/example-module.ts index 93a78bf6d02d..69dc3316cf2d 100644 --- a/src/material-examples/example-module.ts +++ b/src/material-examples/example-module.ts @@ -1,120 +1,88 @@ + +/* tslint:disable */ +/** DO NOT MANUALLY EDIT THIS FILE, IT IS GENERATED VIA GULP 'build-examples-module' */ import {NgModule} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {CommonModule} from '@angular/common'; +import {ExampleMaterialModule} from './material-module'; + +export interface LiveExample { + title: string; + component: any; + additionalFiles?: string[]; + selectorName?: string; +} + import {AutocompleteOverviewExample} from './autocomplete-overview/autocomplete-overview-example'; import {ButtonOverviewExample} from './button-overview/button-overview-example'; +import {ButtonToggleExclusiveExample} from './button-toggle-exclusive/button-toggle-exclusive-example'; +import {ButtonToggleOverviewExample} from './button-toggle-overview/button-toggle-overview-example'; import {ButtonTypesExample} from './button-types/button-types-example'; -import {CheckboxOverviewExample} from './checkbox-overview/checkbox-overview-example'; -import {SliderConfigurableExample} from './slider-configurable/slider-configurable-example'; -import {TabsOverviewExample} from './tabs-overview/tabs-overview-example'; -import { - PizzaPartyComponent, - SnackBarComponentExample -} from './snack-bar-component/snack-bar-component-example'; -import { - ProgressBarConfigurableExample -} from './progress-bar-configurable/progress-bar-configurable-example'; -import { - DialogOverviewExample, - DialogOverviewExampleDialog -} from './dialog-overview/dialog-overview-example'; -import {RadioNgModelExample} from './radio-ng-model/radio-ng-model-example'; import {CardFancyExample} from './card-fancy/card-fancy-example'; -import {ToolbarOverviewExample} from './toolbar-overview/toolbar-overview-example'; -import {ToolbarMultirowExample} from './toolbar-multirow/toolbar-multirow-example'; -import {MenuIconsExample} from './menu-icons/menu-icons-example'; -import {GridListDynamicExample} from './grid-list-dynamic/grid-list-dynamic-example'; -import {IconOverviewExample} from './icon-overview/icon-overview-example'; -import {ProgressBarOverviewExample} from './progress-bar-overview/progress-bar-overview-example'; -import {SlideToggleOverviewExample} from './slide-toggle-overview/slide-toggle-overview-example'; -import {SlideToggleFormsExample} from './slide-toggle-forms/slide-toggle-forms-example'; -import {MenuOverviewExample} from './menu-overview/menu-overview-example'; -import {CheckboxConfigurableExample} from './checkbox-configurable/checkbox-configurable-example'; -import { - ButtonToggleExclusiveExample -} from './button-toggle-exclusive/button-toggle-exclusive-example'; -import {ListSectionsExample} from './list-sections/list-sections-example'; -import {SnackBarOverviewExample} from './snack-bar-overview/snack-bar-overview-example'; -import { - DialogResultExample, - DialogResultExampleDialog -} from './dialog-result/dialog-result-example'; -import { - DialogElementsExample, - DialogElementsExampleDialog -} from './dialog-elements/dialog-elements-example'; -import {TooltipOverviewExample} from './tooltip-overview/tooltip-overview-example'; -import {ButtonToggleOverviewExample} from './button-toggle-overview/button-toggle-overview-example'; -import {GridListOverviewExample} from './grid-list-overview/grid-list-overview-example'; -import {TooltipPositionExample} from './tooltip-position/tooltip-position-example'; -import { - ProgressSpinnerConfigurableExample -} from './progress-spinner-configurable/progress-spinner-configurable-example'; -import {ListOverviewExample} from './list-overview/list-overview-example'; -import {SliderOverviewExample} from './slider-overview/slider-overview-example'; -import { - SlideToggleConfigurableExample -} from './slide-toggle-configurable/slide-toggle-configurable-example'; -import {IconSvgExample} from './icon-svg-example/icon-svg-example'; -import {SidenavFabExample} from './sidenav-fab/sidenav-fab-example'; import {CardOverviewExample} from './card-overview/card-overview-example'; -import { - ProgressSpinnerOverviewExample -} from './progress-spinner-overview/progress-spinner-overview-example'; -import {TabsTemplateLabelExample} from './tabs-template-label/tabs-template-label-example'; -import {RadioOverviewExample} from './radio-overview/radio-overview-example'; -import {SidenavOverviewExample} from './sidenav-overview/sidenav-overview-example'; -import {SelectOverviewExample} from './select-overview/select-overview-example'; +import {CdkTableBasicExample} from './cdk-table-basic/cdk-table-basic-example'; +import {CheckboxConfigurableExample} from './checkbox-configurable/checkbox-configurable-example'; +import {CheckboxOverviewExample} from './checkbox-overview/checkbox-overview-example'; import {ChipsOverviewExample} from './chips-overview/chips-overview-example'; import {ChipsStackedExample} from './chips-stacked/chips-stacked-example'; -import {SelectFormExample} from './select-form/select-form-example'; -import {PaginatorOverviewExample} from './paginator-overview/paginator-overview-example'; +import {DatepickerApiExample} from './datepicker-api/datepicker-api-example'; +import {DatepickerFilterExample} from './datepicker-filter/datepicker-filter-example'; +import {DatepickerMinMaxExample} from './datepicker-min-max/datepicker-min-max-example'; import {DatepickerOverviewExample} from './datepicker-overview/datepicker-overview-example'; -import { - PaginatorConfigurableExample -} from './paginator-configurable/paginator-configurable-example'; -import {InputOverviewExample} from './input-overview/input-overview-example'; -import {InputErrorsExample} from './input-errors/input-errors-example'; -import {InputFormExample} from './input-form/input-form-example'; -import {InputPrefixSuffixExample} from './input-prefix-suffix/input-prefix-suffix-example'; -import {InputHintExample} from './input-hint/input-hint-example'; import {AutocompleteSimpleExample} from './autocomplete-simple/autocomplete-simple-example'; import {AutocompleteFilterExample} from './autocomplete-filter/autocomplete-filter-example'; import {AutocompleteDisplayExample} from './autocomplete-display/autocomplete-display-example'; import {DatepickerStartViewExample} from './datepicker-start-view/datepicker-start-view-example'; -import {DatepickerMinMaxExample} from './datepicker-min-max/datepicker-min-max-example'; -import {DatepickerFilterExample} from './datepicker-filter/datepicker-filter-example'; import {DatepickerTouchExample} from './datepicker-touch/datepicker-touch-example'; -import {DatepickerApiExample} from './datepicker-api/datepicker-api-example'; +import {DialogElementsExampleDialog,DialogElementsExample} from './dialog-elements/dialog-elements-example'; +import {DialogOverviewExampleDialog,DialogOverviewExample} from './dialog-overview/dialog-overview-example'; +import {DialogResultExampleDialog,DialogResultExample} from './dialog-result/dialog-result-example'; +import {GridListDynamicExample} from './grid-list-dynamic/grid-list-dynamic-example'; +import {GridListOverviewExample} from './grid-list-overview/grid-list-overview-example'; +import {IconOverviewExample} from './icon-overview/icon-overview-example'; +import {IconSvgExample} from './icon-svg-example/icon-svg-example'; import {InputClearableExample} from './input-clearable/input-clearable-example'; -import { - MdAutocompleteModule, MdButtonModule, MdButtonToggleModule, MdCardModule, MdCheckboxModule, - MdChipsModule, MdDatepickerModule, MdDialogModule, MdGridListModule, MdIconModule, MdInputModule, - MdListModule, MdMenuModule, MdPaginatorModule, MdProgressBarModule, MdProgressSpinnerModule, - MdRadioModule, MdSelectModule, MdSidenavModule, MdSliderModule, MdSlideToggleModule, - MdSnackBarModule, MdSortModule, MdTableModule, MdTabsModule, MdToolbarModule, MdTooltipModule -} from '@angular/material'; -import {CdkTableModule} from '@angular/cdk'; +import {InputErrorsExample} from './input-errors/input-errors-example'; +import {InputFormExample} from './input-form/input-form-example'; +import {InputHintExample} from './input-hint/input-hint-example'; +import {InputOverviewExample} from './input-overview/input-overview-example'; +import {InputPrefixSuffixExample} from './input-prefix-suffix/input-prefix-suffix-example'; +import {ListOverviewExample} from './list-overview/list-overview-example'; +import {ListSectionsExample} from './list-sections/list-sections-example'; +import {MenuIconsExample} from './menu-icons/menu-icons-example'; +import {MenuOverviewExample} from './menu-overview/menu-overview-example'; +import {PaginatorConfigurableExample} from './paginator-configurable/paginator-configurable-example'; +import {PaginatorOverviewExample} from './paginator-overview/paginator-overview-example'; +import {ProgressBarConfigurableExample} from './progress-bar-configurable/progress-bar-configurable-example'; +import {ProgressBarOverviewExample} from './progress-bar-overview/progress-bar-overview-example'; +import {ProgressSpinnerConfigurableExample} from './progress-spinner-configurable/progress-spinner-configurable-example'; +import {ProgressSpinnerOverviewExample} from './progress-spinner-overview/progress-spinner-overview-example'; +import {RadioNgModelExample} from './radio-ng-model/radio-ng-model-example'; +import {RadioOverviewExample} from './radio-overview/radio-overview-example'; +import {SelectFormExample} from './select-form/select-form-example'; +import {SelectOverviewExample} from './select-overview/select-overview-example'; +import {SidenavFabExample} from './sidenav-fab/sidenav-fab-example'; +import {SidenavOverviewExample} from './sidenav-overview/sidenav-overview-example'; +import {SlideToggleConfigurableExample} from './slide-toggle-configurable/slide-toggle-configurable-example'; +import {SlideToggleFormsExample} from './slide-toggle-forms/slide-toggle-forms-example'; +import {SlideToggleOverviewExample} from './slide-toggle-overview/slide-toggle-overview-example'; +import {SliderConfigurableExample} from './slider-configurable/slider-configurable-example'; +import {SliderOverviewExample} from './slider-overview/slider-overview-example'; +import {PizzaPartyComponent,SnackBarComponentExample} from './snack-bar-component/snack-bar-component-example'; +import {SnackBarOverviewExample} from './snack-bar-overview/snack-bar-overview-example'; +import {SortOverviewExample} from './sort-overview/sort-overview-example'; +import {TableBasicExample} from './table-basic/table-basic-example'; +import {TableFilteringExample} from './table-filtering/table-filtering-example'; import {TableOverviewExample} from './table-overview/table-overview-example'; import {TablePaginationExample} from './table-pagination/table-pagination-example'; -import {TableBasicExample} from './table-basic/table-basic-example'; import {TableSortingExample} from './table-sorting/table-sorting-example'; -import {TableFilteringExample} from './table-filtering/table-filtering-example'; -import {CdkTableBasicExample} from './cdk-table-basic/cdk-table-basic-example'; -import {SortOverviewExample} from './sort-overview/sort-overview-example'; - -export interface LiveExample { - title: string; - component: any; - additionalFiles?: string[]; - selectorName?: string; -} +import {TabsOverviewExample} from './tabs-overview/tabs-overview-example'; +import {TabsTemplateLabelExample} from './tabs-template-label/tabs-template-label-example'; +import {ToolbarMultirowExample} from './toolbar-multirow/toolbar-multirow-example'; +import {ToolbarOverviewExample} from './toolbar-overview/toolbar-overview-example'; +import {TooltipOverviewExample} from './tooltip-overview/tooltip-overview-example'; +import {TooltipPositionExample} from './tooltip-position/tooltip-position-example'; -/** - * The list of example components. - * Key is the example name which will be used in `material-docs-example="key"`. - * Value is the component. - */ export const EXAMPLE_COMPONENTS = { 'autocomplete-overview': {title: 'Basic autocomplete', component: AutocompleteOverviewExample}, 'autocomplete-simple': {title: 'Simple autocomplete', component: AutocompleteSimpleExample}, @@ -125,148 +93,398 @@ export const EXAMPLE_COMPONENTS = { }, 'button-overview': {title: 'Basic buttons', component: ButtonOverviewExample}, 'button-types': {title: 'Button varieties', component: ButtonTypesExample}, + 'button-overview': { + title: 'Basic buttons', + component: ButtonOverviewExample, + additionalFiles: null, + selectorName: null + }, 'button-toggle-exclusive': { title: 'Exclusive selection', - component: ButtonToggleExclusiveExample - }, - 'button-toggle-overview': {title: 'Basic button-toggles', component: ButtonToggleOverviewExample}, - 'chips-overview': {title: 'Basic chips', component: ChipsOverviewExample}, - 'cdk-table-basic': {title: 'Basic CDK data-table', component: CdkTableBasicExample}, - 'chips-stacked': {title: 'Stacked chips', component: ChipsStackedExample}, - 'card-fancy': {title: 'Card with multiple sections', component: CardFancyExample}, - 'card-overview': {title: 'Basic cards', component: CardOverviewExample}, - 'checkbox-configurable': {title: 'Configurable checkbox', component: CheckboxConfigurableExample}, - 'checkbox-overview': {title: 'Basic checkboxes', component: CheckboxOverviewExample}, - 'datepicker-overview': {title: 'Basic datepicker', component: DatepickerOverviewExample}, - 'datepicker-start-view': {title: 'Start View', component: DatepickerStartViewExample}, - 'datepicker-min-max': {title: 'Min/Max Validation', component: DatepickerMinMaxExample}, - 'datepicker-filter': {title: 'Filter Validation', component: DatepickerFilterExample}, - 'datepicker-touch': {title: 'Touch', component: DatepickerTouchExample}, - 'datepicker-api': {title: 'API', component: DatepickerApiExample}, + component: ButtonToggleExclusiveExample, + additionalFiles: null, + selectorName: null + }, + 'button-toggle-overview': { + title: 'Basic button-toggles', + component: ButtonToggleOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'button-types': { + title: 'Button varieties', + component: ButtonTypesExample, + additionalFiles: null, + selectorName: null + }, + 'card-fancy': { + title: 'Card with multiple sections', + component: CardFancyExample, + additionalFiles: null, + selectorName: null + }, + 'card-overview': { + title: 'Basic cards', + component: CardOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'cdk-table-basic': { + title: 'Basic CDK data-table', + component: CdkTableBasicExample, + additionalFiles: null, + selectorName: null + }, + 'checkbox-configurable': { + title: 'Configurable checkbox', + component: CheckboxConfigurableExample, + additionalFiles: null, + selectorName: null + }, + 'checkbox-overview': { + title: 'Basic checkboxes', + component: CheckboxOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'chips-overview': { + title: 'Basic chips', + component: ChipsOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'chips-stacked': { + title: 'Stacked chips', + component: ChipsStackedExample, + additionalFiles: null, + selectorName: null + }, + 'datepicker-api': { + title: 'Datepicker API', + component: DatepickerApiExample, + additionalFiles: null, + selectorName: null + }, + 'datepicker-filter': { + title: 'Datepicker Filter', + component: DatepickerFilterExample, + additionalFiles: null, + selectorName: null + }, + 'datepicker-min-max': { + title: 'Datepicker Min Max', + component: DatepickerMinMaxExample, + additionalFiles: null, + selectorName: null + }, + 'datepicker-overview': { + title: 'Basic datepicker', + component: DatepickerOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'datepicker-start-view': { + title: 'Datepicker start date', + component: DatepickerStartViewExample, + additionalFiles: null, + selectorName: null + }, + 'datepicker-touch': { + title: 'Datepicker Touch', + component: DatepickerTouchExample, + additionalFiles: null, + selectorName: null + }, + 'dialog-elements': { + title: 'Dialog elements', + component: DialogElementsExample, + additionalFiles: ["dialog-elements-example-dialog.html"], + selectorName: 'DialogElementsExample, DialogElementsExampleDialog' + }, 'dialog-overview': { - title: 'Basic dialog', + title: 'Dialog Overview', component: DialogOverviewExample, - additionalFiles: ['dialog-overview-example-dialog.html'], - selectorName: 'DialogOverviewExample, DialogOverviewExampleDialog', + additionalFiles: ["dialog-overview-example-dialog.html"], + selectorName: 'DialogOverviewExample, DialogOverviewExampleDialog' }, 'dialog-result': { title: 'Dialog with a result', component: DialogResultExample, - additionalFiles: ['dialog-result-example-dialog.html'], - selectorName: 'DialogResultExample, DialogResultExampleDialog', + additionalFiles: ["dialog-result-example-dialog.html"], + selectorName: 'DialogResultExample, DialogResultExampleDialog' + }, + 'grid-list-dynamic': { + title: 'Dynamic grid-list', + component: GridListDynamicExample, + additionalFiles: null, + selectorName: null + }, + 'grid-list-overview': { + title: 'Basic grid-list', + component: GridListOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'icon-overview': { + title: 'Basic icons', + component: IconOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'icon-svg': { + title: 'SVG icons', + component: IconSvgExample, + additionalFiles: null, + selectorName: null + }, + 'input-clearable': { + title: 'Input Clearable', + component: InputClearableExample, + additionalFiles: null, + selectorName: null + }, + 'input-errors': { + title: 'Input Errors', + component: InputErrorsExample, + additionalFiles: null, + selectorName: null + }, + 'input-form': { + title: 'Inputs in a form', + component: InputFormExample, + additionalFiles: null, + selectorName: null + }, + 'input-hint': { + title: 'Input hints', + component: InputHintExample, + additionalFiles: null, + selectorName: null + }, + 'input-overview': { + title: 'Basic Inputs', + component: InputOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'input-prefix-suffix': { + title: 'Input Prefixes and Suffixes', + component: InputPrefixSuffixExample, + additionalFiles: null, + selectorName: null + }, + 'list-overview': { + title: 'Basic list', + component: ListOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'list-sections': { + title: 'List with sections', + component: ListSectionsExample, + additionalFiles: null, + selectorName: null + }, + 'menu-icons': { + title: 'Menu with icons', + component: MenuIconsExample, + additionalFiles: null, + selectorName: null + }, + 'menu-overview': { + title: 'Basic menu', + component: MenuOverviewExample, + additionalFiles: null, + selectorName: null }, - 'dialog-elements': { - title: 'Dialog elements', - component: DialogElementsExample, - additionalFiles: ['dialog-elements-example-dialog.html'], - selectorName: 'DialogElementsExample, DialogElementsExampleDialog', - }, - 'grid-list-dynamic': {title: 'Dynamic grid-list', component: GridListDynamicExample}, - 'grid-list-overview': {title: 'Basic grid-list', component: GridListOverviewExample}, - 'icon-overview': {title: 'Basic icons', component: IconOverviewExample}, - 'icon-svg': {title: 'SVG icons', component: IconSvgExample}, - 'input-clearable': {title: 'Input with clear button', component: InputClearableExample}, - 'input-form': {title: 'Inputs in a form', component: InputFormExample}, - 'input-overview': {title: 'Basic inputs', component: InputOverviewExample}, - 'input-errors': {title: 'Input Errors', component: InputErrorsExample}, - 'input-prefix-suffix': {title: 'Input Prefixes/Suffixes', component: InputPrefixSuffixExample}, - 'input-hint': {title: 'Input Hint', component: InputHintExample}, - 'list-overview': {title: 'Basic list', component: ListOverviewExample}, - 'list-sections': {title: 'List with sections', component: ListSectionsExample}, - 'menu-icons': {title: 'Menu with icons', component: MenuIconsExample}, - 'menu-overview': {title: 'Basic menu', component: MenuOverviewExample}, - 'paginator-overview': {title: 'Paginator', component: PaginatorOverviewExample}, 'paginator-configurable': { title: 'Configurable paginator', - component: PaginatorConfigurableExample + component: PaginatorConfigurableExample, + additionalFiles: null, + selectorName: null + }, + 'paginator-overview': { + title: 'Paginator', + component: PaginatorOverviewExample, + additionalFiles: null, + selectorName: null }, 'progress-bar-configurable': { title: 'Configurable progress-bar', - component: ProgressBarConfigurableExample + component: ProgressBarConfigurableExample, + additionalFiles: null, + selectorName: null + }, + 'progress-bar-overview': { + title: 'Basic progress-bar', + component: ProgressBarOverviewExample, + additionalFiles: null, + selectorName: null }, - 'progress-bar-overview': {title: 'Basic progress-bar', component: ProgressBarOverviewExample}, 'progress-spinner-configurable': { - title: 'Configurable progress-bar', - component: ProgressSpinnerConfigurableExample + title: 'Configurable progress spinner', + component: ProgressSpinnerConfigurableExample, + additionalFiles: null, + selectorName: null }, 'progress-spinner-overview': { title: 'Basic progress-spinner', - component: ProgressSpinnerOverviewExample - }, - 'radio-ng-model': {title: 'Radios with ngModel', component: RadioNgModelExample}, - 'radio-overview': {title: 'Basic radios', component: RadioOverviewExample}, - 'select-overview': {title: 'Basic select', component: SelectOverviewExample}, - 'select-form': {title: 'Select in a form', component: SelectFormExample}, - 'sidenav-fab': {title: 'Sidenav with a FAB', component: SidenavFabExample}, - 'sidenav-overview': {title: 'Basic sidenav', component: SidenavOverviewExample}, - 'slider-configurable': {title: 'Configurable slider', component: SliderConfigurableExample}, - 'slider-overview': {title: 'Basic slider', component: SliderOverviewExample}, + component: ProgressSpinnerOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'radio-ng-model': { + title: 'Radios with ngModel', + component: RadioNgModelExample, + additionalFiles: null, + selectorName: null + }, + 'radio-overview': { + title: 'Basic radios', + component: RadioOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'select-form': { + title: 'Select in a form', + component: SelectFormExample, + additionalFiles: null, + selectorName: null + }, + 'select-overview': { + title: 'Basic select', + component: SelectOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'sidenav-fab': { + title: 'Sidenav with a FAB', + component: SidenavFabExample, + additionalFiles: null, + selectorName: null + }, + 'sidenav-overview': { + title: 'Basic sidenav', + component: SidenavOverviewExample, + additionalFiles: null, + selectorName: null + }, 'slide-toggle-configurable': { title: 'Configurable slide-toggle', - component: SlideToggleConfigurableExample + component: SlideToggleConfigurableExample, + additionalFiles: null, + selectorName: null + }, + 'slide-toggle-forms': { + title: 'Slide-toggle with forms', + component: SlideToggleFormsExample, + additionalFiles: null, + selectorName: null + }, + 'slide-toggle-overview': { + title: 'Basic slide-toggles', + component: SlideToggleOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'slider-configurable': { + title: 'Configurable slider', + component: SliderConfigurableExample, + additionalFiles: null, + selectorName: null + }, + 'slider-overview': { + title: 'Basic slider', + component: SliderOverviewExample, + additionalFiles: null, + selectorName: null }, - 'slide-toggle-forms': {title: 'Slide-toggle with forms', component: SlideToggleFormsExample}, - 'slide-toggle-overview': {title: 'Basic slide-toggles', component: SlideToggleOverviewExample}, - 'sort-overview': {title: 'Sorting overview', component: SortOverviewExample}, 'snack-bar-component': { title: 'Snack-bar with a custom component', - component: SnackBarComponentExample - }, - 'snack-bar-overview': {title: 'Basic snack-bar', component: SnackBarOverviewExample}, - 'table-overview': {title: 'Feature-rich data table', component: TableOverviewExample}, - 'table-pagination': {title: 'Table with pagination', component: TablePaginationExample}, - 'table-sorting': {title: 'Table with sorting', component: TableSortingExample}, - 'table-filtering': {title: 'Table with filtering', component: TableFilteringExample}, - 'table-basic': {title: 'Basic table', component: TableBasicExample}, - 'tabs-overview': {title: 'Basic tabs', component: TabsOverviewExample}, - 'tabs-template-label': {title: 'Coming soon!', component: TabsTemplateLabelExample}, - 'toolbar-multirow': {title: 'Multi-row toolbar', component: ToolbarMultirowExample}, - 'toolbar-overview': {title: 'basic toolbar', component: ToolbarOverviewExample}, - 'tooltip-overview': {title: 'Basic tooltip', component: TooltipOverviewExample}, - 'tooltip-position': {title: 'Tooltip with custom position', component: TooltipPositionExample}, + component: SnackBarComponentExample, + additionalFiles: ["snack-bar-component-example-snack.html"], + selectorName: 'SnackBarComponentExample, PizzaPartyComponent' + }, + 'snack-bar-overview': { + title: 'Basic snack-bar', + component: SnackBarOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'sort-overview': { + title: 'Sorting overview', + component: SortOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'table-basic': { + title: 'Basic table', + component: TableBasicExample, + additionalFiles: null, + selectorName: null + }, + 'table-filtering': { + title: 'Table with filtering', + component: TableFilteringExample, + additionalFiles: null, + selectorName: null + }, + 'table-overview': { + title: 'Feature-rich data table', + component: TableOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'table-pagination': { + title: 'Table with pagination', + component: TablePaginationExample, + additionalFiles: null, + selectorName: null + }, + 'table-sorting': { + title: 'Table with sorting', + component: TableSortingExample, + additionalFiles: null, + selectorName: null + }, + 'tabs-overview': { + title: 'Basic tabs', + component: TabsOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'tabs-template-label': { + title: 'Coming soon!', + component: TabsTemplateLabelExample, + additionalFiles: null, + selectorName: null + }, + 'toolbar-multirow': { + title: 'Multi-row toolbar', + component: ToolbarMultirowExample, + additionalFiles: null, + selectorName: null + }, + 'toolbar-overview': { + title: 'Basic toolbar', + component: ToolbarOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'tooltip-overview': { + title: 'Basic tooltip', + component: TooltipOverviewExample, + additionalFiles: null, + selectorName: null + }, + 'tooltip-position': { + title: 'Tooltip with custom position', + component: TooltipPositionExample, + additionalFiles: null, + selectorName: null + }, }; -/** - * NgModule that includes all Material modules that are required to serve the examples. - */ -@NgModule({ - exports: [ - CdkTableModule, - MdAutocompleteModule, - MdButtonModule, - MdButtonToggleModule, - MdCardModule, - MdCheckboxModule, - MdChipsModule, - MdDatepickerModule, - MdDialogModule, - MdGridListModule, - MdIconModule, - MdInputModule, - MdListModule, - MdMenuModule, - MdPaginatorModule, - MdProgressBarModule, - MdProgressSpinnerModule, - MdRadioModule, - MdSortModule, - MdSelectModule, - MdSlideToggleModule, - MdSliderModule, - MdSidenavModule, - MdSnackBarModule, - MdTableModule, - MdTabsModule, - MdToolbarModule, - MdTooltipModule - ] -}) -export class ExampleMaterialModule {} - -/** - * The list of all example components. - * We need to put them in both `declarations` and `entryComponents` to make them work. - */ export const EXAMPLE_LIST = [ AutocompleteOverviewExample, AutocompleteFilterExample, @@ -279,60 +497,56 @@ export const EXAMPLE_LIST = [ CardFancyExample, CardOverviewExample, CdkTableBasicExample, - ChipsOverviewExample, - ChipsStackedExample, CheckboxConfigurableExample, CheckboxOverviewExample, + ChipsOverviewExample, + ChipsStackedExample, + DatepickerApiExample, + DatepickerFilterExample, + DatepickerMinMaxExample, DatepickerOverviewExample, DatepickerStartViewExample, - DatepickerMinMaxExample, - DatepickerFilterExample, DatepickerTouchExample, - DatepickerApiExample, - DialogOverviewExample, - DialogOverviewExampleDialog, - DialogResultExample, - DialogResultExampleDialog, - DialogElementsExample, - DialogElementsExampleDialog, + DialogElementsExampleDialog,DialogElementsExample, + DialogOverviewExampleDialog,DialogOverviewExample, + DialogResultExampleDialog,DialogResultExample, GridListDynamicExample, GridListOverviewExample, IconOverviewExample, IconSvgExample, InputClearableExample, + InputErrorsExample, InputFormExample, + InputHintExample, InputOverviewExample, InputPrefixSuffixExample, - InputHintExample, - InputErrorsExample, ListOverviewExample, ListSectionsExample, MenuIconsExample, MenuOverviewExample, - PaginatorOverviewExample, PaginatorConfigurableExample, + PaginatorOverviewExample, ProgressBarConfigurableExample, ProgressBarOverviewExample, ProgressSpinnerConfigurableExample, ProgressSpinnerOverviewExample, RadioNgModelExample, RadioOverviewExample, - SidenavFabExample, - SelectOverviewExample, SelectFormExample, + SelectOverviewExample, + SidenavFabExample, SidenavOverviewExample, - SliderConfigurableExample, - SliderOverviewExample, SlideToggleConfigurableExample, - SlideToggleOverviewExample, SlideToggleFormsExample, - SortOverviewExample, - SnackBarComponentExample, - PizzaPartyComponent, + SlideToggleOverviewExample, + SliderConfigurableExample, + SliderOverviewExample, + PizzaPartyComponent,SnackBarComponentExample, SnackBarOverviewExample, + SortOverviewExample, TableBasicExample, - TableOverviewExample, TableFilteringExample, + TableOverviewExample, TablePaginationExample, TableSortingExample, TabsOverviewExample, @@ -350,7 +564,7 @@ export const EXAMPLE_LIST = [ ExampleMaterialModule, FormsModule, ReactiveFormsModule, - CommonModule, + CommonModule ] }) export class ExampleModule { } diff --git a/src/material-examples/grid-list-dynamic/grid-list-dynamic-example.ts b/src/material-examples/grid-list-dynamic/grid-list-dynamic-example.ts index d8fee3401527..21dc729aea31 100644 --- a/src/material-examples/grid-list-dynamic/grid-list-dynamic-example.ts +++ b/src/material-examples/grid-list-dynamic/grid-list-dynamic-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Dynamic grid-list + */ @Component({ selector: 'grid-list-dynamic-example', templateUrl: 'grid-list-dynamic-example.html', diff --git a/src/material-examples/grid-list-overview/grid-list-overview-example.ts b/src/material-examples/grid-list-overview/grid-list-overview-example.ts index 29b60b3cc584..dd03daef557e 100644 --- a/src/material-examples/grid-list-overview/grid-list-overview-example.ts +++ b/src/material-examples/grid-list-overview/grid-list-overview-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Basic grid-list + */ @Component({ selector: 'grid-list-overview-example', styleUrls: ['grid-list-overview-example.css'], diff --git a/src/material-examples/icon-overview/icon-overview-example.ts b/src/material-examples/icon-overview/icon-overview-example.ts index 52ed35de3220..f81416f3331b 100644 --- a/src/material-examples/icon-overview/icon-overview-example.ts +++ b/src/material-examples/icon-overview/icon-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic icons + */ @Component({ selector: 'icon-overview-example', templateUrl: 'icon-overview-example.html', diff --git a/src/material-examples/icon-svg-example/icon-svg-example.ts b/src/material-examples/icon-svg-example/icon-svg-example.ts index 228edea1db0b..53775feb59fe 100644 --- a/src/material-examples/icon-svg-example/icon-svg-example.ts +++ b/src/material-examples/icon-svg-example/icon-svg-example.ts @@ -2,7 +2,9 @@ import {Component} from '@angular/core'; import {DomSanitizer} from '@angular/platform-browser'; import {MdIconRegistry} from '@angular/material'; - +/** + * @title SVG icons + */ @Component({ selector: 'icon-svg-example', templateUrl: 'icon-svg-example.html', diff --git a/src/material-examples/input-clearable/input-clearable-example.ts b/src/material-examples/input-clearable/input-clearable-example.ts index 01fba9c163f9..3b469528280b 100644 --- a/src/material-examples/input-clearable/input-clearable-example.ts +++ b/src/material-examples/input-clearable/input-clearable-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Input Clearable + */ @Component({ selector: 'input-clearable-example', templateUrl: './input-clearable-example.html', diff --git a/src/material-examples/input-errors/input-errors-example.ts b/src/material-examples/input-errors/input-errors-example.ts index cee372af8979..8a2dbb861244 100644 --- a/src/material-examples/input-errors/input-errors-example.ts +++ b/src/material-examples/input-errors/input-errors-example.ts @@ -3,6 +3,9 @@ import {FormControl, Validators} from '@angular/forms'; const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; +/** + * @title Input Errors + */ @Component({ selector: 'input-errors-example', templateUrl: 'input-errors-example.html', diff --git a/src/material-examples/input-form/input-form-example.ts b/src/material-examples/input-form/input-form-example.ts index 760ed8571251..192f9cae87dc 100644 --- a/src/material-examples/input-form/input-form-example.ts +++ b/src/material-examples/input-form/input-form-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Inputs in a form + */ @Component({ selector: 'input-form-example', templateUrl: 'input-form-example.html', diff --git a/src/material-examples/input-hint/input-hint-example.ts b/src/material-examples/input-hint/input-hint-example.ts index 202836b27432..3b3a5ebc7b66 100644 --- a/src/material-examples/input-hint/input-hint-example.ts +++ b/src/material-examples/input-hint/input-hint-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Input hints + */ @Component({ selector: 'input-hint-example', templateUrl: 'input-hint-example.html', diff --git a/src/material-examples/input-overview/input-overview-example.ts b/src/material-examples/input-overview/input-overview-example.ts index 4af520da8b6c..c43e8eacb905 100644 --- a/src/material-examples/input-overview/input-overview-example.ts +++ b/src/material-examples/input-overview/input-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic Inputs + */ @Component({ selector: 'input-overview-example', templateUrl: 'input-overview-example.html', diff --git a/src/material-examples/input-prefix-suffix/input-prefix-suffix-example.ts b/src/material-examples/input-prefix-suffix/input-prefix-suffix-example.ts index 5c1755172126..00ed164cd443 100644 --- a/src/material-examples/input-prefix-suffix/input-prefix-suffix-example.ts +++ b/src/material-examples/input-prefix-suffix/input-prefix-suffix-example.ts @@ -1,5 +1,8 @@ import {Component} from '@angular/core'; +/** + * @title Input Prefixes and Suffixes + */ @Component({ selector: 'input-prefix-suffix-example', templateUrl: 'input-prefix-suffix-example.html', diff --git a/src/material-examples/list-overview/list-overview-example.ts b/src/material-examples/list-overview/list-overview-example.ts index f999bbdebfbd..7912329c2ac5 100644 --- a/src/material-examples/list-overview/list-overview-example.ts +++ b/src/material-examples/list-overview/list-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic list + */ @Component({ selector: 'list-overview-example', templateUrl: 'list-overview-example.html', diff --git a/src/material-examples/list-sections/list-sections-example.ts b/src/material-examples/list-sections/list-sections-example.ts index 16f1ff7f07d1..63e7e4c6142c 100644 --- a/src/material-examples/list-sections/list-sections-example.ts +++ b/src/material-examples/list-sections/list-sections-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title List with sections + */ @Component({ selector: 'list-sections-example', styleUrls: ['list-sections-example.css'], diff --git a/src/material-examples/material-module.ts b/src/material-examples/material-module.ts new file mode 100644 index 000000000000..3d7b71a6010f --- /dev/null +++ b/src/material-examples/material-module.ts @@ -0,0 +1,42 @@ +import {NgModule} from '@angular/core'; + +import { + MdAutocompleteModule, MdButtonModule, MdButtonToggleModule, MdPaginatorModule, + MdCardModule, MdCheckboxModule, MdChipsModule, MdDatepickerModule, + MdDialogModule, MdGridListModule, MdIconModule, MdInputModule, + MdListModule, MdMenuModule, MdProgressBarModule, MdProgressSpinnerModule, + MdRadioModule, MdSelectModule, MdSidenavModule, MdSliderModule, MdSortModule, + MdSlideToggleModule, MdSnackBarModule, MdTabsModule, MdToolbarModule, MdTooltipModule +} from '@angular/material'; + +@NgModule({ + exports: [ + MdAutocompleteModule, + MdButtonModule, + MdButtonToggleModule, + MdCardModule, + MdCheckboxModule, + MdChipsModule, + MdDatepickerModule, + MdDialogModule, + MdGridListModule, + MdIconModule, + MdInputModule, + MdListModule, + MdMenuModule, + MdProgressBarModule, + MdProgressSpinnerModule, + MdRadioModule, + MdSelectModule, + MdSlideToggleModule, + MdSliderModule, + MdSidenavModule, + MdSnackBarModule, + MdTabsModule, + MdToolbarModule, + MdTooltipModule, + MdPaginatorModule, + MdSortModule + ] +}) +export class ExampleMaterialModule {} diff --git a/src/material-examples/menu-icons/menu-icons-example.ts b/src/material-examples/menu-icons/menu-icons-example.ts index f09a07e7d15a..bf9c15d30ab3 100644 --- a/src/material-examples/menu-icons/menu-icons-example.ts +++ b/src/material-examples/menu-icons/menu-icons-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Menu with icons + */ @Component({ selector: 'menu-icons-example', templateUrl: 'menu-icons-example.html', diff --git a/src/material-examples/menu-overview/menu-overview-example.ts b/src/material-examples/menu-overview/menu-overview-example.ts index 8c25e07b69c9..83ee676b3179 100644 --- a/src/material-examples/menu-overview/menu-overview-example.ts +++ b/src/material-examples/menu-overview/menu-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic menu + */ @Component({ selector: 'menu-overview-example', templateUrl: 'menu-overview-example.html', diff --git a/src/material-examples/paginator-configurable/paginator-configurable-example.ts b/src/material-examples/paginator-configurable/paginator-configurable-example.ts index 0e432eb6c8eb..ba3df8db743f 100644 --- a/src/material-examples/paginator-configurable/paginator-configurable-example.ts +++ b/src/material-examples/paginator-configurable/paginator-configurable-example.ts @@ -1,6 +1,9 @@ import {Component} from '@angular/core'; import {PageEvent} from '@angular/material'; +/** + * @title Configurable paginator + */ @Component({ selector: 'paginator-configurable-example', templateUrl: 'paginator-configurable-example.html', diff --git a/src/material-examples/paginator-overview/paginator-overview-example.ts b/src/material-examples/paginator-overview/paginator-overview-example.ts index d563732d6233..b2d305e1646a 100644 --- a/src/material-examples/paginator-overview/paginator-overview-example.ts +++ b/src/material-examples/paginator-overview/paginator-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Paginator + */ @Component({ selector: 'paginator-overview-example', templateUrl: 'paginator-overview-example.html', diff --git a/src/material-examples/progress-bar-configurable/progress-bar-configurable-example.ts b/src/material-examples/progress-bar-configurable/progress-bar-configurable-example.ts index 6363a491301d..101408dcf8cb 100644 --- a/src/material-examples/progress-bar-configurable/progress-bar-configurable-example.ts +++ b/src/material-examples/progress-bar-configurable/progress-bar-configurable-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Configurable progress-bar + */ @Component({ selector: 'progress-bar-configurable-example', templateUrl: 'progress-bar-configurable-example.html', diff --git a/src/material-examples/progress-bar-overview/progress-bar-overview-example.ts b/src/material-examples/progress-bar-overview/progress-bar-overview-example.ts index bf455f948425..67a6c94df90d 100644 --- a/src/material-examples/progress-bar-overview/progress-bar-overview-example.ts +++ b/src/material-examples/progress-bar-overview/progress-bar-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic progress-bar + */ @Component({ selector: 'progress-bar-overview-example', templateUrl: 'progress-bar-overview-example.html', diff --git a/src/material-examples/progress-spinner-configurable/progress-spinner-configurable-example.ts b/src/material-examples/progress-spinner-configurable/progress-spinner-configurable-example.ts index 705fda16d612..ed1ca870837a 100644 --- a/src/material-examples/progress-spinner-configurable/progress-spinner-configurable-example.ts +++ b/src/material-examples/progress-spinner-configurable/progress-spinner-configurable-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Configurable progress spinner + */ @Component({ selector: 'progress-spinner-configurable-example', templateUrl: 'progress-spinner-configurable-example.html', diff --git a/src/material-examples/progress-spinner-overview/progress-spinner-overview-example.ts b/src/material-examples/progress-spinner-overview/progress-spinner-overview-example.ts index 55b9f833a8be..67ada8ad585c 100644 --- a/src/material-examples/progress-spinner-overview/progress-spinner-overview-example.ts +++ b/src/material-examples/progress-spinner-overview/progress-spinner-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic progress-spinner + */ @Component({ selector: 'progress-spinner-overview-example', templateUrl: 'progress-spinner-overview-example.html', diff --git a/src/material-examples/radio-ng-model/radio-ng-model-example.ts b/src/material-examples/radio-ng-model/radio-ng-model-example.ts index 1ce419b65cd7..52fc51572cf3 100644 --- a/src/material-examples/radio-ng-model/radio-ng-model-example.ts +++ b/src/material-examples/radio-ng-model/radio-ng-model-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Radios with ngModel + */ @Component({ selector: 'radio-ng-model-example', templateUrl: 'radio-ng-model-example.html', diff --git a/src/material-examples/radio-overview/radio-overview-example.ts b/src/material-examples/radio-overview/radio-overview-example.ts index e3753efc7b12..916ce6632403 100644 --- a/src/material-examples/radio-overview/radio-overview-example.ts +++ b/src/material-examples/radio-overview/radio-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic radios + */ @Component({ selector: 'radio-overview-example', templateUrl: 'radio-overview-example.html', diff --git a/src/material-examples/select-form/select-form-example.ts b/src/material-examples/select-form/select-form-example.ts index ab0bc62cba53..0bb2c305b178 100644 --- a/src/material-examples/select-form/select-form-example.ts +++ b/src/material-examples/select-form/select-form-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Select in a form + */ @Component({ selector: 'select-form-example', templateUrl: 'select-form-example.html', diff --git a/src/material-examples/select-overview/select-overview-example.ts b/src/material-examples/select-overview/select-overview-example.ts index e5e6cf4e668d..a6a2ff822eeb 100644 --- a/src/material-examples/select-overview/select-overview-example.ts +++ b/src/material-examples/select-overview/select-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic select + */ @Component({ selector: 'select-overview-example', templateUrl: 'select-overview-example.html', diff --git a/src/material-examples/sidenav-fab/sidenav-fab-example.ts b/src/material-examples/sidenav-fab/sidenav-fab-example.ts index 9270a564514d..cd228e86f61a 100644 --- a/src/material-examples/sidenav-fab/sidenav-fab-example.ts +++ b/src/material-examples/sidenav-fab/sidenav-fab-example.ts @@ -1,6 +1,8 @@ import {Component, ViewEncapsulation} from '@angular/core'; - +/** + * @title Sidenav with a FAB + */ @Component({ selector: 'sidenav-fab-example', templateUrl: 'sidenav-fab-example.html', diff --git a/src/material-examples/sidenav-overview/sidenav-overview-example.ts b/src/material-examples/sidenav-overview/sidenav-overview-example.ts index a71f112660b6..5496f3f45fee 100644 --- a/src/material-examples/sidenav-overview/sidenav-overview-example.ts +++ b/src/material-examples/sidenav-overview/sidenav-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic sidenav + */ @Component({ selector: 'sidenav-overview-example', templateUrl: 'sidenav-overview-example.html', diff --git a/src/material-examples/slide-toggle-configurable/slide-toggle-configurable-example.ts b/src/material-examples/slide-toggle-configurable/slide-toggle-configurable-example.ts index dd21d0e5ab3f..17835efbc23c 100644 --- a/src/material-examples/slide-toggle-configurable/slide-toggle-configurable-example.ts +++ b/src/material-examples/slide-toggle-configurable/slide-toggle-configurable-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Configurable slide-toggle + */ @Component({ selector: 'slide-toggle-configurable-example', templateUrl: 'slide-toggle-configurable-example.html', diff --git a/src/material-examples/slide-toggle-forms/slide-toggle-forms-example.ts b/src/material-examples/slide-toggle-forms/slide-toggle-forms-example.ts index 9402e23e9c95..2636c474dd85 100644 --- a/src/material-examples/slide-toggle-forms/slide-toggle-forms-example.ts +++ b/src/material-examples/slide-toggle-forms/slide-toggle-forms-example.ts @@ -1,6 +1,9 @@ import {Component} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +/** + * @title Slide-toggle with forms + */ @Component({ selector: 'slide-toggle-forms-example', templateUrl: './slide-toggle-forms-example.html', diff --git a/src/material-examples/slide-toggle-overview/slide-toggle-overview-example.ts b/src/material-examples/slide-toggle-overview/slide-toggle-overview-example.ts index 9bfb08616def..6ed08696a1cb 100644 --- a/src/material-examples/slide-toggle-overview/slide-toggle-overview-example.ts +++ b/src/material-examples/slide-toggle-overview/slide-toggle-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic slide-toggles + */ @Component({ selector: 'slide-toggle-overview-example', templateUrl: 'slide-toggle-overview-example.html', diff --git a/src/material-examples/slider-configurable/slider-configurable-example.ts b/src/material-examples/slider-configurable/slider-configurable-example.ts index 46ab2412839b..80f206709907 100644 --- a/src/material-examples/slider-configurable/slider-configurable-example.ts +++ b/src/material-examples/slider-configurable/slider-configurable-example.ts @@ -1,6 +1,8 @@ import {Component, ViewEncapsulation} from '@angular/core'; - +/** + * @title Configurable slider + */ @Component({ selector: 'slider-configurable-example', templateUrl: 'slider-configurable-example.html', diff --git a/src/material-examples/slider-overview/slider-overview-example.ts b/src/material-examples/slider-overview/slider-overview-example.ts index 8dfaa812bb25..3284b3ae4a0c 100644 --- a/src/material-examples/slider-overview/slider-overview-example.ts +++ b/src/material-examples/slider-overview/slider-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic slider + */ @Component({ selector: 'slider-overview-example', templateUrl: 'slider-overview-example.html', diff --git a/src/material-examples/snack-bar-component/snack-bar-component-example.ts b/src/material-examples/snack-bar-component/snack-bar-component-example.ts index d1e2d197f2c8..8aac00a13153 100644 --- a/src/material-examples/snack-bar-component/snack-bar-component-example.ts +++ b/src/material-examples/snack-bar-component/snack-bar-component-example.ts @@ -1,7 +1,9 @@ import {Component} from '@angular/core'; import {MdSnackBar} from '@angular/material'; - +/** + * @title Snack-bar with a custom component + */ @Component({ selector: 'snack-bar-component-example', templateUrl: 'snack-bar-component-example.html', diff --git a/src/material-examples/snack-bar-overview/snack-bar-overview-example.ts b/src/material-examples/snack-bar-overview/snack-bar-overview-example.ts index c4d5ecb0aea6..4d1980dc9d47 100644 --- a/src/material-examples/snack-bar-overview/snack-bar-overview-example.ts +++ b/src/material-examples/snack-bar-overview/snack-bar-overview-example.ts @@ -1,7 +1,9 @@ import {Component} from '@angular/core'; import {MdSnackBar} from '@angular/material'; - +/** + * @title Basic snack-bar + */ @Component({ selector: 'snack-bar-overview-example', templateUrl: 'snack-bar-overview-example.html', diff --git a/src/material-examples/sort-overview/sort-overview-example.ts b/src/material-examples/sort-overview/sort-overview-example.ts index 6670206444f6..58b19b393021 100644 --- a/src/material-examples/sort-overview/sort-overview-example.ts +++ b/src/material-examples/sort-overview/sort-overview-example.ts @@ -1,7 +1,9 @@ import {Component} from '@angular/core'; import {Sort} from '@angular/material'; - +/** + * @title Sorting overview + */ @Component({ selector: 'sort-overview-example', templateUrl: 'sort-overview-example.html', diff --git a/src/material-examples/table-basic/table-basic-example.ts b/src/material-examples/table-basic/table-basic-example.ts index 1e0d1f88e757..effa3ec33fc0 100644 --- a/src/material-examples/table-basic/table-basic-example.ts +++ b/src/material-examples/table-basic/table-basic-example.ts @@ -6,6 +6,9 @@ import 'rxjs/add/operator/startWith'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/map'; +/** + * @title Basic table + */ @Component({ selector: 'table-basic-example', styleUrls: ['table-basic-example.css'], diff --git a/src/material-examples/table-filtering/table-filtering-example.ts b/src/material-examples/table-filtering/table-filtering-example.ts index b4def33bcbbc..4c7015e9244a 100644 --- a/src/material-examples/table-filtering/table-filtering-example.ts +++ b/src/material-examples/table-filtering/table-filtering-example.ts @@ -9,6 +9,9 @@ import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/observable/fromEvent'; +/** + * @title Table with filtering + */ @Component({ selector: 'table-filtering-example', styleUrls: ['table-filtering-example.css'], diff --git a/src/material-examples/table-overview/table-overview-example.ts b/src/material-examples/table-overview/table-overview-example.ts index 568aebc5f255..937e68993ea8 100644 --- a/src/material-examples/table-overview/table-overview-example.ts +++ b/src/material-examples/table-overview/table-overview-example.ts @@ -10,6 +10,9 @@ import 'rxjs/add/operator/map'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/debounceTime'; +/** + * @title Feature-rich data table + */ @Component({ selector: 'table-overview-example', styleUrls: ['table-overview-example.css'], diff --git a/src/material-examples/table-pagination/table-pagination-example.ts b/src/material-examples/table-pagination/table-pagination-example.ts index 730b0437355d..5cc82408607f 100644 --- a/src/material-examples/table-pagination/table-pagination-example.ts +++ b/src/material-examples/table-pagination/table-pagination-example.ts @@ -7,6 +7,9 @@ import 'rxjs/add/operator/startWith'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/map'; +/** + * @title Table with pagination + */ @Component({ selector: 'table-pagination-example', styleUrls: ['table-pagination-example.css'], diff --git a/src/material-examples/table-sorting/table-sorting-example.ts b/src/material-examples/table-sorting/table-sorting-example.ts index 9578c59b1172..bb1597fee09e 100644 --- a/src/material-examples/table-sorting/table-sorting-example.ts +++ b/src/material-examples/table-sorting/table-sorting-example.ts @@ -7,6 +7,9 @@ import 'rxjs/add/operator/startWith'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/map'; +/** + * @title Table with sorting + */ @Component({ selector: 'table-sorting-example', styleUrls: ['table-sorting-example.css'], diff --git a/src/material-examples/tabs-overview/tabs-overview-example.ts b/src/material-examples/tabs-overview/tabs-overview-example.ts index 77ee5cb51922..96f70260ec78 100644 --- a/src/material-examples/tabs-overview/tabs-overview-example.ts +++ b/src/material-examples/tabs-overview/tabs-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic tabs + */ @Component({ selector: 'tabs-overview-example', templateUrl: 'tabs-overview-example.html', diff --git a/src/material-examples/tabs-template-label/tabs-template-label-example.ts b/src/material-examples/tabs-template-label/tabs-template-label-example.ts index 3b9f436e4b99..fe7d79d68874 100644 --- a/src/material-examples/tabs-template-label/tabs-template-label-example.ts +++ b/src/material-examples/tabs-template-label/tabs-template-label-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Coming soon! + */ @Component({ selector: 'tabs-template-label-example', templateUrl: 'tabs-template-label-example.html', diff --git a/src/material-examples/toolbar-multirow/toolbar-multirow-example.ts b/src/material-examples/toolbar-multirow/toolbar-multirow-example.ts index 558bf8cebda8..3ad5317325d6 100644 --- a/src/material-examples/toolbar-multirow/toolbar-multirow-example.ts +++ b/src/material-examples/toolbar-multirow/toolbar-multirow-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Multi-row toolbar + */ @Component({ selector: 'toolbar-multirow-example', templateUrl: 'toolbar-multirow-example.html', diff --git a/src/material-examples/toolbar-overview/toolbar-overview-example.ts b/src/material-examples/toolbar-overview/toolbar-overview-example.ts index 515d63252a22..5d543043abe4 100644 --- a/src/material-examples/toolbar-overview/toolbar-overview-example.ts +++ b/src/material-examples/toolbar-overview/toolbar-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic toolbar + */ @Component({ selector: 'toolbar-overview-example', templateUrl: 'toolbar-overview-example.html', diff --git a/src/material-examples/tooltip-overview/tooltip-overview-example.ts b/src/material-examples/tooltip-overview/tooltip-overview-example.ts index 97b72348178a..ffb53c02b102 100644 --- a/src/material-examples/tooltip-overview/tooltip-overview-example.ts +++ b/src/material-examples/tooltip-overview/tooltip-overview-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Basic tooltip + */ @Component({ selector: 'tooltip-overview-example', templateUrl: 'tooltip-overview-example.html', diff --git a/src/material-examples/tooltip-position/tooltip-position-example.ts b/src/material-examples/tooltip-position/tooltip-position-example.ts index d505c8aee51f..12741b773e48 100644 --- a/src/material-examples/tooltip-position/tooltip-position-example.ts +++ b/src/material-examples/tooltip-position/tooltip-position-example.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; - +/** + * @title Tooltip with custom position + */ @Component({ selector: 'tooltip-position-example', templateUrl: 'tooltip-position-example.html', diff --git a/tools/dashboard/functions/payload-github-status.ts b/tools/dashboard/functions/payload-github-status.ts index fb0537bf949b..60d939ac4dbd 100644 --- a/tools/dashboard/functions/payload-github-status.ts +++ b/tools/dashboard/functions/payload-github-status.ts @@ -6,10 +6,10 @@ export const payloadGithubStatus = https.onRequest(async (request, response) => const authToken = request.header('auth-token'); const commitSha = request.header('commit-sha'); const packageName = request.header('package-name'); - const packageSize = parseFloat(request.header('package-full-size')); - const packageDiff = parseFloat(request.header('package-size-diff')); + const packageSize = parseFloat(request.header('package-full-size') || ''); + const packageDiff = parseFloat(request.header('package-size-diff') || ''); - if (!verifyToken(authToken)) { + if (!authToken || !verifyToken(authToken)) { return response.status(403).json({message: 'Auth token is not valid'}); } diff --git a/tools/dashboard/package.json b/tools/dashboard/package.json index 30803e93e20d..47c5712e1828 100644 --- a/tools/dashboard/package.json +++ b/tools/dashboard/package.json @@ -10,34 +10,33 @@ }, "private": true, "dependencies": { - "@angular/animations": "^4.2.2", + "@angular/animations": "^4.2.6", "@angular/cdk": "github:angular/cdk-builds", - "@angular/common": "^4.2.2", - "@angular/compiler": "^4.2.2", - "@angular/core": "^4.2.2", - "@angular/forms": "^4.2.2", - "@angular/http": "^4.2.2", + "@angular/common": "^4.2.6", + "@angular/compiler": "^4.2.6", + "@angular/core": "^4.2.6", + "@angular/forms": "^4.2.6", + "@angular/http": "^4.2.6", "@angular/material": "github:angular/material2-builds", - "@angular/platform-browser": "^4.2.2", - "@angular/platform-browser-dynamic": "^4.2.2", - "@angular/router": "^4.2.2", - "@swimlane/ngx-charts": "^5.3.1", + "@angular/platform-browser": "^4.2.6", + "@angular/platform-browser-dynamic": "^4.2.6", + "@angular/router": "^4.2.6", + "@swimlane/ngx-charts": "^6.0.0", "angularfire2": "^4.0.0-rc.1", "core-js": "^2.4.1", "d3": "^4.9.1", - "firebase": "^4.1.2", - "rxjs": "^5.1.0", + "firebase": "^4.1.3", + "rxjs": "^5.4.2", "zone.js": "^0.8.12" }, "devDependencies": { - "@angular/cli": "1.1.1", - "@angular/compiler-cli": "^4.2.2", - "@angular/language-service": "^4.2.2", + "@angular/cli": "^1.2.0", + "@angular/compiler-cli": "^4.2.6", + "@angular/language-service": "^4.2.6", "@types/jasmine": "2.5.45", "@types/node": "~6.0.60", "firebase-tools": "^3.9.1", "ts-node": "~3.0.4", - "tslint": "~5.3.2", - "typescript": "~2.3.3" + "typescript": "^2.4.1" } } diff --git a/tools/dashboard/src/app/dashboard-app.css b/tools/dashboard/src/app/dashboard-app.css index 17b4b6497cb0..97e74af31054 100644 --- a/tools/dashboard/src/app/dashboard-app.css +++ b/tools/dashboard/src/app/dashboard-app.css @@ -1,3 +1,8 @@ .dashboard-content { - margin: 16px; + padding: 16px; +} + +.payload-size-card { + width: 100%; + max-width: 700px; } diff --git a/tools/dashboard/src/app/dashboard-app.html b/tools/dashboard/src/app/dashboard-app.html index 7d15d5f449a9..727af508f05f 100644 --- a/tools/dashboard/src/app/dashboard-app.html +++ b/tools/dashboard/src/app/dashboard-app.html @@ -3,5 +3,10 @@
- + + Payload Size Chart + + + +
diff --git a/tools/dashboard/src/app/dashboard-module.ts b/tools/dashboard/src/app/dashboard-module.ts index a2992a0643d8..84d5f0e8c1eb 100644 --- a/tools/dashboard/src/app/dashboard-module.ts +++ b/tools/dashboard/src/app/dashboard-module.ts @@ -4,14 +4,16 @@ import {AngularFireDatabaseModule} from 'angularfire2/database'; import {NgModule} from '@angular/core'; import {DashboardApp} from './dashboard-app'; import {environment} from '../environments/environment'; -import {MdToolbarModule} from '@angular/material'; +import {MdCardModule, MdProgressSpinnerModule, MdToolbarModule} from '@angular/material'; import {NgxChartsModule} from '@swimlane/ngx-charts'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {PayloadChart} from './payload-chart/payload-chart'; @NgModule({ exports: [ - MdToolbarModule + MdCardModule, + MdToolbarModule, + MdProgressSpinnerModule ] }) export class DashboardMaterialModule {} diff --git a/tools/dashboard/src/app/payload-chart/payload-chart.html b/tools/dashboard/src/app/payload-chart/payload-chart.html index a2e715b74e25..067e2492f6bb 100644 --- a/tools/dashboard/src/app/payload-chart/payload-chart.html +++ b/tools/dashboard/src/app/payload-chart/payload-chart.html @@ -1,4 +1,5 @@ + + + diff --git a/tools/dashboard/src/app/payload-chart/payload-chart.scss b/tools/dashboard/src/app/payload-chart/payload-chart.scss new file mode 100644 index 000000000000..c7f42c0a507b --- /dev/null +++ b/tools/dashboard/src/app/payload-chart/payload-chart.scss @@ -0,0 +1,20 @@ +$payload-chart-loading-size: 50px; + +:host { + display: block; + position: relative; + + height: 400px; + width: 100%; + max-width: 700px; +} + +.payload-chart-loading { + position: absolute; + + top: calc(50% - #{$payload-chart-loading-size}); + left: calc(50% - #{$payload-chart-loading-size}); + + height: $payload-chart-loading-size; + width: $payload-chart-loading-size; +} diff --git a/tools/dashboard/src/app/payload-chart/payload-chart.ts b/tools/dashboard/src/app/payload-chart/payload-chart.ts index 05727d7b9132..bb662efbcc16 100644 --- a/tools/dashboard/src/app/payload-chart/payload-chart.ts +++ b/tools/dashboard/src/app/payload-chart/payload-chart.ts @@ -5,6 +5,7 @@ import {NgxChartItem, NgxChartResult} from './ngx-definitions'; @Component({ selector: 'payload-chart', templateUrl: './payload-chart.html', + styleUrls: ['./payload-chart.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class PayloadChart { @@ -29,6 +30,26 @@ export class PayloadChart { this.chartData = this.createChartResults(value); } + /** Remove duplicate payload results for similar days. */ + private filterDuplicateDays(data: PayloadResult[]) { + const filteredData = new Map(); + + data.forEach(result => { + // Parse the timestamp from the payload results as a date. + const date = new Date(result.timestamp); + + // Ignore hours, minutes, seconds and milliseconds from the date to allow comparisons + // only of the day. + date.setHours(0, 0, 0, 0); + + // Store the ISO string of the date in a Map to overwrite the previous payload result for + // the same day. + filteredData.set(date.toISOString(), result); + }); + + return Array.from(filteredData.values()); + } + /** Creates a list of ngx-chart results of the Payload results. */ private createChartResults(data: PayloadResult[]) { if (!data) { @@ -39,6 +60,11 @@ export class PayloadChart { // manually sort the results by their timestamp. data = data.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1); + // It can happen that there will be multiple payload results for the same day. This happens + // when multiple Pull Requests are merged in the same day. For the charts we only want to + // have the last payload result of a day (for performance and value correctness) + data = this.filterDuplicateDays(data); + const materialChartItems: NgxChartItem[] = []; const cdkChartItems: NgxChartItem[] = []; diff --git a/tools/dashboard/src/theme.scss b/tools/dashboard/src/theme.scss index 2e01b0d58329..3d31a884153c 100644 --- a/tools/dashboard/src/theme.scss +++ b/tools/dashboard/src/theme.scss @@ -8,3 +8,5 @@ $dashboard-light-theme: mat-light-theme($dashboard-primary, $dashboard-accent); // Only include the theme of the components that are used. @include mat-toolbar-theme($dashboard-light-theme); +@include mat-progress-spinner-theme($dashboard-light-theme); +@include mat-card-theme($dashboard-light-theme); diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index 846869fa8465..6d671cf6f631 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -14,6 +14,7 @@ import './tasks/e2e'; import './tasks/lint'; import './tasks/publish'; import './tasks/screenshots'; +import './tasks/examples'; import './tasks/unit-test'; import './tasks/aot'; import './tasks/payload'; diff --git a/tools/gulp/tasks/examples.ts b/tools/gulp/tasks/examples.ts new file mode 100644 index 000000000000..2a2a592610b5 --- /dev/null +++ b/tools/gulp/tasks/examples.ts @@ -0,0 +1,235 @@ +import {task} from 'gulp'; +import {sync as glob} from 'glob'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as ts from 'typescript'; +import {buildConfig} from 'material2-build-tools'; +const {packagesDir} = buildConfig; + +interface ExampleMetadata { + component: string; + sourcePath: string; + id: string; + title: string; + additionalComponents: string[]; + additionalFiles: string[]; + selectorName: string[]; +} + +interface ParsedMetadata { + primary: boolean; + component: string; + title: string; + templateUrl: string; +} + +interface ParsedMetadataResults { + primaryComponent: ParsedMetadata; + secondaryComponents: ParsedMetadata[]; +} + +/** Path to find the examples */ +const examplesPath = path.join(packagesDir, 'material-examples'); + +/** Output path of the module that is being created */ +const outputModuleFilename = path.join(examplesPath, 'example-module.ts'); + +/** Build ecmascript module import statements */ +function buildImportsTemplate(metadata: ExampleMetadata): string { + const components = metadata.additionalComponents.concat(metadata.component); + + // Create a relative path to the source file of the current example. + // The relative path will be used inside of a TypeScript import statement. + const relativeSrcPath = path + .relative('./src/material-examples', metadata.sourcePath) + .replace(/\\/g, '') + .replace('.ts', ''); + + return `import {${components.join(',')}} from './${relativeSrcPath}'; +`; +} + +/** + * Builds the examples metadata including title, component, etc. + */ +function buildExamplesTemplate(metadata: ExampleMetadata): string { + // if no additional files or selectors were provided, + // return null since we don't care about if these were not found + const additionalFiles = metadata.additionalFiles.length ? + JSON.stringify(metadata.additionalFiles) : 'null'; + + const selectorName = metadata.selectorName.length ? + `'${metadata.selectorName.join(', ')}'` : 'null'; + + return `'${metadata.id}': { + title: '${metadata.title}', + component: ${metadata.component}, + additionalFiles: ${additionalFiles}, + selectorName: ${selectorName} + }, + `; +} + +/** + * Build the list of components template + */ +function buildListTemplate(metadata: ExampleMetadata): string { + const components = metadata.additionalComponents.concat(metadata.component); + return `${components.join(',')}, + `; +} + +/** + * Builds the template for the examples module + */ +function generateExampleNgModule(extractedMetadata: ExampleMetadata[]): string { + return ` +/* tslint:disable */ +/** DO NOT MANUALLY EDIT THIS FILE, IT IS GENERATED VIA GULP 'build-examples-module' */ +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {CommonModule} from '@angular/common'; +import {ExampleMaterialModule} from './material-module'; + +export interface LiveExample { + title: string; + component: any; + additionalFiles?: string[]; + selectorName?: string; +} + +${extractedMetadata.map(r => buildImportsTemplate(r)).join('').trim()} + +export const EXAMPLE_COMPONENTS = { + ${extractedMetadata.map(r => buildExamplesTemplate(r)).join('').trim()} +}; + +export const EXAMPLE_LIST = [ + ${extractedMetadata.map(r => buildListTemplate(r)).join('').trim()} +]; + +@NgModule({ + declarations: EXAMPLE_LIST, + entryComponents: EXAMPLE_LIST, + imports: [ + ExampleMaterialModule, + FormsModule, + ReactiveFormsModule, + CommonModule + ] +}) +export class ExampleModule { } +`; +} + +/** + * Given a string that is a camel or pascal case, + * this function will convert to dash case. + */ +function convertToDashCase(name: string): string { + name = name.replace(/[A-Z]/g, ' $&'); + name = name.toLowerCase().trim(); + return name.split(' ').join('-'); +} + +/** + * Parse the AST of a file and get metadata about it + */ +function parseExampleMetadata(fileName: string, sourceContent: string): ParsedMetadataResults { + const sourceFile = ts.createSourceFile( + fileName, sourceContent, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); + + const metas: any[] = []; + + const visit = (node: any): void => { + if (node.kind === ts.SyntaxKind.ClassDeclaration) { + const meta: any = { + component: node.name.text + }; + + if (node.jsDoc && node.jsDoc.length) { + for (const doc of node.jsDoc) { + if (doc.tags && doc.tags.length) { + for (const tag of doc.tags) { + const tagValue = tag.comment; + const tagName = tag.tagName.text; + if (tagName === 'title') { + meta.title = tagValue; + meta.primary = true; + } + } + } + } + } + + if (node.decorators && node.decorators.length) { + for (const decorator of node.decorators) { + if (decorator.expression.expression.text === 'Component') { + for (const arg of decorator.expression.arguments) { + for (const prop of arg.properties) { + const name = prop.name.text; + const value = prop.initializer.text; + meta[name] = value; + } + } + + metas.push(meta); + } + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + + return { + primaryComponent: metas.find(m => m.primary), + secondaryComponents: metas.filter(m => !m.primary) + }; +} + +/** + * Creates the examples module and metadata + */ +task('build-examples-module', () => { + const results: ExampleMetadata[] = []; + + const matchedFiles = glob(path.join(examplesPath, '**/*.ts')); + for (const sourcePath of matchedFiles) { + const sourceContent = fs.readFileSync(sourcePath, 'utf-8'); + const { primaryComponent, secondaryComponents } = + parseExampleMetadata(sourcePath, sourceContent); + + if (primaryComponent) { + // Generate a unique id for the component by converting the class name to dash-case. + const id = convertToDashCase(primaryComponent.component.replace('Example', '')); + + const example: ExampleMetadata = { + sourcePath, + id, + component: primaryComponent.component, + title: primaryComponent.title, + additionalComponents: [], + additionalFiles: [], + selectorName: [] + }; + + if (secondaryComponents.length) { + example.selectorName.push(example.component); + + for (const meta of secondaryComponents) { + example.additionalComponents.push(meta.component); + example.additionalFiles.push(meta.templateUrl); + example.selectorName.push(meta.component); + } + } + + results.push(example); + } + } + + const generatedModuleFile = generateExampleNgModule(results); + fs.writeFileSync(outputModuleFilename, generatedModuleFile); +}); diff --git a/tools/package-tools/rollup-helpers.ts b/tools/package-tools/rollup-helpers.ts index 2aa8d794421b..5068ce0795d3 100644 --- a/tools/package-tools/rollup-helpers.ts +++ b/tools/package-tools/rollup-helpers.ts @@ -24,7 +24,7 @@ const ROLLUP_GLOBALS = { '@angular/material': 'ng.material', '@angular/cdk': 'ng.cdk', - // Rxjs dependencies + // RxJS dependencies 'rxjs/BehaviorSubject': 'Rx', 'rxjs/Observable': 'Rx', 'rxjs/Subject': 'Rx', @@ -49,6 +49,14 @@ const ROLLUP_GLOBALS = { 'rxjs/operator/switchMap': 'Rx.Observable.prototype', 'rxjs/operator/takeUntil': 'Rx.Observable.prototype', 'rxjs/operator/toPromise': 'Rx.Observable.prototype', + + // RxJS imports for the examples package + 'rxjs/add/observable/merge': 'Rx.Observable', + 'rxjs/add/observable/fromEvent': 'Rx.Observable', + 'rxjs/add/operator/startWith': 'Rx.Observable.prototype', + 'rxjs/add/operator/map': 'Rx.Observable.prototype', + 'rxjs/add/operator/debounceTime': 'Rx.Observable.prototype', + 'rxjs/add/operator/distinctUntilChanged': 'Rx.Observable.prototype', }; export type BundleConfig = {