8
0
Pārlūkot izejas kodu

The widget toolbar should not use the fallback position for long widgets when there is enough space above or below it in the viewport.

Kuba Niegowski 5 gadi atpakaļ
vecāks
revīzija
9695f21301

+ 9 - 4
packages/ckeditor5-utils/src/dom/position.js

@@ -129,11 +129,11 @@ export function getOptimalPosition( { element, target, positions, limiter, fitIn
 // @param {Function} position A function returning {@link module:utils/dom/position~Position}.
 // @param {utils/dom/rect~Rect} targetRect A rect of the target.
 // @param {utils/dom/rect~Rect} elementRect A rect of positioned element.
-// @returns {Array} An array containing position name and its Rect.
+// @returns {Array|null} An array containing position name and its Rect (or null if position should be ignored).
 function getPositionNameAndRect( position, targetRect, elementRect ) {
-	const { left, top, name } = position( targetRect, elementRect );
+	const { left, top, name } = position( targetRect, elementRect ) || {};
 
-	return [ name, elementRect.clone().moveTo( left, top ) ];
+	return name ? [ name, elementRect.clone().moveTo( left, top ) ] : null;
 }
 
 // For a given array of positioning functions, returns such that provides the best
@@ -205,7 +205,12 @@ function processPositionsToAreas( positions, { targetRect, elementRect, limiterR
 	const elementRectArea = elementRect.getArea();
 
 	for ( const position of positions ) {
-		const [ positionName, positionRect ] = getPositionNameAndRect( position, targetRect, elementRect );
+		const [ positionName, positionRect ] = getPositionNameAndRect( position, targetRect, elementRect ) || [];
+
+		if ( !positionName ) {
+			continue;
+		}
+
 		let limiterIntersectArea = 0;
 		let viewportIntersectArea = 0;
 

+ 26 - 1
packages/ckeditor5-utils/tests/dom/position.js

@@ -95,6 +95,8 @@ const attachTopRight = ( targetRect, elementRect ) => ( {
 	name: 'top-right'
 } );
 
+const attachNone = () => null;
+
 const allPositions = [
 	attachLeftBottom,
 	attachLeftTop,
@@ -103,7 +105,8 @@ const allPositions = [
 	attachBottomRight,
 	attachBottomLeft,
 	attachTopLeft,
-	attachTopRight
+	attachTopRight,
+	attachNone
 ];
 
 describe( 'getOptimalPosition()', () => {
@@ -291,6 +294,17 @@ describe( 'getOptimalPosition()', () => {
 				name: 'right-bottom'
 			} );
 		} );
+
+		it( 'should allow position function to return null to be ignored', () => {
+			assertPosition( {
+				element, target,
+				positions: [ attachRightBottom, attachNone ]
+			}, {
+				top: 100,
+				left: 110,
+				name: 'right-bottom'
+			} );
+		} );
 	} );
 
 	describe( 'with a limiter', () => {
@@ -392,6 +406,17 @@ describe( 'getOptimalPosition()', () => {
 
 			element.remove();
 		} );
+
+		it( 'should allow position function to return null to be ignored', () => {
+			assertPosition( {
+				element, target, limiter,
+				positions: [ attachLeftBottom, attachNone ]
+			}, {
+				top: 100,
+				left: -20,
+				name: 'left-bottom'
+			} );
+		} );
 	} );
 
 	describe( 'with fitInViewport on', () => {

+ 7 - 0
packages/ckeditor5-widget/src/utils.js

@@ -380,6 +380,13 @@ export function centeredBalloonPositionForLongWidgets( widgetRect, balloonRect )
 	const viewportRect = new Rect( global.window );
 	const viewportWidgetInsersectionRect = viewportRect.getIntersection( widgetRect );
 
+	const balloonTotalHeight = balloonRect.height + BalloonPanelView.arrowVerticalOffset;
+
+	// If there is enough space above or below the widget then this position should not be used.
+	if ( widgetRect.top - balloonTotalHeight > viewportRect.top || widgetRect.bottom + balloonTotalHeight < viewportRect.bottom ) {
+		return null;
+	}
+
 	// Because this is a last resort positioning, to keep things simple we're not playing with positions of the arrow
 	// like, for instance, "south west" or whatever. Just try to keep the balloon in the middle of the visible area of
 	// the widget for as long as it is possible. If the widgets becomes invisible (because cropped by the viewport),

+ 38 - 6
packages/ckeditor5-widget/tests/utils.js

@@ -512,7 +512,7 @@ describe( 'widget utils', () => {
 			testUtils.sinon.stub( global.window, 'innerHeight' ).value( 100 );
 		} );
 
-		it( 'should position the balloon inside a widget – at the top + in the middle', () => {
+		it( 'should return null if there is enough space above the widget', () => {
 			// Widget is a 50x150 rect, translated (25,25) from viewport's beginning (0,0).
 			const widgetRect = new Rect( {
 				top: 25,
@@ -525,8 +525,40 @@ describe( 'widget utils', () => {
 
 			const position = centeredBalloonPositionForLongWidgets( widgetRect, balloonRect );
 
+			expect( position ).to.equal( null );
+		} );
+
+		it( 'should return null if there is enough space below the widget', () => {
+			// Widget is a 50x150 rect, translated (25,-125) from viewport's beginning (0,0).
+			const widgetRect = new Rect( {
+				top: -125,
+				left: 25,
+				right: 75,
+				bottom: 25,
+				width: 50,
+				height: 150
+			} );
+
+			const position = centeredBalloonPositionForLongWidgets( widgetRect, balloonRect );
+
+			expect( position ).to.equal( null );
+		} );
+
+		it( 'should position the balloon inside a widget – at the top + in the middle', () => {
+			// Widget is a 50x150 rect, translated (25,5) from viewport's beginning (0,0).
+			const widgetRect = new Rect( {
+				top: 5,
+				left: 25,
+				right: 75,
+				bottom: 155,
+				width: 50,
+				height: 150
+			} );
+
+			const position = centeredBalloonPositionForLongWidgets( widgetRect, balloonRect );
+
 			expect( position ).to.deep.equal( {
-				top: 25 + arrowVerticalOffset,
+				top: 5 + arrowVerticalOffset,
 				left: 45,
 				name: 'arrow_n'
 			} );
@@ -553,12 +585,12 @@ describe( 'widget utils', () => {
 		} );
 
 		it( 'should horizontally center the balloon in the visible area when the widget is cropped by the viewport', () => {
-			// Widget is a 50x150 rect, translated (25,-25) from viewport's beginning (0,0).
+			// Widget is a 50x150 rect, translated (-25,5) from viewport's beginning (0,0).
 			const widgetRect = new Rect( {
-				top: 25,
+				top: 5,
 				left: -25,
 				right: 25,
-				bottom: 175,
+				bottom: 155,
 				width: 50,
 				height: 150
 			} );
@@ -566,7 +598,7 @@ describe( 'widget utils', () => {
 			const position = centeredBalloonPositionForLongWidgets( widgetRect, balloonRect );
 
 			expect( position ).to.deep.equal( {
-				top: 25 + arrowVerticalOffset,
+				top: 5 + arrowVerticalOffset,
 				left: 7.5,
 				name: 'arrow_n'
 			} );