@@ -15,6 +15,10 @@ import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
1515import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect' ;
1616import normalizeToolbarConfig from '../normalizetoolbarconfig' ;
1717import { debounce } from 'lodash-es' ;
18+ import ResizeObserver from '@ckeditor/ckeditor5-utils/src/dom/resizeobserver' ;
19+ import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit' ;
20+
21+ const toPx = toUnit ( 'px' ) ;
1822
1923/**
2024 * The contextual toolbar.
@@ -44,6 +48,14 @@ export default class BalloonToolbar extends Plugin {
4448 constructor ( editor ) {
4549 super ( editor ) ;
4650
51+ /**
52+ * A cached and normalized `config.balloonToolbar` object.
53+ *
54+ * @type {module:core/editor/editorconfig~EditorConfig#balloonToolbar }
55+ * @private
56+ */
57+ this . _balloonConfig = normalizeToolbarConfig ( editor . config . get ( 'balloonToolbar' ) ) ;
58+
4759 /**
4860 * The toolbar view displayed in the balloon.
4961 *
@@ -66,6 +78,20 @@ export default class BalloonToolbar extends Plugin {
6678 this . focusTracker . add ( this . toolbarView . element ) ;
6779 } ) ;
6880
81+ /**
82+ * An instance of the resize observer that allows to respond to changes in editable's geometry
83+ * so the toolbar can stay within its boundaries (and group toolbar items that do not fit).
84+ *
85+ * **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the
86+ * {@link module:core/editor/editorconfig~EditorConfig#balloonToolbar configuration}.
87+ *
88+ * **Note:** Created in {@link #init}.
89+ *
90+ * @protected
91+ * @member {module:utils/dom/resizeobserver~ResizeObserver}
92+ */
93+ this . _resizeObserver = null ;
94+
6995 /**
7096 * The contextual balloon plugin instance.
7197 *
@@ -125,6 +151,20 @@ export default class BalloonToolbar extends Plugin {
125151 this . show ( ) ;
126152 }
127153 } ) ;
154+
155+ if ( ! this . _balloonConfig . shouldNotGroupWhenFull ) {
156+ this . listenTo ( editor , 'ready' , ( ) => {
157+ const editableElement = editor . ui . view . editable . element ;
158+
159+ // Set #toolbarView's max-width on the initialization and update it on the editable resize.
160+ this . _resizeObserver = new ResizeObserver ( editableElement , ( ) => {
161+ // The max-width equals 90% of the editable's width for the best user experience.
162+ // The value keeps the balloon very close to the boundaries of the editable and limits the cases
163+ // when the balloon juts out from the editable element it belongs to.
164+ this . toolbarView . maxWidth = toPx ( new Rect ( editableElement ) . width * .9 ) ;
165+ } ) ;
166+ } ) ;
167+ }
128168 }
129169
130170 /**
@@ -134,10 +174,9 @@ export default class BalloonToolbar extends Plugin {
134174 * @inheritDoc
135175 */
136176 afterInit ( ) {
137- const config = normalizeToolbarConfig ( this . editor . config . get ( 'balloonToolbar' ) ) ;
138177 const factory = this . editor . ui . componentFactory ;
139178
140- this . toolbarView . fillFromConfig ( config . items , factory ) ;
179+ this . toolbarView . fillFromConfig ( this . _balloonConfig . items , factory ) ;
141180 }
142181
143182 /**
@@ -147,7 +186,10 @@ export default class BalloonToolbar extends Plugin {
147186 * @returns {module:ui/toolbar/toolbarview~ToolbarView }
148187 */
149188 _createToolbarView ( ) {
150- const toolbarView = new ToolbarView ( this . editor . locale ) ;
189+ const shouldGroupWhenFull = ! this . _balloonConfig . shouldNotGroupWhenFull ;
190+ const toolbarView = new ToolbarView ( this . editor . locale , {
191+ shouldGroupWhenFull
192+ } ) ;
151193
152194 toolbarView . extendTemplate ( {
153195 attributes : {
@@ -260,6 +302,10 @@ export default class BalloonToolbar extends Plugin {
260302 this . _fireSelectionChangeDebounced . cancel ( ) ;
261303 this . toolbarView . destroy ( ) ;
262304 this . focusTracker . destroy ( ) ;
305+
306+ if ( this . _resizeObserver ) {
307+ this . _resizeObserver . destroy ( ) ;
308+ }
263309 }
264310
265311 /**
@@ -289,23 +335,33 @@ function getBalloonPositions( isBackward ) {
289335 defaultPositions . northWestArrowSouth ,
290336 defaultPositions . northWestArrowSouthWest ,
291337 defaultPositions . northWestArrowSouthEast ,
338+ defaultPositions . northWestArrowSouthMiddleEast ,
339+ defaultPositions . northWestArrowSouthMiddleWest ,
292340 defaultPositions . southWestArrowNorth ,
293341 defaultPositions . southWestArrowNorthWest ,
294- defaultPositions . southWestArrowNorthEast
342+ defaultPositions . southWestArrowNorthEast ,
343+ defaultPositions . southWestArrowNorthMiddleWest ,
344+ defaultPositions . southWestArrowNorthMiddleEast
295345 ] : [
296346 defaultPositions . southEastArrowNorth ,
297347 defaultPositions . southEastArrowNorthEast ,
298348 defaultPositions . southEastArrowNorthWest ,
349+ defaultPositions . southEastArrowNorthMiddleEast ,
350+ defaultPositions . southEastArrowNorthMiddleWest ,
299351 defaultPositions . northEastArrowSouth ,
300352 defaultPositions . northEastArrowSouthEast ,
301- defaultPositions . northEastArrowSouthWest
353+ defaultPositions . northEastArrowSouthWest ,
354+ defaultPositions . northEastArrowSouthMiddleEast ,
355+ defaultPositions . northEastArrowSouthMiddleWest
302356 ] ;
303357}
304358
305359/**
306360 * Contextual toolbar configuration. Used by the {@link module:ui/toolbar/balloon/balloontoolbar~BalloonToolbar}
307361 * feature.
308362 *
363+ * ## Configuring toolbar items
364+ *
309365 * const config = {
310366 * balloonToolbar: [ 'bold', 'italic', 'undo', 'redo' ]
311367 * };
@@ -318,5 +374,16 @@ function getBalloonPositions( isBackward ) {
318374 *
319375 * Read also about configuring the main editor toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
320376 *
377+ * ## Configuring items grouping
378+ *
379+ * You can prevent automatic items grouping by setting the `shouldNotGroupWhenFull` option:
380+ *
381+ * const config = {
382+ * balloonToolbar: {
383+ * items: [ 'bold', 'italic', 'undo', 'redo' ]
384+ * },
385+ * shouldNotGroupWhenFull: true
386+ * };
387+ *
321388 * @member {Array.<String>|Object} module:core/editor/editorconfig~EditorConfig#balloonToolbar
322389 */
0 commit comments