Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge branch 'master' into t/ckeditor5/447
Browse files Browse the repository at this point in the history
  • Loading branch information
Reinmar committed Aug 21, 2017
2 parents 49aa9df + 8c131a9 commit 3fba8e4
Show file tree
Hide file tree
Showing 11 changed files with 1,188 additions and 35 deletions.
28 changes: 28 additions & 0 deletions src/dom/getborderwidths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module utils/dom/getborderwidths
*/

import global from './global';

/**
* Returns an object containing CSS border widths of a specified HTML element.
*
* @param {HTMLElement} element An element which has CSS borders.
* @param {Object} An object containing `top`, `left`, `right` and `bottom` properties
* with numerical values of the `border-[top,left,right,bottom]-width` CSS styles.
*/
export default function getBorderWidths( element ) {
const style = global.window.getComputedStyle( element );

return {
top: parseInt( style.borderTopWidth, 10 ),
right: parseInt( style.borderRightWidth, 10 ),
bottom: parseInt( style.borderBottomWidth, 10 ),
left: parseInt( style.borderLeftWidth, 10 )
};
}
19 changes: 13 additions & 6 deletions src/dom/position.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import global from './global';
import Rect from './rect';
import getPositionedAncestor from './getpositionedancestor';
import getBorderWidths from './getborderwidths';

/**
* Calculates the `position: absolute` coordinates of a given element so it can be positioned with respect to the
Expand Down Expand Up @@ -82,6 +83,12 @@ export function getOptimalPosition( { element, target, positions, limiter, fitIn
target = target();
}

// If the {@link module:utils/dom/position~Options#limiter} is a function, use what it returns.
// https://github.com/ckeditor/ckeditor5-ui/issues/260
if ( typeof limiter == 'function' ) {
limiter = limiter();
}

const positionedElementAncestor = getPositionedAncestor( element.parentElement );
const elementRect = new Rect( element );
const targetRect = new Rect( target );
Expand All @@ -94,7 +101,7 @@ export function getOptimalPosition( { element, target, positions, limiter, fitIn
[ name, bestPosition ] = getPosition( positions[ 0 ], targetRect, elementRect );
} else {
const limiterRect = limiter && new Rect( limiter ).getVisible();
const viewportRect = fitInViewport && Rect.getViewportRect();
const viewportRect = fitInViewport && new Rect( global.window );

[ name, bestPosition ] =
getBestPosition( positions, targetRect, elementRect, limiterRect, viewportRect ) ||
Expand All @@ -107,7 +114,7 @@ export function getOptimalPosition( { element, target, positions, limiter, fitIn

if ( positionedElementAncestor ) {
const ancestorPosition = getAbsoluteRectCoordinates( new Rect( positionedElementAncestor ) );
const ancestorComputedStyles = global.window.getComputedStyle( positionedElementAncestor );
const ancestorBorderWidths = getBorderWidths( positionedElementAncestor );

// (https://github.com/ckeditor/ckeditor5-ui-default/issues/126)
// If there's some positioned ancestor of the panel, then its `Rect` must be taken into
Expand All @@ -129,8 +136,8 @@ export function getOptimalPosition( { element, target, positions, limiter, fitIn
// while `position: absolute` positioning does not consider it.
// E.g. `{ position: absolute, top: 0, left: 0 }` means upper left corner of the element,
// not upper-left corner of its border.
left -= parseInt( ancestorComputedStyles.borderLeftWidth, 10 );
top -= parseInt( ancestorComputedStyles.borderTopWidth, 10 );
left -= ancestorBorderWidths.left;
top -= ancestorBorderWidths.top;
}

return { left, top, name };
Expand Down Expand Up @@ -261,7 +268,7 @@ function getAbsoluteRectCoordinates( { left, top } ) {
/**
* Target with respect to which the `element` is to be positioned.
*
* @member {HTMLElement|Range|ClientRect|Function} #target
* @member {HTMLElement|Range|ClientRect|Rect|Function} #target
*/

/**
Expand All @@ -275,7 +282,7 @@ function getAbsoluteRectCoordinates( { left, top } ) {
* When set, the algorithm will chose position which fits the most in the
* limiter's bounding rect.
*
* @member {HTMLElement|Range|ClientRect} #limiter
* @member {HTMLElement|Range|ClientRect|Rect|Function} #limiter
*/

/**
Expand Down
105 changes: 85 additions & 20 deletions src/dom/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
import global from './global';
import isRange from './isrange';
import isElement from '../lib/lodash/isElement';
import getBorderWidths from './getborderwidths';

/**
* A helper class representing a `ClientRect` object, e.g. value returned by
* the native `object.getBoundingClientRect()` method. Provides a set of methods
* to manipulate the rect and compare it against other `Rect` instances.
* to manipulate the rect and compare it against other rect instances.
*/
export default class Rect {
/**
Expand All @@ -26,16 +27,23 @@ export default class Rect {
* // Rect of a DOM Range.
* const rectB = new Rect( document.getSelection().getRangeAt( 0 ) );
*
* // Rect of a window (web browser viewport).
* const rectC = new Rect( window );
*
* // Rect out of an object.
* const rectC = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
* const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
*
* // Rect out of another Rect instance.
* const rectD = new Rect( rectC );
* const rectE = new Rect( rectD );
*
* // Rect out of a ClientRect.
* const rectE = new Rect( document.body.getClientRects().item( 0 ) );
* const rectF = new Rect( document.body.getClientRects().item( 0 ) );
*
* **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any)
* ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders}
* to get the inner part of the rect.
*
* @param {HTMLElement|Range|ClientRect|module:utils/dom/rect~Rect|Object} source A source object to create the rect.
* @param {HTMLElement|Range|Window|ClientRect|module:utils/dom/rect~Rect|Object} source A source object to create the rect.
*/
constructor( source ) {
/**
Expand All @@ -46,16 +54,27 @@ export default class Rect {
* @member {HTMLElement|Range|ClientRect|module:utils/dom/rect~Rect|Object} #_source
*/
Object.defineProperty( this, '_source', {
// source._source if already the Rect instance
// If the source is a Rect instance, copy it's #_source.
value: source._source || source,
writable: false,
writable: true,
enumerable: false
} );

if ( isElement( source ) ) {
copyRectProperties( this, source.getBoundingClientRect() );
} else if ( isRange( source ) ) {
copyRectProperties( this, Rect.getDomRangeRects( source )[ 0 ] );
} else if ( source === global.window ) {
const { innerWidth, innerHeight } = global.window;

copyRectProperties( this, {
top: 0,
right: innerWidth,
bottom: innerHeight,
left: 0,
width: innerWidth,
height: innerHeight
} );
} else {
copyRectProperties( this, source );
}
Expand Down Expand Up @@ -234,21 +253,67 @@ export default class Rect {
}

/**
* Returns a rect of the web browser viewport.
* Checks if all property values ({@link #top}, {@link #left}, {@link #right},
* {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect
* instances.
*
* @returns {module:utils/dom/rect~Rect} A viewport rect.
* @param {module:utils/dom/rect~Rect} rect A rect instance to compare with.
* @returns {Boolean} `true` when Rects are equal. `false` otherwise.
*/
static getViewportRect() {
const { innerWidth, innerHeight } = global.window;

return new Rect( {
top: 0,
right: innerWidth,
bottom: innerHeight,
left: 0,
width: innerWidth,
height: innerHeight
} );
isEqual( anotherRect ) {
for ( const prop of rectProperties ) {
if ( this[ prop ] !== anotherRect[ prop ] ) {
return false;
}
}

return true;
}

/**
* Checks whether a rect fully contains another rect instance.
*
* @param {module:utils/dom/rect~Rect} anotherRect
* @returns {Boolean} `true` if contains, `false` otherwise.
*/
contains( anotherRect ) {
const intersectRect = this.getIntersection( anotherRect );

return !!( intersectRect && intersectRect.isEqual( anotherRect ) );
}

/**
* Excludes scrollbars and CSS borders from the rect.
*
* * Borders are removed when {@link #_source} is an HTML element.
* * Scrollbars are excluded from HTML elements and the `window`.
*
* @returns {module:utils/dom/rect~Rect} A rect which has been updated.
*/
excludeScrollbarsAndBorders() {
const source = this._source;
let scrollBarWidth, scrollBarHeight;

if ( source === global.window ) {
scrollBarWidth = global.window.innerWidth - global.document.documentElement.clientWidth;
scrollBarHeight = global.window.innerHeight - global.document.documentElement.clientHeight;
} else {
const borderWidths = getBorderWidths( this._source );

scrollBarWidth = source.offsetWidth - source.clientWidth;
scrollBarHeight = source.offsetHeight - source.clientHeight;

this.moveBy( borderWidths.left, borderWidths.top );
}

// Assuming LTR scrollbars. TODO: RTL.
this.width -= scrollBarWidth;
this.right -= scrollBarWidth;

this.height -= scrollBarHeight;
this.bottom -= scrollBarHeight;

return this;
}

/**
Expand Down
Loading

0 comments on commit 3fba8e4

Please sign in to comment.