diff --git a/src/toolbar/contextual/contextualtoolbar.js b/src/toolbar/contextual/contextualtoolbar.js
index 91634331..d3b635e3 100644
--- a/src/toolbar/contextual/contextualtoolbar.js
+++ b/src/toolbar/contextual/contextualtoolbar.js
@@ -131,11 +131,14 @@ export default class ContextualToolbar extends Plugin {
/**
* Adds panel view to the {@link: #_balloon} and attaches panel to the selection.
*
+ * Fires {@link #event:beforeShow} event just before displaying the panel.
+ *
* @protected
* @return {Promise} A promise resolved when the {@link #toolbarView} {@link module:ui/view~View#init} is done.
*/
_showPanel() {
const editingView = this.editor.editing.view;
+ let isStopped = false;
// Do not add toolbar to the balloon stack twice.
if ( this._balloon.hasView( this.toolbarView ) ) {
@@ -147,17 +150,39 @@ export default class ContextualToolbar extends Plugin {
return Promise.resolve();
}
- // Update panel position when selection changes while balloon will be opened (by a collaboration).
- this.listenTo( this.editor.editing.view, 'render', () => {
- this._balloon.updatePosition( this._getBalloonPositionData() );
+ const showPromise = new Promise( ( resolve ) => {
+ // If `beforeShow` event is not stopped by any external code then panel will be displayed.
+ this.once( 'beforeShow', () => {
+ if ( isStopped ) {
+ resolve();
+
+ 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() );
+ } );
+
+ resolve(
+ // Add panel to the common editor contextual balloon.
+ this._balloon.add( {
+ view: this.toolbarView,
+ position: this._getBalloonPositionData(),
+ balloonClassName: 'ck-toolbar-container'
+ } )
+ );
+ } );
+ }, { priority: 'lowest' } );
+
+ // 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 panel to the common editor contextual balloon.
- return this._balloon.add( {
- view: this.toolbarView,
- position: this._getBalloonPositionData(),
- balloonClassName: 'ck-toolbar-container'
- } );
+ return showPromise;
}
/**
@@ -210,6 +235,15 @@ export default class ContextualToolbar extends Plugin {
super.destroy();
}
+ /**
+ * 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.
+ *
+ * @event beforeShow
+ * @param {Function} stop Calling this function prevents panel from being displayed.
+ */
+
/**
* This is internal plugin event which is fired 200 ms after model selection last change.
* This is to makes easy test debounced action without need to use `setTimeout`.
diff --git a/tests/toolbar/contextual/contextualtoolbar.js b/tests/toolbar/contextual/contextualtoolbar.js
index e301d16b..5beed853 100644
--- a/tests/toolbar/contextual/contextualtoolbar.js
+++ b/tests/toolbar/contextual/contextualtoolbar.js
@@ -49,6 +49,7 @@ describe( 'ContextualToolbar', () => {
afterEach( () => {
sandbox.restore();
+ editorElement.remove();
return editor.destroy();
} );
@@ -234,7 +235,7 @@ describe( 'ContextualToolbar', () => {
let removeBalloonSpy;
beforeEach( () => {
- removeBalloonSpy = sandbox.spy( balloon, 'remove' );
+ removeBalloonSpy = sandbox.stub( balloon, 'remove', () => {} );
editor.editing.view.isFocused = true;
} );
@@ -388,6 +389,35 @@ describe( 'ContextualToolbar', () => {
} );
} );
+ describe( 'beforeShow event', () => {
+ it( 'should fire `beforeShow` event just before panel shows', () => {
+ const spy = sinon.spy();
+
+ contextualToolbar.on( 'beforeShow', spy );
+ setData( editor.document, 'b[a]r' );
+
+ const promise = contextualToolbar._showPanel();
+
+ sinon.assert.calledOnce( spy );
+
+ return promise;
+ } );
+
+ it( 'should not show the panel when `beforeShow` event is stopped', () => {
+ const balloonAddSpy = sandbox.spy( balloon, 'add' );
+
+ setData( editor.document, 'b[a]r' );
+
+ contextualToolbar.on( 'beforeShow', ( evt, stop ) => {
+ stop();
+ } );
+
+ return contextualToolbar._showPanel().then( () => {
+ sinon.assert.notCalled( balloonAddSpy );
+ } );
+ } );
+ } );
+
function stubSelectionRect( forwardSelectionRect, backwardSelectionRect ) {
const editingView = editor.editing.view;
const originalViewRangeToDom = editingView.domConverter.viewRangeToDom;