@@ -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-
237238function 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