diff --git a/src/components/datepicker/datePicker.js b/src/components/datepicker/datePicker.js index 516cb41b3bc..2b0b1a92c84 100644 --- a/src/components/datepicker/datePicker.js +++ b/src/components/datepicker/datePicker.js @@ -131,14 +131,17 @@ * * @ngInject @constructor */ - function DatePickerCtrl($scope, $element, $attrs, $compile, $timeout, $mdConstant, $mdTheming, - $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF) { + function DatePickerCtrl($scope, $element, $attrs, $compile, $timeout, $window, + $mdConstant, $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF) { /** @final */ this.$compile = $compile; /** @final */ this.$timeout = $timeout; + /** @final */ + this.$window = $window; + /** @final */ this.dateLocale = $mdDateLocale; @@ -387,34 +390,33 @@ var paneTop = elementRect.top - bodyRect.top; var paneLeft = elementRect.left - bodyRect.left; + var viewportTop = document.body.scrollTop; + var viewportBottom = viewportTop + this.$window.innerHeight; + + var viewportLeft = document.body.scrollLeft; + var viewportRight = document.body.scrollLeft + this.$window.innerWidth; + // If the right edge of the pane would be off the screen and shifting it left by the // difference would not go past the left edge of the screen. If the calendar pane is too // big to fit on the screen at all, move it to the left of the screen and scale the entire // element down to fit. - if (paneLeft + CALENDAR_PANE_WIDTH > bodyRect.right) { - if (bodyRect.right - CALENDAR_PANE_WIDTH > 0) { - paneLeft = bodyRect.right - CALENDAR_PANE_WIDTH; + if (paneLeft + CALENDAR_PANE_WIDTH > viewportRight) { + if (viewportRight - CALENDAR_PANE_WIDTH > 0) { + paneLeft = viewportRight - CALENDAR_PANE_WIDTH; } else { - paneLeft = 0; - var scale = bodyRect.width / CALENDAR_PANE_WIDTH; + paneLeft = viewportLeft; + var scale = this.$window.innerWidth / CALENDAR_PANE_WIDTH; calendarPane.style.transform = 'scale(' + scale + ')'; } calendarPane.classList.add('md-datepicker-pos-adjusted'); } - - if (paneLeft + CALENDAR_PANE_WIDTH > bodyRect.right && - bodyRect.right - CALENDAR_PANE_WIDTH > 0) { - paneLeft = bodyRect.right - CALENDAR_PANE_WIDTH; - calendarPane.classList.add('md-datepicker-pos-adjusted'); - } - // If the bottom edge of the pane would be off the screen and shifting it up by the // difference would not go past the top edge of the screen. - if (paneTop + CALENDAR_PANE_HEIGHT > bodyRect.bottom && - bodyRect.bottom - CALENDAR_PANE_HEIGHT > 0) { - paneTop = bodyRect.bottom - CALENDAR_PANE_HEIGHT; + if (paneTop + CALENDAR_PANE_HEIGHT > viewportBottom && + viewportBottom - CALENDAR_PANE_HEIGHT > viewportTop) { + paneTop = viewportBottom - CALENDAR_PANE_HEIGHT; calendarPane.classList.add('md-datepicker-pos-adjusted'); } @@ -478,14 +480,16 @@ /** Close the floating calendar pane. */ DatePickerCtrl.prototype.closeCalendarPane = function() { - this.isCalendarOpen = false; - this.detachCalendarPane(); - this.calendarPaneOpenedFrom.focus(); - this.calendarPaneOpenedFrom = null; - this.$mdUtil.enableScrolling(); - - document.body.removeEventListener('click', this.bodyClickHandler); - window.removeEventListener('resize', this.windowResizeHandler); + if (this.isCalendarOpen) { + this.isCalendarOpen = false; + this.detachCalendarPane(); + this.calendarPaneOpenedFrom.focus(); + this.calendarPaneOpenedFrom = null; + this.$mdUtil.enableScrolling(); + + document.body.removeEventListener('click', this.bodyClickHandler); + window.removeEventListener('resize', this.windowResizeHandler); + } }; /** Gets the controller instance for the calendar in the floating pane. */ diff --git a/src/components/datepicker/datePicker.spec.js b/src/components/datepicker/datePicker.spec.js index aa620d6972d..11673e38901 100644 --- a/src/components/datepicker/datePicker.spec.js +++ b/src/components/datepicker/datePicker.spec.js @@ -8,13 +8,14 @@ describe('md-date-picker', function() { var initialDate = new Date(2015, FEB, 15); var ngElement, element, scope, pageScope, controller; - var $timeout, $$rAF, $animate, keyCodes, dateUtil, dateLocale; + var $timeout, $$rAF, $animate, $window, keyCodes, dateUtil, dateLocale; beforeEach(module('material.components.datepicker', 'ngAnimateMock')); beforeEach(inject(function($compile, $rootScope, $injector) { $$rAF = $injector.get('$$rAF'); $animate = $injector.get('$animate'); + $window = $injector.get('$window'); dateUtil = $injector.get('$$mdDateUtil'); dateLocale = $injector.get('$mdDateLocale'); $timeout = $injector.get('$timeout'); @@ -37,6 +38,8 @@ describe('md-date-picker', function() { scope = ngElement.isolateScope(); controller = ngElement.controller('mdDatepicker'); element = ngElement[0]; + + controller.closeCalendarPane(); })); /** @@ -194,41 +197,62 @@ describe('md-date-picker', function() { document.body.removeChild(element); }); - it('should shink the calendar pane when it would otherwise not fit on the screen', function() { - // Make the body narrow so that the calendar pane won't fit on-screen. - document.body.style.width = '300px'; + it('should adjust the pane position if it would go off-screen (w/ scrollable)', function() { + // Make the body super huge. + var superLongElement = document.createElement('div'); + superLongElement.style.height = '10000px'; + superLongElement.style.width = '1px'; + document.body.appendChild(superLongElement); - // Open the calendar pane. + // Absolutely position the picker near (say ~30px) the edge of the viewport. + element.style.position = 'absolute'; + element.style.top = (window.innerHeight - 30) + 'px'; + element.style.left = '0'; + document.body.appendChild(element); + + // Open the pane. element.querySelector('md-button').click(); $timeout.flush(); + // Expect that the pane is on-screen. + var paneRect = controller.calendarPane.getBoundingClientRect(); + expect(paneRect.bottom).toBeLessThan(window.innerHeight + 1); + document.body.removeChild(superLongElement); + + document.body.removeChild(element); + }); + + it('should shink the calendar pane when it would otherwise not fit on the screen', function() { + // Fake the window being very narrow so that the calendar pane won't fit on-screen. + controller.$window = {innerWidth: 200, innherHeight: 800}; + + // Open the calendar pane. + controller.openCalendarPane({}); + // Expect the calendarPane to be scaled by an amount between zero and one. expect(controller.calendarPane.style.transform).toMatch(/scale\(0\.\d+\)/); - - // Reset the body width. - document.body.style.width = ''; }); it('should not open the calendar pane if disabled', function() { - controller.setDisabled(true); - controller.openCalendarPane({ - target: controller.inputElement + controller.setDisabled(true); + controller.openCalendarPane({ + target: controller.inputElement + }); + scope.$apply(); + expect(controller.isCalendarOpen).toBeFalsy(); + expect(controller.calendarPane.offsetHeight).toBe(0); }); - scope.$apply(); - expect(controller.isCalendarOpen).toBeFalsy(); - expect(controller.calendarPane.offsetHeight).toBe(0); - }); it('should close the calendar pane on md-calendar-close', function() { - controller.openCalendarPane({ - target: controller.inputElement - }); + controller.openCalendarPane({ + target: controller.inputElement + }); - scope.$emit('md-calendar-close'); - scope.$apply(); - expect(controller.calendarPaneOpenedFrom).toBe(null); - expect(controller.isCalendarOpen).toBe(false); - }); + scope.$emit('md-calendar-close'); + scope.$apply(); + expect(controller.calendarPaneOpenedFrom).toBe(null); + expect(controller.isCalendarOpen).toBe(false); + }); }); describe('md-calendar-change', function() {