8
0
Pārlūkot izejas kodu

Merge branch 'master' into t/ckeditor5-dev/278

Piotrek Koszuliński 8 gadi atpakaļ
vecāks
revīzija
03033ed9c8

+ 110 - 0
packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.html

@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
+		<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+		<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
+		<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
+	</head>
+	<body>
+		<!-- The outermost cotainer of the editor. -->
+		<div class="ck-editor">
+			<!-- The toolbar of the editor. -->
+			<div class="btn-toolbar" role="toolbar" aria-label="Editor toolbar">
+				<!-- The headings dropdown. -->
+				<div class="btn-group mr-2" role="group" aria-label="Headings">
+					<div class="dropdown" id="heading">
+					  <button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span>Headings</span></button>
+					  <div class="dropdown-menu" aria-labelledby="heading-button"></div>
+					</div>
+				</div>
+
+				<!-- Basic styles buttons. -->
+				<div class="btn-group mr-2" role="group" aria-label="Basic styles">
+					<button type="button" class="btn btn-primary btn-sm" id="bold">B</button>
+					<button type="button" class="btn btn-primary btn-sm" id="italic">I</button>
+					<button type="button" class="btn btn-primary btn-sm" id="underline">U</button>
+				</div>
+
+				<!-- Undo and redo buttons. -->
+				<div class="btn-group mr-2" role="group" aria-label="Undo">
+					<button type="button" class="btn btn-primary btn-sm" id="undo">&#x2190;</button>
+					<button type="button" class="btn btn-primary btn-sm" id="redo">&#x2192;</button>
+				</div>
+			</div>
+
+			<!-- The container containing data of the editor. -->
+			<div id="editor">
+				<h2>Custom UI</h2>
+				<p>
+					This editor uses <b>Bootstrap</b> components to build the user interface.
+				</p>
+			</div>
+		</div>
+
+		<button type="button" id="toggle-readonly">Toggle editor read&#x2013;only</button>
+
+		<style>
+			body {
+				padding: 0 .2em;
+			}
+
+			/* Let's give the editor some space and limits using a border. */
+			.ck-editor {
+				margin: 0 0 1em;
+				border: 1px solid rgba( 0, 0, 0, .1 );
+				border-radius: 4px;
+			}
+
+			/* Adding internal spacing, border and background to the toolbar.  */
+			.ck-editor .btn-toolbar {
+				padding: .5rem;
+				background: #f7f7f9;
+				border-bottom: 1px solid rgba( 0, 0, 0, .1 );
+			}
+
+			/* Tweaking the editable area for better readability. */
+			.ck-editor .ck-editor__editable {
+				padding: 2em 2em 1em;
+				overflow: auto;
+				height: 300px;
+			}
+
+			/* When in read–only mode, the editable should fade out. */
+			.ck-editor .ck-editor__editable:not([contenteditable]) {
+				background: #fafafa;
+				color: #777;
+			}
+
+			/* Make sure the headings dropdown button does not change its size
+			as different headings are selected */
+			.ck-editor .dropdown-toggle span {
+				display: inline-block;
+				width: 100px;
+				text-align: left;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				vertical-align: bottom;
+			}
+
+			/* Make the headings dropdown items visually distinctive. */
+			.ck-editor .heading-item_heading1 { font-size: 1.5em; }
+			.ck-editor .heading-item_heading2 { font-size: 1.3em; }
+			.ck-editor .heading-item_heading3 { font-size: 1.1em; }
+
+			.ck-editor [class*="heading-item_"] {
+				line-height: 22px;
+				padding: 10px;
+			}
+
+			.ck-editor [class*="heading-item_heading"] {
+			  font-weight: bold;
+			}
+
+			/* Give the basic styles buttons some icon–like shape */
+			.ck-editor #bold { font-weight: bold; }
+			.ck-editor #italic { font-style: italic; }
+			.ck-editor #underline { text-decoration: underline; }
+		</style>
+	</body>
+</html>

+ 203 - 0
packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.js

@@ -0,0 +1,203 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals $, window, console:false */
+
+// Basic classes to create an editor.
+import StandardEditor from '@ckeditor/ckeditor5-core/src/editor/standardeditor';
+import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
+
+// Basic features that every editor should enable.
+import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
+import Enter from '@ckeditor/ckeditor5-enter/src/enter';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import UndoEngine from '@ckeditor/ckeditor5-undo/src/undoengine';
+
+// Basic features to associated with the edited content.
+import BoldEngine from '@ckeditor/ckeditor5-basic-styles/src/boldengine';
+import ItalicEngine from '@ckeditor/ckeditor5-basic-styles/src/italicengine';
+import UnderlineEngine from '@ckeditor/ckeditor5-basic-styles/src/underlineengine';
+import HeadingEngine from '@ckeditor/ckeditor5-heading/src/headingengine';
+
+// Extending the StandardEditor, which brings lots of essential API.
+export default class BootstrapEditor extends StandardEditor {
+	constructor( element, config ) {
+		super( element, config );
+
+		// Create the ("main") root element of the model tree.
+		this.document.createRoot();
+
+		// Use the HTML data processor in this editor.
+		this.data.processor = new HtmlDataProcessor();
+
+		// This editor uses a single editable view in DOM.
+		this.editable = new InlineEditableUIView( this.locale );
+
+		// A helper to easily replace the editor#element with editor.editable#element.
+		this._elementReplacer = new ElementReplacer();
+	}
+
+	destroy() {
+		// When destroyed, editor sets the output of editor#getData() into editor#element...
+		this.updateEditorElement();
+
+		// ...and restores editor#element.
+		this._elementReplacer.restore();
+
+		return super.destroy();
+	}
+
+	static create( element, config ) {
+		return new Promise( resolve => {
+			const editor = new this( element, config );
+			const editable = editor.editable;
+
+			resolve(
+				editor.initPlugins()
+					// Initialize the editable view in DOM first.
+					.then( () => editable.init() )
+					// Replace the editor#element with editor.editable#element.
+					.then( () => editor._elementReplacer.replace( element, editable.element ) )
+					// Handle the UI of the editor.
+					.then( () => {
+						// Create an editing root in the editing layer. It will correspond with the
+						// document root created in the constructor().
+						const editingRoot = editor.editing.createRoot( 'div' );
+
+						// Bind the basic attributes of the editable in DOM with the editing layer.
+						editable.bind( 'isReadOnly' ).to( editingRoot );
+						editable.bind( 'isFocused' ).to( editor.editing.view );
+						editable.name = editingRoot.rootName;
+
+						// Setup the external Bootstrap UI so it works with the editor.
+						setupButtons( editor );
+						setupHeadingDropdown( editor );
+
+						// Tell the world that the UI of the editor is ready to use.
+						editor.fire( 'uiReady' );
+					} )
+					// Bind the editor editing layer to the editable in DOM.
+					.then( () => editor.editing.view.attachDomRoot( editable.element ) )
+					.then( () => editor.loadDataFromEditorElement() )
+					// Fire the events that announce that the editor is complete and ready to use.
+					.then( () => {
+						editor.fire( 'dataReady' );
+						editor.fire( 'ready' );
+					} )
+					.then( () => editor )
+			);
+		} );
+	}
+}
+
+// This function activates Bold, Italic, Underline, Undo and Redo buttons in the toolbar.
+function setupButtons( editor ) {
+	[ 'bold', 'italic', 'underline', 'undo', 'redo' ].forEach( commandName => {
+		// Retrieve the editor command corresponding with the id of the button in DOM.
+		const command = editor.commands.get( commandName );
+
+		// Retrieve the jQuery object corresponding with the button in DOM.
+		const button = $( `#${ commandName }` );
+
+		// Clicking on the buttons should execute the editor command...
+		button.click( () => editor.execute( commandName ) );
+
+		// ...but it should not steal the focus so the editing is uninterrupted.
+		button.mousedown( evt => evt.preventDefault() );
+
+		// Commands can become disabled, e.g. when the editor is read-only.
+		// Make sure the buttons reflect this state change.
+		command.on( 'change:isEnabled', onIsEnabledChange );
+		onIsEnabledChange();
+
+		// Bold, Italic and Underline commands have a value that changes
+		// when the selection starts in an element the command creates.
+		// The button should indicate that e.g. editing text which is already bold.
+		if ( !new Set( [ 'undo', 'redo' ] ).has( commandName ) ) {
+			command.on( 'change:value', onValueChange );
+			onValueChange();
+		}
+
+		function onValueChange() {
+			button.toggleClass( 'active', command.value );
+		}
+
+		function onIsEnabledChange() {
+			button.attr( 'disabled', () => !command.isEnabled );
+		}
+	} );
+}
+
+// This function activates the headings dropdown in the toolbar.
+function setupHeadingDropdown( editor ) {
+	const menu = $( '.ck-editor .dropdown-menu' );
+	const button = $( '.ck-editor .dropdown-toggle' );
+
+	// Create a dropdown menu entry for each heading configuration option.
+	editor.config.get( 'heading.options' ).map( option => {
+		// Retrieve the editor command corresponding with the configuration option.
+		const command = editor.commands.get( option.modelElement );
+
+		// Create the menu item DOM element.
+		const menuItem = $(
+			`<a href="#" class="dropdown-item heading-item_${ option.modelElement }">` +
+				`${ option.title }` +
+			'</a>' );
+
+		// Upon click, the dropdown menua item should execute the command and focus
+		// the editing view to keep the editing process uninterrupted.
+		menuItem.click( () => {
+			editor.execute( option.modelElement );
+			editor.editing.view.focus();
+		} );
+
+		menu.append( menuItem );
+
+		// Make sure the dropdown and its items reflect the state of the
+		// currently active command.
+		command.on( 'change:value', onValueChange );
+		onValueChange();
+
+		// Heading commands can become disabled, e.g. when the editor is read-only.
+		// Make sure the UI reflects this state change.
+		command.on( 'change:isEnabled', onIsEnabledChange );
+		onIsEnabledChange();
+
+		function onValueChange() {
+			if ( command.value ) {
+				button.children( ':first' ).text( option.title );
+			}
+
+			menuItem.toggleClass( 'active', command.value );
+		}
+
+		function onIsEnabledChange() {
+			button.attr( 'disabled', () => !command.isEnabled );
+		}
+	} );
+}
+
+// Finally, create the BootstrapEditor instance with a selected set of features.
+BootstrapEditor
+	.create( $( '#editor' ).get( 0 ), {
+		plugins: [
+			Clipboard, Enter, Typing, Paragraph,
+			BoldEngine, ItalicEngine, UnderlineEngine, HeadingEngine, UndoEngine
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+
+		$( '#toggle-readonly' ).on( 'click', () => {
+			editor.isReadOnly = !editor.isReadOnly;
+		} );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+

+ 9 - 0
packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui.html

@@ -0,0 +1,9 @@
+<iframe src="%BASE_PATH%/snippets/examples/bootstrap-ui-inner/snippet.html" id="snippet-bootstrap-ui-inner"></iframe>
+
+<style>
+	#snippet-bootstrap-ui-inner {
+		width: 100%;
+		height: 420px;
+		border: 0;
+	}
+</style>

+ 6 - 0
packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui.js

@@ -0,0 +1,6 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* config { "dependencies": [ "examples/bootstrap-ui-inner" ] } */

+ 9 - 0
packages/ckeditor5-ui/docs/examples/custom-ui.md

@@ -0,0 +1,9 @@
+---
+title: Custom UI (with Bootstrap)
+category: examples-framework
+order: 20
+---
+
+The editor below runs a completely custom user interface written in [Bootstrap](http://getbootstrap.com/), while the editing is provided by CKEditor 5. To learn more, check out the {@link framework/guides/external-ui detailed guide} on how to integrate an external UI with the editor.
+
+{@snippet examples/bootstrap-ui}

+ 376 - 0
packages/ckeditor5-ui/docs/framework/guides/external-ui.md

@@ -0,0 +1,376 @@
+---
+category: framework-ui
+order: 20
+---
+
+# Third–party UI
+
+CKEditor 5 is a modular editing framework and allows various flexible configurations. That includes the usage of a third–party user interface on top of the basic editor classes.
+
+In this guide, a [classic–like](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic) editor will be bound to a completely separate UI created in [Bootstrap](http://getbootstrap.com/) providing the basic structure and toolbar items necessary to start editing.
+
+{@snippet examples/bootstrap-ui}
+
+## Readying the editor side
+
+The ready–to–use builds of CKEditor like {@link examples/builds/classic-editor Classic} or {@link examples/builds/inline-editor Inline} come with a dedicated, default user interface and theme. However, to create an editor instance bound to a Bootstrap UI, only a limited subset of features is required. Let's import them first:
+
+
+```js
+// Basic classes to create an editor.
+import StandardEditor from '@ckeditor/ckeditor5-core/src/editor/standardeditor';
+import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
+
+// Basic features that every editor should enable.
+import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
+import Enter from '@ckeditor/ckeditor5-enter/src/enter';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import UndoEngine from '@ckeditor/ckeditor5-undo/src/undoengine';
+
+// Basic features to associated with the edited content.
+import BoldEngine from '@ckeditor/ckeditor5-basic-styles/src/boldengine';
+import ItalicEngine from '@ckeditor/ckeditor5-basic-styles/src/italicengine';
+import UnderlineEngine from '@ckeditor/ckeditor5-basic-styles/src/underlineengine';
+import HeadingEngine from '@ckeditor/ckeditor5-heading/src/headingengine';
+```
+
+<info-box info>
+	Note that instead of {@link module:basic-styles/bold~Bold}, which is required for any editor with the default UI to work, just the {@link module:basic-styles/boldengine~BoldEngine} is imported. It provides the [engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine) features associated with editing any bold text but does not come with the actual UI.
+
+	Respectively, `ItalicEngine`, `UnderlineEngine`, `HeadingEngine` and `UndoEngine` are also imported.
+
+	This split between the engine and UI part of features is not perfect yet. At the current stage, the UI part introduces some vital functionality, such as keystroke definitions (e.g. <kbd>Ctrl</kbd>+<kbd>B</kbd> to "bold"). This means that by dropping the UI part of fetures you lose also keystrokes. We [plan to improve](https://github.com/ckeditor/ckeditor5/issues/488) this situation.
+</info-box>
+
+Having imported the very basic editor components, we can define the custom `BootstrapEditor` class that extends the {@link module:core/editor/standardeditor~StandardEditor `StandardEditor`}:
+
+```js
+// Extending the StandardEditor, which brings lots of essential API.
+export default class BootstrapEditor extends StandardEditor {
+	constructor( element, config ) {
+		super( element, config );
+
+		// Create the ("main") root element of the model tree.
+		this.document.createRoot();
+
+		// Use the HTML data processor in this editor.
+		this.data.processor = new HtmlDataProcessor();
+
+		// This editor uses a single editable view in DOM.
+		this.editable = new InlineEditableUIView( this.locale );
+
+		// A helper to easily replace the editor#element with editor.editable#element.
+		this._elementReplacer = new ElementReplacer();
+	}
+
+	destroy() {
+		// When destroyed, editor sets the output of editor#getData() into editor#element...
+		this.updateEditorElement();
+
+		// ...and restores editor#element.
+		this._elementReplacer.restore();
+
+		return super.destroy();
+	}
+
+	static create( element, config ) {
+		return new Promise( resolve => {
+			const editor = new this( element, config );
+			const editable = editor.editable;
+
+			resolve(
+				editor.initPlugins()
+					// Initialize the editable view in DOM first.
+					.then( () => editable.init() )
+					// Replace the editor#element with editor.editable#element.
+					.then( () => editor._elementReplacer.replace( element, editable.element ) )
+					// Handle the UI of the editor.
+					.then( () => {
+						// Create an editing root in the editing layer. It will correspond with the
+						// document root created in the constructor().
+						const editingRoot = editor.editing.createRoot( 'div' );
+
+						// Bind the basic attributes of the editable in DOM with the editing layer.
+						editable.bind( 'isReadOnly' ).to( editingRoot );
+						editable.bind( 'isFocused' ).to( editor.editing.view );
+						editable.name = editingRoot.rootName;
+
+						// Setup the external Bootstrap UI so it works with the editor. Check out the code samples bellow to learn more.
+						setupButtons( editor );
+						setupHeadingDropdown( editor );
+
+						// Tell the world that the UI of the editor is ready to use.
+						editor.fire( 'uiReady' );
+					} )
+					// Bind the editor editing layer to the editable in DOM.
+					.then( () => editor.editing.view.attachDomRoot( editable.element ) )
+					.then( () => editor.loadDataFromEditorElement() )
+					// Fire the events that announce that the editor is complete and ready to use.
+					.then( () => {
+						editor.fire( 'dataReady' );
+						editor.fire( 'ready' );
+					} )
+					.then( () => editor )
+			);
+		} );
+	}
+}
+```
+
+## Creating the Bootstrap UI
+
+Although the editor is ready to use, it's just a bare editable area, which isn't much use to the users. Let's give it the actual interface with the toolbar and buttons.
+
+<info-box hint>
+	Please refer to the Bootstrap [quick start guide](https://getbootstrap.com/docs/4.0/getting-started/introduction/) to learn how to include Bootstrap in your web page.
+</info-box>
+
+Having the Bootstrap framework loaded in the web page, we can define the actual UI of the editor in HTML:
+
+```html
+<!-- The outermost cotainer of the editor. -->
+<div class="ck-editor">
+	<!-- The toolbar of the editor. -->
+	<div class="btn-toolbar" role="toolbar" aria-label="Editor toolbar">
+		<!-- The headings dropdown. -->
+		<div class="btn-group mr-2" role="group" aria-label="Headings">
+			<div class="dropdown" id="heading">
+			  <button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span>Headings</span></button>
+			  <div class="dropdown-menu" aria-labelledby="heading-button"></div>
+			</div>
+		</div>
+
+		<!-- Basic styles buttons. -->
+		<div class="btn-group mr-2" role="group" aria-label="Basic styles">
+			<button type="button" class="btn btn-primary btn-sm" id="bold">B</button>
+			<button type="button" class="btn btn-primary btn-sm" id="italic">I</button>
+			<button type="button" class="btn btn-primary btn-sm" id="underline">U</button>
+		</div>
+
+		<!-- Undo and redo buttons. -->
+		<div class="btn-group mr-2" role="group" aria-label="Undo">
+			<button type="button" class="btn btn-primary btn-sm" id="undo">&larr;</button>
+			<button type="button" class="btn btn-primary btn-sm" id="redo">&rarr;</button>
+		</div>
+	</div>
+
+	<!-- The container containing data of the editor. -->
+	<div id="editor">
+		<p>Hello world!</p>
+	</div>
+</div>
+```
+
+Although Bootstrap provides most of the CSS, it does not come with styles dedicated for [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) text editors and some tweaking is needed:
+
+```css
+/* Let's give the editor some space and limits using a border. */
+.ck-editor {
+	margin: 1em 0;
+	border: 1px solid rgba( 0, 0, 0, .1 );
+	border-radius: 4px;
+}
+
+/* Adding internal spacing, border and background to the toolbar.  */
+.ck-editor .btn-toolbar {
+	padding: .5rem;
+	background: #f7f7f9;
+	border-bottom: 1px solid rgba( 0, 0, 0, .1 );
+}
+
+/* Tweaking the editable area for better readability. */
+.ck-editor .ck-editor__editable {
+	padding: 2em 2em 1em;
+	overflow: auto;
+}
+
+/* When in read–only mode, the editable should fade out. */
+.ck-editor .ck-editor__editable:not([contenteditable]) {
+	background: #fafafa;
+	color: #777;
+}
+
+/* Make sure the headings dropdown button does not change its size
+as different headings are selected */
+.ck-editor .dropdown-toggle span {
+	display: inline-block;
+	width: 100px;
+	text-align: left;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	vertical-align: bottom;
+}
+
+/* Make the headings dropdown items visually distinctive. */
+.ck-editor .heading-item_heading1 { font-size: 1.5em; }
+.ck-editor .heading-item_heading2 { font-size: 1.3em; }
+.ck-editor .heading-item_heading3 { font-size: 1.1em; }
+
+.ck-editor [class*="heading-item_"] {
+	line-height: 22px;
+	padding: 10px;
+}
+
+.ck-editor [class*="heading-item_heading"] {
+  font-weight: bold;
+}
+
+/* Give the basic styles buttons the icon–like look and feel. */
+.ck-editor #bold { font-weight: bold; }
+.ck-editor #italic { font-style: italic; }
+.ck-editor #underline { text-decoration: underline; }
+```
+
+## Binding the UI with the editor
+
+At this stage, we're about to bind the editor created at the very beginning of this guide with the Bootstrap UI defined in HTML. Almost every feature in the editor defines some command, e.g. {@link module:heading/headingcommand~HeadingCommand} or {@link module:undo/undocommand~UndoCommand}. Commands can be executed
+
+```js
+editor.exectute( 'undo' );
+```
+
+but they also come with default observable attributes like `value` and `isEnabled`. These are the entry points when it comes to creating a custom user interface because their values represent the actual state of the editor and can be followed in simple event listeners:
+
+```js
+const command = editor.commands.get( 'undo' );
+
+command.on( 'change:isEnabled', ( evt, name, isEnabled ) => {
+	if ( isEnabled ) {
+		console.log( 'Whoa, you can undo some stuff now.' );
+	} else {
+		console.log( 'There is nothing to undo in the editor.' );
+	}
+} );
+```
+
+<info-box hint>
+	To learn more about editor commands, check out the {@link module:core/command~Command} API. You can also `console.log` the {@link module:core/editor/editor~Editor#commands `editor.commands`} collection of a live editor to learn which commands it offers.
+</info-box>
+
+Now let's take a closer look on these two mysterious lines in the `BootstrapEditor#create()` :
+
+```js
+setupButtons( editor );
+setupHeadingDropdown( editor );
+```
+
+`setupButtons()` is a function that binds the Bootstrap toolbar buttons with the editor features. It activates the related editor commands upon clicking and makes the buttons listen to the state of the commands to update their CSS classes:
+
+```js
+// This function activates Bold, Italic, Underline, Undo and Redo buttons in the toolbar.
+function setupButtons( editor ) {
+	[ 'bold', 'italic', 'underline', 'undo', 'redo' ].forEach( commandName => {
+		// Retrieve the editor command corresponding with the id of the button in DOM.
+		const command = editor.commands.get( commandName );
+
+		// Retrieve the jQuery object corresponding with the button in DOM.
+		const button = $( `#${ commandName }` );
+
+		// Clicking on the buttons should execute the editor command...
+		button.click( () => editor.execute( commandName ) );
+
+		// ...but it should not steal the focus so the editing is uninterrupted.
+		button.mousedown( evt => evt.preventDefault() );
+
+		// Commands can become disabled, e.g. when the editor is read-only.
+		// Make sure the buttons reflect this state change.
+		command.on( 'change:isEnabled', onIsEnabledChange );
+		onIsEnabledChange();
+
+		// Bold, Italic and Underline commands have a value that changes
+		// when the selection starts in an element the command creates.
+		// The button should indicate that the user is editing a text which
+		// is already bold.
+		if ( !new Set( [ 'undo', 'redo' ] ).has( commandName ) ) {
+			command.on( 'change:value', onValueChange );
+			onValueChange();
+		}
+
+		function onValueChange() {
+			button.toggleClass( 'active', command.value );
+		}
+
+		function onIsEnabledChange() {
+			button.attr( 'disabled', () => !command.isEnabled );
+		}
+	} );
+}
+```
+
+The dropdown in the toolbar is a more complex case because first it must be populated with heading options for the users to select from. Then, clicking each option must execute a related heading command in the editor. Finally, the dropdown button and the dropdown menu items must reflect the state of the editor, e.g. when the selection lands in a heading, a proper menu item should become active and the button should show the name of the heading level.
+
+```js
+// This function activates the headings dropdown in the toolbar.
+function setupHeadingDropdown( editor ) {
+	const menu = $( '.ck-editor .dropdown-menu' );
+	const button = $( '.ck-editor .dropdown-toggle' );
+
+	// Create a dropdown menu entry for each heading configuration option.
+	editor.config.get( 'heading.options' ).map( option => {
+		// Retrieve the editor command corresponding with the configuration option.
+		const command = editor.commands.get( option.modelElement );
+		// Create the menu item DOM element.
+
+		const menuItem = $(
+			`<a href="#" class="dropdown-item heading-item_${ option.modelElement }">` +
+				`${ option.title }` +
+			'</a>' );
+
+		// Upon click, the dropdown menua item should execute the command and focus
+		// the editing view to keep the editing process uninterrupted.
+		menuItem.click( () => {
+			editor.execute( option.modelElement );
+			editor.editing.view.focus();
+		} );
+
+		menu.append( menuItem );
+
+		// Make sure the dropdown and its items reflect the state of the
+		// currently active command.
+		command.on( 'change:value', onValueChange );
+		onValueChange();
+
+		// Heading commands can become disabled, e.g. when the editor is read-only.
+		// Make sure the UI reflects this state change.
+		command.on( 'change:isEnabled', onIsEnabledChange );
+		onIsEnabledChange();
+
+		function onValueChange() {
+			if ( command.value ) {
+				button.children( ':first' ).text( option.title );
+			}
+
+			menuItem.toggleClass( 'active', command.value );
+		}
+
+		function onIsEnabledChange() {
+			button.attr( 'disabled', () => !command.isEnabled );
+		}
+	} );
+}
+```
+
+## Running the editor
+
+When the editor class and the user interface are ready, it's time to run the editor. Just make sure all the plugins are loaded and the right DOM element is passed to `BootstrapEditor#create`:
+
+```js
+BootstrapEditor
+	.create( $( '#editor' ).get( 0 ), {
+		plugins: [
+			Clipboard, Enter, Typing, Paragraph,
+			BoldEngine, ItalicEngine, UnderlineEngine, HeadingEngine, UndoEngine
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+```
+
+Once everything is working as expected, you may want to create a custom build of your editor to ship it across the applications. To learn more check out the {@link builds/guides/development/custom-builds custom builds} guide.

+ 1 - 1
packages/ckeditor5-ui/package.json

@@ -12,7 +12,7 @@
   },
   "devDependencies": {
     "@ckeditor/ckeditor5-basic-styles": "^0.9.0",
-    "@ckeditor/ckeditor5-dev-lint": "^3.1.0",
+    "@ckeditor/ckeditor5-dev-lint": "^3.1.1",
     "@ckeditor/ckeditor5-editor-classic": "^0.8.0",
     "@ckeditor/ckeditor5-engine": "^0.11.0",
     "@ckeditor/ckeditor5-enter": "^0.10.0",

+ 52 - 42
packages/ckeditor5-ui/src/toolbar/sticky/stickytoolbarview.js

@@ -4,22 +4,20 @@
  */
 
 /**
- * @module ui/toolbar/sticky/stickytoolbarview
+ * @module ui/panel/sticky/stickypanelview
  */
 
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+import View from '../../view';
 import Template from '../../template';
-import ToolbarView from '../toolbarview';
 import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit';
 
 const toPx = toUnit( 'px' );
 
 /**
- * The sticky toolbar view class.
- *
- * @extends module:ui/toolbar/toolbarview~ToolbarView
+ * The sticky panel view class.
  */
-export default class StickyToolbarView extends ToolbarView {
+export default class StickyPanelView extends View {
 	/**
 	 * @inheritDoc
 	 */
@@ -29,8 +27,7 @@ export default class StickyToolbarView extends ToolbarView {
 		const bind = this.bindTemplate;
 
 		/**
-		 * Controls whether the sticky toolbar should be active. When any editable
-		 * is focused in the editor, toolbar becomes active.
+		 * Controls whether the sticky panel should be active.
 		 *
 		 * @readonly
 		 * @observable
@@ -39,7 +36,7 @@ export default class StickyToolbarView extends ToolbarView {
 		this.set( 'isActive', false );
 
 		/**
-		 * Controls whether the sticky toolbar is in the "sticky" state.
+		 * Controls whether the sticky panel is in the "sticky" state.
 		 *
 		 * @readonly
 		 * @observable
@@ -48,10 +45,10 @@ export default class StickyToolbarView extends ToolbarView {
 		this.set( 'isSticky', false );
 
 		/**
-		 * The limiter element for the sticky toolbar instance. Its bounding rect limits
-		 * the "stickyness" of the toolbar, i.e. when the toolbar reaches the bottom
+		 * The limiter element for the sticky panel instance. Its bounding rect limits
+		 * the "stickyness" of the panel, i.e. when the panel reaches the bottom
 		 * edge of the limiter, it becomes sticky to that edge and does not float
-		 * off the limiter. It is mandatory for the toolbar to work properly and once
+		 * off the limiter. It is mandatory for the panel to work properly and once
 		 * set, it cannot be changed.
 		 *
 		 * @readonly
@@ -62,7 +59,7 @@ export default class StickyToolbarView extends ToolbarView {
 
 		/**
 		 * The offset from the bottom edge of {@link #limiterElement}
-		 * which stops the toolbar from stickying any further to prevent limiter's content
+		 * which stops the panel from stickying any further to prevent limiter's content
 		 * from being completely covered.
 		 *
 		 * @readonly
@@ -74,12 +71,12 @@ export default class StickyToolbarView extends ToolbarView {
 
 		/**
 		 * The offset from the top edge of the web browser's viewport which makes the
-		 * toolbar become sticky. The default value is `0`, which means the toolbar becomes
+		 * panel become sticky. The default value is `0`, which means the panel becomes
 		 * sticky when it's upper edge touches the top of the page viewport.
 		 *
 		 * This attribute is useful when the web page has UI elements positioned to the top
 		 * either using `position: fixed` or `position: sticky`, which would cover the
-		 * sticky toolbar or vice–versa (depending on the `z-index` hierarchy).
+		 * sticky panel or vice–versa (depending on the `z-index` hierarchy).
 		 *
 		 * @readonly
 		 * @observable
@@ -89,7 +86,7 @@ export default class StickyToolbarView extends ToolbarView {
 		this.set( 'viewportTopOffset', 0 );
 
 		/**
-		 * Controls the `margin-left` CSS style of the toolbar.
+		 * Controls the `margin-left` CSS style of the panel.
 		 *
 		 * @protected
 		 * @readonly
@@ -99,7 +96,7 @@ export default class StickyToolbarView extends ToolbarView {
 		this.set( '_marginLeft', null );
 
 		/**
-		 * Set `true` if the sticky toolbar reached the bottom edge of the
+		 * Set `true` if the sticky panel reached the bottom edge of the
 		 * {@link #limiterElement}.
 		 *
 		 * @protected
@@ -110,7 +107,7 @@ export default class StickyToolbarView extends ToolbarView {
 		this.set( '_isStickyToTheLimiter', false );
 
 		/**
-		 * Set `true` if the sticky toolbar uses the {@link #viewportTopOffset},
+		 * Set `true` if the sticky panel uses the {@link #viewportTopOffset},
 		 * i.e. not {@link #_isStickyToTheLimiter} and the {@link #viewportTopOffset}
 		 * is not `0`.
 		 *
@@ -122,26 +119,37 @@ export default class StickyToolbarView extends ToolbarView {
 		this.set( '_hasViewportTopOffset', false );
 
 		/**
-		 * The DOM bounding client rect of the {@link module:ui/view~View#element} of the toolbar.
+		 * Collection of the child views which creates balloon panel contents.
+		 *
+		 * @readonly
+		 * @member {module:ui/viewcollection~ViewCollection}
+		 */
+		this.content = this.createCollection();
+
+		/**
+		 * The DOM bounding client rect of the {@link module:ui/view~View#element} of the panel.
 		 *
 		 * @protected
-		 * @member {Object} #_toolbarRect
+		 * @member {Object} #_panelRect
 		 */
 
 		/**
 		 * The DOM bounding client rect of the {@link #limiterElement}
-		 * of the toolbar.
+		 * of the panel.
 		 *
 		 * @protected
 		 * @member {Object} #_limiterRect
 		 */
 
-		Template.extend( this.template, {
+		this.template = new Template( {
+			tag: 'div',
+
 			attributes: {
 				class: [
-					// Toggle class of the toolbar when "sticky" state changes in the view.
-					bind.if( 'isSticky', 'ck-toolbar_sticky' ),
-					bind.if( '_isStickyToTheLimiter', 'ck-toolbar_sticky_bottom-limit' ),
+					'ck-sticky-panel',
+					// Toggle class of the panel when "sticky" state changes in the view.
+					bind.if( 'isSticky', 'ck-sticky-panel_sticky' ),
+					bind.if( '_isStickyToTheLimiter', 'ck-sticky-panel_sticky_bottom-limit' ),
 				],
 				style: {
 					width: bind.to( 'isSticky', isSticky => {
@@ -158,12 +166,14 @@ export default class StickyToolbarView extends ToolbarView {
 
 					marginLeft: bind.to( '_marginLeft' )
 				}
-			}
+			},
+
+			children: this.content
 		} );
 
 		/**
 		 * A dummy element which visually fills the space as long as the
-		 * actual toolbar is sticky. It prevents flickering of the UI.
+		 * actual panel is sticky. It prevents flickering of the UI.
 		 *
 		 * @private
 		 * @property {HTMLElement}
@@ -172,12 +182,12 @@ export default class StickyToolbarView extends ToolbarView {
 			tag: 'div',
 			attributes: {
 				class: [
-					'ck-toolbar__placeholder'
+					'ck-sticky-panel__placeholder'
 				],
 				style: {
 					display: bind.to( 'isSticky', isSticky => isSticky ? 'block' : 'none' ),
 					height: bind.to( 'isSticky', isSticky => {
-						return isSticky ? toPx( this._toolbarRect.height ) : null;
+						return isSticky ? toPx( this._panelRect.height ) : null;
 					} )
 				}
 			}
@@ -192,22 +202,22 @@ export default class StickyToolbarView extends ToolbarView {
 
 		this.element.parentNode.insertBefore( this._elementPlaceholder, this.element );
 
-		// Check if the toolbar should go into the sticky state immediately.
+		// Check if the panel should go into the sticky state immediately.
 		this._checkIfShouldBeSticky();
 
-		// Update sticky state of the toolbar as the window is being scrolled.
+		// Update sticky state of the panel as the window is being scrolled.
 		this.listenTo( global.window, 'scroll', () => {
 			this._checkIfShouldBeSticky();
 		} );
 
-		// Synchronize with `model.isActive` because sticking an inactive toolbar is pointless.
+		// Synchronize with `model.isActive` because sticking an inactive panel is pointless.
 		this.listenTo( this, 'change:isActive', () => {
 			this._checkIfShouldBeSticky();
 		} );
 	}
 
 	/**
-	 * Destroys the toolbar and removes the {@link #_elementPlaceholder}.
+	 * Destroys the panel and removes the {@link #_elementPlaceholder}.
 	 */
 	destroy() {
 		super.destroy();
@@ -215,34 +225,34 @@ export default class StickyToolbarView extends ToolbarView {
 	}
 
 	/**
-	 * Analyzes the environment to decide whether the toolbar should
+	 * Analyzes the environment to decide whether the panel should
 	 * be sticky or not.
 	 *
 	 * @protected
 	 */
 	_checkIfShouldBeSticky() {
 		const limiterRect = this._limiterRect = this.limiterElement.getBoundingClientRect();
-		const toolbarRect = this._toolbarRect = this.element.getBoundingClientRect();
+		const panelRect = this._panelRect = this.element.getBoundingClientRect();
 
-		// The toolbar must be active to become sticky.
+		// The panel must be active to become sticky.
 		this.isSticky = this.isActive &&
 			// The limiter's top edge must be beyond the upper edge of the visible viewport (+the viewportTopOffset).
 			limiterRect.top < this.viewportTopOffset &&
-			// The model#limiterElement's height mustn't be smaller than the toolbar's height and model#limiterBottomOffset.
+			// The model#limiterElement's height mustn't be smaller than the panel's height and model#limiterBottomOffset.
 			// There's no point in entering the sticky mode if the model#limiterElement is very, very small, because
-			// it would immediately set model#_isStickyToTheLimiter true and, given model#limiterBottomOffset, the toolbar
+			// it would immediately set model#_isStickyToTheLimiter true and, given model#limiterBottomOffset, the panel
 			// would be positioned before the model#limiterElement.
-			this._toolbarRect.height + this.limiterBottomOffset < limiterRect.height;
+			this._panelRect.height + this.limiterBottomOffset < limiterRect.height;
 
-		// Stick the toolbar to the top edge of the viewport simulating CSS position:sticky.
+		// Stick the panel to the top edge of the viewport simulating CSS position:sticky.
 		// TODO: Possibly replaced by CSS in the future http://caniuse.com/#feat=css-sticky
 		if ( this.isSticky ) {
 			this._isStickyToTheLimiter =
-				limiterRect.bottom < toolbarRect.height + this.limiterBottomOffset + this.viewportTopOffset;
+				limiterRect.bottom < panelRect.height + this.limiterBottomOffset + this.viewportTopOffset;
 			this._hasViewportTopOffset = !this._isStickyToTheLimiter && !!this.viewportTopOffset;
 			this._marginLeft = this._isStickyToTheLimiter ? null : toPx( -global.window.scrollX );
 		}
-		// Detach the toolbar from the top edge of the viewport.
+		// Detach the panel from the top edge of the viewport.
 		else {
 			this._isStickyToTheLimiter = false;
 			this._hasViewportTopOffset = false;

+ 2 - 2
packages/ckeditor5-ui/src/view.js

@@ -10,7 +10,7 @@
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import ViewCollection from './viewcollection';
 import Template from './template';
-import DomEmmiterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
+import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
@@ -326,5 +326,5 @@ export default class View {
 	}
 }
 
-mix( View, DomEmmiterMixin );
+mix( View, DomEmitterMixin );
 mix( View, ObservableMixin );

+ 4 - 4
packages/ckeditor5-ui/tests/manual/stickytoolbarview/stickytoolbarview.html

@@ -35,7 +35,7 @@
 			<div class="ck-reset_all offset-visualizer"></div>
 		</div>
 
-		<div id="fixed">The toolbar should stick to me instead of the viewport.</div>
+		<div id="fixed">The panel should stick to me instead of the viewport.</div>
 	</div>
 </div>
 
@@ -62,13 +62,13 @@
 		position: relative;
 	}
 
-	.ck-toolbar {
+	.ck-sticky-panel {
 		background: yellow !important;
 		padding: 1em;
 	}
 
-	.ck-toolbar:after {
-		content: "A toolbar mock–up.";
+	.ck-sticky-panel:after {
+		content: "A sticky panel mock–up.";
 	}
 
 	.offset-visualizer {

+ 30 - 0
packages/ckeditor5-ui/tests/manual/panel/sticky/stickypanelview.js

@@ -0,0 +1,30 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import testUtils from '../../../_utils/utils';
+import StickyPanelView from '../../../../src/panel/sticky/stickypanelview';
+
+import '@ckeditor/ckeditor5-theme-lark/theme/theme.scss';
+
+const ui = testUtils.createTestUIView( {
+	stickyToTheTop: '.ck-sticky_to-the-top .ck-editor__top',
+	stickyToTheBox: '.ck-sticky_to-the-box .ck-editor__top'
+} );
+
+createStickyPanel( ui.stickyToTheTop );
+const stickyToTheBoxTPanel = createStickyPanel( ui.stickyToTheBox );
+
+stickyToTheBoxTPanel.viewportTopOffset = 100;
+
+function createStickyPanel( collection ) {
+	const panel = new StickyPanelView();
+
+	panel.limiterElement = collection._parentElement.parentNode;
+
+	collection.add( panel );
+	panel.isActive = true;
+
+	return panel;
+}

+ 5 - 5
packages/ckeditor5-ui/tests/manual/stickytoolbarview/stickytoolbarview.md

@@ -2,24 +2,24 @@
 
 ### Sticky to the top of the viewport
 
-1. When the page is scrolled vertically, the toolbar should
+1. When the page is scrolled vertically, the panel should
 	1. stick to the top of the viewport first,
 	1. then disappear beyond the upper edge of the viewport as it touches the red area
 	1. but never cover the red area or go beyond the upper edge of editor mock–up.
 
 ### Sticky to the green box
 
-1. When the page is scrolled vertically, the toolbar should
+1. When the page is scrolled vertically, the panel should
 	1. stick to the bottom of the green box first,
 	1. then disappear beyond the bottom edge of the green box as it touches the red area
 	1. but never cover the red area or go beyond the upper edge of editor mock–up.
 
 ## Horizontal scrolling
 
-1. The toolbar should always fit horizontally within the editor mock–up, regardless of the position of the h– and v–scrolls of the web page.
+1. The panel should always fit horizontally within the editor mock–up, regardless of the position of the h– and v–scrolls of the web page.
 
 ## On–load positioning
 
-1. Scroll the web page vertically and horizontally, so the toolbar is sticky.
+1. Scroll the web page vertically and horizontally, so the panel is sticky.
 1. Reload the web page.
-1. The toolbar should get sticky as the page reloads, if the position of scrollbars and the geometry of the viewport creates such a need.
+1. The panel should get sticky as the page reloads, if the position of scrollbars and the geometry of the viewport creates such a need.

+ 0 - 30
packages/ckeditor5-ui/tests/manual/stickytoolbarview/stickytoolbarview.js

@@ -1,30 +0,0 @@
-/**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import testUtils from '../../../tests/_utils/utils';
-import StickyToolbarView from '../../../src/toolbar/sticky/stickytoolbarview';
-
-import '@ckeditor/ckeditor5-theme-lark/theme/theme.scss';
-
-const ui = testUtils.createTestUIView( {
-	stickyToTheTop: '.ck-sticky_to-the-top .ck-editor__top',
-	stickyToTheBox: '.ck-sticky_to-the-box .ck-editor__top'
-} );
-
-createToolbar( ui.stickyToTheTop );
-const stickyToTheBoxToolbar = createToolbar( ui.stickyToTheBox );
-
-stickyToTheBoxToolbar.viewportTopOffset = 100;
-
-function createToolbar( collection ) {
-	const toolbar = new StickyToolbarView();
-
-	toolbar.limiterElement = collection._parentElement.parentNode;
-
-	collection.add( toolbar );
-	toolbar.isActive = true;
-
-	return toolbar;
-}

+ 47 - 19
packages/ckeditor5-ui/tests/toolbar/sticky/stickytoolbarview.js

@@ -6,21 +6,23 @@
 /* globals document */
 
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+import StickyPanelView from '../../../src/panel/sticky/stickypanelview';
+import View from '../../../src/view';
+import LabelView from '../../../src/label/labelview';
+import ViewCollection from '../../../src/viewcollection';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-import StickyToolbarView from '../../../src/toolbar/sticky/stickytoolbarview';
-import ToolbarView from '../../../src/toolbar/toolbarview';
 import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
 
 testUtils.createSinonSandbox();
 
-describe( 'StickyToolbarView', () => {
+describe( 'StickyPanelView', () => {
 	let view, element, limiterElement, locale, windowStub;
 
 	beforeEach( () => {
 		locale = {};
 		limiterElement = document.createElement( 'div' );
 
-		view = new StickyToolbarView( locale );
+		view = new StickyPanelView( locale );
 		element = view.element;
 
 		// Dummy values just to let non–geometrical tests pass without reference errors.
@@ -40,8 +42,13 @@ describe( 'StickyToolbarView', () => {
 	} );
 
 	describe( 'constructor()', () => {
-		it( 'inherits from ToolbarView', () => {
-			expect( view ).to.be.instanceof( ToolbarView );
+		it( 'inherits from View', () => {
+			expect( view ).to.be.instanceof( View );
+		} );
+
+		it( 'should create element from template', () => {
+			expect( view.element.tagName ).to.equal( 'DIV' );
+			expect( view.element.classList.contains( 'ck-sticky-panel' ) ).to.true;
 		} );
 
 		it( 'sets view attributes', () => {
@@ -59,30 +66,35 @@ describe( 'StickyToolbarView', () => {
 			expect( view.locale ).to.equal( locale );
 		} );
 
+		it( 'creates view#content collection', () => {
+			expect( view.content ).to.be.instanceOf( ViewCollection );
+		} );
+
 		it( 'creates the _elementPlaceholder', () => {
-			expect( view._elementPlaceholder.classList.contains( 'ck-toolbar__placeholder' ) ).to.be.true;
+			expect( view._elementPlaceholder.classList.contains( 'ck-sticky-panel__placeholder' ) ).to.be.true;
 		} );
 	} );
 
 	describe( 'element view bindings', () => {
 		beforeEach( () => {
 			view.limiterElement = limiterElement;
+			view.init();
 		} );
 
 		it( 'update the class on view#isSticky change', () => {
 			view.isSticky = false;
-			expect( element.classList.contains( 'ck-toolbar_sticky' ) ).to.be.false;
+			expect( element.classList.contains( 'ck-sticky-panel_sticky' ) ).to.be.false;
 
 			view.isSticky = true;
-			expect( element.classList.contains( 'ck-toolbar_sticky' ) ).to.be.true;
+			expect( element.classList.contains( 'ck-sticky-panel_sticky' ) ).to.be.true;
 		} );
 
 		it( 'update the class on view#_isStickyToTheLimiter change', () => {
 			view._isStickyToTheLimiter = false;
-			expect( element.classList.contains( 'ck-toolbar_sticky_bottom-limit' ) ).to.be.false;
+			expect( element.classList.contains( 'ck-sticky-panel_sticky_bottom-limit' ) ).to.be.false;
 
 			view._isStickyToTheLimiter = true;
-			expect( element.classList.contains( 'ck-toolbar_sticky_bottom-limit' ) ).to.be.true;
+			expect( element.classList.contains( 'ck-sticky-panel_sticky_bottom-limit' ) ).to.be.true;
 		} );
 
 		it( 'update the styles.top on view#_hasViewportTopOffset change', () => {
@@ -123,6 +135,11 @@ describe( 'StickyToolbarView', () => {
 	} );
 
 	describe( '_elementPlaceholder view bindings', () => {
+		beforeEach( () => {
+			view.limiterElement = limiterElement;
+			view.init();
+		} );
+
 		it( 'update the styles.display on view#isSticky change', () => {
 			view.isSticky = false;
 			expect( view._elementPlaceholder.style.display ).to.equal( 'none' );
@@ -132,7 +149,7 @@ describe( 'StickyToolbarView', () => {
 		} );
 
 		it( 'update the styles.height on view#isSticky change', () => {
-			view._toolbarRect = { height: 50 };
+			view._panelRect = { height: 50 };
 
 			view.isSticky = false;
 			expect( view._elementPlaceholder.style.height ).to.equal( '' );
@@ -142,6 +159,17 @@ describe( 'StickyToolbarView', () => {
 		} );
 	} );
 
+	describe( 'children', () => {
+		it( 'should react on view#content', () => {
+			expect( view.element.childNodes.length ).to.equal( 0 );
+
+			const label = new LabelView( { t() {} } );
+
+			view.content.add( label );
+			expect( view.element.childNodes.length ).to.equal( 1 );
+		} );
+	} );
+
 	describe( 'init()', () => {
 		beforeEach( () => {
 			view.limiterElement = limiterElement;
@@ -152,7 +180,7 @@ describe( 'StickyToolbarView', () => {
 		} );
 
 		it( 'calls init on parent class', () => {
-			const spy = testUtils.sinon.spy( ToolbarView.prototype, 'init' );
+			const spy = testUtils.sinon.spy( View.prototype, 'init' );
 
 			view.init();
 			expect( spy.calledOnce ).to.be.true;
@@ -163,7 +191,7 @@ describe( 'StickyToolbarView', () => {
 			expect( element.previousSibling ).to.equal( view._elementPlaceholder );
 		} );
 
-		it( 'checks if the toolbar should be sticky', () => {
+		it( 'checks if the panel should be sticky', () => {
 			const spy = testUtils.sinon.spy( view, '_checkIfShouldBeSticky' );
 			expect( spy.notCalled ).to.be.true;
 
@@ -202,7 +230,7 @@ describe( 'StickyToolbarView', () => {
 		} );
 
 		it( 'calls destroy on parent class', () => {
-			const spy = testUtils.sinon.spy( ToolbarView.prototype, 'destroy' );
+			const spy = testUtils.sinon.spy( View.prototype, 'destroy' );
 
 			view.destroy();
 			expect( spy.calledOnce ).to.be.true;
@@ -226,7 +254,7 @@ describe( 'StickyToolbarView', () => {
 				} );
 			} );
 
-			it( 'is true if beyond the top of the viewport (toolbar is active)', () => {
+			it( 'is true if beyond the top of the viewport (panel is active)', () => {
 				testUtils.sinon.stub( view.limiterElement, 'getBoundingClientRect' ).returns( { top: -10, height: 100 } );
 				view.isActive = true;
 
@@ -236,7 +264,7 @@ describe( 'StickyToolbarView', () => {
 				expect( view.isSticky ).to.be.true;
 			} );
 
-			it( 'is false if beyond the top of the viewport (toolbar is inactive)', () => {
+			it( 'is false if beyond the top of the viewport (panel is inactive)', () => {
 				testUtils.sinon.stub( view.limiterElement, 'getBoundingClientRect' ).returns( { top: -10, height: 100 } );
 				view.isActive = false;
 
@@ -246,7 +274,7 @@ describe( 'StickyToolbarView', () => {
 				expect( view.isSticky ).to.be.false;
 			} );
 
-			it( 'is false if in the viewport (toolbar is active)', () => {
+			it( 'is false if in the viewport (panel is active)', () => {
 				testUtils.sinon.stub( view.limiterElement, 'getBoundingClientRect' ).returns( { top: 10, height: 100 } );
 				view.isActive = true;
 
@@ -256,7 +284,7 @@ describe( 'StickyToolbarView', () => {
 				expect( view.isSticky ).to.be.false;
 			} );
 
-			it( 'is false if view.limiterElement is smaller than the toolbar and view.limiterBottomOffset (toolbar is active)', () => {
+			it( 'is false if view.limiterElement is smaller than the panel and view.limiterBottomOffset (panel is active)', () => {
 				testUtils.sinon.stub( view.limiterElement, 'getBoundingClientRect' ).returns( { top: -10, height: 60 } );
 				view.isActive = true;
 				view.limiterBottomOffset = 50;

+ 3 - 3
packages/ckeditor5-ui/theme/components/stickytoolbar.scss

@@ -2,12 +2,12 @@
 // For licensing, see LICENSE.md or http://ckeditor.com/license
 
 @include ck-editor {
-	.ck-toolbar {
-		&.ck-toolbar_sticky {
+	.ck-sticky-panel {
+		&.ck-sticky-panel_sticky {
 			position: fixed;
 			top: 0;
 
-			&.ck-toolbar_sticky_bottom-limit {
+			&.ck-sticky-panel_sticky_bottom-limit {
 				top: auto;
 				position: absolute;
 			}

+ 0 - 14
packages/ckeditor5-ui/theme/components/toolbar/stickytoolbar.scss

@@ -1,14 +0,0 @@
-// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-@include ck-editor {
-	.ck-toolbar.ck-toolbar_sticky {
-		position: fixed;
-		top: 0;
-
-		&.ck-toolbar_sticky_bottom-limit {
-			top: auto;
-			position: absolute;
-		}
-	}
-}

+ 2 - 2
packages/ckeditor5-ui/theme/theme.scss

@@ -13,6 +13,6 @@
 @import 'components/dropdown';
 @import 'components/list';
 @import 'components/label';
-@import 'components/panel/balloonpanel';
 @import 'components/editor';
-@import 'components/toolbar/stickytoolbar';
+@import 'components/panel/balloonpanel';
+@import 'components/panel/stickypanel';