8
0
Просмотр исходного кода

Merge branch 'master' into t/ckeditor5-core/64

Kamil Piechaczek 7 лет назад
Родитель
Сommit
f8bdc4048e
27 измененных файлов с 519 добавлено и 339 удалено
  1. 20 0
      packages/ckeditor5-ui/CHANGELOG.md
  2. 1 1
      packages/ckeditor5-ui/lang/translations/cs.po
  3. 14 0
      packages/ckeditor5-ui/lang/translations/fa.po
  4. 1 1
      packages/ckeditor5-ui/lang/translations/kn.po
  5. 1 1
      packages/ckeditor5-ui/lang/translations/sk.po
  6. 1 1
      packages/ckeditor5-ui/lang/translations/tr.po
  7. 2 2
      packages/ckeditor5-ui/lang/translations/uk.po
  8. 20 20
      packages/ckeditor5-ui/package.json
  9. 14 0
      packages/ckeditor5-ui/src/button/button.jsdoc
  10. 16 4
      packages/ckeditor5-ui/src/button/buttonview.js
  11. 97 0
      packages/ckeditor5-ui/src/button/switchbuttonview.js
  12. 6 0
      packages/ckeditor5-ui/src/dropdown/button/dropdownbuttonview.js
  13. 2 1
      packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js
  14. 2 2
      packages/ckeditor5-ui/src/dropdown/dropdownview.js
  15. 48 17
      packages/ckeditor5-ui/src/dropdown/utils.js
  16. 9 105
      packages/ckeditor5-ui/src/list/listitemview.js
  17. 40 0
      packages/ckeditor5-ui/tests/button/buttonview.js
  18. 46 0
      packages/ckeditor5-ui/tests/button/switchbuttonview.js
  19. 1 0
      packages/ckeditor5-ui/tests/dropdown/button/dropdownbuttonview.js
  20. 1 0
      packages/ckeditor5-ui/tests/dropdown/button/splitbuttonview.js
  21. 139 40
      packages/ckeditor5-ui/tests/dropdown/utils.js
  22. 9 140
      packages/ckeditor5-ui/tests/list/listitemview.js
  23. 5 0
      packages/ckeditor5-ui/tests/toolbar/block/blocktoolbar.js
  24. 8 0
      packages/ckeditor5-ui/theme/components/button/button.css
  25. 14 0
      packages/ckeditor5-ui/theme/components/button/switchbutton.css
  26. 0 4
      packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
  27. 2 0
      packages/ckeditor5-ui/theme/components/list/list.css

+ 20 - 0
packages/ckeditor5-ui/CHANGELOG.md

@@ -1,6 +1,26 @@
 Changelog
 =========
 
+## [10.1.0](https://github.com/ckeditor/ckeditor5-ui/compare/v10.0.0...v10.1.0) (2018-06-21)
+
+### Features
+
+* Implemented list component separators (see [ckeditor/ckeditor5-table#24](https://github.com/ckeditor/ckeditor5-table/issues/24)). ([0808a8c](https://github.com/ckeditor/ckeditor5-ui/commit/0808a8c))
+* Introduced the `BlockToolbar` plugin. Closes [#391](https://github.com/ckeditor/ckeditor5-ui/issues/391). ([5868516](https://github.com/ckeditor/ckeditor5-ui/commit/5868516))
+
+### Bug fixes
+
+* The action should be executed upon the first click on a tooltip-enabled UI in iOS. Closes [ckeditor/ckeditor5#920](https://github.com/ckeditor/ckeditor5/issues/920). ([6508ba2](https://github.com/ckeditor/ckeditor5-ui/commit/6508ba2))
+* The balloon toolbar should be attached correctly in case of a multi-range selection. Closes [#385](https://github.com/ckeditor/ckeditor5-ui/issues/385). ([714ef21](https://github.com/ckeditor/ckeditor5-ui/commit/714ef21))
+* The buttons in the editor should provide basic accessibility. Closes [ckeditor/ckeditor5#1013](https://github.com/ckeditor/ckeditor5/issues/1013). ([9e17d13](https://github.com/ckeditor/ckeditor5-ui/commit/9e17d13))
+
+### Other changes
+
+* Disabling a `ListItemView` should be possible using the `isEnabled` property. Closes [#389](https://github.com/ckeditor/ckeditor5-ui/issues/389). ([76a4d47](https://github.com/ckeditor/ckeditor5-ui/commit/76a4d47))
+* Made the `ContextualBalloon` always use the position of the topmost view in the stack (see: [ckeditor/ckeditor5-table#28](https://github.com/ckeditor/ckeditor5-table/issues/28)). Closes [ckeditor/ckeditor5#900](https://github.com/ckeditor/ckeditor5/issues/900). ([a2ef073](https://github.com/ckeditor/ckeditor5-ui/commit/a2ef073))
+* Updated translations. ([084e8c6](https://github.com/ckeditor/ckeditor5-ui/commit/084e8c6))
+
+
 ## [10.0.0](https://github.com/ckeditor/ckeditor5-ui/compare/v1.0.0-beta.4...v10.0.0) (2018-04-25)
 
 ### Other changes

+ 1 - 1
packages/ckeditor5-ui/lang/translations/cs.po

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Language: cs\n"
 "Language-Team: Czech (https://www.transifex.com/ckeditor/teams/11143/cs/)\n"
-"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor, %0"

+ 14 - 0
packages/ckeditor5-ui/lang/translations/fa.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: fa\n"
+"Language-Team: Persian (https://www.transifex.com/ckeditor/teams/11143/fa/)\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "Title of the CKEditor5 editor."
+msgid "Rich Text Editor, %0"
+msgstr "ویرایشگر متن غنی، %0"
+
+msgctxt "Title of the CKEditor5 editor."
+msgid "Rich Text Editor"
+msgstr "ویرایشگر متن غنی"

+ 1 - 1
packages/ckeditor5-ui/lang/translations/kn.po

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Language: kn\n"
 "Language-Team: Kannada (https://www.transifex.com/ckeditor/teams/11143/kn/)\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor, %0"

+ 1 - 1
packages/ckeditor5-ui/lang/translations/sk.po

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Language: sk\n"
 "Language-Team: Slovak (https://www.transifex.com/ckeditor/teams/11143/sk/)\n"
-"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor, %0"

+ 1 - 1
packages/ckeditor5-ui/lang/translations/tr.po

@@ -7,7 +7,7 @@ msgstr ""
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor, %0"
-msgstr "Zengin İÇerik Editörü, %0"
+msgstr "Zengin İçerik Editörü, %0"
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor"

+ 2 - 2
packages/ckeditor5-ui/lang/translations/uk.po

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Language: uk\n"
 "Language-Team: Ukrainian (https://www.transifex.com/ckeditor/teams/11143/uk/)\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor, %0"
@@ -11,4 +11,4 @@ msgstr "Розширений текстовий редактор, %0"
 
 msgctxt "Title of the CKEditor5 editor."
 msgid "Rich Text Editor"
-msgstr ""
+msgstr "Розширений текстовий редактор"

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

@@ -1,6 +1,6 @@
 {
   "name": "@ckeditor/ckeditor5-ui",
-  "version": "10.0.0",
+  "version": "10.1.0",
   "description": "The UI framework and standard UI library of CKEditor 5.",
   "keywords": [
     "ckeditor",
@@ -9,27 +9,27 @@
     "ckeditor5-lib"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor5-core": "^10.0.0",
-    "@ckeditor/ckeditor5-theme-lark": "^10.0.0",
-    "@ckeditor/ckeditor5-utils": "^10.0.0"
+    "@ckeditor/ckeditor5-core": "^10.1.0",
+    "@ckeditor/ckeditor5-theme-lark": "^10.1.0",
+    "@ckeditor/ckeditor5-utils": "^10.1.0"
   },
   "devDependencies": {
-    "@ckeditor/ckeditor5-basic-styles": "^10.0.0",
-    "@ckeditor/ckeditor5-block-quote": "^10.0.0",
-    "@ckeditor/ckeditor5-cloud-services": "^10.0.0",
-    "@ckeditor/ckeditor5-editor-balloon": "^10.0.0",
-    "@ckeditor/ckeditor5-editor-classic": "^10.0.0",
-    "@ckeditor/ckeditor5-engine": "^10.0.0",
-    "@ckeditor/ckeditor5-enter": "^10.0.0",
-    "@ckeditor/ckeditor5-easy-image": "^10.0.0",
-    "@ckeditor/ckeditor5-essentials": "^10.0.0",
-    "@ckeditor/ckeditor5-heading": "^10.0.0",
-    "@ckeditor/ckeditor5-image": "^10.0.0",
-    "@ckeditor/ckeditor5-link": "^10.0.0",
-    "@ckeditor/ckeditor5-list": "^10.0.0",
-    "@ckeditor/ckeditor5-paragraph": "^10.0.0",
-    "@ckeditor/ckeditor5-typing": "^10.0.0",
-    "@ckeditor/ckeditor5-undo": "^10.0.0",
+    "@ckeditor/ckeditor5-basic-styles": "^10.0.1",
+    "@ckeditor/ckeditor5-block-quote": "^10.0.1",
+    "@ckeditor/ckeditor5-cloud-services": "^10.0.1",
+    "@ckeditor/ckeditor5-editor-balloon": "^10.0.1",
+    "@ckeditor/ckeditor5-editor-classic": "^10.0.1",
+    "@ckeditor/ckeditor5-engine": "^10.1.0",
+    "@ckeditor/ckeditor5-enter": "^10.1.0",
+    "@ckeditor/ckeditor5-easy-image": "^10.0.1",
+    "@ckeditor/ckeditor5-essentials": "^10.1.0",
+    "@ckeditor/ckeditor5-heading": "^10.0.1",
+    "@ckeditor/ckeditor5-image": "^10.1.0",
+    "@ckeditor/ckeditor5-link": "^10.0.2",
+    "@ckeditor/ckeditor5-list": "^11.0.0",
+    "@ckeditor/ckeditor5-paragraph": "^10.0.1",
+    "@ckeditor/ckeditor5-typing": "^10.0.1",
+    "@ckeditor/ckeditor5-undo": "^10.0.1",
     "del": "^2.2.0",
     "eslint": "^4.15.0",
     "eslint-config-ckeditor5": "^1.0.7",

+ 14 - 0
packages/ckeditor5-ui/src/button/button.jsdoc

@@ -120,6 +120,20 @@
  * @member {String} #tabindex
  */
 
+/**
+ * (Optional) The additional CSS class set on the button.
+ *
+ * @observable
+ * @member {String} #class
+ */
+
+/**
+ * (Optional) The value of the `style` attribute of the label.
+ *
+ * @observable
+ * @member {String} #labelStyle
+ */
+
 /**
  * Fired when the button view is clicked. It won't be fired when the button {@link #isEnabled}
  * is `false`.

+ 16 - 4
packages/ckeditor5-ui/src/button/buttonview.js

@@ -11,6 +11,7 @@ import View from '../view';
 import IconView from '../icon/iconview';
 import TooltipView from '../tooltip/tooltipview';
 
+import uid from '@ckeditor/ckeditor5-utils/src/uid';
 import { getEnvKeystrokeText } from '@ckeditor/ckeditor5-utils/src/keyboard';
 
 import '../../theme/components/button/button.css';
@@ -42,8 +43,11 @@ export default class ButtonView extends View {
 		super( locale );
 
 		const bind = this.bindTemplate;
+		const ariaLabelUid = uid();
 
 		// Implement the Button interface.
+		this.set( 'class' );
+		this.set( 'labelStyle' );
 		this.set( 'icon' );
 		this.set( 'isEnabled', true );
 		this.set( 'isOn', false );
@@ -78,7 +82,7 @@ export default class ButtonView extends View {
 		 * @readonly
 		 * @member {module:ui/view~View} #labelView
 		 */
-		this.labelView = this._createLabelView();
+		this.labelView = this._createLabelView( ariaLabelUid );
 
 		/**
 		 * The icon view of the button. Will be added to {@link #children} when the
@@ -118,13 +122,17 @@ export default class ButtonView extends View {
 				class: [
 					'ck',
 					'ck-button',
+					bind.to( 'class' ),
 					bind.if( 'isEnabled', 'ck-disabled', value => !value ),
 					bind.if( 'isVisible', 'ck-hidden', value => !value ),
 					bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' ),
 					bind.if( 'withText', 'ck-button_with-text' )
 				],
 				type: bind.to( 'type', value => value ? value : 'button' ),
-				tabindex: bind.to( 'tabindex' )
+				tabindex: bind.to( 'tabindex' ),
+				'aria-labelledby': `ck-editor__aria-label_${ ariaLabelUid }`,
+				'aria-disabled': bind.if( 'isEnabled', true, value => !value ),
+				'aria-pressed': bind.if( 'isOn', true )
 			},
 
 			children: this.children,
@@ -191,10 +199,12 @@ export default class ButtonView extends View {
 	 * Creates a label view instance and binds it with button attributes.
 	 *
 	 * @private
+	 * @param {String} ariaLabelUid The aria label UID.
 	 * @returns {module:ui/view~View}
 	 */
-	_createLabelView() {
+	_createLabelView( ariaLabelUid ) {
 		const labelView = new View();
+		const bind = this.bindTemplate;
 
 		labelView.setTemplate( {
 			tag: 'span',
@@ -203,7 +213,9 @@ export default class ButtonView extends View {
 				class: [
 					'ck',
 					'ck-button__label'
-				]
+				],
+				style: bind.to( 'labelStyle' ),
+				id: `ck-editor__aria-label_${ ariaLabelUid }`,
 			},
 
 			children: [

+ 97 - 0
packages/ckeditor5-ui/src/button/switchbuttonview.js

@@ -0,0 +1,97 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module ui/button/switchbuttonview
+ */
+
+import View from '../view';
+import ButtonView from './buttonview';
+
+import '../../theme/components/button/switchbutton.css';
+
+/**
+ * The switch button view class.
+ *
+ *		const view = new SwitchButtonView();
+ *
+ *		view.set( {
+ *			withText: true,
+ *			label: 'Switch me!'
+ *		} );
+ *
+ *		view.render();
+ *
+ *		document.body.append( view.element );
+ *
+ * @extends module:ui/buttonview~ButtonView
+ */
+export default class SwitchButtonView extends ButtonView {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( locale ) {
+		super( locale );
+
+		/**
+		 * The toggle switch of the button.
+		 *
+		 * @readonly
+		 * @member {module:ui/view~View} #toggleSwitchView
+		 */
+		this.toggleSwitchView = this._createToggleView();
+
+		this.extendTemplate( {
+			attributes: {
+				class: 'ck-switchbutton'
+			}
+		} );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	render() {
+		super.render();
+
+		this.children.add( this.toggleSwitchView );
+	}
+
+	/**
+	 * Creates a toggle child view.
+	 *
+	 * @private
+	 * @returns {module:ui/view~View}
+	 */
+	_createToggleView() {
+		const toggleSwitchView = new View();
+
+		toggleSwitchView.setTemplate( {
+			tag: 'span',
+
+			attributes: {
+				class: [
+					'ck',
+					'ck-button__toggle'
+				],
+			},
+
+			children: [
+				{
+					tag: 'span',
+
+					attributes: {
+						class: [
+							'ck',
+							'ck-button__toggle__inner'
+						],
+					}
+				}
+			]
+		} );
+
+		return toggleSwitchView;
+	}
+}

+ 6 - 0
packages/ckeditor5-ui/src/dropdown/button/dropdownbuttonview.js

@@ -47,6 +47,12 @@ export default class DropdownButtonView extends ButtonView {
 		 */
 		this.arrowView = this._createArrowView();
 
+		this.extendTemplate( {
+			attributes: {
+				'aria-haspopup': true
+			}
+		} );
+
 		// The DropdownButton interface expects the open event upon which will open the dropdown.
 		this.delegate( 'execute' ).to( this, 'open' );
 	}

+ 2 - 1
packages/ckeditor5-ui/src/dropdown/button/splitbuttonview.js

@@ -207,7 +207,8 @@ export default class SplitButtonView extends View {
 
 		arrowView.extendTemplate( {
 			attributes: {
-				class: 'ck-splitbutton__arrow'
+				class: 'ck-splitbutton__arrow',
+				'aria-haspopup': true
 			}
 		} );
 

+ 2 - 2
packages/ckeditor5-ui/src/dropdown/dropdownview.js

@@ -188,8 +188,8 @@ export default class DropdownView extends View {
 		/**
 		 * Fired when the toolbar button or list item is executed.
 		 *
-		 * For {@link #listView} It fires when one of the list items has been
-		 * {@link module:ui/list/listitemview~ListItemView#event:execute executed}.
+		 * For {@link #listView} It fires when a child of some {@link module:ui/list/listitemview~ListItemView}
+		 * fired `execute`.
 		 *
 		 * For {@link #toolbarView} It fires when one of the buttons has been
 		 * {@link module:ui/button/buttonview~ButtonView#event:execute executed}.

+ 48 - 17
packages/ckeditor5-ui/src/dropdown/utils.js

@@ -14,6 +14,8 @@ import ToolbarView from '../toolbar/toolbarview';
 import ListView from '../list/listview';
 import ListItemView from '../list/listitemview';
 import ListSeparatorView from '../list/listseparatorview';
+import ButtonView from '../button/buttonview';
+import SwitchButtonView from '../button/switchbuttonview';
 
 import clickOutsideHandler from '../bindings/clickoutsidehandler';
 
@@ -146,8 +148,15 @@ export function addToolbarToDropdown( dropdownView, buttons ) {
  *
  *		const items = new Collection();
  *
- *		items.add( new Model( { label: 'First item', style: 'color: red' } ) );
- *		items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) );
+ *		items.add( {
+ *			type: 'button',
+ *			model: new Model( { label: 'First item', labelStyle: 'color: red' } )
+ *		} );
+ *
+ *		items.add( {
+ *			 type: 'button',
+ *			 model: new Model( { label: 'Second item', labelStyle: 'color: green', class: 'foo' } )
+ * 		} );
  *
  *		const dropdown = createDropdown( locale );
  *
@@ -164,28 +173,34 @@ export function addToolbarToDropdown( dropdownView, buttons ) {
  * See {@link module:ui/dropdown/utils~createDropdown} and {@link module:list/list~List}.
  *
  * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ListVIew` will be added.
- * @param {module:utils/collection~Collection} items
- * that the inner dropdown {@link module:ui/list/listview~ListView} children are created from.
- *
- * Usually, it is a collection of {@link module:ui/model~Model models}.
+ * @param {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>} items
+ * A collection of the list item definitions to populate the list.
  */
 export function addListToDropdown( dropdownView, items ) {
 	const locale = dropdownView.locale;
 	const listView = dropdownView.listView = new ListView( locale );
 
-	listView.items.bindTo( items ).using( itemModel => {
-		let item;
+	listView.items.bindTo( items ).using( ( { type, model } ) => {
+		if ( type === 'separator' ) {
+			return new ListSeparatorView( locale );
+		} else if ( type === 'button' || type === 'switchbutton' ) {
+			const listItemView = new ListItemView( locale );
+			let buttonView;
 
-		if ( itemModel.isSeparator ) {
-			item = new ListSeparatorView( locale );
-		} else {
-			item = new ListItemView( locale );
+			if ( type === 'button' ) {
+				buttonView = new ButtonView( locale );
+			} else {
+				buttonView = new SwitchButtonView( locale );
+			}
 
-			// Bind all attributes of the model to the item view.
-			item.bind( ...Object.keys( itemModel ) ).to( itemModel );
-		}
+			// Bind all model properties to the button view.
+			buttonView.bind( ...Object.keys( model ) ).to( model );
+			buttonView.delegate( 'execute' ).to( listItemView );
+
+			listItemView.children.add( buttonView );
 
-		return item;
+			return listItemView;
+		}
 	} );
 
 	dropdownView.panelView.children.add( listView );
@@ -223,7 +238,12 @@ function closeDropdownOnBlur( dropdownView ) {
 // @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
 function closeDropdownOnExecute( dropdownView ) {
 	// Close the dropdown when one of the list items has been executed.
-	dropdownView.on( 'execute', () => {
+	dropdownView.on( 'execute', evt => {
+		// Toggling a switch button view should not close the dropdown.
+		if ( evt.source instanceof SwitchButtonView ) {
+			return;
+		}
+
 		dropdownView.isOpen = false;
 	} );
 }
@@ -248,3 +268,14 @@ function focusDropdownContentsOnArrows( dropdownView ) {
 		}
 	} );
 }
+
+/**
+ * A definition of the list item used by the {@link module:ui/dropdown/utils~addListToDropdown}
+ * utility.
+ *
+ * @typedef {Object} module:ui/dropdown/utils~ListDropdownItemDefinition
+ *
+ * @property {String} type Either `'separator'`, `'button'` or `'switchbutton'`.
+ * @property {module:ui/model~Model} [model] Model of the item (when **not** `'separator'`).
+ * Its properties fuel the newly created list item (or its children, depending on the `type`).
+ */

+ 9 - 105
packages/ckeditor5-ui/src/list/listitemview.js

@@ -8,7 +8,6 @@
  */
 
 import View from '../view';
-import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
 
 /**
  * The list item view class.
@@ -19,27 +18,16 @@ export default class ListItemView extends View {
 	/**
 	 * @inheritDoc
 	 */
-	constructor() {
-		super();
+	constructor( locale ) {
+		super( locale );
 
 		/**
-		 * Controls the `tabindex` attribute of the item.
-		 *
-		 * @observable
-		 * @default -1
-		 * @member {String} #tabindex
-		 */
-		this.set( 'tabindex', -1 );
-
-		/**
-		 * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
+		 * Collection of the child views inside of the list item {@link #element}.
 		 *
 		 * @readonly
-		 * @member {module:utils/keystrokehandler~KeystrokeHandler}
+		 * @member {module:ui/viewcollection~ViewCollection}
 		 */
-		this.keystrokes = new KeystrokeHandler();
-
-		const bind = this.bindTemplate;
+		this.children = this.createCollection();
 
 		this.setTemplate( {
 			tag: 'li',
@@ -47,102 +35,18 @@ export default class ListItemView extends View {
 			attributes: {
 				class: [
 					'ck',
-					'ck-list__item',
-					bind.to( 'class' ),
-					bind.if( 'isActive', 'ck-list__item_active' ),
-					bind.if( 'isEnabled', 'ck-disabled', value => !value )
-				],
-				style: bind.to( 'style' ),
-				tabindex: bind.to( 'tabindex' )
+					'ck-list__item'
+				]
 			},
 
-			children: [
-				{
-					text: bind.to( 'label' )
-				}
-			],
-
-			on: {
-				click: bind.to( evt => {
-					// We can't make the button disabled using the disabled attribute, because it won't be focusable.
-					// Though, shouldn't this condition be moved to the button controller?
-					if ( this.isEnabled ) {
-						this.fire( 'execute' );
-					} else {
-						// Prevent the default when button is disabled, to block e.g.
-						// automatic form submitting. See ckeditor/ckeditor5-link#74.
-						evt.preventDefault();
-					}
-				} )
-			}
+			children: this.children
 		} );
-
-		/**
-		 * (Optional) Controls whether the list item is enabled, i.e. it can be clicked and execute an action.
-		 *
-		 * @observable
-		 * @default true
-		 * @member {Boolean} #isEnabled
-		 */
-		this.set( 'isEnabled', true );
-
-		/**
-		 * The label of the list item.
-		 *
-		 * @observable
-		 * @member {String} #label
-		 */
-
-		/**
-		 * (Optional) The DOM style attribute of the list item.
-		 *
-		 * @observable
-		 * @member {String} #style
-		 */
-
-		/**
-		 * (Optional) The additional class set on the {@link #element}.
-		 *
-		 * @observable
-		 * @member {String} #class
-		 */
-
-		/**
-		 * (Optional) When set, it marks the item as active among the others.
-		 *
-		 * @observable
-		 * @member {Boolean} #isActive
-		 */
-
-		/**
-		 * Fired when the list item has been clicked.
-		 *
-		 * @event execute
-		 */
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	render() {
-		super.render();
-
-		const onKeystrokePress = ( data, cancel ) => {
-			this.fire( 'execute' );
-			cancel();
-		};
-
-		this.keystrokes.listenTo( this.element );
-
-		// Execute on Enter and Space key press.
-		this.keystrokes.set( 'Enter', onKeystrokePress );
-		this.keystrokes.set( 'Space', onKeystrokePress );
 	}
 
 	/**
 	 * Focuses the list item.
 	 */
 	focus() {
-		this.element.focus();
+		this.children.first.focus();
 	}
 }

+ 40 - 0
packages/ckeditor5-ui/tests/button/buttonview.js

@@ -97,6 +97,22 @@ describe( 'ButtonView', () => {
 				view.type = null;
 				expect( view.element.getAttribute( 'type' ) ).to.equal( 'button' );
 			} );
+
+			it( 'reacts on view#class', () => {
+				view.set( 'class', 'foo' );
+
+				expect( view.element.classList.contains( 'foo' ) ).to.be.true;
+			} );
+		} );
+
+		describe( 'labelView', () => {
+			it( 'reacts on view#labelStyle', () => {
+				expect( view.labelView.element.attributes.getNamedItem( 'style' ) ).to.be.null;
+
+				view.labelStyle = 'color: red';
+
+				expect( view.labelView.element.attributes.getNamedItem( 'style' ).value ).to.equal( 'color: red' );
+			} );
 		} );
 
 		describe( 'tooltip', () => {
@@ -213,6 +229,30 @@ describe( 'ButtonView', () => {
 			} );
 		} );
 
+		describe( 'aria', () => {
+			it( '-labelledby is set', () => {
+				expect( view.element.attributes[ 'aria-labelledby' ].value )
+					.to.equal( view.element.lastChild.id )
+					.to.match( /^ck-editor__aria-label_\w+$/ );
+			} );
+
+			it( '-disabled reacts to #isEnabled', () => {
+				view.isEnabled = true;
+				expect( view.element.attributes[ 'aria-disabled' ] ).to.be.undefined;
+
+				view.isEnabled = false;
+				expect( view.element.attributes[ 'aria-disabled' ].value ).to.equal( 'true' );
+			} );
+
+			it( '-pressed reacts to #isOn', () => {
+				view.isOn = true;
+				expect( view.element.attributes[ 'aria-pressed' ].value ).to.equal( 'true' );
+
+				view.isOn = false;
+				expect( view.element.attributes[ 'aria-pressed' ] ).to.be.undefined;
+			} );
+		} );
+
 		describe( 'mousedown event', () => {
 			it( 'should be prevented', () => {
 				const ret = view.element.dispatchEvent( new Event( 'mousedown', { cancelable: true } ) );

+ 46 - 0
packages/ckeditor5-ui/tests/button/switchbuttonview.js

@@ -0,0 +1,46 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import SwitchButtonView from '../../src/button/switchbuttonview';
+import View from '../../src/view';
+
+describe( 'SwitchButtonView', () => {
+	let locale, view;
+
+	beforeEach( () => {
+		locale = { t() {} };
+
+		view = new SwitchButtonView( locale );
+		view.render();
+	} );
+
+	describe( 'constructor()', () => {
+		it( 'creates #toggleSwitchView', () => {
+			expect( view.toggleSwitchView ).to.be.instanceOf( View );
+		} );
+
+		it( 'sets CSS class', () => {
+			expect( view.element.classList.contains( 'ck-switchbutton' ) ).to.be.true;
+		} );
+	} );
+
+	describe( 'render', () => {
+		it( 'adds #toggleSwitchView to #children', () => {
+			expect( view.children.get( 2 ) ).to.equal( view.toggleSwitchView );
+		} );
+	} );
+
+	describe( '#toggleSwitchView', () => {
+		it( 'has proper DOM structure', () => {
+			const toggleElement = view.toggleSwitchView.element;
+
+			expect( toggleElement.classList.contains( 'ck' ) ).to.be.true;
+			expect( toggleElement.classList.contains( 'ck-button__toggle' ) ).to.be.true;
+
+			expect( toggleElement.firstChild.classList.contains( 'ck' ) ).to.be.true;
+			expect( toggleElement.firstChild.classList.contains( 'ck-button__toggle__inner' ) ).to.be.true;
+		} );
+	} );
+} );

+ 1 - 0
packages/ckeditor5-ui/tests/dropdown/button/dropdownbuttonview.js

@@ -30,6 +30,7 @@ describe( 'DropdownButtonView', () => {
 
 		it( 'creates element from template', () => {
 			expect( view.element.tagName ).to.equal( 'BUTTON' );
+			expect( view.element.attributes[ 'aria-haspopup' ].value ).to.equal( 'true' );
 		} );
 	} );
 

+ 1 - 0
packages/ckeditor5-ui/tests/dropdown/button/splitbuttonview.js

@@ -33,6 +33,7 @@ describe( 'SplitButtonView', () => {
 		it( 'creates view#arrowView', () => {
 			expect( view.arrowView ).to.be.instanceOf( ButtonView );
 			expect( view.arrowView.element.classList.contains( 'ck-splitbutton__arrow' ) ).to.be.true;
+			expect( view.arrowView.element.attributes[ 'aria-haspopup' ].value ).to.equal( 'true' );
 			expect( view.arrowView.icon ).to.be.not.undefined;
 		} );
 

+ 139 - 40
packages/ckeditor5-ui/tests/dropdown/utils.js

@@ -12,6 +12,7 @@ import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import Model from '../../src/model';
 
 import ButtonView from '../../src/button/buttonview';
+import SwitchButtonView from '../../src/button/switchbuttonview';
 import DropdownView from '../../src/dropdown/dropdownview';
 import DropdownPanelView from '../../src/dropdown/dropdownpanelview';
 import SplitButtonView from '../../src/dropdown/button/splitbuttonview';
@@ -164,6 +165,24 @@ describe( 'utils', () => {
 					dropdownView.fire( 'execute' );
 					expect( dropdownView.isOpen ).to.be.false;
 				} );
+
+				it( 'does not change #isOpen if #execute triggered by a SwitchButtonView', () => {
+					const items = new Collection();
+
+					items.add( {
+						type: 'switchbutton',
+						model: new Model( {
+							label: 'foo'
+						} )
+					} );
+
+					addListToDropdown( dropdownView, items );
+
+					dropdownView.isOpen = true;
+
+					dropdownView.listView.items.first.children.first.fire( 'execute' );
+					expect( dropdownView.isOpen ).to.be.true;
+				} );
 			} );
 
 			describe( 'focusDropdownContentsOnArrows()', () => {
@@ -260,28 +279,28 @@ describe( 'utils', () => {
 				const panelChildren = dropdownView.panelView.children;
 
 				expect( panelChildren ).to.have.length( 1 );
-				expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView );
+				expect( panelChildren.first ).to.equal( dropdownView.toolbarView );
 				expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView );
 			} );
 
 			it( 'delegates view.toolbarView.items#execute to the view', done => {
 				dropdownView.on( 'execute', evt => {
-					expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) );
-					expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] );
+					expect( evt.source ).to.equal( dropdownView.toolbarView.items.first );
+					expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.first, dropdownView ] );
 
 					done();
 				} );
 
-				dropdownView.toolbarView.items.get( 0 ).fire( 'execute' );
+				dropdownView.toolbarView.items.first.fire( 'execute' );
 			} );
 		} );
 	} );
 
 	describe( 'addListToDropdown()', () => {
-		let items;
+		let definitions, listItems;
 
 		beforeEach( () => {
-			items = new Collection();
+			definitions = new Collection();
 
 			dropdownView = createDropdown( locale );
 			dropdownView.buttonView.set( {
@@ -290,8 +309,9 @@ describe( 'utils', () => {
 				label: 'foo'
 			} );
 
-			addListToDropdown( dropdownView, items );
+			addListToDropdown( dropdownView, definitions );
 
+			listItems = dropdownView.listView.items;
 			dropdownView.render();
 			document.body.appendChild( dropdownView.element );
 		} );
@@ -305,58 +325,137 @@ describe( 'utils', () => {
 				const panelChildren = dropdownView.panelView.children;
 
 				expect( panelChildren ).to.have.length( 1 );
-				expect( panelChildren.get( 0 ) ).to.equal( dropdownView.listView );
+				expect( panelChildren.first ).to.equal( dropdownView.listView );
 				expect( dropdownView.listView ).to.be.instanceof( ListView );
 			} );
 
-			it( 'is bound to model#items', () => {
-				items.add( new Model( { label: 'a', style: 'b' } ) );
-				items.add( new Model( { label: 'c', style: 'd' } ) );
-
-				expect( dropdownView.listView.items ).to.have.length( 2 );
-				expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView );
-				expect( dropdownView.listView.items.get( 1 ).label ).to.equal( 'c' );
-				expect( dropdownView.listView.items.get( 1 ).style ).to.equal( 'd' );
+			it( 'ignores unknown definition types', () => {
+				definitions.add( { type: 'foo' } );
 
-				items.remove( 1 );
-				expect( dropdownView.listView.items ).to.have.length( 1 );
-				expect( dropdownView.listView.items.get( 0 ).label ).to.equal( 'a' );
-				expect( dropdownView.listView.items.get( 0 ).style ).to.equal( 'b' );
+				expect( listItems.length ).to.equal( 0 );
 			} );
 
-			it( 'binds all attributes in model#items', () => {
-				const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } );
+			describe( 'with ButtonView', () => {
+				it( 'is populated using item definitions', () => {
+					definitions.add( {
+						type: 'button',
+						model: new Model( { label: 'a', labelStyle: 'b' } )
+					} );
+
+					definitions.add( {
+						type: 'button',
+						model: new Model( { label: 'c', labelStyle: 'd' } )
+					} );
+
+					expect( listItems ).to.have.length( 2 );
+					expect( listItems.first ).to.be.instanceOf( ListItemView );
+					expect( listItems.first.children.first ).to.be.instanceOf( ButtonView );
+
+					expect( listItems.get( 1 ).children.first.label ).to.equal( 'c' );
+					expect( listItems.get( 1 ).children.first.labelStyle ).to.equal( 'd' );
+
+					definitions.remove( 1 );
+					expect( listItems ).to.have.length( 1 );
+					expect( listItems.first.children.first.label ).to.equal( 'a' );
+					expect( listItems.first.children.first.labelStyle ).to.equal( 'b' );
+				} );
+
+				it( 'binds all button properties', () => {
+					const def = {
+						type: 'button',
+						model: new Model( { label: 'a', labelStyle: 'b', foo: 'bar', baz: 'qux' } )
+					};
+
+					definitions.add( def );
+
+					const button = listItems.first.children.first;
+
+					expect( button.foo ).to.equal( 'bar' );
+					expect( button.baz ).to.equal( 'qux' );
+
+					def.model.baz = 'foo?';
+					expect( button.baz ).to.equal( 'foo?' );
+				} );
+
+				it( 'delegates ButtonView#execute to the ListItemView', done => {
+					definitions.add( {
+						type: 'button',
+						model: new Model( { label: 'a', labelStyle: 'b' } )
+					} );
 
-				items.add( itemModel );
+					const listItem = listItems.first;
+					const button = listItem.children.first;
 
-				const item = dropdownView.listView.items.get( 0 );
+					dropdownView.on( 'execute', evt => {
+						expect( evt.source ).to.equal( button );
+						expect( evt.path ).to.deep.equal( [ button, listItem, dropdownView ] );
 
-				expect( item.foo ).to.equal( 'bar' );
-				expect( item.baz ).to.equal( 'qux' );
+						done();
+					} );
 
-				itemModel.baz = 'foo?';
-				expect( item.baz ).to.equal( 'foo?' );
+					button.fire( 'execute' );
+				} );
 			} );
 
-			it( 'delegates view.listView#execute to the view', done => {
-				items.add( new Model( { label: 'a', style: 'b' } ) );
+			describe( 'with SwitchButtonView', () => {
+				it( 'is populated using item definitions', () => {
+					definitions.add( {
+						type: 'switchbutton',
+						model: new Model( { label: 'a', labelStyle: 'b' } )
+					} );
 
-				dropdownView.on( 'execute', evt => {
-					expect( evt.source ).to.equal( dropdownView.listView.items.get( 0 ) );
-					expect( evt.path ).to.deep.equal( [ dropdownView.listView.items.get( 0 ), dropdownView ] );
+					expect( listItems ).to.have.length( 1 );
+					expect( listItems.first ).to.be.instanceOf( ListItemView );
+					expect( listItems.first.children.first ).to.be.instanceOf( SwitchButtonView );
 
-					done();
+					expect( listItems ).to.have.length( 1 );
+					expect( listItems.first.children.first.label ).to.equal( 'a' );
+					expect( listItems.first.children.first.labelStyle ).to.equal( 'b' );
 				} );
 
-				dropdownView.listView.items.get( 0 ).fire( 'execute' );
-			} );
+				it( 'binds all button properties', () => {
+					const def = {
+						type: 'switchbutton',
+						model: new Model( { label: 'a', labelStyle: 'b', foo: 'bar', baz: 'qux' } )
+					};
 
-			it( 'is filled with separators', () => {
-				const itemModel = new Model( { isSeparator: true } );
+					definitions.add( def );
+
+					const button = listItems.first.children.first;
+
+					expect( button.foo ).to.equal( 'bar' );
+					expect( button.baz ).to.equal( 'qux' );
+
+					def.model.baz = 'foo?';
+					expect( button.baz ).to.equal( 'foo?' );
+				} );
 
-				items.add( itemModel );
+				it( 'delegates SwitchButtonView#execute to the ListItemView', done => {
+					definitions.add( {
+						type: 'switchbutton',
+						model: new Model( { label: 'a', labelStyle: 'b' } )
+					} );
 
-				expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListSeparatorView );
+					const listItem = listItems.first;
+					const button = listItem.children.first;
+
+					dropdownView.on( 'execute', evt => {
+						expect( evt.source ).to.equal( button );
+						expect( evt.path ).to.deep.equal( [ button, listItem, dropdownView ] );
+
+						done();
+					} );
+
+					button.fire( 'execute' );
+				} );
+			} );
+
+			describe( 'with ListSeparatorView', () => {
+				it( 'creates a separator from the definition', () => {
+					definitions.add( { type: 'separator' } );
+
+					expect( listItems.first ).to.be.instanceOf( ListSeparatorView );
+				} );
 			} );
 		} );
 	} );

+ 9 - 140
packages/ckeditor5-ui/tests/list/listitemview.js

@@ -3,20 +3,15 @@
  * For licensing, see LICENSE.md.
  */
 
-/* global Event */
-
+import ButtonView from '../../src/button/buttonview';
 import ListItemView from '../../src/list/listitemview';
-import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
+import ViewCollection from '../../src/viewcollection';
 
 describe( 'ListItemView', () => {
 	let view;
 
 	beforeEach( () => {
 		view = new ListItemView();
-		view.set( {
-			style: 'foo',
-			label: 'bar'
-		} );
 
 		return view.render();
 	} );
@@ -27,145 +22,19 @@ describe( 'ListItemView', () => {
 			expect( view.element.classList.contains( 'ck-list__item' ) ).to.be.true;
 		} );
 
-		it( 'should create #keystrokes instance', () => {
-			expect( view.keystrokes ).to.be.instanceOf( KeystrokeHandler );
-		} );
-	} );
-
-	describe( 'render()', () => {
-		it( 'starts listening for #keystrokes coming from #element', () => {
-			view = new ListItemView();
-
-			const spy = sinon.spy( view.keystrokes, 'listenTo' );
-
-			view.render();
-			sinon.assert.calledOnce( spy );
-			sinon.assert.calledWithExactly( spy, view.element );
-		} );
-
-		// https://github.com/ckeditor/ckeditor5-ui/issues/153
-		it( 'triggers view#execute event when Enter or Space key is pressed', () => {
-			const spy = sinon.spy();
-			const evtData = {
-				keyCode: 10,
-				preventDefault: sinon.spy(),
-				stopPropagation: sinon.spy()
-			};
-
-			view.on( 'execute', spy );
-			view.keystrokes.press( evtData );
-
-			sinon.assert.notCalled( spy );
-			sinon.assert.notCalled( evtData.preventDefault );
-
-			evtData.keyCode = 13;
-			view.keystrokes.press( evtData );
-
-			sinon.assert.calledOnce( spy );
-			sinon.assert.calledOnce( evtData.preventDefault );
-
-			evtData.keyCode = 32;
-			view.keystrokes.press( evtData );
-
-			sinon.assert.calledTwice( spy );
-			sinon.assert.calledTwice( evtData.preventDefault );
-		} );
-	} );
-
-	describe( 'DOM bindings', () => {
-		describe( '"class" attribute', () => {
-			it( 'reacts on view#class', () => {
-				expect( view.element.classList ).to.have.length( 2 );
-
-				view.set( 'class', 'foo' );
-
-				expect( view.element.classList.contains( 'foo' ) ).to.be.true;
-			} );
-
-			it( 'reacts on view#isActive', () => {
-				expect( view.element.classList ).to.have.length( 2 );
-
-				view.set( 'isActive', true );
-
-				expect( view.element.classList.contains( 'ck-list__item_active' ) ).to.be.true;
-			} );
-
-			it( 'reacts on view#isEnabled', () => {
-				expect( view.element.classList ).to.have.length( 2 );
-
-				expect( view.element.classList.contains( 'ck-disabled' ) ).to.be.false;
-
-				view.set( 'isEnabled', false );
-
-				expect( view.element.classList.contains( 'ck-disabled' ) ).to.be.true;
-
-				view.set( 'isEnabled', true );
-
-				expect( view.element.classList.contains( 'ck-disabled' ) ).to.be.false;
-			} );
-		} );
-
-		describe( '"style" attribute', () => {
-			it( 'reacts on view#style', () => {
-				expect( view.element.attributes.getNamedItem( 'style' ).value ).to.equal( 'foo' );
-
-				view.style = 'color: red';
-
-				expect( view.element.attributes.getNamedItem( 'style' ).value ).to.equal( 'color: red' );
-			} );
-		} );
-
-		describe( 'text content', () => {
-			it( 'reacts on view#label', () => {
-				expect( view.element.innerHTML ).to.equal( 'bar' );
-
-				view.label = 'baz';
-
-				expect( view.element.innerHTML ).to.equal( 'baz' );
-			} );
-		} );
-
-		describe( 'tabindex', () => {
-			it( 'is initially set ', () => {
-				expect( view.element.attributes.tabindex.value ).to.equal( '-1' );
-			} );
-
-			it( 'reacts on view#tabindex', () => {
-				view.tabindex = 3;
-
-				expect( view.element.attributes.tabindex.value ).to.equal( '3' );
-			} );
-		} );
-
-		describe( 'view#execute event', () => {
-			it( 'triggers view#execute event when "click" is fired in DOM', () => {
-				const spy = sinon.spy();
-
-				view.on( 'execute', spy );
-
-				view.element.dispatchEvent( new Event( 'click' ) );
-				expect( spy.calledOnce ).to.be.true;
-			} );
-
-			it( 'does not triggers view#execute event when "click" is fired in DOM and isEnables=false', () => {
-				const spy = sinon.spy();
-
-				view.on( 'execute', spy );
-
-				view.isEnabled = false;
-
-				view.element.dispatchEvent( new Event( 'click' ) );
-				sinon.assert.notCalled( spy );
-			} );
+		it( 'creates view#children collection', () => {
+			expect( view.children ).to.be.instanceOf( ViewCollection );
 		} );
 	} );
 
 	describe( 'focus()', () => {
-		it( 'focuses the item in DOM', () => {
-			const spy = sinon.spy( view.element, 'focus' );
+		it( 'focuses the first child item', () => {
+			const button = new ButtonView();
+			view.children.add( button );
 
-			view.focus();
+			const spy = sinon.spy( button.element, 'focus' );
 
+			view.focus();
 			sinon.assert.calledOnce( spy );
 		} );
 	} );

+ 5 - 0
packages/ckeditor5-ui/tests/toolbar/block/blocktoolbar.js

@@ -227,6 +227,8 @@ describe( 'BlockToolbar', () => {
 		} );
 
 		it( 'should not display the button when the selection is placed in a root element', () => {
+			editor.model.schema.extend( '$text', { allowIn: '$root' } );
+
 			setData( editor.model, '<paragraph>foo</paragraph>[]<paragraph>bar</paragraph>' );
 
 			expect( blockToolbar.buttonView.isVisible ).to.be.false;
@@ -400,8 +402,11 @@ describe( 'BlockToolbar', () => {
 		} );
 
 		it( 'should update the button position on browser resize only when the button is visible', () => {
+			editor.model.schema.extend( '$text', { allowIn: '$root' } );
+
 			const spy = testUtils.sinon.spy( blockToolbar, '_attachButtonToElement' );
 
+			// Place the selection outside of any block because the toolbar will not be shown in this case.
 			setData( editor.model, '[]<paragraph>bar</paragraph>' );
 
 			window.dispatchEvent( new Event( 'resize' ) );

+ 8 - 0
packages/ckeditor5-ui/theme/components/button/button.css

@@ -12,6 +12,9 @@ a.ck.ck-button {
 	@mixin ck-tooltip_enabled;
 
 	position: relative;
+	display: inline-flex;
+	align-items: center;
+	justify-content: left;
 
 	&.ck-button_with-text {
 		& .ck-button__label {
@@ -19,6 +22,11 @@ a.ck.ck-button {
 		}
 	}
 
+	/* Center the icon horizontally in a button without text. */
+	&:not(.ck-button_with-text)  {
+		justify-content: center;
+	}
+
 	&:hover {
 		@mixin ck-tooltip_visible;
 	}

+ 14 - 0
packages/ckeditor5-ui/theme/components/button/switchbutton.css

@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+.ck.ck-button.ck-switchbutton {
+	& .ck-button__toggle {
+		display: block;
+
+		& .ck-button__toggle__inner {
+			display: block;
+		}
+	}
+}

+ 0 - 4
packages/ckeditor5-ui/theme/components/dropdown/dropdown.css

@@ -12,10 +12,6 @@
 	& .ck-dropdown__arrow {
 		pointer-events: none;
 		z-index: var(--ck-z-default);
-
-		position: absolute;
-		top: 50%;
-		transform: translate3d( 0, -50%, 0 );
 	}
 
 	/* Dropdown button should span horizontally, e.g. in vertical toolbars */

+ 2 - 0
packages/ckeditor5-ui/theme/components/list/list.css

@@ -10,6 +10,8 @@
 
 	/* Crop the the items when the list has huge border-radius. */
 	overflow: hidden;
+	display: flex;
+	flex-direction: column;
 
 	& .ck-list__item,
 	& .ck-list__separator {