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

Commit ab499c1

Browse files
committed
Merge branch 'master' into i/6194
2 parents db27824 + 9ba69a1 commit ab499c1

File tree

13 files changed

+648
-128
lines changed

13 files changed

+648
-128
lines changed

src/panel/balloon/balloonpanelview.js

Lines changed: 295 additions & 112 deletions
Large diffs are not rendered by default.

src/toolbar/balloon/balloontoolbar.js

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
1515
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
1616
import normalizeToolbarConfig from '../normalizetoolbarconfig';
1717
import { 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
*/

src/toolbar/toolbarview.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ export default class ToolbarView extends View {
6161
*/
6262
this.set( 'ariaLabel', t( 'Editor toolbar' ) );
6363

64+
/**
65+
* The maximum width of the toolbar element.
66+
*
67+
* **Note**: When set to a specific value (e.g. `'200px'`), the value will affect the behavior of the
68+
* {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull}
69+
* option by changing the number of {@link #items} that will be displayed in the toolbar at a time.
70+
*
71+
* @observable
72+
* @default 'auto'
73+
* @member {String} #maxWidth
74+
*/
75+
this.set( 'maxWidth', 'auto' );
76+
6477
/**
6578
* A collection of toolbar items (buttons, dropdowns, etc.).
6679
*
@@ -181,7 +194,10 @@ export default class ToolbarView extends View {
181194
bind.if( 'isCompact', 'ck-toolbar_compact' )
182195
],
183196
role: 'toolbar',
184-
'aria-label': bind.to( 'ariaLabel' )
197+
'aria-label': bind.to( 'ariaLabel' ),
198+
style: {
199+
maxWidth: bind.to( 'maxWidth' )
200+
}
185201
},
186202

187203
children: this.children,
@@ -570,6 +586,7 @@ class DynamicGrouping {
570586
this.viewElement = view.element;
571587

572588
this._enableGroupingOnResize();
589+
this._enableGroupingOnMaxWidthChange( view );
573590
}
574591

575592
/**
@@ -694,6 +711,18 @@ class DynamicGrouping {
694711
this._updateGrouping();
695712
}
696713

714+
/**
715+
* Enables the grouping functionality, just like {@link #_enableGroupingOnResize} but the difference is that
716+
* it listens to the changes of {@link module:ui/toolbar/toolbarview~ToolbarView#maxWidth} instead.
717+
*
718+
* @private
719+
*/
720+
_enableGroupingOnMaxWidthChange( view ) {
721+
view.on( 'change:maxWidth', () => {
722+
this._updateGrouping();
723+
} );
724+
}
725+
697726
/**
698727
* When called, it will remove the last item from {@link #ungroupedItems} and move it back
699728
* to the {@link #groupedItems} collection.
@@ -798,6 +827,8 @@ class DynamicGrouping {
798827
* would normally wrap to the next line when there is not enough space to display them in a single row, for
799828
* instance, if the parent container of the toolbar is narrow.
800829
*
830+
* Also see: {@link module:ui/toolbar/toolbarview~ToolbarView#maxWidth}.
831+
*
801832
* @member {Boolean} module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull
802833
*/
803834

tests/manual/panel/balloon/balloonpanelview.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@
55
padding: 5em;
66
}
77

8+
h1 {
9+
font-size: 22px;
10+
text-align: center;
11+
margin: 70px 0;
12+
}
13+
814
.target {
915
width: 125px;
1016
height: 20px;
11-
background: green;
12-
margin: 0 0 0 100px;
17+
background: lightblue;
18+
transform: translateX(-50%);
19+
margin-left: 50%;
1320
}
1421

1522
.target + .target {
@@ -18,5 +25,7 @@
1825

1926
.ck-balloon-panel {
2027
padding: .3em;
28+
width: 250px;
29+
text-align: center;
2130
}
2231
</style>

tests/manual/panel/balloon/balloonpanelview.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,22 @@ import BalloonPanelView from '../../../../src/panel/balloon/balloonpanelview';
1010
const defaultPositions = BalloonPanelView.defaultPositions;
1111
const container = document.querySelector( '#container' );
1212

13+
let currentHeading = '';
14+
1315
for ( const i in defaultPositions ) {
1416
const target = document.createElement( 'div' );
17+
const heading = document.createElement( 'h1' );
18+
const headingText = parseHeadingText( i );
19+
20+
heading.textContent = headingText;
1521
target.classList.add( 'target' );
22+
23+
// Lazy heading
24+
if ( currentHeading !== headingText ) {
25+
container.appendChild( heading );
26+
currentHeading = headingText;
27+
}
28+
1629
container.appendChild( target );
1730

1831
const balloon = new BalloonPanelView();
@@ -27,3 +40,21 @@ for ( const i in defaultPositions ) {
2740
]
2841
} );
2942
}
43+
44+
function parseHeadingText( text ) {
45+
const normalizedText = getNormalizeHeading( text );
46+
return getCapitalizedHeading( normalizedText );
47+
}
48+
49+
// This helper function creates normalize heading text from a full name of the position,
50+
// removing `ArrowXyz` part, like in the example:
51+
// `southEastArrowNorthMiddleEast` -> `south East`.
52+
function getNormalizeHeading( text ) {
53+
return text
54+
.replace( /(w*)arrow\w*/i, '$1' )
55+
.replace( /([a-z])([A-Z])/, '$1 $2' );
56+
}
57+
58+
function getCapitalizedHeading( text ) {
59+
return text.charAt( 0 ).toUpperCase() + text.slice( 1 );
60+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## `BalloonPanelView` and `defaultPositions`
22

3-
1. A number of green rectangles should be displayed in the page.
3+
1. A number of colorful rectangles should be displayed in the page.
44
2. Each rectangle should have a panel attached.
55
3. Make sure the description in each panel matches the location of the panel.

0 commit comments

Comments
 (0)