Skip to content

Commit

Permalink
fix(datepicker): stop calendar going off-screen if body is scrollable.
Browse files Browse the repository at this point in the history
…Fixes angular#4781.
  • Loading branch information
jelbourn committed Sep 28, 2015
1 parent b983c0d commit 9c30038
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 37 deletions.
36 changes: 19 additions & 17 deletions src/components/datepicker/datePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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');
}

Expand Down
67 changes: 47 additions & 20 deletions src/components/datepicker/datePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -194,9 +195,35 @@ describe('md-date-picker', function() {
document.body.removeChild(element);
});

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);

// 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() {
// Make the body narrow so that the calendar pane won't fit on-screen.
document.body.style.width = '300px';
// Fake the window being very narrow so that the calendar pane won't fit on-screen.
var innerWidth = $window.innerWidth;
$window.innerWidth = 20;

// Open the calendar pane.
element.querySelector('md-button').click();
Expand All @@ -205,30 +232,30 @@ describe('md-date-picker', function() {
// 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 = '';
// Reset the width.
$window.innerWidth = innerWidth;
});

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() {
Expand Down

0 comments on commit 9c30038

Please sign in to comment.