瀏覽代碼

Fix northArrowSouthMiddleWest calculations & rearrange balloonpanelview docs and manual tests

panr 5 年之前
父節點
當前提交
fcf51c3ce0

+ 198 - 186
packages/ckeditor5-ui/src/panel/balloon/balloonpanelview.js

@@ -439,31 +439,53 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *
  * The available positioning functions are as follows:
  *
- * **North**
  *
- * * `northArrowSouth`
+ *
+ * **North west**
+ *
+ * * `northWestArrowSouthWest`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		         V
+ * 		 V
+ * 		 [ Target ]
+ *
+ * * `northWestArrowSouthMiddleWest`
+ *
+ * 		+-----------------+
+ * 		|     Balloon     |
+ * 		+-----------------+
+ * 		    V
  * 		    [ Target ]
  *
- * * `northArrowSouthEast`
+ * * `northWestArrowSouth`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		                V
- * 		           [ Target ]
+ * 		         V
+ * 		         [ Target ]
  *
- * * `northArrowSouthMiddleEast`
+ * * `northWestArrowSouthMiddleEast`
+ *
+ * 		 +-----------------+
+ * 		 |     Balloon     |
+ * 		 +-----------------+
+ * 		              V
+ * 		              [ Target ]
+ *
+ * * `northWestArrowSouthEast`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		             V
- * 		        [ Target ]
+ * 		                 V
+ * 		                 [ Target ]
+ *
+ *
+ *
+ * **North**
  *
  * * `northArrowSouthWest`
  *
@@ -481,65 +503,58 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  * 		     V
  * 		[ Target ]
  *
- * **North west**
- *
- * * `northWestArrowSouth`
+ * * `northArrowSouth`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
  * 		         V
- * 		         [ Target ]
+ * 		    [ Target ]
  *
- * * `northWestArrowSouthWest`
+ * * `northArrowSouthMiddleEast`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		 V
- * 		 [ Target ]
+ * 		             V
+ * 		        [ Target ]
  *
- * * `northWestArrowSouthMiddleWest`
+ * * `northArrowSouthEast`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		    V
- * 		    [ Target ]
+ * 		                V
+ * 		           [ Target ]
  *
- * * `northWestArrowSouthEast`
  *
- * 		+-----------------+
- * 		|     Balloon     |
- * 		+-----------------+
- * 		                 V
- * 		                 [ Target ]
  *
- * * `northWestArrowSouthMiddleEast`
+ * **North east**
  *
- * 		 +-----------------+
- * 		 |     Balloon     |
- * 		 +-----------------+
- * 		              V
- * 		              [ Target ]
+ * * `northEastArrowSouthWest`
  *
- * **North east**
+ * 		        +-----------------+
+ * 		        |     Balloon     |
+ * 		        +-----------------+
+ * 		         V
+ * 		[ Target ]
  *
- * * `northEastArrowSouth`
  *
- * 		+-----------------+
- * 		|     Balloon     |
- * 		+-----------------+
+ * * `northEastArrowSoutMiddlehWest`
+ *
+ * 		     +-----------------+
+ * 		     |     Balloon     |
+ * 		     +-----------------+
  * 		         V
  * 		[ Target ]
  *
- * * `northEastArrowSouthEast`
+ * * `northEastArrowSouth`
  *
  * 		+-----------------+
  * 		|     Balloon     |
  * 		+-----------------+
- * 		                 V
- * 		        [ Target ]
+ * 		         V
+ * 		[ Target ]
  *
  * * `northEastArrowSouthMiddleEast`
  *
@@ -549,47 +564,18 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  * 		              V
  * 		     [ Target ]
  *
- * * `northEastArrowSouthWest`
+ * * `northEastArrowSouthEast`
  *
- * 		        +-----------------+
- * 		        |     Balloon     |
- * 		        +-----------------+
- * 		         V
- * 		[ Target ]
+ * 		+-----------------+
+ * 		|     Balloon     |
+ * 		+-----------------+
+ * 		                 V
+ * 		        [ Target ]
  *
- * * `northEastArrowSoutMiddlehWest`
  *
- * 		     +-----------------+
- * 		     |     Balloon     |
- * 		     +-----------------+
- * 		         V
- * 		[ Target ]
  *
  * **South**
  *
- * * `southArrowNorth`
- *
- *		    [ Target ]
- *		         ^
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *
- * * `southArrowNorthEast`
- *
- *		            [ Target ]
- *		                 ^
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *
- * * `southArrowNorthMiddleEast`
- *
- *		            [ Target ]
- *		                 ^
- *		   +-----------------+
- *		   |     Balloon     |
- *		   +-----------------+
  *
  * * `southArrowNorthWest`
  *
@@ -607,16 +593,34 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *		    |     Balloon     |
  *		    +-----------------+
  *
- * **South west**
- *
- * * `southWestArrowNorth`
+ * * `southArrowNorth`
  *
- *		         [ Target ]
+ *		    [ Target ]
  *		         ^
  *		+-----------------+
  *		|     Balloon     |
  *		+-----------------+
  *
+ * * `southArrowNorthMiddleEast`
+ *
+ *		            [ Target ]
+ *		                 ^
+ *		   +-----------------+
+ *		   |     Balloon     |
+ *		   +-----------------+
+ *
+ * * `southArrowNorthEast`
+ *
+ *		            [ Target ]
+ *		                 ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ *
+ *
+ * **South west**
+ *
  * * `southWestArrowNorthWest`
  *
  *		  [ Target ]
@@ -624,7 +628,7 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *		 +-----------------+
  *		 |     Balloon     |
  *		 +-----------------+
-
+ *
  * * `southWestArrowNorthMiddleWest`
  *
  *		     [ Target ]
@@ -633,10 +637,10 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *		 |     Balloon     |
  *		 +-----------------+
  *
- * * `southWestArrowNorthEast`
+ * * `southWestArrowNorth`
  *
- *		                 [ Target ]
- *		                 ^
+ *		         [ Target ]
+ *		         ^
  *		+-----------------+
  *		|     Balloon     |
  *		+-----------------+
@@ -649,31 +653,17 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *		|     Balloon     |
  *		+-----------------+
  *
- * **South east**
- *
- * * `southEastArrowNorth`
- *
- *		[ Target ]
- *		         ^
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
- *
- * * `southEastArrowNorthEast`
+ * * `southWestArrowNorthEast`
  *
- *		        [ Target ]
+ *		                 [ Target ]
  *		                 ^
  *		+-----------------+
  *		|     Balloon     |
  *		+-----------------+
  *
- * * `southEastArrowNorthMiddleEast`
  *
- *		     [ Target ]
- *		              ^
- *		+-----------------+
- *		|     Balloon     |
- *		+-----------------+
+ *
+ * **South east**
  *
  * * `southEastArrowNorthWest`
  *
@@ -682,8 +672,7 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *		        +-----------------+
  *		        |     Balloon     |
  *		        +-----------------+
-
- * * `southEastArrowNorthMiddleWest`
+* * `southEastArrowNorthMiddleWest`
  *
  *		   [ Target ]
  *		            ^
@@ -691,6 +680,31 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  *		        |     Balloon     |
  *		        +-----------------+
  *
+ * * `southEastArrowNorth`
+ *
+ *		[ Target ]
+ *		         ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southEastArrowNorthMiddleEast`
+ *
+ *		     [ Target ]
+ *		              ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ * * `southEastArrowNorthEast`
+ *
+ *		        [ Target ]
+ *		                 ^
+ *		+-----------------+
+ *		|     Balloon     |
+ *		+-----------------+
+ *
+ *
  * See {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#attachTo}.
  *
  * Positioning functions must be compatible with {@link module:utils/dom/position~Position}.
@@ -702,26 +716,40 @@ BalloonPanelView._getOptimalPosition = getOptimalPosition;
  */
 BalloonPanelView.defaultPositions = {
 
-	// ------- North
+	// ------- North west
 
-	northArrowSouth: ( targetRect, balloonRect ) => ( {
+	northWestArrowSouthWest: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
-		name: 'arrow_s'
+		left: targetRect.left - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_sw'
 	} ),
 
-	northArrowSouthEast: ( targetRect, balloonRect ) => ( {
+	northWestArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_se'
+		left: targetRect.left - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_smw'
 	} ),
 
-	northArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
+	northWestArrowSouth: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
+		left: targetRect.left - balloonRect.width / 2,
+		name: 'arrow_s'
+	} ),
+
+	northWestArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.left - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_sme'
 	} ),
 
+	northWestArrowSouthEast: ( targetRect, balloonRect ) => ( {
+		top: getNorthTop( targetRect, balloonRect ),
+		left: targetRect.left - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_se'
+	} ),
+
+	// ------- North
+
 	northArrowSouthWest: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
 		left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
@@ -730,169 +758,153 @@ BalloonPanelView.defaultPositions = {
 
 	northArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 + ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
+		left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_smw'
 	} ),
 
-	// ------- North west
-
-	northWestArrowSouth: ( targetRect, balloonRect ) => ( {
+	northArrowSouth: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left - balloonRect.width / 2,
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
 		name: 'arrow_s'
 	} ),
 
-	northWestArrowSouthWest: ( targetRect, balloonRect ) => ( {
+	northArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_sw'
+		left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_sme'
 	} ),
 
-	northWestArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
+	northArrowSouthEast: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_smw'
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_se'
 	} ),
 
-	northWestArrowSouthEast: ( targetRect, balloonRect ) => ( {
+	// ------- North east
+
+	northEastArrowSouthWest: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_se'
+		left: targetRect.right - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_sw'
 	} ),
 
-	northWestArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
+	northEastArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.left - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_sme'
+		left: targetRect.right - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_smw'
 	} ),
-
-	// ------- North east
-
 	northEastArrowSouth: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
 		left: targetRect.right - balloonRect.width / 2,
 		name: 'arrow_s'
 	} ),
 
-	northEastArrowSouthEast: ( targetRect, balloonRect ) => ( {
-		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.right - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_se'
-	} ),
-
 	northEastArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
 		left: targetRect.right - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_sme'
 	} ),
 
-	northEastArrowSouthWest: ( targetRect, balloonRect ) => ( {
+	northEastArrowSouthEast: ( targetRect, balloonRect ) => ( {
 		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.right - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_sw'
+		left: targetRect.right - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_se'
 	} ),
+	// ------- South west
 
-	northEastArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
-		top: getNorthTop( targetRect, balloonRect ),
-		left: targetRect.right - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_smw'
+	southWestArrowNorthWest: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nw'
 	} ),
 
-	// ------- South
+	southWestArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
+		top: getSouthTop( targetRect, balloonRect ),
+		left: targetRect.left - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nmw'
+	} ),
 
-	southArrowNorth: ( targetRect, balloonRect ) => ( {
+	southWestArrowNorth: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
+		left: targetRect.left - balloonRect.width / 2,
 		name: 'arrow_n'
 	} ),
 
-	southArrowNorthEast: ( targetRect, balloonRect ) => ( {
+	southWestArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_ne'
+		left: targetRect.left - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nme'
 	} ),
 
-	southArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
+	southWestArrowNorthEast: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * 0.75 ) + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nme'
+		left: targetRect.left - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_ne'
 	} ),
 
+	// ------- South
+
 	southArrowNorthWest: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
 		left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_nw'
 	} ),
-
 	southArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
 		left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * 0.25 ) - BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_nmw'
 	} ),
 
-	// ------- South west
-
-	southWestArrowNorth: ( targetRect, balloonRect ) => ( {
+	southArrowNorth: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left - balloonRect.width / 2,
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
 		name: 'arrow_n'
 	} ),
 
-	southWestArrowNorthWest: ( targetRect, balloonRect ) => ( {
+	southArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nw'
+		left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * 0.75 ) + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nme'
 	} ),
 
-	southWestArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
+	southArrowNorthEast: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nmw'
+		left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_ne'
 	} ),
 
-	southWestArrowNorthEast: ( targetRect, balloonRect ) => ( {
+	// ------- South east
+
+	southEastArrowNorthWest: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_ne'
+		left: targetRect.right - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nw'
 	} ),
 
-	southWestArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
+	southEastArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.left - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nme'
+		left: targetRect.right - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_nmw'
 	} ),
 
-	// ------- South east
-
 	southEastArrowNorth: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
 		left: targetRect.right - balloonRect.width / 2,
 		name: 'arrow_n'
 	} ),
 
-	southEastArrowNorthEast: ( targetRect, balloonRect ) => ( {
-		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.right - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_ne'
-	} ),
-
 	southEastArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
 		left: targetRect.right - ( balloonRect.width * .75 ) + BalloonPanelView.arrowHorizontalOffset,
 		name: 'arrow_nme'
 	} ),
 
-	southEastArrowNorthWest: ( targetRect, balloonRect ) => ( {
+	southEastArrowNorthEast: ( targetRect, balloonRect ) => ( {
 		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.right - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nw'
+		left: targetRect.right - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
+		name: 'arrow_ne'
 	} ),
 
-	southEastArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
-		top: getSouthTop( targetRect, balloonRect ),
-		left: targetRect.right - ( balloonRect.width * .25 ) - BalloonPanelView.arrowHorizontalOffset,
-		name: 'arrow_nmw'
-	} ),
 };
 
 // Returns the top coordinate for positions starting with `north*`.

+ 9 - 2
packages/ckeditor5-ui/tests/manual/panel/balloon/balloonpanelview.html

@@ -5,11 +5,18 @@
 		padding: 5em;
 	}
 
+	h1 {
+		font-size: 22px;
+		text-align: center;
+		margin: 70px 0;
+	}
+
 	.target {
 		width: 125px;
 		height: 20px;
-		background: green;
-		margin: 0 0 0 100px;
+		background: lightblue;
+		transform: translateX(-50%);
+		margin-left: 50%;
 	}
 
 	.target + .target {

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

@@ -9,10 +9,24 @@ import BalloonPanelView from '../../../../src/panel/balloon/balloonpanelview';
 
 const defaultPositions = BalloonPanelView.defaultPositions;
 const container = document.querySelector( '#container' );
+const headingRegex = /(w*)arrow\w*/i;
+
+let currentHeading = '';
 
 for ( const i in defaultPositions ) {
 	const target = document.createElement( 'div' );
+	const heading = document.createElement( 'h1' );
+	const headingText = getCapitalizedHeading( i );
+
+	heading.textContent = headingText;
 	target.classList.add( 'target' );
+
+	// Lazy heading
+	if ( currentHeading !== headingText ) {
+		container.appendChild( heading );
+		currentHeading = headingText;
+	}
+
 	container.appendChild( target );
 
 	const balloon = new BalloonPanelView();
@@ -27,3 +41,11 @@ for ( const i in defaultPositions ) {
 		]
 	} );
 }
+
+function getCapitalizedHeading( text ) {
+	const headingText = text.replace( headingRegex, '$1' );
+	const normalizedHeading = headingText.replace( /([a-z])([A-Z])/, '$1 $2' );
+	const capitalizedText = normalizedHeading.charAt( 0 ).toUpperCase() + normalizedHeading.slice( 1 );
+
+	return capitalizedText;
+}

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

@@ -755,7 +755,7 @@ describe( 'BalloonPanelView', () => {
 		it( 'should define the "northArrowSouthMiddleWest" position', () => {
 			expect( positions.northArrowSouthMiddleWest( targetRect, balloonRect ) ).to.deep.equal( {
 				top: 50 - arrowVOffset,
-				left: 162.5 - arrowHOffset,
+				left: 137.5 - arrowHOffset,
 				name: 'arrow_smw'
 			} );
 		} );