Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 7e11a24

Browse files
authored
Merge pull request #64 from ckeditor/t/60
Fix: Ensured only the widget toolbar attached to the view element which is deepest in the view tree will show up. Code and documentation refactoring in the `WidgetToolbarRepository`. Closes #60. BREAKING CHANGE: The `visibleWhen()` function, a property of an object passed into `WidgetToolbarRepository.register()`, has been renamed to `getRelatedElement()` and must return an editing `View` element the toolbar should be attached to (instead of `Boolean`).
2 parents dde537a + 257040e commit 7e11a24

File tree

2 files changed

+235
-80
lines changed

2 files changed

+235
-80
lines changed

src/widgettoolbarrepository.js

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
3333
*
3434
* widgetToolbarRepository.register( 'image', {
3535
* items: editor.config.get( 'image.toolbar' ),
36-
* visibleWhen: viewSelection => isImageWidgetSelected( viewSelection )
36+
* getRelatedElement: getSelectedImageWidget
3737
* } );
3838
* }
3939
* }
@@ -71,12 +71,12 @@ export default class WidgetToolbarRepository extends Plugin {
7171
}
7272

7373
/**
74-
* A map of toolbars.
74+
* A map of toolbar definitions.
7575
*
7676
* @protected
77-
* @member {Map.<string,Object>} #_toolbars
77+
* @member {Map.<String,module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition>} #_toolbarDefinitions
7878
*/
79-
this._toolbars = new Map();
79+
this._toolbarDefinitions = new Map();
8080

8181
/**
8282
* @private
@@ -95,7 +95,7 @@ export default class WidgetToolbarRepository extends Plugin {
9595

9696
/**
9797
* Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked
98-
* `visibleWhen` function. Toolbar items are gathered from `items` array.
98+
* `getRelatedElement` function. Toolbar items are gathered from `items` array.
9999
* The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option.
100100
*
101101
* Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`}
@@ -104,14 +104,14 @@ export default class WidgetToolbarRepository extends Plugin {
104104
* @param {String} toolbarId An id for the toolbar. Used to
105105
* @param {Object} options
106106
* @param {Array.<String>} options.items Array of toolbar items.
107-
* @param {Function} options.visibleWhen Callback which specifies when the toolbar should be visible for the widget.
107+
* @param {Function} options.getRelatedElement Callback which returns an element the toolbar should be attached to.
108108
* @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon.
109109
*/
110-
register( toolbarId, { items, visibleWhen, balloonClassName = 'ck-toolbar-container' } ) {
110+
register( toolbarId, { items, getRelatedElement, balloonClassName = 'ck-toolbar-container' } ) {
111111
const editor = this.editor;
112112
const toolbarView = new ToolbarView();
113113

114-
if ( this._toolbars.has( toolbarId ) ) {
114+
if ( this._toolbarDefinitions.has( toolbarId ) ) {
115115
/**
116116
* Toolbar with the given id was already added.
117117
*
@@ -123,9 +123,9 @@ export default class WidgetToolbarRepository extends Plugin {
123123

124124
toolbarView.fillFromConfig( items, editor.ui.componentFactory );
125125

126-
this._toolbars.set( toolbarId, {
126+
this._toolbarDefinitions.set( toolbarId, {
127127
view: toolbarView,
128-
visibleWhen,
128+
getRelatedElement,
129129
balloonClassName,
130130
} );
131131
}
@@ -136,47 +136,68 @@ export default class WidgetToolbarRepository extends Plugin {
136136
* @private
137137
*/
138138
_updateToolbarsVisibility() {
139-
for ( const toolbar of this._toolbars.values() ) {
140-
if ( !this.editor.ui.focusTracker.isFocused || !toolbar.visibleWhen( this.editor.editing.view.document.selection ) ) {
141-
this._hideToolbar( toolbar );
139+
let maxRelatedElementDepth = 0;
140+
let deepestRelatedElement = null;
141+
let deepestToolbarDefinition = null;
142+
143+
for ( const definition of this._toolbarDefinitions.values() ) {
144+
const relatedElement = definition.getRelatedElement( this.editor.editing.view.document.selection );
145+
146+
if ( !this.editor.ui.focusTracker.isFocused || !relatedElement ) {
147+
this._hideToolbar( definition );
142148
} else {
143-
this._showToolbar( toolbar );
149+
const relatedElementDepth = relatedElement.getAncestors().length;
150+
151+
// Many toolbars can express willingness to be displayed but they do not know about
152+
// each other. Figure out which toolbar is deepest in the view tree to decide which
153+
// should be displayed. For instance, if a selected image is inside a table cell, display
154+
// the ImageToolbar rather than the TableToolbar (#60).
155+
if ( relatedElementDepth > maxRelatedElementDepth ) {
156+
maxRelatedElementDepth = relatedElementDepth;
157+
deepestRelatedElement = relatedElement;
158+
deepestToolbarDefinition = definition;
159+
}
144160
}
145161
}
162+
163+
if ( deepestToolbarDefinition ) {
164+
this._showToolbar( deepestToolbarDefinition, deepestRelatedElement );
165+
}
146166
}
147167

148168
/**
149169
* Hides the given toolbar.
150170
*
151171
* @private
152-
* @param {Object} toolbar
172+
* @param {module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition} toolbarDefinition
153173
*/
154-
_hideToolbar( toolbar ) {
155-
if ( !this._isToolbarVisible( toolbar ) ) {
174+
_hideToolbar( toolbarDefinition ) {
175+
if ( !this._isToolbarVisible( toolbarDefinition ) ) {
156176
return;
157177
}
158178

159-
this._balloon.remove( toolbar.view );
179+
this._balloon.remove( toolbarDefinition.view );
160180
}
161181

162182
/**
163-
* Shows up the toolbar if the toolbar is not visible and repositions the toolbar's balloon when toolbar's
164-
* view is the most top view in balloon stack.
183+
* Shows up the toolbar if the toolbar is not visible.
184+
* Otherwise, repositions the toolbar's balloon when toolbar's view is the most top view in balloon stack.
165185
*
166186
* It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view
167187
* should be still visible after the {@link module:core/editor/editorui~EditorUI#event:update}.
168188
*
169189
* @private
170-
* @param {Object} toolbar
190+
* @param {module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition} toolbarDefinition
191+
* @param {module:engine/view/element~Element} relatedElement
171192
*/
172-
_showToolbar( toolbar ) {
173-
if ( this._isToolbarVisible( toolbar ) ) {
174-
repositionContextualBalloon( this.editor );
175-
} else if ( !this._balloon.hasView( toolbar.view ) ) {
193+
_showToolbar( toolbarDefinition, relatedElement ) {
194+
if ( this._isToolbarVisible( toolbarDefinition ) ) {
195+
repositionContextualBalloon( this.editor, relatedElement );
196+
} else if ( !this._balloon.hasView( toolbarDefinition.view ) ) {
176197
this._balloon.add( {
177-
view: toolbar.view,
178-
position: getBalloonPositionData( this.editor ),
179-
balloonClassName: toolbar.balloonClassName,
198+
view: toolbarDefinition.view,
199+
position: getBalloonPositionData( this.editor, relatedElement ),
200+
balloonClassName: toolbarDefinition.balloonClassName,
180201
} );
181202
}
182203
}
@@ -190,20 +211,19 @@ export default class WidgetToolbarRepository extends Plugin {
190211
}
191212
}
192213

193-
function repositionContextualBalloon( editor ) {
214+
function repositionContextualBalloon( editor, relatedElement ) {
194215
const balloon = editor.plugins.get( 'ContextualBalloon' );
195-
const position = getBalloonPositionData( editor );
216+
const position = getBalloonPositionData( editor, relatedElement );
196217

197218
balloon.updatePosition( position );
198219
}
199220

200-
function getBalloonPositionData( editor ) {
221+
function getBalloonPositionData( editor, relatedElement ) {
201222
const editingView = editor.editing.view;
202223
const defaultPositions = BalloonPanelView.defaultPositions;
203-
const widget = getParentWidget( editingView.document.selection );
204224

205225
return {
206-
target: editingView.domConverter.viewToDom( widget ),
226+
target: editingView.domConverter.viewToDom( relatedElement ),
207227
positions: [
208228
defaultPositions.northArrowSouth,
209229
defaultPositions.northArrowSouthWest,
@@ -215,27 +235,23 @@ function getBalloonPositionData( editor ) {
215235
};
216236
}
217237

218-
function getParentWidget( selection ) {
219-
const selectedElement = selection.getSelectedElement();
220-
221-
if ( selectedElement && isWidget( selectedElement ) ) {
222-
return selectedElement;
223-
}
224-
225-
const position = selection.getFirstPosition();
226-
let parent = position.parent;
227-
228-
while ( parent ) {
229-
if ( parent.is( 'element' ) && isWidget( parent ) ) {
230-
return parent;
231-
}
232-
233-
parent = parent.parent;
234-
}
235-
}
236-
237238
function isWidgetSelected( selection ) {
238239
const viewElement = selection.getSelectedElement();
239240

240241
return !!( viewElement && isWidget( viewElement ) );
241242
}
243+
244+
/**
245+
* The toolbar definition object used by the toolbar repository to manage toolbars.
246+
* It contains information necessary to display the toolbar in the
247+
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} and
248+
* update it during its life (display) cycle.
249+
*
250+
* @typedef {Object} module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition
251+
*
252+
* @property {module:ui/view~View} view The UI view of the toolbar.
253+
* @property {Function} getRelatedElement A function that returns an engine {@link module:engine/view/view~View}
254+
* element the toolbar is to be attached to. For instance, an image widget or a table widget (or `null` when
255+
* there is no such element). The function accepts an instance of {@link module:engine/view/selection~Selection}.
256+
* @property {String} balloonClassName CSS class for the widget balloon when a toolbar is displayed.
257+
*/

0 commit comments

Comments
 (0)