浏览代码

Merge pull request #154 from ckeditor/t/153

Fix: Rect utility should work for collapsed DOM Ranges. Closes #153.
Piotrek Koszuliński 8 年之前
父节点
当前提交
c41b4b5f4b
共有 2 个文件被更改,包括 66 次插入7 次删除
  1. 37 6
      packages/ckeditor5-utils/src/dom/rect.js
  2. 29 1
      packages/ckeditor5-utils/tests/dom/rect.js

+ 37 - 6
packages/ckeditor5-utils/src/dom/rect.js

@@ -11,8 +11,6 @@ import global from './global';
 import isRange from './isrange';
 import isElement from '../lib/lodash/isElement';
 
-const rectProperties = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ];
-
 /**
  * A helper class representing a `ClientRect` object, e.g. value returned by
  * the native `object.getBoundingClientRect()` method. Provides a set of methods
@@ -54,11 +52,31 @@ export default class Rect {
 			enumerable: false
 		} );
 
-		if ( isElement( source ) || isRange( source ) ) {
-			source = source.getBoundingClientRect();
-		}
+		if ( isElement( source ) ) {
+			copyRectProperties( this, source.getBoundingClientRect() );
+		} else if ( isRange( source ) ) {
+			// Use getClientRects() when the range is collapsed.
+			// https://github.com/ckeditor/ckeditor5-utils/issues/153
+			if ( source.collapsed ) {
+				const rects = source.getClientRects();
 
-		rectProperties.forEach( p => this[ p ] = source[ p ] );
+				if ( rects.length ) {
+					copyRectProperties( this, rects[ 0 ] );
+				}
+				// If there's no client rects for the Range, use parent container's bounding
+				// rect instead and adjust rect's width to simulate the actual geometry of such
+				// range.
+				// https://github.com/ckeditor/ckeditor5-utils/issues/153
+				else {
+					copyRectProperties( this, source.startContainer.getBoundingClientRect() );
+					this.width = 0;
+				}
+			} else {
+				copyRectProperties( this, source.getBoundingClientRect() );
+			}
+		} else {
+			copyRectProperties( this, source );
+		}
 
 		/**
 		 * The "top" value of the rect.
@@ -251,3 +269,16 @@ export default class Rect {
 		} );
 	}
 }
+
+const rectProperties = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ];
+
+// Acquires all the rect properties from the passed source.
+//
+// @private
+// @param {module:utils/dom/rect~Rect} rect
+// @param {ClientRect|module:utils/dom/rect~Rect|Object} source
+function copyRectProperties( rect, source ) {
+	for ( const p of rectProperties ) {
+		rect[ p ] = source[ p ];
+	}
+}

+ 29 - 1
packages/ckeditor5-utils/tests/dom/rect.js

@@ -41,14 +41,41 @@ describe( 'Rect', () => {
 			assertRect( new Rect( element ), geometry );
 		} );
 
-		it( 'should accept Range', () => {
+		it( 'should accept Range (non–collapsed)', () => {
 			const range = document.createRange();
 
+			range.selectNode( document.body );
 			testUtils.sinon.stub( range, 'getBoundingClientRect' ).returns( geometry );
 
 			assertRect( new Rect( range ), geometry );
 		} );
 
+		// https://github.com/ckeditor/ckeditor5-utils/issues/153
+		it( 'should accept Range (collapsed)', () => {
+			const range = document.createRange();
+
+			range.collapse();
+			testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
+
+			assertRect( new Rect( range ), geometry );
+		} );
+
+		// https://github.com/ckeditor/ckeditor5-utils/issues/153
+		it( 'should accept Range (collapsed, no Range rects available)', () => {
+			const range = document.createRange();
+			const element = document.createElement( 'div' );
+
+			range.setStart( element, 0 );
+			range.collapse();
+			testUtils.sinon.stub( range, 'getClientRects' ).returns( [] );
+			testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
+
+			const expectedGeometry = Object.assign( {}, geometry );
+			expectedGeometry.width = 0;
+
+			assertRect( new Rect( range ), expectedGeometry );
+		} );
+
 		it( 'should accept Rect', () => {
 			const sourceRect = new Rect( geometry );
 			const rect = new Rect( sourceRect );
@@ -481,6 +508,7 @@ describe( 'Rect', () => {
 
 		it( 'should return the visible rect (Range), partially cropped', () => {
 			range.setStart( ancestorA, 0 );
+			range.setEnd( ancestorA, 1 );
 
 			testUtils.sinon.stub( range, 'getBoundingClientRect' ).returns( {
 				top: 0,