Parcourir la source

Added `stickTo` method to the BalloonPanelView.

Oskar Wróbel il y a 8 ans
Parent
commit
ee3ca5f701

+ 20 - 0
packages/ckeditor5-ui/src/panel/balloon/balloonpanelview.js

@@ -159,6 +159,26 @@ export default class BalloonPanelView extends View {
 
 		Object.assign( this, { top, left, position } );
 	}
+
+	/**
+	 * Works exactly the same as {module:ui/panel/balloon/balloonpanelview~BalloonPanelView.attachTo} with one exception.
+	 * Position of attached panel is constantly updated when any of element in document is scrolled. Thanks to this
+	 * panel always sticks to the target element. See #170.
+	 *
+	 * @param {module:utils/dom/position~Options} options Positioning options compatible with
+	 * {@link module:utils/dom/position~getOptimalPosition}. Default `positions` array is
+	 * {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
+	 */
+	stickTo( options ) {
+		// First we need to attach the balloon panel to target element.
+		this.attachTo( options );
+
+		// Then we need to listen to scroll of eny element in the document and update position of the balloon panel.
+		this.listenTo( document, 'scroll', () => this.attachTo( options ), { useCapture: true } );
+
+		// After all we need to clean up the listener.
+		this.once( 'change:isVisible', () => this.stopListening( document, 'scroll' ) );
+	}
 }
 
 /**

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

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md.
  */
 
-/* global window, document */
+/* global window, document, Event */
 
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
 import ViewCollection from '../../../src/viewcollection';
@@ -409,6 +409,55 @@ describe( 'BalloonPanelView', () => {
 			} );
 		} );
 	} );
+
+	describe( 'stickTo()', () => {
+		let attachToSpy, target, limiter;
+
+		beforeEach( () => {
+			attachToSpy = testUtils.sinon.spy( view, 'attachTo' );
+			limiter = document.createElement( 'div' );
+			target = document.createElement( 'div' );
+
+			document.body.appendChild( limiter );
+			document.body.appendChild( target );
+		} );
+
+		afterEach( () => {
+			attachToSpy.restore();
+			limiter.remove();
+			target.remove();
+		} );
+
+		it( 'should attach balloon to the target constantly when any of element in document is scrolled', () => {
+			view.stickTo( { target, limiter } );
+
+			expect( attachToSpy.calledOnce ).to.true;
+			expect( attachToSpy.lastCall.args[ 0 ] ).to.deep.equal( { target, limiter } );
+
+			document.dispatchEvent( new Event( 'scroll' ) );
+
+			expect( attachToSpy.calledTwice ).to.true;
+			expect( attachToSpy.lastCall.args[ 0 ] ).to.deep.equal( { target, limiter } );
+
+			limiter.dispatchEvent( new Event( 'scroll' ) );
+
+			expect( attachToSpy.calledThrice ).to.true;
+			expect( attachToSpy.lastCall.args[ 0 ] ).to.deep.equal( { target, limiter } );
+		} );
+
+		it( 'should stop attach when balloon is hidden', () => {
+			view.stickTo( { target, limiter } );
+
+			expect( attachToSpy.calledOnce ).to.true;
+
+			view.hide();
+
+			document.dispatchEvent( new Event( 'scroll' ) );
+
+			// Still once.
+			expect( attachToSpy.calledOnce ).to.true;
+		} );
+	} );
 } );
 
 function mockBoundingBox( element, data ) {