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

Commit 7674959

Browse files
Splaktarandrewseguin
authored andcommitted
fix(autocomplete): default dropdown to position bottom as default (#11670)
fix horizontal alignment edge cases fix $mdUtil.getViewportTop to handle when body scrolling is disabled Fixes #11656. Relates to #10859, #11629, #11575.
1 parent b588fd6 commit 7674959

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

src/components/autocomplete/autocomplete.spec.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3014,6 +3014,60 @@ describe('<md-autocomplete>', function() {
30143014
document.body.removeChild(parent[0]);
30153015
}));
30163016

3017+
it('should default dropdown position to the bottom', inject(function($timeout, $window) {
3018+
var scope = createScope();
3019+
scope.topMargin = '0px';
3020+
3021+
scope.match = fakeItemMatch;
3022+
3023+
var template = '<div style="margin-top: {{topMargin}}">' +
3024+
'<md-autocomplete ' +
3025+
'md-search-text="searchText" ' +
3026+
'md-items="item in match(searchText)" ' +
3027+
'md-item-text="item" ' +
3028+
'md-min-length="0" ' +
3029+
'placeholder="placeholder">' +
3030+
'<span md-highlight-text="searchText">{{item}}</span>' +
3031+
'</md-autocomplete>' +
3032+
'</div>';
3033+
3034+
var parent = compile(template, scope);
3035+
var element = parent.find('md-autocomplete');
3036+
var ctrl = element.controller('mdAutocomplete');
3037+
3038+
// Add container to the DOM to be able to test the rect calculations.
3039+
document.body.appendChild(parent[0]);
3040+
3041+
$timeout.flush();
3042+
3043+
// Focus the autocomplete and trigger a query to be able to open the dropdown.
3044+
ctrl.focus();
3045+
scope.$apply('searchText = "Query 1"');
3046+
waitForVirtualRepeat(element);
3047+
3048+
var scrollContainer = document.body.querySelector('.md-virtual-repeat-container');
3049+
3050+
expect(scrollContainer).toBeTruthy();
3051+
// Test that the dropdown displays with position = bottom automatically because there is no
3052+
// room above the element to display the dropdown using position = top.
3053+
expect(scrollContainer.style.bottom).toBe('auto');
3054+
expect(scrollContainer.style.top).toMatch(/[0-9]+px/);
3055+
3056+
// Change position and resize to force a DOM update.
3057+
scope.$apply('topMargin = "300px"');
3058+
3059+
angular.element($window).triggerHandler('resize');
3060+
$timeout.flush();
3061+
3062+
expect(scrollContainer).toBeTruthy();
3063+
// Test that the dropdown displays with position = bottom by default, even when there is room
3064+
// for it to display on the top.
3065+
expect(scrollContainer.style.bottom).toBe('auto');
3066+
expect(scrollContainer.style.top).toMatch(/[0-9]+px/);
3067+
3068+
parent.remove();
3069+
}));
3070+
30173071
it('should allow dropdown position to be specified (virtual list)', inject(function($timeout, $window) {
30183072
var scope = createScope();
30193073

src/components/autocomplete/js/autocompleteController.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,21 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
139139
width = hrect.width,
140140
offset = getVerticalOffset(),
141141
position = $scope.dropdownPosition,
142-
styles;
142+
styles, enoughBottomSpace, enoughTopSpace;
143+
var bottomSpace = root.bottom - vrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();
144+
var topSpace = vrect.top - MENU_PADDING;
143145

144146
// Automatically determine dropdown placement based on available space in viewport.
145147
if (!position) {
146-
position = (vrect.top + MENU_PADDING > dropdownHeight) ? 'top' : 'bottom';
148+
enoughTopSpace = topSpace > dropdownHeight;
149+
enoughBottomSpace = bottomSpace > dropdownHeight;
150+
if (enoughBottomSpace) {
151+
position = 'bottom';
152+
} else if (enoughTopSpace) {
153+
position = 'top';
154+
} else {
155+
position = topSpace > bottomSpace ? 'top' : 'bottom';
156+
}
147157
}
148158
// Adjust the width to account for the padding provided by `md-input-container`
149159
if ($attrs.mdFloatingLabel) {
@@ -159,17 +169,17 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
159169
if (position === 'top') {
160170
styles.top = 'auto';
161171
styles.bottom = bot + 'px';
162-
styles.maxHeight = Math.min(dropdownHeight, hrect.top - root.top - MENU_PADDING) + 'px';
172+
styles.maxHeight = Math.min(dropdownHeight, topSpace) + 'px';
163173
} else {
164-
var bottomSpace = root.bottom - hrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();
174+
bottomSpace = root.bottom - hrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();
165175

166176
styles.top = (top - offset) + 'px';
167177
styles.bottom = 'auto';
168178
styles.maxHeight = Math.min(dropdownHeight, bottomSpace) + 'px';
169179
}
170180

171181
elements.$.scrollContainer.css(styles);
172-
$mdUtil.nextTick(correctHorizontalAlignment, false);
182+
$mdUtil.nextTick(correctHorizontalAlignment, false, $scope);
173183

174184
/**
175185
* Calculates the vertical offset for floating label examples to account for ngMessages
@@ -195,7 +205,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
195205
function correctHorizontalAlignment () {
196206
var dropdown = elements.scrollContainer.getBoundingClientRect(),
197207
styles = {};
198-
if (dropdown.right > root.right - MENU_PADDING) {
208+
if (dropdown.right > root.right) {
199209
styles.left = (hrect.right - dropdown.width) + 'px';
200210
}
201211
elements.$.scrollContainer.css(styles);

src/core/util/util.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,13 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
157157
* @returns {number}
158158
*/
159159
getViewportTop: function() {
160-
return window.scrollY || window.pageYOffset || 0;
160+
// If body scrolling is disabled, then use the cached viewport top value, otherwise get it
161+
// fresh from the $window.
162+
if ($mdUtil.disableScrollAround._count && $mdUtil.disableScrollAround._viewPortTop) {
163+
return $mdUtil.disableScrollAround._viewPortTop;
164+
} else {
165+
return $window.scrollY || $window.pageYOffset || 0;
166+
}
161167
},
162168

163169
/**
@@ -232,6 +238,7 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
232238

233239
return $mdUtil.disableScrollAround._restoreScroll = function() {
234240
if (--$mdUtil.disableScrollAround._count <= 0) {
241+
delete $mdUtil.disableScrollAround._viewPortTop;
235242
restoreBody();
236243
restoreElement();
237244
delete $mdUtil.disableScrollAround._restoreScroll;
@@ -282,6 +289,7 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
282289
var prevBodyStyle = body.style.cssText || '';
283290

284291
var viewportTop = $mdUtil.getViewportTop();
292+
$mdUtil.disableScrollAround._viewPortTop = viewportTop;
285293
var clientWidth = body.clientWidth;
286294
var hasVerticalScrollbar = body.scrollHeight > body.clientHeight + 1;
287295

0 commit comments

Comments
 (0)