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

Commit

Permalink
Merge pull request #65 from ckeditor/i/5597
Browse files Browse the repository at this point in the history
Feature: The inline editor toolbar should group items when its width exceeds the editable’s width (see ckeditor/ckeditor5#5597).

BREAKING CHANGE: From now on, the inline toolbar groups overflowing items by default. This behavior can be disabled via the editor config by setting the [`config.toolbar.shouldNotGroupWhenFull: true`](https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editorconfig-EditorConfig.html#member-toolbar) option.
  • Loading branch information
oleq authored Feb 26, 2020
2 parents 18e67c1 + e6f4e31 commit 1c5746c
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 12 deletions.
6 changes: 5 additions & 1 deletion src/inlineeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ export default class InlineEditor extends Editor {
secureSourceElement( this );
}

const view = new InlineEditorUIView( this.locale, this.editing.view, this.sourceElement );
const shouldToolbarGroupWhenFull = !this.config.get( 'toolbar.shouldNotGroupWhenFull' );

const view = new InlineEditorUIView( this.locale, this.editing.view, this.sourceElement, {
shouldToolbarGroupWhenFull
} );
this.ui = new InlineEditorUI( this, view );

attachToForm( this );
Expand Down
49 changes: 47 additions & 2 deletions src/inlineeditoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
import ResizeObserver from '@ckeditor/ckeditor5-utils/src/dom/resizeobserver';
import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit';

const toPx = toUnit( 'px' );

/**
* Inline editor UI view. Uses an nline editable and a floating toolbar.
Expand All @@ -25,8 +30,12 @@ export default class InlineEditorUIView extends EditorUIView {
* @param {module:engine/view/view~View} editingView The editing view instance this view is related to.
* @param {HTMLElement} [editableElement] The editable element. If not specified, it will be automatically created by
* {@link module:ui/editableui/editableuiview~EditableUIView}. Otherwise, the given element will be used.
* @param {Object} [options={}] Configuration options for the view instance.
* @param {Boolean} [options.shouldToolbarGroupWhenFull] When set `true` enables automatic items grouping
* in the main {@link module:editor-inline/inlineeditoruiview~InlineEditorUIView#toolbar toolbar}.
* See {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull} to learn more.
*/
constructor( locale, editingView, editableElement ) {
constructor( locale, editingView, editableElement, options = {} ) {
super( locale );

/**
Expand All @@ -35,7 +44,9 @@ export default class InlineEditorUIView extends EditorUIView {
* @readonly
* @member {module:ui/toolbar/toolbarview~ToolbarView}
*/
this.toolbar = new ToolbarView( locale );
this.toolbar = new ToolbarView( locale, {
shouldGroupWhenFull: options.shouldToolbarGroupWhenFull
} );

/**
* The offset from the top edge of the web browser's viewport which makes the
Expand Down Expand Up @@ -133,6 +144,17 @@ export default class InlineEditorUIView extends EditorUIView {
* @member {module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView}
*/
this.editable = new InlineEditableUIView( locale, editingView, editableElement );

/**
* An instance of the resize observer that helps dynamically determine the geometry of the toolbar
* and manage items that do not fit into a single row.
*
* **Note:** Created in {@link #render}.
*
* @private
* @member {module:utils/dom/resizeobserver~ResizeObserver}
*/
this._resizeObserver = null;
}

/**
Expand All @@ -144,6 +166,29 @@ export default class InlineEditorUIView extends EditorUIView {
this.body.add( this.panel );
this.registerChild( this.editable );
this.panel.content.add( this.toolbar );

const options = this.toolbar.options;

// Set toolbar's max-width on the initialization and update it on the editable resize,
// if 'shouldToolbarGroupWhenFull' in config is set to 'true'.
if ( options.shouldGroupWhenFull ) {
const editableElement = this.editable.element;

this._resizeObserver = new ResizeObserver( editableElement, () => {
this.toolbar.maxWidth = toPx( new Rect( editableElement ).width );
} );
}
}

/**
* @inheritDoc
*/
destroy() {
super.destroy();

if ( this._resizeObserver ) {
this._resizeObserver.destroy();
}
}

/**
Expand Down
16 changes: 16 additions & 0 deletions tests/inlineeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,22 @@ describe( 'InlineEditor', () => {
} );
} );

it( 'should pass the config.toolbar.shouldNotGroupWhenFull configuration to the view', () => {
const editorElement = document.createElement( 'div' );

return InlineEditor.create( editorElement, {
toolbar: {
shouldNotGroupWhenFull: true
}
} ).then( editor => {
expect( editor.ui.view.toolbar.options.shouldGroupWhenFull ).to.be.false;

return editor.destroy();
} ).then( () => {
editorElement.remove();
} );
} );

it( 'throws if initial data is passed in Editor#create and config.initialData is also used', done => {
InlineEditor.create( '<p>Hello world!</p>', {
initialData: '<p>I am evil!</p>',
Expand Down
14 changes: 9 additions & 5 deletions tests/inlineeditorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ describe( 'InlineEditorUI', () => {
it( 'pin() is called on editor.ui#update', () => {
const spy = sinon.stub( view.panel, 'pin' );

view.panel.hide();

editor.ui.fire( 'update' );
sinon.assert.notCalled( spy );

view.panel.show();

editor.ui.fire( 'update' );
Expand All @@ -100,6 +95,15 @@ describe( 'InlineEditorUI', () => {
positions: sinon.match.array
} );
} );

it( 'pin() is not called on editor.ui#update when panel is hidden', () => {
const spy = sinon.stub( view.panel, 'pin' );

view.panel.hide();

editor.ui.fire( 'update' );
sinon.assert.notCalled( spy );
} );
} );

describe( 'editable', () => {
Expand Down
81 changes: 77 additions & 4 deletions tests/inlineeditoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpa
import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
import Locale from '@ckeditor/ckeditor5-utils/src/locale';
import createRoot from '@ckeditor/ckeditor5-engine/tests/view/_utils/createroot.js';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import ResizeObserver from '@ckeditor/ckeditor5-utils/src/dom/resizeobserver';

const toPx = toUnit( 'px' );

import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';

describe( 'InlineEditorUIView', () => {
let locale, view, editingView, editingViewRoot;
let resizeCallback;

testUtils.createSinonSandbox();

Expand All @@ -24,6 +31,24 @@ describe( 'InlineEditorUIView', () => {
editingViewRoot = createRoot( editingView.document );
view = new InlineEditorUIView( locale, editingView );
view.editable.name = editingViewRoot.rootName;

// Make sure other tests of the editor do not affect tests that follow.
// Without it, if an instance of ResizeObserver already exists somewhere undestroyed
// in DOM, the following DOM mock will have no effect.
ResizeObserver._observerInstance = null;

testUtils.sinon.stub( global.window, 'ResizeObserver' ).callsFake( callback => {
resizeCallback = callback;

return {
observe: sinon.spy(),
unobserve: sinon.spy()
};
} );
} );

afterEach( () => {
view.destroy();
} );

describe( 'constructor()', () => {
Expand All @@ -39,6 +64,16 @@ describe( 'InlineEditorUIView', () => {
it( 'is not rendered', () => {
expect( view.toolbar.isRendered ).to.be.false;
} );

it( 'should have the shouldGroupWhenFull option set based on constructor options', () => {
const view = new InlineEditorUIView( locale, editingView, null, {
shouldToolbarGroupWhenFull: true
} );

expect( view.toolbar.options.shouldGroupWhenFull ).to.be.true;

view.destroy();
} );
} );

describe( '#panel', () => {
Expand Down Expand Up @@ -79,10 +114,6 @@ describe( 'InlineEditorUIView', () => {
view.render();
} );

afterEach( () => {
view.destroy();
} );

describe( '#toolbar', () => {
it( 'is given the right CSS classes', () => {
expect( view.toolbar.element.classList.contains( 'ck-toolbar_floating' ) ).to.be.true;
Expand All @@ -91,6 +122,48 @@ describe( 'InlineEditorUIView', () => {
it( 'sets the default value of the #viewportTopOffset attribute', () => {
expect( view.viewportTopOffset ).to.equal( 0 );
} );

describe( 'automatic resizing when shouldToolbarGroupWhenFull is "true"', () => {
it( 'should set and update toolbar max-width according to the width of the editable element', () => {
const locale = new Locale();
const editingView = new EditingView();
const editingViewRoot = createRoot( editingView.document );
const view = new InlineEditorUIView( locale, editingView, null, {
shouldToolbarGroupWhenFull: true
} );
view.editable.name = editingViewRoot.rootName;
view.render();

const editableElement = view.editable.element;

// View element should be inside the body, otherwise the `Rect` instance will complain
// that it's not available in the DOM.
global.document.body.appendChild( editableElement );

editableElement.style.width = '400px';

resizeCallback( [ {
target: editableElement,
contentRect: new Rect( editableElement )
} ] );

// Include paddings.
expect( view.toolbar.maxWidth ).to.be.equal( toPx( new Rect( editableElement ).width ) );

editableElement.style.width = '200px';

resizeCallback( [ {
target: editableElement,
contentRect: new Rect( editableElement )
} ] );

// Include paddings.
expect( view.toolbar.maxWidth ).to.be.equal( toPx( new Rect( editableElement ).width ) );

editableElement.remove();
view.destroy();
} );
} );
} );

describe( '#panel', () => {
Expand Down

0 comments on commit 1c5746c

Please sign in to comment.