+ direction = this.$floatableContainer.css( 'direction' );
+ $offsetParent = this.$floatable.offsetParent();
+ if ( $offsetParent.is( 'html' ) ) {
+ // The innerHeight/Width and clientHeight/Width calculations don't work well on the
+ // <html> element, but they do work on the <body>
+ $offsetParent = $( $offsetParent[ 0 ].ownerDocument.body );
+ }
+ isBody = $offsetParent.is( 'body' );
+ scrollableX = $offsetParent.css( 'overflow-x' ) === 'scroll' || $offsetParent.css( 'overflow-x' ) === 'auto';
+ scrollableY = $offsetParent.css( 'overflow-y' ) === 'scroll' || $offsetParent.css( 'overflow-y' ) === 'auto';
+
+ vertScrollbarWidth = $offsetParent.innerWidth() - $offsetParent.prop( 'clientWidth' );
+ horizScrollbarHeight = $offsetParent.innerHeight() - $offsetParent.prop( 'clientHeight' );
+ // We don't need to compute and add scrollTop and scrollLeft if the scrollable container is the body,
+ // or if it isn't scrollable
+ scrollTop = scrollableY && !isBody ? $offsetParent.scrollTop() : 0;
+ scrollLeft = scrollableX && !isBody ? OO.ui.Element.static.getScrollLeft( $offsetParent[ 0 ] ) : 0;
+
+ // Avoid passing the <body> to getRelativePosition(), because it won't return what we expect
+ // if the <body> has a margin
+ containerPos = isBody ?
+ this.$floatableContainer.offset() :
+ OO.ui.Element.static.getRelativePosition( this.$floatableContainer, $offsetParent );
+ containerPos.bottom = containerPos.top + this.$floatableContainer.outerHeight();
+ containerPos.right = containerPos.left + this.$floatableContainer.outerWidth();
+ containerPos.start = direction === 'rtl' ? containerPos.right : containerPos.left;
+ containerPos.end = direction === 'rtl' ? containerPos.left : containerPos.right;
+
+ if ( this.verticalPosition === 'below' ) {
+ newPos.top = containerPos.bottom;
+ } else if ( this.verticalPosition === 'above' ) {
+ newPos.bottom = $offsetParent.outerHeight() - containerPos.top;
+ } else if ( this.verticalPosition === 'top' ) {
+ newPos.top = containerPos.top;
+ } else if ( this.verticalPosition === 'bottom' ) {
+ newPos.bottom = $offsetParent.outerHeight() - containerPos.bottom;
+ } else if ( this.verticalPosition === 'center' ) {
+ newPos.top = containerPos.top +
+ ( this.$floatableContainer.height() - this.$floatable.height() ) / 2;
+ }
+
+ if ( this.horizontalPosition === 'before' ) {
+ newPos.end = containerPos.start;
+ } else if ( this.horizontalPosition === 'after' ) {
+ newPos.start = containerPos.end;
+ } else if ( this.horizontalPosition === 'start' ) {
+ newPos.start = containerPos.start;
+ } else if ( this.horizontalPosition === 'end' ) {
+ newPos.end = containerPos.end;
+ } else if ( this.horizontalPosition === 'center' ) {
+ newPos.left = containerPos.left +
+ ( this.$floatableContainer.width() - this.$floatable.width() ) / 2;
+ }
+
+ if ( newPos.start !== undefined ) {
+ if ( direction === 'rtl' ) {
+ newPos.right = ( isBody ? $( $offsetParent[ 0 ].ownerDocument.documentElement ) : $offsetParent ).outerWidth() - newPos.start;
+ } else {
+ newPos.left = newPos.start;
+ }
+ delete newPos.start;
+ }
+ if ( newPos.end !== undefined ) {
+ if ( direction === 'rtl' ) {
+ newPos.left = newPos.end;
+ } else {
+ newPos.right = ( isBody ? $( $offsetParent[ 0 ].ownerDocument.documentElement ) : $offsetParent ).outerWidth() - newPos.end;
+ }
+ delete newPos.end;
+ }
+
+ // Account for scroll position
+ if ( newPos.top !== '' ) {
+ newPos.top += scrollTop;
+ }
+ if ( newPos.bottom !== '' ) {
+ newPos.bottom -= scrollTop;
+ }
+ if ( newPos.left !== '' ) {
+ newPos.left += scrollLeft;
+ }
+ if ( newPos.right !== '' ) {
+ newPos.right -= scrollLeft;