8
0
Aleksander Nowodzinski 9 лет назад
Родитель
Сommit
5cac14e773
2 измененных файлов с 512 добавлено и 0 удалено
  1. 164 0
      packages/ckeditor5-utils/src/dom/rect.js
  2. 348 0
      packages/ckeditor5-utils/tests/dom/rect.js

+ 164 - 0
packages/ckeditor5-utils/src/dom/rect.js

@@ -0,0 +1,164 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals window, Range, HTMLElement */
+
+/**
+ * @module utils/dom/boundingrect
+ */
+
+/**
+ * A helper class representing a `DOMRect` 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.
+ *
+ * @memberOf utils.dom
+ */
+export default class Rect {
+	/**
+	 * Creates an instance of rect.
+	 *
+	 * @param {HTMLElement|Range|utils.dom.Rect|Object} obj A source object of the rect.
+	 */
+	constructor( obj ) {
+		Object.assign( this, getRect( obj ) );
+	}
+
+	/**
+	 * Returns a clone of the rect.
+	 *
+	 * @returns {utils.dom.Rect} A cloned rect.
+	 */
+	clone() {
+		return new Rect( this );
+	}
+
+	/**
+	 * Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location.
+	 *
+	 * @param {Number} x Desired horizontal location.
+	 * @param {Number} y Desired vertical location.
+	 * @returns {utils.dom.Rect} A rect which has been moved.
+	 */
+	moveTo( x, y ) {
+		this.top = y;
+		this.right = x + this.width;
+		this.bottom = y + this.height;
+		this.left = x;
+
+		return this;
+	}
+
+	/**
+	 * Moves the rect in–place by a dedicated offset.
+	 *
+	 * @param {Number} x A horizontal offset.
+	 * @param {Number} y A vertical offset
+	 * @returns {utils.dom.Rect} A rect which has been moved.
+	 */
+	moveBy( x, y ) {
+		this.top += y;
+		this.right += x;
+		this.left += x;
+		this.bottom += y;
+
+		return this;
+	}
+
+	/**
+	 * Returns a new rect a a result of intersection with another rect.
+	 *
+	 * @param {utils.dom.Rect} anotherRect
+	 * @returns {utils.dom.Rect}
+	 */
+	getIntersection( anotherRect ) {
+		const rect = {
+			top: Math.max( this.top, anotherRect.top ),
+			right: Math.min( this.right, anotherRect.right ),
+			bottom: Math.min( this.bottom, anotherRect.bottom ),
+			left: Math.max( this.left, anotherRect.left )
+		};
+
+		rect.width = rect.right - rect.left;
+		rect.height = rect.bottom - rect.top;
+
+		return new Rect( rect );
+	}
+
+	/**
+	 * Returns the area of intersection with another rect.
+	 *
+	 * @param {utils.dom.Rect} anotherRect [description]
+	 * @returns {Number} Area of intersection.
+	 */
+	getIntersectionArea( anotherRect ) {
+		return this.getIntersection( anotherRect ).getArea();
+	}
+
+	/**
+	 * Returns the area of the rect.
+	 *
+	 * @returns {Number}
+	 */
+	getArea() {
+		return this.width * this.height;
+	}
+
+	/**
+	 * Returns a rect of the web browser viewport.
+	 *
+	 * @returns {utils.dom.Rect} A viewport rect.
+	 */
+	static getViewportRect() {
+		const { scrollX, scrollY, innerWidth, innerHeight } = window;
+
+		return new Rect( {
+			top: scrollY,
+			right: innerWidth + scrollX,
+			bottom: innerHeight + scrollY,
+			left: scrollX,
+			width: innerWidth,
+			height: innerHeight
+		} );
+	}
+}
+
+// Returns the client rect of an HTMLElement, Range, or rect. The obtained geometry of the rect
+// corresponds with `position: absolute` relative to the `<body>` (`document.body`).
+//
+// @private
+// @param {HTMLElement|Range|Object} object Target object witch rect is to be determined.
+// @returns {Object} Client rect object.
+function getRect( object ) {
+	// A HTMLElement or DOM Range has been passed.
+	if ( isDomElement( object ) || isDomRange( object ) ) {
+		// De-structuring the native DOMRect.
+		const { top, right, bottom, left, width, height } = object.getBoundingClientRect();
+
+		return { top, right, bottom, left, width, height };
+	}
+	// A Rect instance or a rect–like object has been passed.
+	else {
+		return Object.assign( {}, object );
+	}
+}
+
+// Checks if the object is a DOM Range.
+//
+// @private
+// @param {*} obj
+// @returns {Boolean}
+function isDomRange( obj ) {
+	return obj instanceof Range;
+}
+
+// Checks if the object is a DOM HTMLElement.
+//
+// @private
+// @param {*} obj
+// @returns {Boolean}
+function isDomElement( obj ) {
+	return obj instanceof HTMLElement;
+}

+ 348 - 0
packages/ckeditor5-utils/tests/dom/rect.js

@@ -0,0 +1,348 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document, window */
+
+import Rect from 'ckeditor5/utils/dom/rect.js';
+import testUtils from 'tests/core/_utils/utils.js';
+
+testUtils.createSinonSandbox();
+
+describe( 'Rect', () => {
+	let geometry;
+
+	beforeEach( () => {
+		geometry = {
+			top: 10,
+			right: 40,
+			bottom: 30,
+			left: 20,
+			width: 20,
+			height: 20
+		};
+	} );
+
+	describe( 'constructor()', () => {
+		it( 'should accept HTMLElement', () => {
+			const element = document.createElement( 'div' );
+
+			testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
+
+			assertRect( new Rect( element ), geometry );
+		} );
+
+		it( 'should accept Range', () => {
+			const range = document.createRange();
+
+			testUtils.sinon.stub( range, 'getBoundingClientRect' ).returns( geometry );
+
+			assertRect( new Rect( range ), geometry );
+		} );
+
+		it( 'should accept Rect', () => {
+			const sourceRect = new Rect( geometry );
+			const rect = new Rect( sourceRect );
+
+			expect( rect ).to.not.equal( sourceRect );
+			assertRect( rect, geometry );
+		} );
+
+		it( 'should accept geometry object', () => {
+			assertRect( new Rect( geometry ), geometry );
+		} );
+
+		it( 'should copy the properties (Rect)', () => {
+			const sourceGeometry = Object.assign( {}, geometry );
+			const sourceRect = new Rect( geometry );
+			const rect = new Rect( sourceRect );
+
+			assertRect( rect, geometry );
+
+			rect.top = 100;
+			rect.width = 200;
+
+			assertRect( sourceRect, sourceGeometry );
+		} );
+
+		it( 'should copy the properties (geomerty object)', () => {
+			const sourceGeometry = Object.assign( {}, geometry );
+			const rect = new Rect( geometry );
+
+			assertRect( rect, geometry );
+
+			rect.top = 100;
+			rect.width = 200;
+
+			assertRect( geometry, sourceGeometry );
+		} );
+	} );
+
+	describe( 'clone()', () => {
+		it( 'should clone the source rect', () => {
+			const rect = new Rect( geometry );
+			const clone = rect.clone();
+
+			expect( clone ).to.be.instanceOf( Rect );
+			expect( clone ).not.equal( rect );
+			assertRect( clone, rect );
+		} );
+	} );
+
+	describe( 'moveTo()', () => {
+		it( 'should return the source rect', () => {
+			const rect = new Rect( geometry );
+			const returned = rect.moveTo( 100, 200 );
+
+			expect( returned ).to.equal( rect );
+		} );
+
+		it( 'should move the rect', () => {
+			const rect = new Rect( geometry );
+
+			rect.moveTo( 100, 200 );
+
+			assertRect( rect, {
+				top: 200,
+				right: 120,
+				bottom: 220,
+				left: 100,
+				width: 20,
+				height: 20
+			} );
+		} );
+	} );
+
+	describe( 'moveBy()', () => {
+		it( 'should return the source rect', () => {
+			const rect = new Rect( geometry );
+			const returned = rect.moveBy( 100, 200 );
+
+			expect( returned ).to.equal( rect );
+		} );
+
+		it( 'should move the rect', () => {
+			const rect = new Rect( geometry );
+
+			rect.moveBy( 100, 200 );
+
+			assertRect( rect, {
+				top: 210,
+				right: 140,
+				bottom: 230,
+				left: 120,
+				width: 20,
+				height: 20
+			} );
+		} );
+	} );
+
+	describe( 'getIntersection()', () => {
+		it( 'should return a new rect', () => {
+			const rect = new Rect( geometry );
+			const insersect = rect.getIntersection( new Rect( geometry ) );
+
+			expect( insersect ).to.be.instanceOf( Rect );
+			expect( insersect ).to.not.equal( rect );
+		} );
+
+		it( 'should calculate the geometry (#1)', () => {
+			const rectA = new Rect( {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 0,
+				width: 100,
+				height: 100
+			} );
+
+			const rectB = new Rect( {
+				top: 50,
+				right: 150,
+				bottom: 150,
+				left: 50,
+				width: 100,
+				height: 100
+			} );
+
+			const insersect = rectA.getIntersection( rectB );
+
+			assertRect( insersect, {
+				top: 50,
+				right: 100,
+				bottom: 100,
+				left: 50,
+				width: 50,
+				height: 50
+			} );
+		} );
+
+		it( 'should calculate the geometry (#2)', () => {
+			const rectA = new Rect( {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 0,
+				width: 100,
+				height: 100
+			} );
+
+			const rectB = new Rect( {
+				top: 0,
+				right: 200,
+				bottom: 100,
+				left: 100,
+				width: 100,
+				height: 100
+			} );
+
+			const insersect = rectA.getIntersection( rectB );
+
+			assertRect( insersect, {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 100,
+				width: 0,
+				height: 100
+			} );
+		} );
+
+		it( 'should calculate the geometry (#3)', () => {
+			const rectA = new Rect( {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 0,
+				width: 100,
+				height: 100
+			} );
+
+			const rectB = new Rect( {
+				top: 100,
+				right: 300,
+				bottom: 200,
+				left: 200,
+				width: 100,
+				height: 100
+			} );
+
+			const insersect = rectA.getIntersection( rectB );
+
+			assertRect( insersect, {
+				top: 100,
+				right: 100,
+				bottom: 100,
+				left: 200,
+				width: -100,
+				height: 0
+			} );
+		} );
+	} );
+
+	describe( 'getIntersectionArea()', () => {
+		it( 'should calculate the area (#1)', () => {
+			const rectA = new Rect( {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 0,
+				width: 100,
+				height: 100
+			} );
+
+			const rectB = new Rect( {
+				top: 50,
+				right: 150,
+				bottom: 150,
+				left: 50,
+				width: 100,
+				height: 100
+			} );
+
+			expect( rectA.getIntersectionArea( rectB ) ).to.equal( 2500 );
+		} );
+
+		it( 'should calculate the area (#2)', () => {
+			const rectA = new Rect( {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 0,
+				width: 100,
+				height: 100
+			} );
+
+			const rectB = new Rect( {
+				top: 0,
+				right: 200,
+				bottom: 100,
+				left: 100,
+				width: 100,
+				height: 100
+			} );
+
+			expect( rectA.getIntersectionArea( rectB ) ).to.equal( 0 );
+		} );
+
+		it( 'should calculate the area (#3)', () => {
+			const rectA = new Rect( {
+				top: 0,
+				right: 100,
+				bottom: 100,
+				left: 0,
+				width: 100,
+				height: 100
+			} );
+
+			const rectB = new Rect( {
+				top: 100,
+				right: 300,
+				bottom: 200,
+				left: 200,
+				width: 100,
+				height: 100
+			} );
+
+			expect( rectA.getIntersectionArea( rectB ) ).to.equal( 0 );
+		} );
+	} );
+
+	describe( 'getArea()', () => {
+		it( 'should calculate the area', () => {
+			const rect = new Rect( {
+				width: 100,
+				height: 50
+			} );
+
+			expect( rect.getArea() ).to.equal( 5000 );
+		} );
+	} );
+
+	describe( 'getViewportRect()', () => {
+		it( 'should reaturn a rect', () => {
+			expect( Rect.getViewportRect() ).to.be.instanceOf( Rect );
+		} );
+
+		it( 'should return the viewport\'s rect', () => {
+			window.scrollX = 100;
+			window.scrollY = 200;
+			window.innerWidth = 1000;
+			window.innerHeight = 500;
+
+			assertRect( Rect.getViewportRect(), {
+				top: 200,
+				right: 1100,
+				bottom: 700,
+				left: 100,
+				width: 1000,
+				height: 500
+			} );
+		} );
+	} );
+} );
+
+function assertRect( rect, expected ) {
+	expect( rect ).to.deep.equal( expected );
+}