Browse Source

Other: Provided a static `Rect#getDomRangeRects` method for external usage. Closes #168.

Aleksander Nowodzinski 8 years ago
parent
commit
b1f09018dd
2 changed files with 82 additions and 22 deletions
  1. 31 19
      packages/ckeditor5-utils/src/dom/rect.js
  2. 51 3
      packages/ckeditor5-utils/tests/dom/rect.js

+ 31 - 19
packages/ckeditor5-utils/src/dom/rect.js

@@ -55,25 +55,7 @@ export default class Rect {
 		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();
-
-				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() );
-			}
+			copyRectProperties( this, Rect.getDomRangeRects( source )[ 0 ] );
 		} else {
 			copyRectProperties( this, source );
 		}
@@ -268,6 +250,36 @@ export default class Rect {
 			height: innerHeight
 		} );
 	}
+
+	/**
+	 * Returns an array of rects of the given native DOM Range.
+	 *
+	 * @param {Range} range A native DOM range.
+	 * @returns {Array.<module:utils/dom/rect~Rect>} DOM Range rects.
+	 */
+	static getDomRangeRects( range ) {
+		const rects = [];
+		// Safari does not iterate over ClientRectList using for...of loop.
+		const clientRects = Array.from( range.getClientRects() );
+
+		if ( clientRects.length ) {
+			for ( const rect of clientRects ) {
+				rects.push( new Rect( rect ) );
+			}
+		}
+		// 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 {
+			const startContainerRect = new Rect( range.startContainer.getBoundingClientRect() );
+			startContainerRect.right = startContainerRect.left;
+			startContainerRect.width = 0;
+
+			rects.push( startContainerRect );
+		}
+
+		return rects;
+	}
 }
 
 const rectProperties = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ];

+ 51 - 3
packages/ckeditor5-utils/tests/dom/rect.js

@@ -45,7 +45,7 @@ describe( 'Rect', () => {
 			const range = document.createRange();
 
 			range.selectNode( document.body );
-			testUtils.sinon.stub( range, 'getBoundingClientRect' ).returns( geometry );
+			testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
 
 			assertRect( new Rect( range ), geometry );
 		} );
@@ -71,6 +71,7 @@ describe( 'Rect', () => {
 			testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
 
 			const expectedGeometry = Object.assign( {}, geometry );
+			expectedGeometry.right = expectedGeometry.left;
 			expectedGeometry.width = 0;
 
 			assertRect( new Rect( range ), expectedGeometry );
@@ -510,14 +511,14 @@ describe( 'Rect', () => {
 			range.setStart( ancestorA, 0 );
 			range.setEnd( ancestorA, 1 );
 
-			testUtils.sinon.stub( range, 'getBoundingClientRect' ).returns( {
+			testUtils.sinon.stub( range, 'getClientRects' ).returns( [ {
 				top: 0,
 				right: 100,
 				bottom: 100,
 				left: 0,
 				width: 100,
 				height: 100
-			} );
+			} ] );
 
 			testUtils.sinon.stub( ancestorA, 'getBoundingClientRect' ).returns( {
 				top: 50,
@@ -584,6 +585,53 @@ describe( 'Rect', () => {
 			} );
 		} );
 	} );
+
+	describe( 'getDomRangeRects() ', () => {
+		it( 'should return rects for a Range (non–collapsed)', () => {
+			const range = document.createRange();
+
+			range.selectNode( document.body );
+			testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
+
+			const rects = Rect.getDomRangeRects( range );
+			expect( rects ).to.have.length( 1 );
+			assertRect( rects[ 0 ], geometry );
+		} );
+
+		// https://github.com/ckeditor/ckeditor5-utils/issues/153
+		it( 'should return rects for a Range (collapsed)', () => {
+			const range = document.createRange();
+			const secondGeometry = { top: 20, right: 80, bottom: 60, left: 40, width: 40, height: 40 };
+
+			range.collapse();
+			testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry, secondGeometry ] );
+
+			const rects = Rect.getDomRangeRects( range );
+			expect( rects ).to.have.length( 2 );
+
+			assertRect( rects[ 0 ], geometry );
+			assertRect( rects[ 1 ], secondGeometry );
+		} );
+
+		// https://github.com/ckeditor/ckeditor5-utils/issues/153
+		it( 'should return rects for a 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.right = expectedGeometry.left;
+			expectedGeometry.width = 0;
+
+			const rects = Rect.getDomRangeRects( range );
+			expect( rects ).to.have.length( 1 );
+			assertRect( rects[ 0 ], expectedGeometry );
+		} );
+	} );
 } );
 
 function assertRect( rect, expected ) {