diff --git a/src/components/datepicker/datePicker.scss b/src/components/datepicker/datePicker.scss index 39487cd9b3d..8dfb110cb65 100644 --- a/src/components/datepicker/datePicker.scss +++ b/src/components/datepicker/datePicker.scss @@ -47,9 +47,10 @@ md-datepicker { // If the datepicker is inside of a md-input-container ._md-datepicker-floating-label { > label:not(.md-no-float):not(._md-container-ignore) { - $offset: $md-datepicker-triangle-button-width + $md-datepicker-button-gap + $icon-button-margin + $baseline-grid; - @include rtl-prop(left, right, $offset); - width: calc(100% - #{$offset + $md-datepicker-triangle-button-width}); + $offset: $md-datepicker-triangle-button-width * 2 + $md-datepicker-button-gap + $icon-button-margin + $baseline-grid; + @include rtl(right, $icon-button-margin, auto); + @include rtl(left, auto, $icon-button-margin); + width: calc(100% - #{$offset}); } > md-datepicker { @@ -80,7 +81,10 @@ md-datepicker { display: inline-block; width: auto; - @include rtl-prop(margin-left, margin-right, $md-datepicker-button-gap); + + .md-icon-button + & { + @include rtl-prop(margin-left, margin-right, $md-datepicker-button-gap); + } &.md-datepicker-focused { border-bottom-width: 2px; @@ -209,13 +213,15 @@ md-datepicker[disabled] { overflow: hidden; .md-datepicker-input-container { - @include rtl-prop(margin-left, margin-right, -$md-datepicker-button-gap); - // The negative bottom margin prevents the content around the datepicker // from jumping when it gets opened. margin-bottom: -$md-datepicker-border-bottom-gap; } + .md-icon-button + .md-datepicker-input-container { + @include rtl-prop(margin-left, margin-right, -$md-datepicker-button-gap); + } + .md-datepicker-input, label:not(.md-no-float):not(._md-container-ignore) { margin-bottom: -$md-datepicker-border-bottom-gap; diff --git a/src/components/datepicker/demoBasicUsage/index.html b/src/components/datepicker/demoBasicUsage/index.html index 3a9424480de..ddd7bd5d89a 100644 --- a/src/components/datepicker/demoBasicUsage/index.html +++ b/src/components/datepicker/demoBasicUsage/index.html @@ -1,59 +1,80 @@ -
- - -

Standard date-picker

- - -

Disabled date-picker

- - -

Date-picker with min date and max date

- - -

Only weekends are selectable

- - -

Only weekends within given range are selectable

- - -

Opening the calendar when the input is focused

- - -

With ngMessages

-
- - -
-
The entered value is not a date!
-
This date is required!
-
Date is too early!
-
Date is too late!
-
Only weekends are allowed!
+
+ + +
+
+

Standard date-picker

+ +
+ +
+

Disabled date-picker

+ +
+
+ +
+
+

Date-picker with min date and max date

+ +
+ +
+

Only weekends are selectable

+
- +
-

Inside a md-input-container

-
- - +
+
+

Only weekends within given range are selectable

+ +
+ +
+

Opening the calendar when the input is focused

+ +
+
- +
+ +

With ngMessages

+ -
+
The entered value is not a date!
This date is required!
Date is too early!
Date is too late!
+
Only weekends are allowed!
- - + + +
+

Inside a md-input-container

+ + + + + +
+
The entered value is not a date!
+
This date is required!
+
Date is too early!
+
Date is too late!
+
+
+
+
+
diff --git a/src/components/datepicker/demoBasicUsage/script.js b/src/components/datepicker/demoBasicUsage/script.js index c68cd823318..b804eb2d6bd 100644 --- a/src/components/datepicker/demoBasicUsage/script.js +++ b/src/components/datepicker/demoBasicUsage/script.js @@ -11,9 +11,9 @@ angular.module('datepickerBasicUsage', $scope.myDate.getFullYear(), $scope.myDate.getMonth() + 2, $scope.myDate.getDate()); - + $scope.onlyWeekendsPredicate = function(date) { var day = date.getDay(); return day === 0 || day === 6; - } + }; }); diff --git a/src/components/datepicker/js/datepickerDirective.js b/src/components/datepicker/js/datepickerDirective.js index 3fee4b13f47..2b3a30869fd 100644 --- a/src/components/datepicker/js/datepickerDirective.js +++ b/src/components/datepicker/js/datepickerDirective.js @@ -27,6 +27,12 @@ * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not. * @param {String=} md-placeholder The date input placeholder value. * @param {String=} md-open-on-focus When present, the calendar will be opened when the input is focused. + * @param {Boolean=} md-is-open Expression that can be used to open the datepicker's calendar on-demand. + * @param {String=} md-hide-icons Determines which datepicker icons should be hidden. Note that this may cause the + * datepicker to not align properly with other components. **Use at your own risk.** Possible values are: + * * `"all"` - Hides all icons. + * * `"calendar"` - Only hides the calendar icon. + * * `"triangle"` - Only hides the triangle icon. * @param {boolean=} ng-disabled Whether the datepicker is disabled. * @param {boolean=} ng-required Whether a value is required for the datepicker. * @@ -49,47 +55,58 @@ */ function datePickerDirective($$mdSvgRegistry) { return { - template: - // Buttons are not in the tab order because users can open the calendar via keyboard - // interaction on the text input, and multiple tab stops for one component (picker) - // may be confusing. + template: function(tElement, tAttrs) { + // Buttons are not in the tab order because users can open the calendar via keyboard + // interaction on the text input, and multiple tab stops for one component (picker) + // may be confusing. + var hiddenIcons = tAttrs.mdHideIcons; + + var calendarButton = (hiddenIcons === 'all' || hiddenIcons === 'calendar') ? '' : '' + - '
' + - '' + - '' + - '
' + - '
' + + ''; + + var triangleButton = (hiddenIcons === 'all' || hiddenIcons === 'triangle') ? '' : + '' + + '
' + + '
'; + + return '' + + calendarButton + + '
' + + '' + + triangleButton + + '
' + + + // This pane will be detached from here and re-attached to the document body. + '
' + + '
' + + '
' + '
' + - - // This pane will be detached from here and re-attached to the document body. - '
' + - '
' + - '
' + - '
' + - '
' + - '' + - '' + - '
' + - '
', + '
' + + '' + + '' + + '
' + + '
'; + }, require: ['ngModel', 'mdDatepicker', '?^mdInputContainer'], scope: { minDate: '=mdMinDate', maxDate: '=mdMaxDate', placeholder: '@mdPlaceholder', - dateFilter: '=mdDateFilter' + dateFilter: '=mdDateFilter', + isOpen: '=?mdIsOpen' }, controller: DatePickerCtrl, controllerAs: 'ctrl', @@ -287,9 +304,22 @@ this.attachInteractionListeners(); var self = this; + $scope.$on('$destroy', function() { self.detachCalendarPane(); }); + + if ($attrs.mdIsOpen) { + $scope.$watch('ctrl.isOpen', function(shouldBeOpen) { + if (shouldBeOpen) { + self.openCalendarPane({ + target: self.inputElement + }); + } else { + self.closeCalendarPane(); + } + }); + } } /** @@ -396,7 +426,10 @@ DatePickerCtrl.prototype.setDisabled = function(isDisabled) { this.isDisabled = isDisabled; this.inputElement.disabled = isDisabled; - this.calendarButton.disabled = isDisabled; + + if (this.calendarButton) { + this.calendarButton.disabled = isDisabled; + } }; /** @@ -594,7 +627,7 @@ */ DatePickerCtrl.prototype.openCalendarPane = function(event) { if (!this.isCalendarOpen && !this.isDisabled) { - this.isCalendarOpen = true; + this.isCalendarOpen = this.isOpen = true; this.calendarPaneOpenedFrom = event.target; // Because the calendar pane is attached directly to the body, it is possible that the @@ -643,7 +676,7 @@ function detach() { self.detachCalendarPane(); - self.isCalendarOpen = false; + self.isCalendarOpen = self.isOpen = false; self.ngModelCtrl.$setTouched(); self.documentElement.off('click touchstart', self.bodyClickHandler); diff --git a/src/components/datepicker/js/datepickerDirective.spec.js b/src/components/datepicker/js/datepickerDirective.spec.js index e34da0055f1..483316da0e1 100644 --- a/src/components/datepicker/js/datepickerDirective.spec.js +++ b/src/components/datepicker/js/datepickerDirective.spec.js @@ -1,5 +1,5 @@ -describe('md-date-picker', function() { +describe('md-datepicker', function() { // When constructing a Date, the month is zero-based. This can be confusing, since people are // used to seeing them one-based. So we create these aliases to make reading the tests easier. var JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9, @@ -368,6 +368,29 @@ describe('md-date-picker', function() { }); + it('should open and close the floating calendar pane element via an expression on the scope', function() { + pageScope.isOpen = false; + createDatepickerInstance(''); + + expect(controller.calendarPane.offsetHeight).toBe(0); + expect(controller.isCalendarOpen).toBe(false); + + pageScope.$apply(function() { + pageScope.isOpen = true; + }); + + // Open the calendar externally + expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0); + expect(controller.isCalendarOpen).toBe(true); + + // Close the calendar via the datepicker + controller.$scope.$apply(function() { + controller.closeCalendarPane(); + }); + expect(pageScope.isOpen).toBe(false); + expect(controller.isCalendarOpen).toBe(false); + }); + it('should adjust the position of the floating pane if it would go off-screen', function() { // Absolutely position the picker near the edge of the screen. var bodyRect = document.body.getBoundingClientRect(); @@ -541,4 +564,25 @@ describe('md-date-picker', function() { controller.ngInputElement.triggerHandler('focus'); expect(document.querySelector('md-calendar')).toBeTruthy(); }); + + describe('hiding the icons', function() { + var calendarSelector = '.md-datepicker-button .md-datepicker-calendar-icon'; + var triangleSelector = '.md-datepicker-triangle-button'; + + it('should be able to hide the calendar icon', function() { + createDatepickerInstance(''); + expect(element.querySelector(calendarSelector)).toBeNull(); + }); + + it('should be able to hide the triangle icon', function() { + createDatepickerInstance(''); + expect(element.querySelector(triangleSelector)).toBeNull(); + }); + + it('should be able to hide all icons', function() { + createDatepickerInstance(''); + expect(element.querySelector(calendarSelector)).toBeNull(); + expect(element.querySelector(triangleSelector)).toBeNull(); + }); + }); });