diff --git a/src/toolbar/contextual/contextualtoolbar.js b/src/toolbar/contextual/contextualtoolbar.js index 40daf823..8c666ad7 100644 --- a/src/toolbar/contextual/contextualtoolbar.js +++ b/src/toolbar/contextual/contextualtoolbar.js @@ -79,6 +79,10 @@ export default class ContextualToolbar extends Plugin { // Attach lifecycle actions. this._handleSelectionChange(); this._handleFocusChange(); + + // The appearance of the ContextualToolbar method is event–driven. + // It is possible to stop the #show event and this prevent the toolbar from showing up. + this.decorate( 'show' ); } /** @@ -95,7 +99,7 @@ export default class ContextualToolbar extends Plugin { } /** - * Handles editor focus change and hides panel if it's needed. + * Handles the editor focus change and hides the toolbar if it's needed. * * @private */ @@ -146,41 +150,31 @@ export default class ContextualToolbar extends Plugin { /** * Shows the toolbar and attaches it to the selection. * - * Fires {@link #event:beforeShow} event just before displaying the panel. + * Fires {@link #event:show} event which can be stopped to prevent the toolbar from showing up. */ show() { - const editingView = this.editor.editing.view; - let isStopped = false; - - // Do not add toolbar to the balloon stack twice. + // Do not add the toolbar to the balloon stack twice. if ( this._balloon.hasView( this.toolbarView ) ) { return; } - // If `beforeShow` event is not stopped by any external code then panel will be displayed. - this.once( 'beforeShow', () => { - if ( isStopped ) { - return; - } + // Don not show the toolbar when all components inside are disabled + // see https://github.com/ckeditor/ckeditor5-ui/issues/269. + if ( Array.from( this.toolbarView.items ).every( item => item.isEnabled !== undefined && !item.isEnabled ) ) { + return; + } - // Update panel position when selection changes while balloon will be opened - // (by an external document changes). - this.listenTo( editingView, 'render', () => { - this._balloon.updatePosition( this._getBalloonPositionData() ); - } ); - - // Add panel to the common editor contextual balloon. - this._balloon.add( { - view: this.toolbarView, - position: this._getBalloonPositionData(), - balloonClassName: 'ck-toolbar-container ck-editor-toolbar-container' - } ); + // Update the toolbar position upon #render (e.g. external document changes) + // while it's visible. + this.listenTo( this.editor.editing.view, 'render', () => { + this._balloon.updatePosition( this._getBalloonPositionData() ); } ); - // Fire this event to inform that `ContextualToolbar` is going to be shown. - // Helper function for preventing the panel from being displayed is passed along with the event. - this.fire( 'beforeShow', () => { - isStopped = true; + // Add the toolbar to the common editor contextual balloon. + this._balloon.add( { + view: this.toolbarView, + position: this._getBalloonPositionData(), + balloonClassName: 'ck-toolbar-container ck-editor-toolbar-container' } ); } @@ -234,12 +228,9 @@ export default class ContextualToolbar extends Plugin { } /** - * This event is fired just before the toolbar shows. - * Using this event, an external code can prevent ContextualToolbar - * from being displayed by calling a `stop` function which is passed along with this event. + * This event is fired just before the toolbar shows up. Stopping this event will prevent this. * - * @event beforeShow - * @param {Function} stop Calling this function prevents panel from being displayed. + * @event show */ /** diff --git a/tests/manual/contextualtoolbar/contextualtoolbar.html b/tests/manual/contextualtoolbar/contextualtoolbar.html index 05c39012..eaeccbf3 100644 --- a/tests/manual/contextualtoolbar/contextualtoolbar.html +++ b/tests/manual/contextualtoolbar/contextualtoolbar.html @@ -1,5 +1,6 @@
-

This is a first line of text.

+

ContextualToolbar won't show for the first block element.

+

This is a first line of text.

This is a second line of text.

This is the end of text.

diff --git a/tests/manual/contextualtoolbar/contextualtoolbar.js b/tests/manual/contextualtoolbar/contextualtoolbar.js index 64f47a83..73be2ee2 100644 --- a/tests/manual/contextualtoolbar/contextualtoolbar.js +++ b/tests/manual/contextualtoolbar/contextualtoolbar.js @@ -8,6 +8,7 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; import ArticlePresets from '@ckeditor/ckeditor5-presets/src/article'; import ContextualToolbar from '../../../src/toolbar/contextual/contextualtoolbar'; +import Range from '@ckeditor/ckeditor5-engine/src/model/range'; ClassicEditor.create( document.querySelector( '#editor' ), { plugins: [ ArticlePresets, ContextualToolbar ], @@ -16,6 +17,17 @@ ClassicEditor.create( document.querySelector( '#editor' ), { } ) .then( editor => { window.editor = editor; + + const contextualToolbar = editor.plugins.get( 'ContextualToolbar' ); + + contextualToolbar.on( 'show', evt => { + const selectionRange = editor.document.selection.getFirstRange(); + const blockRange = Range.createOn( editor.document.getRoot().getChild( 0 ) ); + + if ( selectionRange.containsRange( blockRange ) || selectionRange.isIntersecting( blockRange ) ) { + evt.stop(); + } + }, { priority: 'high' } ); } ) .catch( err => { console.error( err.stack ); diff --git a/tests/manual/contextualtoolbar/contextualtoolbar.md b/tests/manual/contextualtoolbar/contextualtoolbar.md index 04555303..9ef00aed 100644 --- a/tests/manual/contextualtoolbar/contextualtoolbar.md +++ b/tests/manual/contextualtoolbar/contextualtoolbar.md @@ -3,3 +3,4 @@ 1. Create a non–collapsed selection. 2. Create another non–collapsed selection but in another direction. 3. For each selection, a contextual toolbar should appear and the beginning/end of the selection, duplicating main editor toolbar. +4. Toolbar should not display when selection is placed in the first block element. diff --git a/tests/toolbar/contextual/contextualtoolbar.js b/tests/toolbar/contextual/contextualtoolbar.js index 4957be6f..54e3a493 100644 --- a/tests/toolbar/contextual/contextualtoolbar.js +++ b/tests/toolbar/contextual/contextualtoolbar.js @@ -217,6 +217,29 @@ describe( 'ContextualToolbar', () => { sinon.assert.calledOnce( balloonAddSpy ); } ); + it( 'should not add #toolbarView to the #_balloon when all components inside #toolbarView are disabled', () => { + Array.from( contextualToolbar.toolbarView.items ).forEach( item => { + item.isEnabled = false; + } ); + setData( editor.document, 'b[a]r' ); + + contextualToolbar.show(); + sinon.assert.notCalled( balloonAddSpy ); + } ); + + it( 'should add #toolbarView to the #_balloon when at least one component inside does not have #isEnabled interface', () => { + Array.from( contextualToolbar.toolbarView.items ).forEach( item => { + item.isEnabled = false; + } ); + + delete contextualToolbar.toolbarView.items.get( 0 ).isEnabled; + + setData( editor.document, 'b[a]r' ); + + contextualToolbar.show(); + sinon.assert.calledOnce( balloonAddSpy ); + } ); + describe( 'on #_selectionChangeDebounced event', () => { let showSpy; @@ -408,25 +431,23 @@ describe( 'ContextualToolbar', () => { } ); } ); - describe( 'beforeShow event', () => { - it( 'should fire `beforeShow` event just before panel shows', () => { + describe( 'show event', () => { + it( 'should fire `show` event just before panel shows', () => { const spy = sinon.spy(); - contextualToolbar.on( 'beforeShow', spy ); + contextualToolbar.on( 'show', spy ); setData( editor.document, 'b[a]r' ); contextualToolbar.show(); sinon.assert.calledOnce( spy ); } ); - it( 'should not show the panel when `beforeShow` event is stopped', () => { + it( 'should not show the panel when `show` event is stopped', () => { const balloonAddSpy = sandbox.spy( balloon, 'add' ); setData( editor.document, 'b[a]r' ); - contextualToolbar.on( 'beforeShow', ( evt, stop ) => { - stop(); - } ); + contextualToolbar.on( 'show', evt => evt.stop(), { priority: 'high' } ); contextualToolbar.show(); sinon.assert.notCalled( balloonAddSpy );