Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 3c876c1

Browse files
committed
fix(datepicker): stop calendar going off-screen if body is scrollable. Fixes #4781.
1 parent b983c0d commit 3c876c1

File tree

2 files changed

+76
-48
lines changed

2 files changed

+76
-48
lines changed

src/components/datepicker/datePicker.js

+29-25
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,17 @@
131131
*
132132
* @ngInject @constructor
133133
*/
134-
function DatePickerCtrl($scope, $element, $attrs, $compile, $timeout, $mdConstant, $mdTheming,
135-
$mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF) {
134+
function DatePickerCtrl($scope, $element, $attrs, $compile, $timeout, $window,
135+
$mdConstant, $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF) {
136136
/** @final */
137137
this.$compile = $compile;
138138

139139
/** @final */
140140
this.$timeout = $timeout;
141141

142+
/** @final */
143+
this.$window = $window;
144+
142145
/** @final */
143146
this.dateLocale = $mdDateLocale;
144147

@@ -387,34 +390,33 @@
387390
var paneTop = elementRect.top - bodyRect.top;
388391
var paneLeft = elementRect.left - bodyRect.left;
389392

393+
var viewportTop = document.body.scrollTop;
394+
var viewportBottom = viewportTop + this.$window.innerHeight;
395+
396+
var viewportLeft = document.body.scrollLeft;
397+
var viewportRight = document.body.scrollLeft + this.$window.innerWidth;
398+
390399
// If the right edge of the pane would be off the screen and shifting it left by the
391400
// difference would not go past the left edge of the screen. If the calendar pane is too
392401
// big to fit on the screen at all, move it to the left of the screen and scale the entire
393402
// element down to fit.
394-
if (paneLeft + CALENDAR_PANE_WIDTH > bodyRect.right) {
395-
if (bodyRect.right - CALENDAR_PANE_WIDTH > 0) {
396-
paneLeft = bodyRect.right - CALENDAR_PANE_WIDTH;
403+
if (paneLeft + CALENDAR_PANE_WIDTH > viewportRight) {
404+
if (viewportRight - CALENDAR_PANE_WIDTH > 0) {
405+
paneLeft = viewportRight - CALENDAR_PANE_WIDTH;
397406
} else {
398-
paneLeft = 0;
399-
var scale = bodyRect.width / CALENDAR_PANE_WIDTH;
407+
paneLeft = viewportLeft;
408+
var scale = this.$window.innerWidth / CALENDAR_PANE_WIDTH;
400409
calendarPane.style.transform = 'scale(' + scale + ')';
401410
}
402411

403412
calendarPane.classList.add('md-datepicker-pos-adjusted');
404413
}
405414

406-
407-
if (paneLeft + CALENDAR_PANE_WIDTH > bodyRect.right &&
408-
bodyRect.right - CALENDAR_PANE_WIDTH > 0) {
409-
paneLeft = bodyRect.right - CALENDAR_PANE_WIDTH;
410-
calendarPane.classList.add('md-datepicker-pos-adjusted');
411-
}
412-
413415
// If the bottom edge of the pane would be off the screen and shifting it up by the
414416
// difference would not go past the top edge of the screen.
415-
if (paneTop + CALENDAR_PANE_HEIGHT > bodyRect.bottom &&
416-
bodyRect.bottom - CALENDAR_PANE_HEIGHT > 0) {
417-
paneTop = bodyRect.bottom - CALENDAR_PANE_HEIGHT;
417+
if (paneTop + CALENDAR_PANE_HEIGHT > viewportBottom &&
418+
viewportBottom - CALENDAR_PANE_HEIGHT > viewportTop) {
419+
paneTop = viewportBottom - CALENDAR_PANE_HEIGHT;
418420
calendarPane.classList.add('md-datepicker-pos-adjusted');
419421
}
420422

@@ -478,14 +480,16 @@
478480

479481
/** Close the floating calendar pane. */
480482
DatePickerCtrl.prototype.closeCalendarPane = function() {
481-
this.isCalendarOpen = false;
482-
this.detachCalendarPane();
483-
this.calendarPaneOpenedFrom.focus();
484-
this.calendarPaneOpenedFrom = null;
485-
this.$mdUtil.enableScrolling();
486-
487-
document.body.removeEventListener('click', this.bodyClickHandler);
488-
window.removeEventListener('resize', this.windowResizeHandler);
483+
if (this.isCalendarOpen) {
484+
this.isCalendarOpen = false;
485+
this.detachCalendarPane();
486+
this.calendarPaneOpenedFrom.focus();
487+
this.calendarPaneOpenedFrom = null;
488+
this.$mdUtil.enableScrolling();
489+
490+
document.body.removeEventListener('click', this.bodyClickHandler);
491+
window.removeEventListener('resize', this.windowResizeHandler);
492+
}
489493
};
490494

491495
/** Gets the controller instance for the calendar in the floating pane. */

src/components/datepicker/datePicker.spec.js

+47-23
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ describe('md-date-picker', function() {
88
var initialDate = new Date(2015, FEB, 15);
99

1010
var ngElement, element, scope, pageScope, controller;
11-
var $timeout, $$rAF, $animate, keyCodes, dateUtil, dateLocale;
11+
var $timeout, $$rAF, $animate, $window, keyCodes, dateUtil, dateLocale;
1212

1313
beforeEach(module('material.components.datepicker', 'ngAnimateMock'));
1414

1515
beforeEach(inject(function($compile, $rootScope, $injector) {
1616
$$rAF = $injector.get('$$rAF');
1717
$animate = $injector.get('$animate');
18+
$window = $injector.get('$window');
1819
dateUtil = $injector.get('$$mdDateUtil');
1920
dateLocale = $injector.get('$mdDateLocale');
2021
$timeout = $injector.get('$timeout');
@@ -37,6 +38,8 @@ describe('md-date-picker', function() {
3738
scope = ngElement.isolateScope();
3839
controller = ngElement.controller('mdDatepicker');
3940
element = ngElement[0];
41+
42+
controller.closeCalendarPane();
4043
}));
4144

4245
/**
@@ -194,41 +197,62 @@ describe('md-date-picker', function() {
194197
document.body.removeChild(element);
195198
});
196199

197-
it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
198-
// Make the body narrow so that the calendar pane won't fit on-screen.
199-
document.body.style.width = '300px';
200+
it('should adjust the pane position if it would go off-screen (w/ scrollable)', function() {
201+
// Make the body super huge.
202+
var superLongElement = document.createElement('div');
203+
superLongElement.style.height = '10000px';
204+
superLongElement.style.width = '1px';
205+
document.body.appendChild(superLongElement);
200206

201-
// Open the calendar pane.
207+
// Absolutely position the picker near (say ~30px) the edge of the viewport.
208+
element.style.position = 'absolute';
209+
element.style.top = (window.innerHeight - 30) + 'px';
210+
element.style.left = '0';
211+
document.body.appendChild(element);
212+
213+
// Open the pane.
202214
element.querySelector('md-button').click();
203215
$timeout.flush();
204216

217+
// Expect that the pane is on-screen.
218+
var paneRect = controller.calendarPane.getBoundingClientRect();
219+
expect(paneRect.bottom).toBeLessThan(window.innerHeight + 1);
220+
document.body.removeChild(superLongElement);
221+
222+
document.body.removeChild(element);
223+
});
224+
225+
it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
226+
// Fake the window being very narrow so that the calendar pane won't fit on-screen.
227+
controller.$window = {innerWidth: 200, innherHeight: 800};
228+
229+
// Open the calendar pane.
230+
controller.openCalendarPane({});
231+
205232
// Expect the calendarPane to be scaled by an amount between zero and one.
206233
expect(controller.calendarPane.style.transform).toMatch(/scale\(0\.\d+\)/);
207-
208-
// Reset the body width.
209-
document.body.style.width = '';
210234
});
211235

212236
it('should not open the calendar pane if disabled', function() {
213-
controller.setDisabled(true);
214-
controller.openCalendarPane({
215-
target: controller.inputElement
237+
controller.setDisabled(true);
238+
controller.openCalendarPane({
239+
target: controller.inputElement
240+
});
241+
scope.$apply();
242+
expect(controller.isCalendarOpen).toBeFalsy();
243+
expect(controller.calendarPane.offsetHeight).toBe(0);
216244
});
217-
scope.$apply();
218-
expect(controller.isCalendarOpen).toBeFalsy();
219-
expect(controller.calendarPane.offsetHeight).toBe(0);
220-
});
221245

222246
it('should close the calendar pane on md-calendar-close', function() {
223-
controller.openCalendarPane({
224-
target: controller.inputElement
225-
});
247+
controller.openCalendarPane({
248+
target: controller.inputElement
249+
});
226250

227-
scope.$emit('md-calendar-close');
228-
scope.$apply();
229-
expect(controller.calendarPaneOpenedFrom).toBe(null);
230-
expect(controller.isCalendarOpen).toBe(false);
231-
});
251+
scope.$emit('md-calendar-close');
252+
scope.$apply();
253+
expect(controller.calendarPaneOpenedFrom).toBe(null);
254+
expect(controller.isCalendarOpen).toBe(false);
255+
});
232256
});
233257

234258
describe('md-calendar-change', function() {

0 commit comments

Comments
 (0)