浏览代码

Fix: Hide BlockToolbar when the editor is blurred.

Oskar Wróbel 7 年之前
父节点
当前提交
fad59a0dd3

+ 91 - 66
packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js

@@ -14,7 +14,6 @@ import BlockButtonView from './blockbuttonview';
 import BalloonPanelView from '../../panel/balloon/balloonpanelview';
 import ToolbarView from '../toolbarview';
 
-import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
 import clickOutsideHandler from '../../bindings/clickoutsidehandler';
 
 import { getOptimalPosition } from '@ckeditor/ckeditor5-utils/src/dom/position';
@@ -67,10 +66,6 @@ export default class BlockToolbar extends Plugin {
 	 * @inheritDoc
 	 */
 	init() {
-		const editor = this.editor;
-
-		editor.editing.view.addObserver( ClickObserver );
-
 		/**
 		 * The toolbar view.
 		 *
@@ -100,15 +95,6 @@ export default class BlockToolbar extends Plugin {
 			callback: () => this._hidePanel()
 		} );
 
-		// Try to hide button when the editor switches to the read-only mode.
-		// Do not hide when panel if already visible to avoid a confusing UX when the panel
-		// unexpectedly disappears.
-		this.listenTo( editor, 'change:isReadOnly', () => {
-			if ( !this.panelView.isVisible ) {
-				this.buttonView.isVisible = false;
-			}
-		} );
-
 		// Enable as default.
 		this._initListeners();
 	}
@@ -219,9 +205,7 @@ export default class BlockToolbar extends Plugin {
 	 */
 	_initListeners() {
 		const editor = this.editor;
-		const model = editor.model;
 		const view = editor.editing.view;
-		let modelTarget, domTarget;
 
 		// Hides panel on a direct selection change.
 		this.listenTo( editor.model.document.selection, 'change:range', ( evt, data ) => {
@@ -230,36 +214,16 @@ export default class BlockToolbar extends Plugin {
 			}
 		} );
 
-		this.listenTo( view, 'render', () => {
-			// Get first selected block, button will be attached to this element.
-			modelTarget = Array.from( model.document.selection.getSelectedBlocks() )[ 0 ];
-
-			// Do not attach block button when there is no enabled item in toolbar for current block element.
-			if ( !modelTarget || Array.from( this.toolbarView.items ).every( item => !item.isEnabled ) ) {
-				this.buttonView.isVisible = false;
-
-				return;
-			}
-
-			// Get DOM target element.
-			domTarget = view.domConverter.mapViewToDom( editor.editing.mapper.toViewElement( modelTarget ) );
-
-			// Show block button.
-			this.buttonView.isVisible = true;
-
-			// Attach block button to target DOM element.
-			this._attachButtonToElement( domTarget );
-
-			// When panel is opened then refresh it position to be properly aligned with block button.
-			if ( this.panelView.isVisible ) {
-				this._showPanel();
-			}
-		}, { priority: 'low' } );
+		this.listenTo( view, 'render', () => this._updateButton() );
+		// `low` priority is used because of https://github.com/ckeditor/ckeditor5-core/issues/133.
+		this.listenTo( editor, 'change:isReadOnly', () => this._updateButton(), { priority: 'low' } );
+		this.listenTo( editor.ui.focusTracker, 'change:isFocused', () => this._updateButton() );
 
+		// Reposition button on resize.
 		this.listenTo( this.buttonView, 'change:isVisible', ( evt, name, isVisible ) => {
 			if ( isVisible ) {
 				// Keep correct position of button and panel on window#resize.
-				this.buttonView.listenTo( window, 'resize', () => this._attachButtonToElement( domTarget ) );
+				this.buttonView.listenTo( window, 'resize', () => this._updateButton() );
 			} else {
 				// Stop repositioning button when is hidden.
 				this.buttonView.stopListening( window, 'resize' );
@@ -271,35 +235,64 @@ export default class BlockToolbar extends Plugin {
 	}
 
 	/**
-	 * Attaches the {@link #buttonView} to the target block of content.
+	 * Shows or hides the button.
+	 * When the all conditions for displaying button are matched then shows the button. Hides otherwise.
 	 *
-	 * @protected
-	 * @param {HTMLElement} targetElement Target element.
+	 * @private
 	 */
-	_attachButtonToElement( targetElement ) {
-		const contentStyles = window.getComputedStyle( targetElement );
+	_updateButton() {
+		const editor = this.editor;
+		const model = editor.model;
+		const view = editor.editing.view;
 
-		const editableRect = new Rect( this.editor.ui.view.editableElement );
-		const contentPaddingTop = parseInt( contentStyles.paddingTop, 10 );
-		// When line height is not an integer then thread it as "normal".
-		// MDN says that 'normal' == ~1.2 on desktop browsers.
-		const contentLineHeight = parseInt( contentStyles.lineHeight, 10 ) || parseInt( contentStyles.fontSize, 10 ) * 1.2;
+		// Hides the button when editor is not focused.
+		if ( !editor.ui.focusTracker.isFocused ) {
+			this._hideButton();
 
-		const position = getOptimalPosition( {
-			element: this.buttonView.element,
-			target: targetElement,
-			positions: [
-				( contentRect, buttonRect ) => {
-					return {
-						top: contentRect.top + contentPaddingTop + ( ( contentLineHeight - buttonRect.height ) / 2 ),
-						left: editableRect.left - buttonRect.width
-					};
-				}
-			]
-		} );
+			return;
+		}
 
-		this.buttonView.top = position.top;
-		this.buttonView.left = position.left;
+		// Hides button when the editor switches to the read-only mode.
+		// Do not hide when panel is already visible to avoid a confusing UX when the panel
+		// unexpectedly disappears.
+		if ( editor.isReadOnly && !this.panelView.isVisible ) {
+			this._hideButton();
+
+			return;
+		}
+
+		// Get the first selected block, button will be attached to this element.
+		const modelTarget = Array.from( model.document.selection.getSelectedBlocks() )[ 0 ];
+
+		// Hides the button when there is no enabled item in toolbar for the current block element.
+		if ( !modelTarget || Array.from( this.toolbarView.items ).every( item => !item.isEnabled ) ) {
+			this._hideButton();
+
+			return;
+		}
+
+		// Get DOM target element.
+		const domTarget = view.domConverter.mapViewToDom( editor.editing.mapper.toViewElement( modelTarget ) );
+
+		// Show block button.
+		this.buttonView.isVisible = true;
+
+		// Attach block button to target DOM element.
+		this._attachButtonToElement( domTarget );
+
+		// When panel is opened then refresh it position to be properly aligned with block button.
+		if ( this.panelView.isVisible ) {
+			this._showPanel();
+		}
+	}
+
+	/**
+	 * Hides the button.
+	 *
+	 * @private
+	 */
+	_hideButton() {
+		this.buttonView.isVisible = false;
 	}
 
 	/**
@@ -334,6 +327,38 @@ export default class BlockToolbar extends Plugin {
 			this.editor.editing.view.focus();
 		}
 	}
+
+	/**
+	 * Attaches the {@link #buttonView} to the target block of content.
+	 *
+	 * @protected
+	 * @param {HTMLElement} targetElement Target element.
+	 */
+	_attachButtonToElement( targetElement ) {
+		const contentStyles = window.getComputedStyle( targetElement );
+
+		const editableRect = new Rect( this.editor.ui.view.editableElement );
+		const contentPaddingTop = parseInt( contentStyles.paddingTop, 10 );
+		// When line height is not an integer then thread it as "normal".
+		// MDN says that 'normal' == ~1.2 on desktop browsers.
+		const contentLineHeight = parseInt( contentStyles.lineHeight, 10 ) || parseInt( contentStyles.fontSize, 10 ) * 1.2;
+
+		const position = getOptimalPosition( {
+			element: this.buttonView.element,
+			target: targetElement,
+			positions: [
+				( contentRect, buttonRect ) => {
+					return {
+						top: contentRect.top + contentPaddingTop + ( ( contentLineHeight - buttonRect.height ) / 2 ),
+						left: editableRect.left - buttonRect.width
+					};
+				}
+			]
+		} );
+
+		this.buttonView.top = position.top;
+		this.buttonView.left = position.left;
+	}
 }
 
 /**

+ 38 - 12
packages/ckeditor5-ui/tests/toolbar/block/blocktoolbar.js

@@ -5,7 +5,6 @@
 /* global document, window, Event */
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
-import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
 
 import BlockToolbar from '../../../src/toolbar/block/blocktoolbar';
 import ToolbarView from '../../../src/toolbar/toolbarview';
@@ -39,6 +38,7 @@ describe( 'BlockToolbar', () => {
 		} ).then( newEditor => {
 			editor = newEditor;
 			blockToolbar = editor.plugins.get( BlockToolbar );
+			editor.ui.focusTracker.isFocused = true;
 		} );
 	} );
 
@@ -51,10 +51,6 @@ describe( 'BlockToolbar', () => {
 		expect( BlockToolbar.pluginName ).to.equal( 'BlockToolbar' );
 	} );
 
-	it( 'should register a click observer', () => {
-		expect( editor.editing.view.getObserver( ClickObserver ) ).to.be.instanceOf( ClickObserver );
-	} );
-
 	describe( 'child views', () => {
 		describe( 'panelView', () => {
 			it( 'should create a view instance', () => {
@@ -70,7 +66,7 @@ describe( 'BlockToolbar', () => {
 			} );
 
 			it( 'should add the #panelView to ui.focusTracker', () => {
-				expect( editor.ui.focusTracker.isFocused ).to.be.false;
+				editor.ui.focusTracker.isFocused = false;
 
 				blockToolbar.panelView.element.dispatchEvent( new Event( 'focus' ) );
 
@@ -149,7 +145,7 @@ describe( 'BlockToolbar', () => {
 			} );
 
 			it( 'should add the #buttonView to the ui.focusTracker', () => {
-				expect( editor.ui.focusTracker.isFocused ).to.be.false;
+				editor.ui.focusTracker.isFocused = false;
 
 				blockToolbar.buttonView.element.dispatchEvent( new Event( 'focus' ) );
 
@@ -377,7 +373,7 @@ describe( 'BlockToolbar', () => {
 			expect( blockToolbar.panelView.isVisible ).to.be.true;
 		} );
 
-		it( 'should hide the UI when editor switches to readonly when the panel is not visible', () => {
+		it( 'should hide the UI when editor switched to readonly when the panel is not visible', () => {
 			setData( editor.model, '<paragraph>foo[]bar</paragraph>' );
 
 			blockToolbar.buttonView.isVisible = true;
@@ -389,16 +385,46 @@ describe( 'BlockToolbar', () => {
 			expect( blockToolbar.panelView.isVisible ).to.be.false;
 		} );
 
-		it( 'should not hide button when the editor switches to readonly when the panel is visible', () => {
+		it( 'should show the button when the editor switched from readonly', () => {
 			setData( editor.model, '<paragraph>foo[]bar</paragraph>' );
 
-			blockToolbar.buttonView.isVisible = true;
-			blockToolbar.panelView.isVisible = true;
+			expect( blockToolbar.buttonView.isVisible ).to.true;
 
 			editor.isReadOnly = true;
 
+			expect( blockToolbar.buttonView.isVisible ).to.false;
+
+			editor.isReadOnly = false;
+
 			expect( blockToolbar.buttonView.isVisible ).to.be.true;
-			expect( blockToolbar.panelView.isVisible ).to.be.true;
+		} );
+
+		it( 'should show/hide the button on editor focus/blur', () => {
+			setData( editor.model, '<paragraph>foo[]bar</paragraph>' );
+
+			editor.ui.focusTracker.isFocused = true;
+
+			expect( blockToolbar.buttonView.isVisible ).to.true;
+
+			editor.ui.focusTracker.isFocused = false;
+
+			expect( blockToolbar.buttonView.isVisible ).to.false;
+
+			editor.ui.focusTracker.isFocused = true;
+
+			expect( blockToolbar.buttonView.isVisible ).to.true;
+		} );
+
+		it( 'should hide the UI when editor switched to readonly when the panel is not visible', () => {
+			setData( editor.model, '<paragraph>foo[]bar</paragraph>' );
+
+			blockToolbar.buttonView.isVisible = true;
+			blockToolbar.panelView.isVisible = false;
+
+			editor.isReadOnly = true;
+
+			expect( blockToolbar.buttonView.isVisible ).to.be.false;
+			expect( blockToolbar.panelView.isVisible ).to.be.false;
 		} );
 
 		it( 'should update the button position on browser resize only when the button is visible', () => {