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}}
Increase
Decrease
- 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
+ I am a button
`})
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 = {