Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

fix(modal): body content shift #5711

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require('../stackedMap');
require('../../template/modal/backdrop.html.js');
require('../../template/modal/window.html.js');
require('./modal');
require('../position/position.css');

var MODULE_NAME = 'ui.bootstrap.module.modal';

Expand Down
27 changes: 18 additions & 9 deletions src/modal/modal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
/**
* A helper, internal data structure that stores all references attached to key
*/
Expand Down Expand Up @@ -247,8 +247,8 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
})

.factory('$uibModalStack', ['$animate', '$animateCss', '$document',
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
var OPENED_MODAL_CLASS = 'modal-open';

var backdropDomEl, backdropScope;
Expand All @@ -262,6 +262,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
var tabableSelector = 'a[href], area[href], input:not([disabled]), ' +
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
var scrollbarPadding;

function isVisible(element) {
return !!(element.offsetWidth ||
Expand Down Expand Up @@ -297,6 +298,14 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
openedClasses.remove(modalBodyClass, modalInstance);
appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
if (scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
if (scrollbarPadding.originalRight) {
appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
} else {
appendToElement.css({paddingRight: ''});
}
scrollbarPadding = null;
}
toggleTopWindowClass(true);
}, modalWindow.closedDeferred);
checkRemoveBackdrop();
Expand Down Expand Up @@ -472,12 +481,12 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
angularDomEl.attr('modal-animation', 'true');
}

$animate.enter($compile(angularDomEl)(modal.scope), appendToElement)
.then(function() {
if (!modal.scope.$$uibDestructionScheduled) {
$animate.addClass(appendToElement, modalBodyClass);
}
});
scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
}
appendToElement.addClass(modalBodyClass);
$animate.enter($compile(angularDomEl)(modal.scope), appendToElement);

openedWindows.top().value.modalDomEl = angularDomEl;
openedWindows.top().value.modalOpener = modalOpener;
Expand Down
52 changes: 50 additions & 2 deletions src/position/docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,60 @@ Gets the closest positioned ancestor.
* _(Type: `element`)_ -
The closest positioned ancestor.

#### scrollbarWidth()
#### scrollbarWidth(isBody)

Calculates the browser scrollbar width and caches the result for future calls. Concept from the TWBS measureScrollbar() function in [modal.js](https://github.com/twbs/bootstrap/blob/master/js/modal.js).

##### parameters

* `isBody`
_(Type: `boolean`, Default: `false`, optional)_ - Is the requested scrollbar width for the body/html element. IE and Edge overlay the scrollbar on the body/html element and should be considered 0.

##### returns

* _(Type: `number`)_ -
The width of the browser scrollbar.

#### scrollbarPadding(element)

Calculates the padding required to replace the scrollbar on an element.

##### parameters

* 'element' _(Type: `element`)_ - The element to calculate the padding on (should be a scrollable element).

##### returns

An object with the following properties:

* `scrollbarWidth`
_(Type: `number`)_ -
The width of the scrollbar.

* `widthOverflow`
_(Type: `boolean`)_ -
Whether the width is overflowing.

* `right`
_(Type: `number`)_ -
The total right padding required to replace the scrollbar.

* `originalRight`
_(Type: `number`)_ -
The oringal right padding on the element.

* `heightOverflow`
_(Type: `boolean`)_ -
Whether the height is overflowing.

* `bottom`
_(Type: `number`)_ -
The total bottom padding required to replace the scrollbar.

* `originalBottom`
_(Type: `number`)_ -
The oringal bottom padding on the element.

#### isScrollable(element, includeHidden)

Determines if an element is scrollable.
Expand All @@ -72,7 +117,7 @@ Determines if an element is scrollable.
* _(Type: `boolean`)_ -
Whether the element is scrollable.

#### scrollParent(element, includeHidden)
#### scrollParent(element, includeHidden, includeSelf)

Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.js](https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js).

Expand All @@ -85,6 +130,9 @@ Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.j
* `includeHidden`
_(Type: `boolean`, Default: `false`, optional)_ - Should scroll style of 'hidden' be considered.

* `includeSelf`
_(Type: `boolean`, Default: `false`, optional)_ - Should the element passed in be included in the scrollable lookup.

##### returns

* _(Type: `element`)_ -
Expand Down
14 changes: 9 additions & 5 deletions src/position/position.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
}

.uib-position-scrollbar-measure {
position: absolute;
top: -9999px;
width: 50px;
height: 50px;
overflow: scroll;
position: absolute !important;
top: -9999px !important;
width: 50px !important;
height: 50px !important;
overflow: scroll !important;
}

.uib-position-body-scrollbar-measure {
overflow: scroll !important;
}
62 changes: 60 additions & 2 deletions src/position/position.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ angular.module('ui.bootstrap.position', [])
* Do not access this variable directly, use scrollbarWidth() instead.
*/
var SCROLLBAR_WIDTH;
/**
* scrollbar on body and html element in IE and Edge overlay
* content and should be considered 0 width.
*/
var BODY_SCROLLBAR_WIDTH;
var OVERFLOW_REGEX = {
normal: /(auto|scroll)/,
hidden: /(auto|scroll|hidden)/
Expand All @@ -22,6 +27,7 @@ angular.module('ui.bootstrap.position', [])
secondary: /^(top|bottom|left|right|center)$/,
vertical: /^(top|bottom)$/
};
var BODY_REGEX = /(HTML|BODY)/;

return {

Expand Down Expand Up @@ -75,10 +81,23 @@ angular.module('ui.bootstrap.position', [])
/**
* Provides the scrollbar width, concept from TWBS measureScrollbar()
* function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
* In IE and Edge, scollbar on body and html element overlay and should
* return a width of 0.
*
* @returns {number} The width of the browser scollbar.
*/
scrollbarWidth: function() {
scrollbarWidth: function(isBody) {
if (isBody) {
if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
var bodyElem = $document.find('body');
bodyElem.addClass('uib-position-body-scrollbar-measure');
BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
bodyElem.removeClass('uib-position-body-scrollbar-measure');
}
return BODY_SCROLLBAR_WIDTH;
}

if (angular.isUndefined(SCROLLBAR_WIDTH)) {
var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
$document.find('body').append(scrollElem);
Expand All @@ -90,6 +109,40 @@ angular.module('ui.bootstrap.position', [])
return SCROLLBAR_WIDTH;
},

/**
* Provides the padding required on an element to replace the scrollbar.
*
* @returns {object} An object with the following properties:
* <ul>
* <li>**scrollbarWidth**: the width of the scrollbar</li>
* <li>**widthOverflow**: whether the the width is overflowing</li>
* <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
* <li>**rightOriginal**: the amount of right padding currently on the element</li>
* <li>**heightOverflow**: whether the the height is overflowing</li>
* <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
* <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
* </ul>
*/
scrollbarPadding: function(elem) {
elem = this.getRawNode(elem);

var elemStyle = $window.getComputedStyle(elem);
var paddingRight = this.parseStyle(elemStyle.paddingRight);
var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
var scrollParent = this.scrollParent(elem, false, true);
var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName));

return {
scrollbarWidth: scrollbarWidth,
widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
right: paddingRight + scrollbarWidth,
originalRight: paddingRight,
heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
bottom: paddingBottom + scrollbarWidth,
originalBottom: paddingBottom
};
},

/**
* Checks to see if the element is scrollable.
*
Expand All @@ -115,15 +168,20 @@ angular.module('ui.bootstrap.position', [])
* @param {element} elem - The element to find the scroll parent of.
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
* default is false.
* @param {boolean=} [includeSelf=false] - Should the element being passed be
* included in the scrollable llokup.
*
* @returns {element} A HTML element.
*/
scrollParent: function(elem, includeHidden) {
scrollParent: function(elem, includeHidden, includeSelf) {
elem = this.getRawNode(elem);

var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
var documentEl = $document[0].documentElement;
var elemStyle = $window.getComputedStyle(elem);
if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
return elem;
}
var excludeStatic = elemStyle.position === 'absolute';
var scrollParent = elem.parentElement || documentEl;

Expand Down