Sfoglia il codice sorgente

Renamed and enhanced BalloonPanelView#keepAttachedTo->pin(). Added BalloonPanelView#unpin().

Aleksander Nowodzinski 8 anni fa
parent
commit
fd796e9a73

+ 49 - 18
packages/ckeditor5-ui/src/panel/balloon/balloonpanelview.js

@@ -170,15 +170,16 @@ export default class BalloonPanelView extends View {
 	 *
 	 * Thanks to this, the panel always sticks to the {@link module:utils/dom/position~Options#target}.
 	 *
-	 * See https://github.com/ckeditor/ckeditor5-ui/issues/170.
+	 * See: {@link #unpin}.
 	 *
 	 * @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}.
 	 */
-	keepAttachedTo( options ) {
-		// First we need to attach the balloon panel to the target element.
-		this.attachTo( options );
+	pin( options ) {
+		// See https://github.com/ckeditor/ckeditor5-ui/issues/170.
+
+		this.unpin();
 
 		const limiter = options.limiter || defaultLimiterElement;
 		let target = null;
@@ -190,32 +191,62 @@ export default class BalloonPanelView extends View {
 			target = options.target.commonAncestorContainer;
 		}
 
-		// Then we need to listen on scroll event of eny element in the document.
-		this.listenTo( global.document, 'scroll', ( evt, domEvt ) => {
-			// We need to update position if scrolled element contains related to the balloon elements.
-			if ( ( target && domEvt.target.contains( target ) ) || domEvt.target.contains( limiter ) ) {
+		// Starts managing the pinned state of the panel.
+		//
+		// @private
+		const startPinning = () => {
+			this.attachTo( options );
+
+			// Then we need to listen on scroll event of eny element in the document.
+			this.listenTo( global.document, 'scroll', ( evt, domEvt ) => {
+				// We need to update position if scrolled element contains related to the balloon elements.
+				if ( ( target && domEvt.target.contains( target ) ) || domEvt.target.contains( limiter ) ) {
+					this.attachTo( options );
+				}
+			}, { useCapture: true } );
+
+			// We need to listen on window resize event and update position.
+			this.listenTo( global.window, 'resize', () => {
 				this.attachTo( options );
-			}
-		}, { useCapture: true } );
+			} );
+		};
 
-		// We need to listen on window resize event and update position.
-		this.listenTo( global.window, 'resize', () => this.attachTo( options ) );
-
-		// After all we need to clean up the listeners.
-		this.once( 'change:isVisible', () => {
+		// Stops managing the pinned state of the panel.
+		//
+		// @private
+		const stopPinning = () => {
 			this.stopListening( global.document, 'scroll' );
 			this.stopListening( global.window, 'resize' );
+		};
+
+		// If the panel is already visible, enable the listeners immediately.
+		if ( this.isVisible ) {
+			startPinning();
+		}
+
+		// Control the state of the listeners depending on whether the panel is visible
+		// or not.
+		// TODO: Use on() (https://github.com/ckeditor/ckeditor5-utils/issues/144).
+		this.listenTo( this, 'change:isVisible', () => {
+			if ( this.isVisible ) {
+				startPinning();
+			} else {
+				stopPinning();
+			}
 		} );
 	}
 
 	/**
-	 * @inheritDoc
+	 * Stops pinning the panel, as set up by {@link #pin}.
 	 */
-	destroy() {
+	unpin() {
+		// Deactivate listeners attached by pin().
 		this.stopListening( global.document, 'scroll' );
 		this.stopListening( global.window, 'resize' );
 
-		return super.destroy();
+		// Deactivate the panel pin() control logic.
+		// TODO: Use off() (https://github.com/ckeditor/ckeditor5-utils/issues/144).
+		this.stopListening( this, 'change:isVisible' );
 	}
 }
 

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

@@ -405,7 +405,7 @@ describe( 'BalloonPanelView', () => {
 		} );
 	} );
 
-	describe( 'keepAttachedTo()', () => {
+	describe( 'pin()', () => {
 		let attachToSpy, target, targetParent, limiter, notRelatedElement;
 
 		beforeEach( () => {
@@ -415,6 +415,8 @@ describe( 'BalloonPanelView', () => {
 			target = document.createElement( 'div' );
 			notRelatedElement = document.createElement( 'div' );
 
+			view.show();
+
 			targetParent.appendChild( target );
 			document.body.appendChild( targetParent );
 			document.body.appendChild( limiter );
@@ -422,13 +424,35 @@ describe( 'BalloonPanelView', () => {
 		} );
 
 		afterEach( () => {
-			attachToSpy.restore();
+			targetParent.remove();
 			limiter.remove();
 			notRelatedElement.remove();
 		} );
 
-		it( 'should keep the balloon attached to the target when any of the related elements is scrolled', () => {
-			view.keepAttachedTo( { target, limiter } );
+		it( 'should not pin until the balloon gets visible', () => {
+			view.hide();
+
+			view.pin( { target, limiter } );
+			sinon.assert.notCalled( attachToSpy );
+
+			view.show();
+			sinon.assert.calledOnce( attachToSpy );
+		} );
+
+		it( 'should stop pinning when the balloon becomes invisible', () => {
+			view.show();
+
+			view.pin( { target, limiter } );
+			sinon.assert.calledOnce( attachToSpy );
+
+			view.hide();
+
+			targetParent.dispatchEvent( new Event( 'scroll' ) );
+			sinon.assert.calledOnce( attachToSpy );
+		} );
+
+		it( 'should keep the balloon pinned to the target when any of the related elements is scrolled', () => {
+			view.pin( { target, limiter } );
 
 			sinon.assert.calledOnce( attachToSpy );
 			sinon.assert.calledWith( attachToSpy.lastCall, { target, limiter } );
@@ -450,8 +474,8 @@ describe( 'BalloonPanelView', () => {
 			sinon.assert.calledWith( attachToSpy.lastCall, { target, limiter } );
 		} );
 
-		it( 'should keep the balloon attached to the target when the browser window is being resized', () => {
-			view.keepAttachedTo( { target, limiter } );
+		it( 'should keep the balloon pinned to the target when the browser window is being resized', () => {
+			view.pin( { target, limiter } );
 
 			sinon.assert.calledOnce( attachToSpy );
 			sinon.assert.calledWith( attachToSpy.lastCall, { target, limiter } );
@@ -463,7 +487,7 @@ describe( 'BalloonPanelView', () => {
 		} );
 
 		it( 'should stop attaching when the balloon is hidden', () => {
-			view.keepAttachedTo( { target, limiter } );
+			view.pin( { target, limiter } );
 
 			sinon.assert.calledOnce( attachToSpy );
 
@@ -477,7 +501,7 @@ describe( 'BalloonPanelView', () => {
 		} );
 
 		it( 'should stop attaching once the view is destroyed', () => {
-			view.keepAttachedTo( { target, limiter } );
+			view.pin( { target, limiter } );
 
 			sinon.assert.calledOnce( attachToSpy );
 
@@ -491,7 +515,7 @@ describe( 'BalloonPanelView', () => {
 		} );
 
 		it( 'should set document.body as the default limiter', () => {
-			view.keepAttachedTo( { target } );
+			view.pin( { target } );
 
 			sinon.assert.calledOnce( attachToSpy );
 
@@ -508,7 +532,7 @@ describe( 'BalloonPanelView', () => {
 			document.body.appendChild( element );
 			range.selectNodeContents( element );
 
-			view.keepAttachedTo( { target: range } );
+			view.pin( { target: range } );
 
 			sinon.assert.calledOnce( attachToSpy );
 
@@ -521,7 +545,7 @@ describe( 'BalloonPanelView', () => {
 			// Just check if this normally works without errors.
 			const rect = {};
 
-			view.keepAttachedTo( { target: rect, limiter } );
+			view.pin( { target: rect, limiter } );
 
 			sinon.assert.calledOnce( attachToSpy );
 
@@ -530,6 +554,44 @@ describe( 'BalloonPanelView', () => {
 			sinon.assert.calledTwice( attachToSpy );
 		} );
 	} );
+
+	describe( 'unpin()', () => {
+		let attachToSpy, target, targetParent, limiter;
+
+		beforeEach( () => {
+			attachToSpy = testUtils.sinon.spy( view, 'attachTo' );
+			limiter = document.createElement( 'div' );
+			targetParent = document.createElement( 'div' );
+			target = document.createElement( 'div' );
+
+			view.show();
+
+			targetParent.appendChild( target );
+			document.body.appendChild( targetParent );
+			document.body.appendChild( limiter );
+		} );
+
+		afterEach( () => {
+			targetParent.remove();
+			limiter.remove();
+		} );
+
+		it( 'should stop attaching when the balloon is hidden', () => {
+			view.pin( { target, limiter } );
+			sinon.assert.calledOnce( attachToSpy );
+
+			view.unpin();
+
+			view.hide();
+			window.dispatchEvent( new Event( 'resize' ) );
+			window.dispatchEvent( new Event( 'scroll' ) );
+			view.show();
+			window.dispatchEvent( new Event( 'resize' ) );
+			window.dispatchEvent( new Event( 'scroll' ) );
+
+			sinon.assert.calledOnce( attachToSpy );
+		} );
+	} );
 } );
 
 function mockBoundingBox( element, data ) {