8
0
Просмотр исходного кода

Merge pull request #237 from ckeditor/t/236

Other: Improvements in the `BalloonPanelView`–based components for the balloon toolbar editor. Closes #236. Closes #234. Closes #224.

BREAKING CHANGE: The position names in `BalloonPanelView.defaultPositions` and their results have changed. Please refer to the latest API documentation to learn more.
Szymon Kupś 8 лет назад
Родитель
Сommit
b16e5d25c4

+ 243 - 94
packages/ckeditor5-ui/src/panel/balloon/balloonpanelview.js

@@ -53,7 +53,7 @@ export default class BalloonPanelView extends View {
 
 		/**
 		 * Balloon panel's current position. The position name is reflected in the CSS class set
-		 * to the balloon, i.e. `.ck-balloon-panel_arrow_ne` for "arrow_ne" position. The class
+		 * to the balloon, i.e. `.ck-balloon-panel_arrow_nw` for "arrow_nw" position. The class
 		 * controls the minor aspects of the balloon's visual appearance like placement
 		 * of the "arrow". To support a new position, an additional CSS must be created.
 		 *
@@ -65,10 +65,10 @@ export default class BalloonPanelView extends View {
 		 * See {@link #withArrow}.
 		 *
 		 * @observable
-		 * @default 'arrow_ne'
-		 * @member {'arrow_ne'|'arrow_nw'|'arrow_se'|'arrow_sw'} #position
+		 * @default 'arrow_nw'
+		 * @member {'arrow_nw'|'arrow_ne'|'arrow_sw'|'arrow_se'} #position
 		 */
-		this.set( 'position', 'arrow_ne' );
+		this.set( 'position', 'arrow_nw' );
 
 		/**
 		 * Controls whether the balloon panel is visible or not.
@@ -175,10 +175,10 @@ export default class BalloonPanelView extends View {
 		const positionOptions = Object.assign( {}, {
 			element: this.element,
 			positions: [
-				defaultPositions.southEastArrowNorthEast,
-				defaultPositions.southWestArrowNorthEast,
-				defaultPositions.northEastArrowSouthWest,
-				defaultPositions.northWestArrowSouthEast
+				defaultPositions.southArrowNorthWest,
+				defaultPositions.southArrowNorthEast,
+				defaultPositions.northArrowSouthWest,
+				defaultPositions.northArrowSouthEast
 			],
 			limiter: defaultLimiterElement,
 			fitInViewport: true
@@ -334,94 +334,163 @@ BalloonPanelView.arrowVerticalOffset = 15;
  * A default set of positioning functions used by the balloon panel view
  * when attaching using {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#attachTo} method.
  *
- * The available positioning functions are as follows:
+ * The available positioning functions are as follow:
  *
- * * South east (arrow north west):
+ * **North**
  *
- *		[ Target ]
- *		    ^
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *
- *
- * * South west (arrow north east):
- *
- *		         [ Target ]
- *		              ^
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *
- *
- * * North east (arrow south west):
- *
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *		    V
- *		[ Target ]
- *
- *
- * * North west (arrow south east):
- *
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *		              V
- *		         [ Target ]
+ * * `northArrowSouth`
  *
+ * 		+-----------------+
+ * 		|     Balloon     |
+ * 		+-----------------+
+ * 		         V
+ * 		    [ Target ]
  *
- * * South east (arrow north):
- *
- * 		      [ Target ]
- * 		           ^
- * 		  +-----------------+
- * 		  |     Balloon     |
- * 		  +-----------------+
+ * * `northArrowSouthEast`
  *
+ * 		+-----------------+
+ * 		|     Balloon     |
+ * 		+-----------------+
+ * 		               V
+ * 		          [ Target ]
  *
- * * North east (arrow south):
+ * * `northArrowSouthWest`
  *
  * 		  +-----------------+
  * 		  |     Balloon     |
  * 		  +-----------------+
- * 		           V
- * 		      [ Target ]
+ * 		     V
+ * 		[ Target ]
  *
+ * **North west**
  *
- * * North west (arrow south):
+ * * `northWestArrowSouth`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
  * 		         V
- * 		    [ Target ]
+ * 		         [ Target ]
+ *
+ * * `northWestArrowSouthWest`
  *
+ * 		+-----------------+
+ * 		|     Balloon     |
+ * 		+-----------------+
+ * 		   V
+ * 		   [ Target ]
  *
- * * South west (arrow north):
+ * * `northWestArrowSouthEast`
  *
- * 		    [ Target ]
- * 		         ^
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
+ * 		               V
+ * 		               [ Target ]
+ *
+ * **North east**
  *
- * * South (arrow north):
+ * * `northEastArrowSouth`
  *
- * 		     [ Target ]
- * 		         ^
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
+ * 		         V
+ * 		[ Target ]
  *
- * * North (arrow south):
+ * * `northEastArrowSouthEast`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		        V
- * 		    [ Target ]
+ * 		               V
+ * 		      [ Target ]
+ *
+ * * `northEastArrowSouthWest`
+ *
+ * 		      +-----------------+
+ * 		      |     Balloon     |
+ * 		      +-----------------+
+ * 		         V
+ * 		[ Target ]
+ *
+ * **South**
+ *
+ * * `southArrowNorth`
+ *
+ *		    [ Target ]
+ *		         ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southArrowNorthEast`
+ *
+ *		          [ Target ]
+ *		               ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southArrowNorthWest`
+ *
+ *		[ Target ]
+ *		     ^
+ *		   +-----------------+
+ *		   |     Balloon     |
+ *		   +-----------------+
+ *
+ * **South west**
+ *
+ * * `southWestArrowNorth`
+ *
+ *		         [ Target ]
+ *		         ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southWestArrowNorthWest`
+ *
+ *		  [ Target ]
+ *		  ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southWestArrowNorthEast`
+ *
+ *		               [ Target ]
+ *		               ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * **South east**
+ *
+ * * `southEastArrowNorth`
+ *
+ *		[ Target ]
+ *		         ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southEastArrowNorthEast`
+ *
+ *		       [ Target ]
+ *		                ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southEastArrowNorthWest`
+ *
+ *		[ Target ]
+ *		         ^
+ *		       +-----------------+
+ *		       |     Balloon     |
+ *		       +-----------------+
  *
  * See {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#attachTo}.
  *
@@ -433,63 +502,143 @@ BalloonPanelView.arrowVerticalOffset = 15;
  * @member {Object} module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions
  */
 BalloonPanelView.defaultPositions = {
-	southEastArrowNorthEast: targetRect => ( {
-		top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
-		left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_ne'
+	// ------- North
+
+	northArrowSouth: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
+		name: 'arrow_s'
 	} ),
 
-	southWestArrowNorthEast: ( targetRect, balloonRect ) => ( {
-		top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
+	northArrowSouthEast: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
 		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nw'
+		name: 'arrow_se'
 	} ),
 
-	northEastArrowSouthWest: ( targetRect, balloonRect ) => ( {
-		top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
+	northArrowSouthWest: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
 		left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_se'
+		name: 'arrow_sw'
 	} ),
 
-	northWestArrowSouthEast: ( targetRect, balloonRect ) => ( {
-		top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+	// ------- North west
+
+	northWestArrowSouth: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.left - balloonRect.width / 2,
+		name: 'arrow_s'
+	} ),
+
+	northWestArrowSouthWest: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.left - BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_sw'
 	} ),
 
-	southEastArrowNorth: ( targetRect, balloonRect ) => ( {
-		top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
-		left: targetRect.right - balloonRect.width / 2,
-		name: 'arrow_n'
+	northWestArrowSouthEast: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.left - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_se'
 	} ),
 
+	// ------- North east
+
 	northEastArrowSouth: ( targetRect, balloonRect ) => ( {
-		top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
+		top: getNorthTop( targetRect, balloonRect ),
 		left: targetRect.right - balloonRect.width / 2,
 		name: 'arrow_s'
 	} ),
 
-	northWestArrowSouth: ( targetRect, balloonRect ) => ( {
-		top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
-		left: targetRect.left - balloonRect.width / 2,
-		name: 'arrow_s'
+	northEastArrowSouthEast: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.right - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_se'
 	} ),
 
+	northEastArrowSouthWest: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.right - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_sw'
+	} ),
+
+	// ------- South
+
+	southArrowNorth: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
+		name: 'arrow_n'
+	} ),
+
+	southArrowNorthEast: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_ne'
+	} ),
+
+	southArrowNorthWest: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nw'
+	} ),
+
+	// ------- South west
+
 	southWestArrowNorth: ( targetRect, balloonRect ) => ( {
-		top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
+		top: getSouthTop( targetRect, balloonRect ),
 		left: targetRect.left - balloonRect.width / 2,
 		name: 'arrow_n'
 	} ),
 
-	southArrowNorth: ( targetRect, balloonRect ) => ( {
-		top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
+	southWestArrowNorthWest: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nw'
+	} ),
+
+	southWestArrowNorthEast: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_ne'
+	} ),
+
+	// ------- South east
+
+	southEastArrowNorth: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.right - balloonRect.width / 2,
 		name: 'arrow_n'
 	} ),
 
-	northArrowSouth: ( targetRect, balloonRect ) => ( {
-		top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
-		name: 'arrow_s'
-	} )
+	southEastArrowNorthEast: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.right - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_ne'
+	} ),
+
+	southEastArrowNorthWest: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.right - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nw'
+	} ),
 };
+
+// Returns the top coordinate for positions starting with `north*`.
+//
+// @private
+// @param {utils/dom/rect~Rect} targetRect A rect of the target.
+// @param {utils/dom/rect~Rect} elementRect A rect of the balloon.
+// @returns {Number}
+function getNorthTop( targetRect, balloonRect ) {
+	return targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset;
+}
+
+// Returns the top coordinate for positions starting with `south*`.
+//
+// @private
+// @param {utils/dom/rect~Rect} targetRect A rect of the target.
+// @param {utils/dom/rect~Rect} elementRect A rect of the balloon.
+// @returns {Number}
+function getSouthTop( targetRect ) {
+	return targetRect.bottom + BalloonPanelView.arrowVerticalOffset;
+}

+ 31 - 6
packages/ckeditor5-ui/src/toolbar/contextual/contextualtoolbar.js

@@ -14,8 +14,6 @@ import ToolbarView from '../toolbarview';
 import BalloonPanelView from '../../panel/balloon/balloonpanelview.js';
 import debounce from '@ckeditor/ckeditor5-utils/src/lib/lodash/debounce';
 
-const defaultPositions = BalloonPanelView.defaultPositions;
-
 /**
  * The contextual toolbar.
  *
@@ -51,7 +49,10 @@ export default class ContextualToolbar extends Plugin {
 
 		Template.extend( this.toolbarView.template, {
 			attributes: {
-				class: 'ck-editor-toolbar'
+				class: [
+					'ck-editor-toolbar',
+					'ck-toolbar_floating'
+				]
 			}
 		} );
 
@@ -231,9 +232,8 @@ export default class ContextualToolbar extends Plugin {
 				// Select the proper range rect depending on the direction of the selection.
 				return isBackward ? rangeRects.item( 0 ) : rangeRects.item( rangeRects.length - 1 );
 			},
-			positions: isBackward ?
-				[ defaultPositions.northWestArrowSouth, defaultPositions.southWestArrowNorth ] :
-				[ defaultPositions.southEastArrowNorth, defaultPositions.northEastArrowSouth ]
+			limiter: this.editor.ui.view.editable.element,
+			positions: getBalloonPositions( isBackward )
 		};
 	}
 
@@ -263,3 +263,28 @@ export default class ContextualToolbar extends Plugin {
 	 * @event _selectionChangeDebounced
 	 */
 }
+
+// Returns toolbar positions for the given direction of the selection.
+//
+// @private
+// @param {Boolean} isBackward
+// @returns {Array.<module:utils/dom/position~Position>}
+function getBalloonPositions( isBackward ) {
+	const defaultPositions = BalloonPanelView.defaultPositions;
+
+	return isBackward ? [
+		defaultPositions.northWestArrowSouth,
+		defaultPositions.northWestArrowSouthWest,
+		defaultPositions.northWestArrowSouthEast,
+		defaultPositions.southWestArrowNorth,
+		defaultPositions.southWestArrowNorthWest,
+		defaultPositions.southWestArrowNorthEast
+	] : [
+		defaultPositions.southEastArrowNorth,
+		defaultPositions.southEastArrowNorthEast,
+		defaultPositions.southEastArrowNorthWest,
+		defaultPositions.northEastArrowSouth,
+		defaultPositions.northEastArrowSouthEast,
+		defaultPositions.northEastArrowSouthWest
+	];
+}

+ 22 - 0
packages/ckeditor5-ui/tests/manual/panel/balloon/balloonpanelview.html

@@ -0,0 +1,22 @@
+<div id="container"></div>
+
+<style>
+	#container {
+		padding: 5em;
+	}
+
+	.target {
+		width: 125px;
+		height: 20px;
+		background: green;
+		margin: 0 0 0 100px;
+	}
+
+	.target + .target {
+		margin-top: 70px;
+	}
+
+	.ck-balloon-panel {
+		padding: .3em;
+	}
+</style>

+ 29 - 0
packages/ckeditor5-ui/tests/manual/panel/balloon/balloonpanelview.js

@@ -0,0 +1,29 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+
+import BalloonPanelView from '../../../../src/panel/balloon/balloonpanelview';
+import '@ckeditor/ckeditor5-theme-lark/theme/theme.scss';
+
+const defaultPositions = BalloonPanelView.defaultPositions;
+const container = document.querySelector( '#container' );
+
+for ( const i in defaultPositions ) {
+	const target = document.createElement( 'div' );
+	target.classList.add( 'target' );
+	container.appendChild( target );
+
+	const balloon = new BalloonPanelView();
+	balloon.element.textContent = i;
+	document.body.appendChild( balloon.element );
+
+	balloon.attachTo( {
+		target,
+		positions: [
+			defaultPositions[ i ]
+		]
+	} );
+}

+ 5 - 0
packages/ckeditor5-ui/tests/manual/panel/balloon/balloonpanelview.md

@@ -0,0 +1,5 @@
+## `BalloonPanelView` and `defaultPositions`
+
+1. A number of green rectangles should be displayed in the page.
+2. Each rectangle should have a panel attached.
+3. Make sure the description in each panel matches the location of the panel.

+ 131 - 51
packages/ckeditor5-ui/tests/panel/balloon/balloonpanelview.js

@@ -38,7 +38,7 @@ describe( 'BalloonPanelView', () => {
 		it( 'should set default values', () => {
 			expect( view.top ).to.equal( 0 );
 			expect( view.left ).to.equal( 0 );
-			expect( view.position ).to.equal( 'arrow_ne' );
+			expect( view.position ).to.equal( 'arrow_nw' );
 			expect( view.isVisible ).to.equal( false );
 			expect( view.withArrow ).to.equal( true );
 		} );
@@ -51,11 +51,11 @@ describe( 'BalloonPanelView', () => {
 	describe( 'DOM bindings', () => {
 		describe( 'arrow', () => {
 			it( 'should react on view#position', () => {
-				expect( view.element.classList.contains( 'ck-balloon-panel_arrow_ne' ) ).to.true;
+				expect( view.element.classList.contains( 'ck-balloon-panel_arrow_nw' ) ).to.true;
 
-				view.position = 'arrow_nw';
+				view.position = 'arrow_ne';
 
-				expect( view.element.classList.contains( 'ck-balloon-panel_arrow_nw' ) ).to.true;
+				expect( view.element.classList.contains( 'ck-balloon-panel_arrow_ne' ) ).to.true;
 			} );
 
 			it( 'should react on view#withArrow', () => {
@@ -188,10 +188,10 @@ describe( 'BalloonPanelView', () => {
 				element: view.element,
 				target,
 				positions: [
-					BalloonPanelView.defaultPositions.southEastArrowNorthEast,
-					BalloonPanelView.defaultPositions.southWestArrowNorthEast,
-					BalloonPanelView.defaultPositions.northEastArrowSouthWest,
-					BalloonPanelView.defaultPositions.northWestArrowSouthEast
+					BalloonPanelView.defaultPositions.southArrowNorthWest,
+					BalloonPanelView.defaultPositions.southArrowNorthEast,
+					BalloonPanelView.defaultPositions.northArrowSouthWest,
+					BalloonPanelView.defaultPositions.northArrowSouthEast
 				],
 				limiter: document.body,
 				fitInViewport: true
@@ -220,7 +220,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_ne' );
+				expect( view.position ).to.equal( 'arrow_nw' );
 			} );
 
 			it( 'should put balloon on the `south east` side of the target element when ' +
@@ -234,7 +234,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_ne' );
+				expect( view.position ).to.equal( 'arrow_nw' );
 			} );
 
 			it( 'should put balloon on the `south west` side of the target element when target is on the right side of the limiter', () => {
@@ -247,7 +247,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_nw' );
+				expect( view.position ).to.equal( 'arrow_ne' );
 			} );
 
 			it( 'should put balloon on the `north east` side of the target element when target is on the bottom of the limiter ', () => {
@@ -260,7 +260,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_se' );
+				expect( view.position ).to.equal( 'arrow_sw' );
 			} );
 
 			it( 'should put balloon on the `north west` side of the target element when ' +
@@ -274,7 +274,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_sw' );
+				expect( view.position ).to.equal( 'arrow_se' );
 			} );
 
 			// https://github.com/ckeditor/ckeditor5-ui-default/issues/126
@@ -355,7 +355,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_nw' );
+				expect( view.position ).to.equal( 'arrow_ne' );
 			} );
 
 			it( 'should put balloon on the `south east` position when `south west` is limited', () => {
@@ -375,7 +375,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_ne' );
+				expect( view.position ).to.equal( 'arrow_nw' );
 			} );
 
 			it( 'should put balloon on the `north east` position when `south east` is limited', () => {
@@ -399,7 +399,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_se' );
+				expect( view.position ).to.equal( 'arrow_sw' );
 			} );
 
 			it( 'should put balloon on the `south east` position when `north east` is limited', () => {
@@ -419,7 +419,7 @@ describe( 'BalloonPanelView', () => {
 
 				view.attachTo( { target, limiter } );
 
-				expect( view.position ).to.equal( 'arrow_ne' );
+				expect( view.position ).to.equal( 'arrow_nw' );
 			} );
 		} );
 	} );
@@ -670,47 +670,65 @@ describe( 'BalloonPanelView', () => {
 			};
 		} );
 
-		it( 'southEastArrowNorthEast', () => {
-			expect( positions.southEastArrowNorthEast( targetRect ) ).to.deep.equal( {
-				top: 215,
-				left: 120,
-				name: 'arrow_ne'
+		it( 'should have a proper length', () => {
+			expect( Object.keys( positions ) ).to.have.length( 18 );
+		} );
+
+		// ------- North
+
+		it( 'should define the "northArrowSouth" position', () => {
+			expect( positions.northArrowSouth( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 35,
+				left: 125,
+				name: 'arrow_s'
 			} );
 		} );
 
-		it( 'southWestArrowNorthEast', () => {
-			expect( positions.southWestArrowNorthEast( targetRect, balloonRect ) ).to.deep.equal( {
-				top: 215,
+		it( 'should define the "northArrowSouthEast" position', () => {
+			expect( positions.northArrowSouthEast( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 35,
 				left: 130,
-				name: 'arrow_nw'
+				name: 'arrow_se'
 			} );
 		} );
 
-		it( 'northEastArrowSouthWest', () => {
-			expect( positions.northEastArrowSouthWest( targetRect, balloonRect ) ).to.deep.equal( {
+		it( 'should define the "northArrowSouthWest" position', () => {
+			expect( positions.northArrowSouthWest( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 35,
 				left: 120,
-				name: 'arrow_se'
+				name: 'arrow_sw'
 			} );
 		} );
 
-		it( 'northWestArrowSouthEast', () => {
-			expect( positions.northWestArrowSouthEast( targetRect, balloonRect ) ).to.deep.equal( {
+		// ------- North west
+
+		it( 'should define the "northWestArrowSouth" position', () => {
+			expect( positions.northWestArrowSouth( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 35,
-				left: 130,
+				left: 75,
+				name: 'arrow_s'
+			} );
+		} );
+
+		it( 'should define the "northWestArrowSouthWest" position', () => {
+			expect( positions.northWestArrowSouthWest( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 35,
+				left: 70,
 				name: 'arrow_sw'
 			} );
 		} );
 
-		it( 'southEastArrowNorth', () => {
-			expect( positions.southEastArrowNorth( targetRect, balloonRect ) ).to.deep.equal( {
-				top: 215,
-				left: 175,
-				name: 'arrow_n'
+		it( 'should define the "northWestArrowSouthEast" position', () => {
+			expect( positions.northWestArrowSouthEast( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 35,
+				left: 80,
+				name: 'arrow_se'
 			} );
 		} );
 
-		it( 'northEastArrowSouth', () => {
+		// ------- North east
+
+		it( 'should define the "northEastArrowSouth" position', () => {
 			expect( positions.northEastArrowSouth( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 35,
 				left: 175,
@@ -718,15 +736,51 @@ describe( 'BalloonPanelView', () => {
 			} );
 		} );
 
-		it( 'northWestArrowSouth', () => {
-			expect( positions.northWestArrowSouth( targetRect, balloonRect ) ).to.deep.equal( {
+		it( 'should define the "northEastArrowSouthEast" position', () => {
+			expect( positions.northEastArrowSouthEast( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 35,
-				left: 75,
-				name: 'arrow_s'
+				left: 180,
+				name: 'arrow_se'
+			} );
+		} );
+
+		it( 'should define the "northEastArrowSouthWest" position', () => {
+			expect( positions.northEastArrowSouthWest( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 35,
+				left: 170,
+				name: 'arrow_sw'
+			} );
+		} );
+
+		// ------- South
+
+		it( 'should define the "southArrowNorth" position', () => {
+			expect( positions.southArrowNorth( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 125,
+				name: 'arrow_n'
+			} );
+		} );
+
+		it( 'should define the "southArrowNorthEast" position', () => {
+			expect( positions.southArrowNorthEast( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 130,
+				name: 'arrow_ne'
+			} );
+		} );
+
+		it( 'should define the "southArrowNorthWest" position', () => {
+			expect( positions.southArrowNorthWest( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 120,
+				name: 'arrow_nw'
 			} );
 		} );
 
-		it( 'southWestArrowNorth', () => {
+		// ------- South west
+
+		it( 'should define the "southWestArrowNorth" position', () => {
 			expect( positions.southWestArrowNorth( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 215,
 				left: 75,
@@ -734,19 +788,45 @@ describe( 'BalloonPanelView', () => {
 			} );
 		} );
 
-		it( 'southArrowNorth', () => {
-			expect( positions.southArrowNorth( targetRect, balloonRect ) ).to.deep.equal( {
+		it( 'should define the "southWestArrowNorthWest" position', () => {
+			expect( positions.southWestArrowNorthWest( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 215,
-				left: 125,
+				left: 70,
+				name: 'arrow_nw'
+			} );
+		} );
+
+		it( 'should define the "southWestArrowNorthEast" position', () => {
+			expect( positions.southWestArrowNorthEast( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 80,
+				name: 'arrow_ne'
+			} );
+		} );
+
+		// ------- South east
+
+		it( 'should define the "southEastArrowNorth" position', () => {
+			expect( positions.southEastArrowNorth( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 175,
 				name: 'arrow_n'
 			} );
 		} );
 
-		it( 'northArrowSouth', () => {
-			expect( positions.northArrowSouth( targetRect, balloonRect ) ).to.deep.equal( {
-				top: 35,
-				left: 125,
-				name: 'arrow_s'
+		it( 'should define the "southEastArrowNorthEast" position', () => {
+			expect( positions.southEastArrowNorthEast( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 180,
+				name: 'arrow_ne'
+			} );
+		} );
+
+		it( 'should define the "southEastArrowNorthWest" position', () => {
+			expect( positions.southEastArrowNorthWest( targetRect, balloonRect ) ).to.deep.equal( {
+				top: 215,
+				left: 170,
+				name: 'arrow_nw'
 			} );
 		} );
 	} );

+ 19 - 2
packages/ckeditor5-ui/tests/toolbar/contextual/contextualtoolbar.js

@@ -59,6 +59,7 @@ describe( 'ContextualToolbar', () => {
 		expect( contextualToolbar ).to.instanceOf( ContextualToolbar );
 		expect( contextualToolbar.toolbarView ).to.instanceof( ToolbarView );
 		expect( contextualToolbar.toolbarView.element.classList.contains( 'ck-editor-toolbar' ) ).to.be.true;
+		expect( contextualToolbar.toolbarView.element.classList.contains( 'ck-toolbar_floating' ) ).to.be.true;
 	} );
 
 	it( 'should load ContextualBalloon', () => {
@@ -161,7 +162,15 @@ describe( 'ContextualToolbar', () => {
 					balloonClassName: 'ck-toolbar-container ck-editor-toolbar-container',
 					position: {
 						target: sinon.match( value => value() == backwardSelectionRect ),
-						positions: [ defaultPositions.southEastArrowNorth, defaultPositions.northEastArrowSouth ]
+						limiter: editor.ui.view.editable.element,
+						positions: [
+							defaultPositions.southEastArrowNorth,
+							defaultPositions.southEastArrowNorthEast,
+							defaultPositions.southEastArrowNorthWest,
+							defaultPositions.northEastArrowSouth,
+							defaultPositions.northEastArrowSouthEast,
+							defaultPositions.northEastArrowSouthWest
+						]
 					}
 				} );
 			} );
@@ -179,7 +188,15 @@ describe( 'ContextualToolbar', () => {
 						balloonClassName: 'ck-toolbar-container ck-editor-toolbar-container',
 						position: {
 							target: sinon.match( value => value() == forwardSelectionRect ),
-							positions: [ defaultPositions.northWestArrowSouth, defaultPositions.southWestArrowNorth ]
+							limiter: editor.ui.view.editable.element,
+							positions: [
+								defaultPositions.northWestArrowSouth,
+								defaultPositions.northWestArrowSouthWest,
+								defaultPositions.northWestArrowSouthEast,
+								defaultPositions.southWestArrowNorth,
+								defaultPositions.southWestArrowNorthWest,
+								defaultPositions.southWestArrowNorthEast
+							]
 						}
 					} );
 				} );