diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html
index f974fc7228c7..c975b470e636 100644
--- a/src/demo-app/datepicker/datepicker-demo.html
+++ b/src/demo-app/datepicker/datepicker-demo.html
@@ -1,6 +1,25 @@
Work in progress, not ready for use.
-
-
-
-
+
+ Use touch UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/demo-app/datepicker/datepicker-demo.ts b/src/demo-app/datepicker/datepicker-demo.ts
index 214febf1eff0..f0927ec1203c 100644
--- a/src/demo-app/datepicker/datepicker-demo.ts
+++ b/src/demo-app/datepicker/datepicker-demo.ts
@@ -10,4 +10,5 @@ import {SimpleDate} from '@angular/material';
})
export class DatepickerDemo {
date: SimpleDate;
+ touch = false;
}
diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts
index f7be57566e25..12ab3b830c3b 100644
--- a/src/lib/datepicker/datepicker-input.ts
+++ b/src/lib/datepicker/datepicker-input.ts
@@ -1,5 +1,11 @@
import {
- AfterContentInit, Directive, ElementRef, forwardRef, Input, OnDestroy,
+ AfterContentInit,
+ Directive,
+ ElementRef,
+ forwardRef,
+ Input,
+ OnDestroy,
+ Optional,
Renderer
} from '@angular/core';
import {MdDatepicker} from './datepicker';
@@ -7,6 +13,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {SimpleDate} from '../core/datetime/simple-date';
import {CalendarLocale} from '../core/datetime/calendar-locale';
import {Subscription} from 'rxjs';
+import {MdInputContainer} from '../input/input-container';
export const MD_DATEPICKER_VALUE_ACCESSOR: any = {
@@ -55,8 +62,11 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor
private _datepickerSubscription: Subscription;
- constructor(private _elementRef: ElementRef, private _renderer: Renderer,
- private _locale: CalendarLocale) {}
+ constructor(
+ private _elementRef: ElementRef,
+ private _renderer: Renderer,
+ private _locale: CalendarLocale,
+ @Optional() private _mdInputContainer: MdInputContainer) {}
ngAfterContentInit() {
if (this._datepicker) {
@@ -75,7 +85,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor
}
getPopupConnectionElementRef(): ElementRef {
- return this._elementRef;
+ return this._mdInputContainer ? this._mdInputContainer.underlineRef : this._elementRef;
}
// Implemented as part of ControlValueAccessor
diff --git a/src/lib/datepicker/datepicker-toggle.scss b/src/lib/datepicker/datepicker-toggle.scss
new file mode 100644
index 000000000000..92f1f5a42519
--- /dev/null
+++ b/src/lib/datepicker/datepicker-toggle.scss
@@ -0,0 +1,13 @@
+$mat-datepicker-toggle-icon-size: 24px !default;
+
+
+.mat-datepicker-toggle {
+ display: inline-block;
+ background: url('data:image/svg+xml;utf8,') no-repeat;
+ background-size: contain;
+ height: $mat-datepicker-toggle-icon-size;
+ width: $mat-datepicker-toggle-icon-size;
+ border: none;
+ outline: none;
+ vertical-align: middle;
+}
diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts
new file mode 100644
index 000000000000..a6ba593205ac
--- /dev/null
+++ b/src/lib/datepicker/datepicker-toggle.ts
@@ -0,0 +1,30 @@
+import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core';
+import {MdDatepicker} from './datepicker';
+
+
+@Component({
+ moduleId: module.id,
+ selector: 'button[mdDatepickerToggle], button[matDatepickerToggle]',
+ template: '',
+ styleUrls: ['datepicker-toggle.css'],
+ host: {
+ '[class.mat-datepicker-toggle]': 'true',
+ '(click)': '_open($event)',
+ },
+ encapsulation: ViewEncapsulation.None,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class MdDatepickerToggle {
+ @Input('mdDatepickerToggle') datepicker: MdDatepicker;
+
+ @Input('matDatepickerToggle')
+ get _datepicker() { return this.datepicker; }
+ set _datepicker(v: MdDatepicker) { this.datepicker = v; }
+
+ _open(event: Event): void {
+ if (this.datepicker) {
+ this.datepicker.open();
+ event.stopPropagation();
+ }
+ }
+}
diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index 45923506150a..37f479907508 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -6,13 +6,14 @@ import {MdDatepickerInput} from './datepicker-input';
import {SimpleDate} from '../core/datetime/simple-date';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
-import {dispatchFakeEvent} from '../core/testing/dispatch-events';
+import {dispatchFakeEvent, dispatchMouseEvent} from '../core/testing/dispatch-events';
+import {MdInputModule} from '../input/index';
describe('MdDatepicker', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [MdDatepickerModule, FormsModule, ReactiveFormsModule],
+ imports: [MdDatepickerModule, MdInputModule, FormsModule, ReactiveFormsModule],
declarations: [
StandardDatepicker,
MultiInputDatepicker,
@@ -20,6 +21,8 @@ describe('MdDatepicker', () => {
DatepickerWithStartAt,
DatepickerWithNgModel,
DatepickerWithFormControl,
+ DatepickerWithToggle,
+ InputContainerDatepicker,
],
});
@@ -37,26 +40,29 @@ describe('MdDatepicker', () => {
testComponent = fixture.componentInstance;
});
- it('openStandardUi should open popup', () => {
+ it('open non-touch should open popup', () => {
expect(document.querySelector('.cdk-overlay-pane')).toBeNull();
- testComponent.datepicker.openStandardUi();
+ testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull();
});
- it('openTouchUi should open dialog', () => {
+ it('open touch should open dialog', () => {
+ testComponent.touch = true;
+ fixture.detectChanges();
+
expect(document.querySelector('md-dialog-container')).toBeNull();
- testComponent.datepicker.openTouchUi();
+ testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('md-dialog-container')).not.toBeNull();
});
it('close should close popup', async(() => {
- testComponent.datepicker.openStandardUi();
+ testComponent.datepicker.open();
fixture.detectChanges();
let popup = document.querySelector('.cdk-overlay-pane');
@@ -72,7 +78,10 @@ describe('MdDatepicker', () => {
}));
it('close should close dialog', async(() => {
- testComponent.datepicker.openTouchUi();
+ testComponent.touch = true;
+ fixture.detectChanges();
+
+ testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('md-dialog-container')).not.toBeNull();
@@ -86,7 +95,10 @@ describe('MdDatepicker', () => {
}));
it('setting selected should update input and close calendar', async(() => {
- testComponent.datepicker.openTouchUi();
+ testComponent.touch = true;
+ fixture.detectChanges();
+
+ testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('md-dialog-container')).not.toBeNull();
@@ -105,6 +117,12 @@ describe('MdDatepicker', () => {
it('startAt should fallback to input value', () => {
expect(testComponent.datepicker.startAt).toEqual(new SimpleDate(2020, 0, 1));
});
+
+ it('should attach popup to native input', () => {
+ let attachToRef = testComponent.datepickerInput.getPopupConnectionElementRef();
+ expect(attachToRef.nativeElement.tagName.toLowerCase())
+ .toBe('input', 'popup should be attached to native input');
+ });
});
describe('datepicker with too many inputs', () => {
@@ -126,7 +144,7 @@ describe('MdDatepicker', () => {
});
it('should throw when opened with no registered inputs', () => {
- expect(() => testComponent.datepicker.openStandardUi()).toThrow();
+ expect(() => testComponent.datepicker.open()).toThrow();
});
});
@@ -277,6 +295,46 @@ describe('MdDatepicker', () => {
expect(inputEl.disabled).toBe(true);
});
});
+
+ describe('datepicker with mdDatepickerToggle', () => {
+ let fixture: ComponentFixture;
+ let testComponent: DatepickerWithToggle;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DatepickerWithToggle);
+ fixture.detectChanges();
+
+ testComponent = fixture.componentInstance;
+ });
+
+ it('should open calendar when toggle clicked', () => {
+ 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')).not.toBeNull();
+ });
+ });
+
+ describe('datepicker inside input-container', () => {
+ let fixture: ComponentFixture;
+ let testComponent: InputContainerDatepicker;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(InputContainerDatepicker);
+ fixture.detectChanges();
+
+ testComponent = fixture.componentInstance;
+ });
+
+ it('should attach popup to input-container underline', () => {
+ let attachToRef = testComponent.datepickerInput.getPopupConnectionElementRef();
+ expect(attachToRef.nativeElement.classList.contains('mat-input-underline'))
+ .toBe(true, 'popup should be attached to input-container underline');
+ });
+ });
});
@@ -288,9 +346,13 @@ function detectModelChanges(fixture: ComponentFixture) {
@Component({
- template: ``,
+ template: `
+
+
+ `,
})
class StandardDatepicker {
+ touch = false;
@ViewChild('d') datepicker: MdDatepicker;
@ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput;
}
@@ -324,7 +386,7 @@ class DatepickerWithStartAt {
@Component({
- template: ``
+ template: ``,
})
class DatepickerWithNgModel {
selected: SimpleDate = null;
@@ -337,10 +399,33 @@ class DatepickerWithNgModel {
template: `
- `
+ `,
})
class DatepickerWithFormControl {
formControl = new FormControl();
@ViewChild('d') datepicker: MdDatepicker;
@ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput;
}
+
+
+@Component({
+ template: `
+
+
+
+ `,
+})
+class DatepickerWithToggle {}
+
+
+@Component({
+ template: `
+
+
+
+
+ `,
+})
+class InputContainerDatepicker {
+ @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput;
+}
diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts
index 27a325bb0d73..5e0e20cb3cf6 100644
--- a/src/lib/datepicker/datepicker.ts
+++ b/src/lib/datepicker/datepicker.ts
@@ -53,6 +53,13 @@ export class MdDatepicker implements OnDestroy {
set startAt(date: SimpleDate) { this._startAt = this._locale.parseDate(date); }
private _startAt: SimpleDate;
+ /**
+ * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather
+ * than a popup and elements have more padding to allow for bigger touch targets.
+ */
+ @Input()
+ touchUi = false;
+
@Output() selectedChanged = new EventEmitter();
get _selected(): SimpleDate {
@@ -63,12 +70,6 @@ export class MdDatepicker implements OnDestroy {
this.close();
}
- /**
- * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather
- * than a popup and elements have more padding to allow for bigger touch targets.
- */
- touchUi: boolean;
-
/** The calendar template. */
@ViewChild(TemplateRef) calendarTemplate: TemplateRef;
@@ -106,21 +107,11 @@ export class MdDatepicker implements OnDestroy {
this._datepickerInput = input;
}
- /** Opens the calendar in standard UI mode. */
- openStandardUi(): void {
- this._open();
- }
-
- /** Opens the calendar in touch UI mode. */
- openTouchUi(): void {
- this._open(true);
- }
-
/**
* Open the calendar.
* @param touchUi Whether to use the touch UI.
*/
- private _open(touchUi = false): void {
+ open(): void {
if (!this._datepickerInput) {
throw new MdError('Attempted to open an MdDatepicker with no associated input.');
}
@@ -129,8 +120,7 @@ export class MdDatepicker implements OnDestroy {
this._calendarPortal = new TemplatePortal(this.calendarTemplate, this._viewContainerRef);
}
- this.touchUi = touchUi;
- touchUi ? this._openAsDialog() : this._openAsPopup();
+ this.touchUi ? this._openAsDialog() : this._openAsPopup();
}
/** Close the calendar. */
diff --git a/src/lib/datepicker/index.ts b/src/lib/datepicker/index.ts
index b8138a0fd46c..f6afa6f473f2 100644
--- a/src/lib/datepicker/index.ts
+++ b/src/lib/datepicker/index.ts
@@ -9,6 +9,7 @@ import {MdDatepicker} from './datepicker';
import {MdDatepickerInput} from './datepicker-input';
import {MdDialogModule} from '../dialog/index';
import {MdCalendar} from './calendar';
+import {MdDatepickerToggle} from './datepicker-toggle';
export * from './calendar';
@@ -29,12 +30,14 @@ export * from './year-view';
exports: [
MdDatepicker,
MdDatepickerInput,
+ MdDatepickerToggle,
],
declarations: [
MdCalendar,
MdCalendarTable,
MdDatepicker,
MdDatepickerInput,
+ MdDatepickerToggle,
MdMonthView,
MdYearView,
],
diff --git a/src/lib/input/input-container.html b/src/lib/input/input-container.html
index cefe5a06f8d1..e19f0c715c53 100644
--- a/src/lib/input/input-container.html
+++ b/src/lib/input/input-container.html
@@ -29,7 +29,7 @@
-