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

Merge tag 'v1.0.0-beta.1' into stable

Release: v1.0.0-beta.1.
Piotrek Koszuliński 7 лет назад
Родитель
Сommit
a7fab9ca18

+ 1 - 1
packages/ckeditor5-widget/.eslintrc.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 

+ 15 - 11
packages/ckeditor5-widget/.travis.yml

@@ -1,24 +1,28 @@
 sudo: required
 dist: trusty
 addons:
+  firefox: "latest"
   apt:
     sources:
-      - google-chrome
+    - google-chrome
     packages:
-      - google-chrome-stable
+    - google-chrome-stable
 language: node_js
 node_js:
-  - "6"
+- '6'
 cache:
-  - node_modules
+- node_modules
 before_install:
-  - export DISPLAY=:99.0
-  - sh -e /etc/init.d/xvfb start
+- export DISPLAY=:99.0
+- sh -e /etc/init.d/xvfb start
 install:
-  - npm install @ckeditor/ckeditor5-dev-tests
-  - ckeditor5-dev-tests-install-dependencies
+- npm install @ckeditor/ckeditor5-dev-tests
+- ckeditor5-dev-tests-install-dependencies
 script:
-  - ckeditor5-dev-tests-travis
+- ckeditor5-dev-tests-travis
 after_success:
-  - codeclimate-test-reporter < coverage/lcov.info
-  - ckeditor5-dev-tests-save-revision
+- ckeditor5-dev-tests-save-revision
+env:
+  global:
+  - secure: HvkSDjAVlYflmRnzq4WLYDccK+Ka44NfA7RVnycYmTzpviyjYSt1nSx6bMa/tmWbK+TCzjj6sA/B88Wn1udYe7K6SxZfz4zW1E3kPTgoT3mGuvhpaGfx4Fhbr8N1tXAJ/LCvwFbF5D5tMjMQs1CwCIirtBT8i5f6kqdbDAntA7xSq789yZTdAwgtqEVCOFyNHVKoCQdGmy+7u5rdRHFdTVWcO5k7m6qGeG0L7BOGRQrCBPRfvG+qLrWBJNbxf3jaCiVd760VsmTPXHMjUftDao6X79eXWq+LxG0tZKP+DHxJo88/GIdAP4U2JPupqPua/hKHUkXEn729NtAQsDCnhM3mKbJjc4dTQphR+WVCmNaqoGljjjflQ6M9QgaDqARTq2Vr7sBAgNvf8G5eShklL1k8oQ/4QRCMr6a8AeExvAa9M3rx5Czb3HSwgzf8jDl0g5vd34hiBsTC94GFVZjVGu1c7URcVKV1BWvIaOSHTbJxf4DEpc7+gfOtYy+YN+zS2Wc18TlxoD3NHcAjFVkAY+K4v6QaTmkqQsWmP1N/B0ySTQf3UhoXnyifm22alYMnTzvJgN3IlQ6kht4YeLTCRBZwAjs5p93ymUs6BjTJasB/x3MytsWoXYk2O1LuSitk+kvjEy5ogDixhBeLXZeKlmYhMPNcSz+RqGCqbClTzpI=
+  - secure: uoedx5n7VAfe4o4aRsPtb+BhSArMcfDPy0E+gY9yGBR/qprmck6cyvBAiWUUEehDfhyWKupXk0pWIIQSo7/BfK6SOzwutfBHsIKiJmSP8KYEjT4rpW03Vhd4dkExU8Qg5Er1R878/PunF7XJq51cXfnfcS0lpzwwh6o+qsFoPgs3yRcDZCVVg60MxetQ/5Ob4Pf1ew26MHwqDzT7N0nETkp8EljNe8+RZYMqEeX3FD4oJDli072NJ+0hYOpCt8LfMQecZ/cR+vLdW/nWeqzuXPBp5TkmIqB/MVUG8yVQv6y77Fh3RGuo7ch2gJcVJCMpkmI0zhBtk6Up8Y//wCUBcdXabnO2AOlCnOphBfhFrCd0/lc3OrUDE1vsvwm/mT2rSGpNRXcXD0cpP33RQ2s/stlYkoDOM9sb9S/CGxqRHB8jdrvfU47aee1IYV8vZgr+I0NefK1uNGBk9n+WXNF+d8svpQ1hgTArED7MG4NO1+rUYgKcftCQzmO3RSzC3MOyuH7kq+eTnMU3InEEByzUPBGIStndmv11wIGGh/qCUuVivPTnn6JsR6FEBfF0ADuOSB3Z8RZMBhv1kOBKCv3xsWG6et/ASRFte/eZu0v1AN3LaiZEsfCJ22TNwGaqwmYAzHrpqYf/FViUlGmJCUuJfC9/EWm3fF09o2BqyDu7ZTk=

+ 9 - 0
packages/ckeditor5-widget/CHANGELOG.md

@@ -1,6 +1,15 @@
 Changelog
 =========
 
+## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-widget/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (2018-03-15)
+
+### Other changes
+
+* Aligned feature class naming to the new scheme. ([23991a4](https://github.com/ckeditor/ckeditor5-widget/commit/23991a4))
+* Migrated package styles to PostCSS. Moved visual styles to `@ckeditor/ckeditor5-theme-lark` (see [ckeditor/ckeditor5-ui#144](https://github.com/ckeditor/ckeditor5-ui/issues/144)). ([857d6d4](https://github.com/ckeditor/ckeditor5-widget/commit/857d6d4))
+* Switched to handling deletion around widgets by using the `delete` event instead of listening directly on key events. Closes [#29](https://github.com/ckeditor/ckeditor5-widget/issues/29). ([ee6cc95](https://github.com/ckeditor/ckeditor5-widget/commit/ee6cc95))
+
+
 ## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-widget/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2017-11-14)
 
 ### Bug fixes

+ 1 - 1
packages/ckeditor5-widget/LICENSE.md

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 
 **CKEditor 5 widget API** – https://github.com/ckeditor/ckeditor5-image <br>
-Copyright (c) 2003-2017, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
 Licensed under the terms of any of the following licenses at your choice:
 

+ 3 - 1
packages/ckeditor5-widget/README.md

@@ -4,7 +4,9 @@ CKEditor 5 widget API
 [![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-widget.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget)
 [![Build Status](https://travis-ci.org/ckeditor/ckeditor5-widget.svg)](https://travis-ci.org/ckeditor/ckeditor5-widget)
-[![Test Coverage](https://codeclimate.com/github/ckeditor/ckeditor5-widget/badges/coverage.svg)](https://codeclimate.com/github/ckeditor/ckeditor5-widget/coverage)
+[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://www.browserstack.com/automate/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
+[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-widget/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-widget?branch=master)
+<br>
 [![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-widget/status.svg)](https://david-dm.org/ckeditor/ckeditor5-widget)
 [![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-widget/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-widget?type=dev)
 

+ 1 - 1
packages/ckeditor5-widget/docs/api/widget.md

@@ -17,7 +17,7 @@ Browse the API documentation of this package by using the module tree on the lef
 <info-box>
 	The widget API is proposed in a very different way than it was in CKEditor 4. It is just a set of utilities that allow you to implement typical object-like entities. Most of the work actually happens in the {@link api/engine engine} and this API's role is only to properly conduct the engine.
 
-	Therefore, this is just one of the ways how widgets can be proposed. This API can be expected to change (grow) and for now, the only available documentation is in the {@link module:image/image/imageengine~ImageEngine}'s code (which is the only widget API consumer at this stage).
+	Therefore, this is just one of the ways how widgets can be proposed. This API can be expected to change (grow) and for now, the only available documentation is in the {@link module:image/image/imageediting~ImageEditing}'s code (which is the only widget API consumer at this stage).
 </info-box>
 
 ## Installation

+ 9 - 8
packages/ckeditor5-widget/package.json

@@ -1,22 +1,23 @@
 {
   "name": "@ckeditor/ckeditor5-widget",
-  "version": "1.0.0-alpha.2",
+  "version": "1.0.0-beta.1",
   "description": "Widget API for CKEditor 5.",
   "keywords": [
     "ckeditor5",
     "ckeditor5-lib"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor5-core": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-utils": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-theme-lark": "^1.0.0-alpha.2"
+    "@ckeditor/ckeditor5-core": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-engine": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-utils": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-theme-lark": "^1.0.0-beta.1"
   },
   "devDependencies": {
-    "eslint": "^4.8.0",
-    "eslint-config-ckeditor5": "^1.0.6",
+    "@ckeditor/ckeditor5-typing": "^1.0.0-beta.1",
+    "eslint": "^4.15.0",
+    "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",
-    "lint-staged": "^4.2.3"
+    "lint-staged": "^6.0.0"
   },
   "engines": {
     "node": ">=6.0.0",

+ 25 - 18
packages/ckeditor5-widget/src/highlightstack.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -11,12 +11,14 @@ import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 
 /**
- * Class used to handle correct order of
- * {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toHighlight highlights} on
- * elements. When different highlights are applied to same element correct order should be preserved:
+ * Class used to handle correct order of highlights on elements.
+ *
+ * When different highlights are applied to same element correct order should be preserved:
+ *
  * * highlight with highest priority should be applied,
  * * if two highlights have same priority - sort by CSS class provided in
- * {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor}.
+ * {@link module:engine/conversion/downcast-converters~HighlightDescriptor}.
+ *
  * This way, highlight will be applied with the same rules it is applied on texts.
  */
 export default class HighlightStack {
@@ -31,9 +33,10 @@ export default class HighlightStack {
 	 * Adds highlight descriptor to the stack.
 	 *
 	 * @fires change:top
-	 * @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} descriptor
+	 * @param {module:engine/conversion/downcast-converters~HighlightDescriptor} descriptor
+	 * @param {module:engine/view/writer~Writer} writer
 	 */
-	add( descriptor ) {
+	add( descriptor, writer ) {
 		const stack = this._stack;
 
 		// Save top descriptor and insert new one. If top is changed - fire event.
@@ -45,7 +48,8 @@ export default class HighlightStack {
 		if ( oldTop !== newTop && !compareDescriptors( oldTop, newTop ) ) {
 			this.fire( 'change:top', {
 				oldDescriptor: oldTop,
-				newDescriptor: newTop
+				newDescriptor: newTop,
+				writer
 			} );
 		}
 	}
@@ -55,8 +59,9 @@ export default class HighlightStack {
 	 *
 	 * @fires change:top
 	 * @param {String} id Id of the descriptor to remove.
+	 * @param {module:engine/view/writer~Writer} writer
 	 */
-	remove( id ) {
+	remove( id, writer ) {
 		const stack = this._stack;
 
 		const oldTop = stack[ 0 ];
@@ -67,7 +72,8 @@ export default class HighlightStack {
 		if ( oldTop !== newTop && !compareDescriptors( oldTop, newTop ) ) {
 			this.fire( 'change:top', {
 				oldDescriptor: oldTop,
-				newDescriptor: newTop
+				newDescriptor: newTop,
+				writer
 			} );
 		}
 	}
@@ -77,7 +83,7 @@ export default class HighlightStack {
 	 * descriptor with same id is already present.
 	 *
 	 * @private
-	 * @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} descriptor
+	 * @param {module:engine/conversion/downcast-converters~HighlightDescriptor} descriptor
 	 */
 	_insertDescriptor( descriptor ) {
 		const stack = this._stack;
@@ -125,8 +131,8 @@ mix( HighlightStack, EmitterMixin );
 
 // Compares two descriptors by checking their priority and class list.
 //
-// @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} a
-// @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} b
+// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} a
+// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} b
 // @returns {Boolean} Returns true if both descriptors are defined and have same priority and classes.
 function compareDescriptors( a, b ) {
 	return a && b && a.priority == b.priority && classesToString( a.class ) == classesToString( b.class );
@@ -134,8 +140,8 @@ function compareDescriptors( a, b ) {
 
 // Checks whenever first descriptor should be placed in the stack before second one.
 //
-// @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} a
-// @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} b
+// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} a
+// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} b
 // @returns {Boolean}
 function shouldABeBeforeB( a, b ) {
 	if ( a.priority > b.priority ) {
@@ -148,7 +154,7 @@ function shouldABeBeforeB( a, b ) {
 	return classesToString( a.class ) > classesToString( b.class );
 }
 
-// Converts CSS classes passed with {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} to
+// Converts CSS classes passed with {@link module:engine/conversion/downcast-converters~HighlightDescriptor} to
 // sorted string.
 //
 // @param {String|Array<String>} descriptor
@@ -162,8 +168,9 @@ function classesToString( classes ) {
  *
  * @event change:top
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} [data.newDescriptor] New highlight
+ * @param {module:engine/conversion/downcast-converters~HighlightDescriptor} [data.newDescriptor] New highlight
  * descriptor. It will be `undefined` when last descriptor is removed from the stack.
- * @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} [data.oldDescriptor] Old highlight
+ * @param {module:engine/conversion/downcast-converters~HighlightDescriptor} [data.oldDescriptor] Old highlight
  * descriptor. It will be `undefined` when first descriptor is added to the stack.
+ * @param {module:engine/view/writer~Writer} writer View writer that can be used to modify element.
  */

+ 25 - 21
packages/ckeditor5-widget/src/utils.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -45,25 +45,27 @@ export function isWidget( element ) {
  * * implements `addHighlight` and `removeHighlight` custom properties to handle view highlight on widgets.
  *
  * @param {module:engine/view/element~Element} element
+ * @param {module:engine/view/writer~Writer} writer
  * @param {Object} [options={}]
  * @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as
  * a plain string or a function returning a string.
  * @returns {module:engine/view/element~Element} Returns same element.
  */
-export function toWidget( element, options = {} ) {
-	element.setAttribute( 'contenteditable', 'false' );
+export function toWidget( element, writer, options = {} ) {
+	writer.setAttribute( 'contenteditable', 'false', element );
+	writer.addClass( WIDGET_CLASS_NAME, element );
+	writer.setCustomProperty( widgetSymbol, true, element );
 	element.getFillerOffset = getFillerOffset;
-	element.addClass( WIDGET_CLASS_NAME );
-	element.setCustomProperty( widgetSymbol, true );
 
 	if ( options.label ) {
-		setLabel( element, options.label );
+		setLabel( element, options.label, writer );
 	}
 
 	setHighlightHandling(
 		element,
-		( element, descriptor ) => element.addClass( ...normalizeToArray( descriptor.class ) ),
-		( element, descriptor ) => element.removeClass( ...normalizeToArray( descriptor.class ) )
+		writer,
+		( element, descriptor, writer ) => writer.addClass( normalizeToArray( descriptor.class ), element ),
+		( element, descriptor, writer ) => writer.removeClass( normalizeToArray( descriptor.class ), element )
 	);
 
 	return element;
@@ -79,24 +81,25 @@ export function toWidget( element, options = {} ) {
  * properly determine which highlight descriptor should be used at given time.
  *
  * @param {module:engine/view/element~Element} element
+ * @param {module:engine/view/writer~Writer} writer
  * @param {Function} add
  * @param {Function} remove
  */
-export function setHighlightHandling( element, add, remove ) {
+export function setHighlightHandling( element, writer, add, remove ) {
 	const stack = new HighlightStack();
 
 	stack.on( 'change:top', ( evt, data ) => {
 		if ( data.oldDescriptor ) {
-			remove( element, data.oldDescriptor );
+			remove( element, data.oldDescriptor, data.writer );
 		}
 
 		if ( data.newDescriptor ) {
-			add( element, data.newDescriptor );
+			add( element, data.newDescriptor, data.writer );
 		}
 	} );
 
-	element.setCustomProperty( 'addHighlight', ( element, descriptor ) => stack.add( descriptor ) );
-	element.setCustomProperty( 'removeHighlight', ( element, id ) => stack.remove( id ) );
+	writer.setCustomProperty( 'addHighlight', ( element, descriptor, writer ) => stack.add( descriptor, writer ), element );
+	writer.setCustomProperty( 'removeHighlight', ( element, id, writer ) => stack.remove( id, writer ), element );
 }
 
 /**
@@ -106,9 +109,10 @@ export function setHighlightHandling( element, add, remove ) {
  *
  * @param {module:engine/view/element~Element} element
  * @param {String|Function} labelOrCreator
+ *  * @param {module:engine/view/writer~Writer} writer
  */
-export function setLabel( element, labelOrCreator ) {
-	element.setCustomProperty( labelSymbol, labelOrCreator );
+export function setLabel( element, labelOrCreator, writer ) {
+	writer.setCustomProperty( labelSymbol, labelOrCreator, element );
 }
 
 /**
@@ -137,22 +141,22 @@ export function getLabel( element ) {
  * @param {module:engine/view/editableelement~EditableElement} editable
  * @returns {module:engine/view/editableelement~EditableElement} Returns same element that was provided in `editable` param.
  */
-export function toWidgetEditable( editable ) {
-	editable.addClass( 'ck-editable' );
+export function toWidgetEditable( editable, writer ) {
+	writer.addClass( 'ck-editable', editable );
 
 	// Set initial contenteditable value.
-	editable.setAttribute( 'contenteditable', editable.isReadOnly ? 'false' : 'true' );
+	writer.setAttribute( 'contenteditable', editable.isReadOnly ? 'false' : 'true', editable );
 
 	// Bind contenteditable property to element#isReadOnly.
 	editable.on( 'change:isReadOnly', ( evt, property, is ) => {
-		editable.setAttribute( 'contenteditable', is ? 'false' : 'true' );
+		writer.setAttribute( 'contenteditable', is ? 'false' : 'true', editable );
 	} );
 
 	editable.on( 'change:isFocused', ( evt, property, is ) => {
 		if ( is ) {
-			editable.addClass( 'ck-editable_focused' );
+			writer.addClass( 'ck-editable_focused', editable );
 		} else {
-			editable.removeClass( 'ck-editable_focused' );
+			writer.removeClass( 'ck-editable_focused', editable );
 		}
 	} );
 

+ 61 - 58
packages/ckeditor5-widget/src/widget.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -17,7 +17,7 @@ import RootEditableElement from '@ckeditor/ckeditor5-engine/src/view/rooteditabl
 import { isWidget, WIDGET_SELECTED_CLASS_NAME, getLabel } from './utils';
 import { keyCodes, getCode, parseKeystroke } from '@ckeditor/ckeditor5-utils/src/keyboard';
 
-import '../theme/theme.scss';
+import '../theme/widget.css';
 
 const selectAllKeystrokeCode = parseKeystroke( 'Ctrl+A' );
 
@@ -42,7 +42,8 @@ export default class Widget extends Plugin {
 	 * @inheritDoc
 	 */
 	init() {
-		const viewDocument = this.editor.editing.view;
+		const view = this.editor.editing.view;
+		const viewDocument = view.document;
 
 		/**
 		 * Holds previously selected widgets.
@@ -54,11 +55,12 @@ export default class Widget extends Plugin {
 
 		// Model to view selection converter.
 		// Converts selection placed over widget element to fake selection
-		this.editor.editing.modelToView.on( 'selection', ( evt, data, consumable, conversionApi ) => {
+		this.editor.editing.downcastDispatcher.on( 'selection', ( evt, data, conversionApi ) => {
 			// Remove selected class from previously selected widgets.
-			this._clearPreviouslySelectedWidgets();
+			this._clearPreviouslySelectedWidgets( conversionApi.writer );
 
-			const viewSelection = conversionApi.viewSelection;
+			const viewWriter = conversionApi.writer;
+			const viewSelection = viewWriter.document.selection;
 			const selectedElement = viewSelection.getSelectedElement();
 
 			for ( const range of viewSelection.getRanges() ) {
@@ -66,12 +68,12 @@ export default class Widget extends Plugin {
 					const node = value.item;
 
 					if ( node.is( 'element' ) && isWidget( node ) ) {
-						node.addClass( WIDGET_SELECTED_CLASS_NAME );
+						viewWriter.addClass( WIDGET_SELECTED_CLASS_NAME, node );
 						this._previouslySelected.add( node );
 
 						// Check if widget is a single element selected.
 						if ( node == selectedElement ) {
-							viewSelection.setFake( true, { label: getLabel( selectedElement ) } );
+							viewWriter.setSelection( viewSelection.getRanges(), { fake: true, label: getLabel( selectedElement ) } );
 						}
 					}
 				}
@@ -79,11 +81,19 @@ export default class Widget extends Plugin {
 		}, { priority: 'low' } );
 
 		// If mouse down is pressed on widget - create selection over whole widget.
-		viewDocument.addObserver( MouseObserver );
+		view.addObserver( MouseObserver );
 		this.listenTo( viewDocument, 'mousedown', ( ...args ) => this._onMousedown( ...args ) );
 
 		// Handle custom keydown behaviour.
 		this.listenTo( viewDocument, 'keydown', ( ...args ) => this._onKeydown( ...args ), { priority: 'high' } );
+
+		// Handle custom delete behaviour.
+		this.listenTo( viewDocument, 'delete', ( evt, data ) => {
+			if ( this._handleDelete( data.direction == 'forward' ) ) {
+				data.preventDefault();
+				evt.stop();
+			}
+		}, { priority: 'high' } );
 	}
 
 	/**
@@ -95,7 +105,8 @@ export default class Widget extends Plugin {
 	 */
 	_onMousedown( eventInfo, domEventData ) {
 		const editor = this.editor;
-		const viewDocument = editor.editing.view;
+		const view = editor.editing.view;
+		const viewDocument = view.document;
 		let element = domEventData.target;
 
 		// Do nothing if inside nested editable.
@@ -116,13 +127,13 @@ export default class Widget extends Plugin {
 
 		// Focus editor if is not focused already.
 		if ( !viewDocument.isFocused ) {
-			viewDocument.focus();
+			view.focus();
 		}
 
 		// Create model selection over widget.
 		const modelElement = editor.editing.mapper.toModelElement( element );
 
-		editor.document.enqueueChanges( ( ) => {
+		editor.model.change( () => {
 			this._setSelectionOverElement( modelElement );
 		} );
 	}
@@ -141,9 +152,7 @@ export default class Widget extends Plugin {
 
 		// Checks if the keys were handled and then prevents the default event behaviour and stops
 		// the propagation.
-		if ( isDeleteKeyCode( keyCode ) ) {
-			wasHandled = this._handleDelete( isForward );
-		} else if ( isArrowKeyCode( keyCode ) ) {
+		if ( isArrowKeyCode( keyCode ) ) {
 			wasHandled = this._handleArrowKeys( isForward );
 		} else if ( isSelectAllKeyCode( domEventData ) ) {
 			wasHandled = this._selectAllNestedEditableContent() || this._selectAllContent();
@@ -168,7 +177,7 @@ export default class Widget extends Plugin {
 			return;
 		}
 
-		const modelDocument = this.editor.document;
+		const modelDocument = this.editor.model.document;
 		const modelSelection = modelDocument.selection;
 
 		// Do nothing on non-collapsed selection.
@@ -179,8 +188,7 @@ export default class Widget extends Plugin {
 		const objectElement = this._getObjectElementNextToSelection( isForward );
 
 		if ( objectElement ) {
-			modelDocument.enqueueChanges( () => {
-				const batch = modelDocument.batch();
+			this.editor.model.change( writer => {
 				let previousNode = modelSelection.anchor.parent;
 
 				// Remove previous element if empty.
@@ -188,7 +196,7 @@ export default class Widget extends Plugin {
 					const nodeToRemove = previousNode;
 					previousNode = nodeToRemove.parent;
 
-					batch.remove( nodeToRemove );
+					writer.remove( nodeToRemove );
 				}
 
 				this._setSelectionOverElement( objectElement );
@@ -205,19 +213,20 @@ export default class Widget extends Plugin {
 	 * @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
 	 */
 	_handleArrowKeys( isForward ) {
-		const modelDocument = this.editor.document;
-		const schema = modelDocument.schema;
+		const model = this.editor.model;
+		const schema = model.schema;
+		const modelDocument = model.document;
 		const modelSelection = modelDocument.selection;
 		const objectElement = modelSelection.getSelectedElement();
 
-		// if object element is selected.
-		if ( objectElement && schema.objects.has( objectElement.name ) ) {
+		// If object element is selected.
+		if ( objectElement && schema.isObject( objectElement ) ) {
 			const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
-			const newRange = modelDocument.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );
+			const newRange = schema.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );
 
 			if ( newRange ) {
-				modelDocument.enqueueChanges( () => {
-					modelSelection.setRanges( [ newRange ] );
+				model.change( writer => {
+					writer.setSelection( newRange );
 				} );
 			}
 
@@ -232,8 +241,8 @@ export default class Widget extends Plugin {
 
 		const objectElement2 = this._getObjectElementNextToSelection( isForward );
 
-		if ( objectElement2 instanceof ModelElement && modelDocument.schema.objects.has( objectElement2.name ) ) {
-			modelDocument.enqueueChanges( () => {
+		if ( objectElement2 instanceof ModelElement && schema.isObject( objectElement2 ) ) {
+			model.change( () => {
 				this._setSelectionOverElement( objectElement2 );
 			} );
 
@@ -250,17 +259,16 @@ export default class Widget extends Plugin {
 	 * @private
 	 */
 	_selectAllNestedEditableContent() {
-		const modelDocument = this.editor.document;
-		const modelSelection = modelDocument.selection;
-		const schema = modelDocument.schema;
-		const limitElement = schema.getLimitElement( modelSelection );
+		const model = this.editor.model;
+		const documentSelection = model.document.selection;
+		const limitElement = model.schema.getLimitElement( documentSelection );
 
-		if ( modelSelection.getFirstRange().root == limitElement ) {
+		if ( documentSelection.getFirstRange().root == limitElement ) {
 			return false;
 		}
 
-		modelDocument.enqueueChanges( () => {
-			modelSelection.setIn( limitElement );
+		model.change( writer => {
+			writer.setSelection( ModelRange.createIn( limitElement ) );
 		} );
 
 		return true;
@@ -273,10 +281,10 @@ export default class Widget extends Plugin {
 	 * @returns {Boolean} Returns true if widget was selected and selecting all was handled by this method.
 	 */
 	_selectAllContent() {
-		const modelDocument = this.editor.document;
-		const modelSelection = modelDocument.selection;
+		const model = this.editor.model;
 		const editing = this.editor.editing;
-		const viewDocument = editing.view;
+		const view = editing.view;
+		const viewDocument = view.document;
 		const viewSelection = viewDocument.selection;
 
 		const selectedElement = viewSelection.getSelectedElement();
@@ -286,8 +294,8 @@ export default class Widget extends Plugin {
 		if ( selectedElement && isWidget( selectedElement ) ) {
 			const widgetParent = editing.mapper.toModelElement( selectedElement.parent );
 
-			modelDocument.enqueueChanges( () => {
-				modelSelection.setRanges( [ ModelRange.createIn( widgetParent ) ] );
+			model.change( writer => {
+				writer.setSelection( ModelRange.createIn( widgetParent ) );
 			} );
 
 			return true;
@@ -303,7 +311,9 @@ export default class Widget extends Plugin {
 	 * @param {module:engine/model/element~Element} element
 	 */
 	_setSelectionOverElement( element ) {
-		this.editor.document.selection.setRanges( [ ModelRange.createOn( element ) ] );
+		this.editor.model.change( writer => {
+			writer.setSelection( ModelRange.createOn( element ) );
+		} );
 	}
 
 	/**
@@ -316,18 +326,17 @@ export default class Widget extends Plugin {
 	 * @returns {module:engine/model/element~Element|null}
 	 */
 	_getObjectElementNextToSelection( forward ) {
-		const modelDocument = this.editor.document;
-		const schema = modelDocument.schema;
-		const modelSelection = modelDocument.selection;
-		const dataController = this.editor.data;
+		const model = this.editor.model;
+		const schema = model.schema;
+		const modelSelection = model.document.selection;
 
 		// Clone current selection to use it as a probe. We must leave default selection as it is so it can return
 		// to its current state after undo.
-		const probe = ModelSelection.createFromSelection( modelSelection );
-		dataController.modifySelection( probe, { direction: forward ? 'forward' : 'backward' } );
+		const probe = new ModelSelection( modelSelection );
+		model.modifySelection( probe, { direction: forward ? 'forward' : 'backward' } );
 		const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
 
-		if ( objectElement instanceof ModelElement && schema.objects.has( objectElement.name ) ) {
+		if ( objectElement instanceof ModelElement && schema.isObject( objectElement ) ) {
 			return objectElement;
 		}
 
@@ -336,11 +345,13 @@ export default class Widget extends Plugin {
 
 	/**
 	 * Removes CSS class from previously selected widgets.
+	 *
 	 * @private
+	 * @param {module:engine/view/writer~Writer} writer
 	 */
-	_clearPreviouslySelectedWidgets() {
+	_clearPreviouslySelectedWidgets( writer ) {
 		for ( const widget of this._previouslySelected ) {
-			widget.removeClass( WIDGET_SELECTED_CLASS_NAME );
+			writer.removeClass( WIDGET_SELECTED_CLASS_NAME, widget );
 		}
 
 		this._previouslySelected.clear();
@@ -358,14 +369,6 @@ function isArrowKeyCode( keyCode ) {
 		keyCode == keyCodes.arrowdown;
 }
 
-// Returns 'true' if provided key code represents one of the delete keys: delete or backspace.
-//
-// @param {Number} keyCode
-// @returns {Boolean}
-function isDeleteKeyCode( keyCode ) {
-	return keyCode == keyCodes.delete || keyCode == keyCodes.backspace;
-}
-
 // Returns 'true' if provided (DOM) key event data corresponds with the Ctrl+A keystroke.
 //
 // @param {module:engine/view/observer/keyobserver~KeyEventData} domEventData

+ 1 - 1
packages/ckeditor5-widget/tests/highlightstack.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 

+ 27 - 23
packages/ckeditor5-widget/tests/utils.js

@@ -1,8 +1,9 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
+import Writer from '@ckeditor/ckeditor5-engine/src/view/writer';
 import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
 import ViewEditableElement from '@ckeditor/ckeditor5-engine/src/view/editableelement';
 import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
@@ -17,11 +18,14 @@ import {
 } from '../src/utils';
 
 describe( 'widget utils', () => {
-	let element;
+	let element, writer, viewDocument;
 
 	beforeEach( () => {
-		element = new ViewElement( 'div' );
-		toWidget( element );
+		viewDocument = new ViewDocument();
+		writer = new Writer( viewDocument );
+
+		element = writer.createContainerElement( 'div' );
+		toWidget( element, writer );
 	} );
 
 	describe( 'toWidget()', () => {
@@ -39,19 +43,19 @@ describe( 'widget utils', () => {
 		} );
 
 		it( 'should add element\'s label if one is provided', () => {
-			toWidget( element, { label: 'foo bar baz label' } );
+			toWidget( element, writer, { label: 'foo bar baz label' } );
 
 			expect( getLabel( element ) ).to.equal( 'foo bar baz label' );
 		} );
 
 		it( 'should add element\'s label if one is provided as function', () => {
-			toWidget( element, { label: () => 'foo bar baz label' } );
+			toWidget( element, writer, { label: () => 'foo bar baz label' } );
 
 			expect( getLabel( element ) ).to.equal( 'foo bar baz label' );
 		} );
 
 		it( 'should set default highlight handling methods', () => {
-			toWidget( element );
+			toWidget( element, writer );
 
 			const set = element.getCustomProperty( 'addHighlight' );
 			const remove = element.getCustomProperty( 'removeHighlight' );
@@ -59,15 +63,15 @@ describe( 'widget utils', () => {
 			expect( typeof set ).to.equal( 'function' );
 			expect( typeof remove ).to.equal( 'function' );
 
-			set( element, { priority: 1, class: 'highlight', id: 'highlight' } );
+			set( element, { priority: 1, class: 'highlight', id: 'highlight' }, writer );
 			expect( element.hasClass( 'highlight' ) ).to.be.true;
 
-			remove( element, 'highlight' );
+			remove( element, 'highlight', writer );
 			expect( element.hasClass( 'highlight' ) ).to.be.false;
 		} );
 
 		it( 'should set default highlight handling methods - CSS classes array', () => {
-			toWidget( element );
+			toWidget( element, writer );
 
 			const set = element.getCustomProperty( 'addHighlight' );
 			const remove = element.getCustomProperty( 'removeHighlight' );
@@ -75,11 +79,11 @@ describe( 'widget utils', () => {
 			expect( typeof set ).to.equal( 'function' );
 			expect( typeof remove ).to.equal( 'function' );
 
-			set( element, { priority: 1, class: [ 'highlight', 'foo' ], id: 'highlight' } );
+			set( element, { priority: 1, class: [ 'highlight', 'foo' ], id: 'highlight' }, writer );
 			expect( element.hasClass( 'highlight' ) ).to.be.true;
 			expect( element.hasClass( 'foo' ) ).to.be.true;
 
-			remove( element, 'highlight' );
+			remove( element, 'highlight', writer );
 			expect( element.hasClass( 'highlight' ) ).to.be.false;
 			expect( element.hasClass( 'foo' ) ).to.be.false;
 		} );
@@ -98,7 +102,7 @@ describe( 'widget utils', () => {
 	describe( 'label utils', () => {
 		it( 'should allow to set label for element', () => {
 			const element = new ViewElement( 'p' );
-			setLabel( element, 'foo bar baz' );
+			setLabel( element, 'foo bar baz', writer );
 
 			expect( getLabel( element ) ).to.equal( 'foo bar baz' );
 		} );
@@ -112,7 +116,7 @@ describe( 'widget utils', () => {
 		it( 'should allow to use a function as label creator', () => {
 			const element = new ViewElement( 'p' );
 			let caption = 'foo';
-			setLabel( element, () => caption );
+			setLabel( element, () => caption, writer );
 
 			expect( getLabel( element ) ).to.equal( 'foo' );
 			caption = 'bar';
@@ -126,8 +130,8 @@ describe( 'widget utils', () => {
 		beforeEach( () => {
 			viewDocument = new ViewDocument();
 			element = new ViewEditableElement( 'div' );
-			element.document = viewDocument;
-			toWidgetEditable( element );
+			element._document = viewDocument;
+			toWidgetEditable( element, writer );
 		} );
 
 		it( 'should be created in context of proper document', () => {
@@ -140,9 +144,9 @@ describe( 'widget utils', () => {
 
 		it( 'should add proper contenteditable value when element is read-only - initialization', () => {
 			const element = new ViewEditableElement( 'div' );
-			element.document = viewDocument;
+			element._document = viewDocument;
 			element.isReadOnly = true;
-			toWidgetEditable( element );
+			toWidgetEditable( element, writer );
 
 			expect( element.getAttribute( 'contenteditable' ) ).to.equal( 'false' );
 		} );
@@ -172,7 +176,7 @@ describe( 'widget utils', () => {
 			addSpy = sinon.spy();
 			removeSpy = sinon.spy();
 
-			setHighlightHandling( element, addSpy, removeSpy );
+			setHighlightHandling( element, writer, addSpy, removeSpy );
 			set = element.getCustomProperty( 'addHighlight' );
 			remove = element.getCustomProperty( 'removeHighlight' );
 		} );
@@ -185,14 +189,14 @@ describe( 'widget utils', () => {
 		it( 'should call highlight methods when descriptor is added and removed', () => {
 			const descriptor = { priority: 10, class: 'highlight', id: 'highlight' };
 
-			set( element, descriptor );
-			remove( element, descriptor.id );
+			set( element, descriptor, writer );
+			remove( element, descriptor.id, writer );
 
 			sinon.assert.calledOnce( addSpy );
-			sinon.assert.calledWithExactly( addSpy, element, descriptor );
+			sinon.assert.calledWithExactly( addSpy, element, descriptor, writer );
 
 			sinon.assert.calledOnce( removeSpy );
-			sinon.assert.calledWithExactly( removeSpy, element, descriptor );
+			sinon.assert.calledWithExactly( removeSpy, element, descriptor, writer );
 		} );
 
 		it( 'should call highlight methods when next descriptor is added', () => {

+ 523 - 528
packages/ckeditor5-widget/tests/widget.js

@@ -1,17 +1,16 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import Widget from '../src/widget';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
-import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
+import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
 import { toWidget } from '../src/utils';
-import ViewContainer from '@ckeditor/ckeditor5-engine/src/view/containerelement';
-import ViewEditable from '@ckeditor/ckeditor5-engine/src/view/editableelement';
 import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
-import AttributeContainer from '@ckeditor/ckeditor5-engine/src/view/attributeelement';
+import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
@@ -19,81 +18,81 @@ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
 /* global document */
 
 describe( 'Widget', () => {
-	let editor, doc, viewDocument;
+	let editor, model, view, viewDocument;
 
 	beforeEach( () => {
-		return VirtualTestEditor.create( { plugins: [ Widget ] } )
+		return VirtualTestEditor.create( { plugins: [ Widget, Typing ] } )
 			.then( newEditor => {
 				editor = newEditor;
-				doc = editor.document;
-				viewDocument = editor.editing.view;
-
-				doc.schema.registerItem( 'widget', '$block' );
-				doc.schema.objects.add( 'widget' );
-				doc.schema.registerItem( 'paragraph', '$block' );
-				doc.schema.registerItem( 'inline', '$inline' );
-				doc.schema.objects.add( 'inline' );
-				doc.schema.registerItem( 'nested' );
-				doc.schema.limits.add( 'nested' );
-				doc.schema.allow( { name: '$inline', inside: 'nested' } );
-				doc.schema.allow( { name: 'nested', inside: 'widget' } );
-				doc.schema.registerItem( 'editable' );
-				doc.schema.allow( { name: '$inline', inside: 'editable' } );
-				doc.schema.allow( { name: 'editable', inside: 'widget' } );
-				doc.schema.allow( { name: 'editable', inside: '$root' } );
+				model = editor.model;
+				view = editor.editing.view;
+				viewDocument = view.document;
+
+				model.schema.register( 'widget', {
+					inheritAllFrom: '$block',
+					isObject: true
+				} );
+				model.schema.register( 'paragraph', {
+					inheritAllFrom: '$block',
+					allowIn: 'div'
+				} );
+				model.schema.register( 'inline', {
+					allowWhere: '$text',
+					isObject: true
+				} );
+				model.schema.register( 'nested', {
+					allowIn: 'widget',
+					isLimit: true
+				} );
+				model.schema.extend( '$text', {
+					allowIn: [ 'nested', 'editable' ]
+				} );
+				model.schema.register( 'editable', {
+					allowIn: [ 'widget', '$root' ]
+				} );
 
 				// Image feature.
-				doc.schema.registerItem( 'image' );
-				doc.schema.allow( { name: 'image', inside: '$root' } );
-				doc.schema.objects.add( 'image' );
+				model.schema.register( 'image', {
+					allowIn: '$root',
+					isObject: true,
+					isBlock: true
+				} );
 
 				// Block-quote feature.
-				doc.schema.registerItem( 'blockQuote' );
-				doc.schema.allow( { name: 'blockQuote', inside: '$root' } );
-				doc.schema.allow( { name: '$block', inside: 'blockQuote' } );
+				model.schema.register( 'blockQuote', {
+					allowIn: '$root'
+				} );
+				model.schema.extend( '$block', { allowIn: 'blockQuote' } );
 
 				// Div element which helps nesting elements.
-				doc.schema.registerItem( 'div' );
-				doc.schema.allow( { name: 'div', inside: 'blockQuote' } );
-				doc.schema.allow( { name: 'div', inside: 'div' } );
-				doc.schema.allow( { name: 'paragraph', inside: 'div' } );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'paragraph' )
-					.toElement( 'p' );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'widget' )
-					.toElement( () => {
-						const b = new AttributeContainer( 'b' );
-						const div = new ViewContainer( 'div', null, b );
-
-						return toWidget( div, { label: 'element label' } );
-					} );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'inline' )
-					.toElement( 'figure' );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'nested' )
-					.toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'editable' )
-					.toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'image' )
-					.toElement( 'img' );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'blockQuote' )
-					.toElement( 'blockquote' );
-
-				buildModelConverter().for( editor.editing.modelToView )
-					.fromElement( 'div' )
-					.toElement( 'div' );
+				model.schema.register( 'div', {
+					allowIn: [ 'blockQuote', 'div' ]
+				} );
+
+				editor.conversion.for( 'downcast' )
+					.add( downcastElementToElement( { model: 'paragraph', view: 'p' } ) )
+					.add( downcastElementToElement( { model: 'inline', view: 'figure' } ) )
+					.add( downcastElementToElement( { model: 'image', view: 'img' } ) )
+					.add( downcastElementToElement( { model: 'blockQuote', view: 'blockquote' } ) )
+					.add( downcastElementToElement( { model: 'div', view: 'div' } ) )
+					.add( downcastElementToElement( {
+						model: 'widget',
+						view: ( modelItem, viewWriter ) => {
+							const b = viewWriter.createAttributeElement( 'b' );
+							const div = viewWriter.createContainerElement( 'div' );
+							viewWriter.insert( ViewPosition.createAt( div ), b );
+
+							return toWidget( div, viewWriter, { label: 'element label' } );
+						}
+					} ) )
+					.add( downcastElementToElement( {
+						model: 'nested',
+						view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+					} ) )
+					.add( downcastElementToElement( {
+						model: 'editable',
+						view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
+					} ) );
 			} );
 	} );
 
@@ -102,11 +101,11 @@ describe( 'Widget', () => {
 	} );
 
 	it( 'should add MouseObserver', () => {
-		expect( editor.editing.view.getObserver( MouseObserver ) ).to.be.instanceof( MouseObserver );
+		expect( view.getObserver( MouseObserver ) ).to.be.instanceof( MouseObserver );
 	} );
 
 	it( 'should create selection over clicked widget', () => {
-		setModelData( doc, '[]<widget></widget>' );
+		setModelData( model, '[]<widget></widget>' );
 		const viewDiv = viewDocument.getRoot().getChild( 0 );
 		const domEventDataMock = {
 			target: viewDiv,
@@ -115,12 +114,12 @@ describe( 'Widget', () => {
 
 		viewDocument.fire( 'mousedown', domEventDataMock );
 
-		expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
+		expect( getModelData( model ) ).to.equal( '[<widget></widget>]' );
 		sinon.assert.calledOnce( domEventDataMock.preventDefault );
 	} );
 
 	it( 'should create selection when clicked in nested element', () => {
-		setModelData( doc, '[]<widget></widget>' );
+		setModelData( model, '[]<widget></widget>' );
 		const viewDiv = viewDocument.getRoot().getChild( 0 );
 		const viewB = viewDiv.getChild( 0 );
 		const domEventDataMock = {
@@ -130,12 +129,12 @@ describe( 'Widget', () => {
 
 		viewDocument.fire( 'mousedown', domEventDataMock );
 
-		expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
+		expect( getModelData( model ) ).to.equal( '[<widget></widget>]' );
 		sinon.assert.calledOnce( domEventDataMock.preventDefault );
 	} );
 
 	it( 'should do nothing if clicked inside nested editable', () => {
-		setModelData( doc, '[]<widget><nested>foo bar</nested></widget>' );
+		setModelData( model, '[]<widget><nested>foo bar</nested></widget>' );
 		const viewDiv = viewDocument.getRoot().getChild( 0 );
 		const viewFigcaption = viewDiv.getChild( 0 );
 
@@ -150,57 +149,57 @@ describe( 'Widget', () => {
 	} );
 
 	it( 'should do nothing if clicked in non-widget element', () => {
-		setModelData( doc, '<paragraph>[]foo bar</paragraph><widget></widget>' );
+		setModelData( model, '<paragraph>[]foo bar</paragraph><widget></widget>' );
 		const viewP = viewDocument.getRoot().getChild( 0 );
 		const domEventDataMock = {
 			target: viewP,
 			preventDefault: sinon.spy()
 		};
 
-		viewDocument.focus();
+		view.focus();
 		viewDocument.fire( 'mousedown', domEventDataMock );
 
-		expect( getModelData( doc ) ).to.equal( '<paragraph>[]foo bar</paragraph><widget></widget>' );
+		expect( getModelData( model ) ).to.equal( '<paragraph>[]foo bar</paragraph><widget></widget>' );
 		sinon.assert.notCalled( domEventDataMock.preventDefault );
 	} );
 
 	it( 'should not focus editable if already is focused', () => {
-		setModelData( doc, '<widget></widget>' );
+		setModelData( model, '<widget></widget>' );
 		const widget = viewDocument.getRoot().getChild( 0 );
 		const domEventDataMock = {
 			target: widget,
 			preventDefault: sinon.spy()
 		};
-		const focusSpy = sinon.spy( viewDocument, 'focus' );
+		const focusSpy = sinon.spy( view, 'focus' );
 
 		viewDocument.isFocused = true;
 		viewDocument.fire( 'mousedown', domEventDataMock );
 
 		sinon.assert.calledOnce( domEventDataMock.preventDefault );
 		sinon.assert.notCalled( focusSpy );
-		expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
+		expect( getModelData( model ) ).to.equal( '[<widget></widget>]' );
 	} );
 
 	it( 'should apply fake view selection if model selection is on widget element', () => {
-		setModelData( doc, '[<widget>foo bar</widget>]' );
+		setModelData( model, '[<widget>foo bar</widget>]' );
 
-		expect( getViewData( viewDocument ) ).to.equal(
+		expect( getViewData( view ) ).to.equal(
 			'[<div class="ck-widget ck-widget_selected" contenteditable="false">foo bar<b></b></div>]'
 		);
 		expect( viewDocument.selection.isFake ).to.be.true;
 	} );
 
 	it( 'should use element\'s label to set fake selection if one is provided', () => {
-		setModelData( doc, '[<widget>foo bar</widget>]' );
+		setModelData( model, '[<widget>foo bar</widget>]' );
 
 		expect( viewDocument.selection.fakeSelectionLabel ).to.equal( 'element label' );
 	} );
 
 	it( 'should add selected class when no only a widget is selected', () => {
-		setModelData( doc, '[<paragraph>foo</paragraph><widget></widget><widget></widget>]' );
+		setModelData( model, '[<paragraph>foo</paragraph><widget></widget><widget></widget>]' );
 
 		expect( viewDocument.selection.isFake ).to.be.false;
-		expect( getViewData( viewDocument ) ).to.equal(
+		expect( getViewData( view ) ).to.equal(
 			'[' +
 			'<p>foo</p>' +
 			'<div class="ck-widget ck-widget_selected" contenteditable="false"><b></b></div>' +
@@ -210,31 +209,31 @@ describe( 'Widget', () => {
 	} );
 
 	it( 'fake selection should be empty if widget is not selected', () => {
-		setModelData( doc, '<paragraph>foo</paragraph><widget>foo bar</widget>' );
+		setModelData( model, '<paragraph>foo</paragraph><widget>foo bar</widget>' );
 
 		expect( viewDocument.selection.fakeSelectionLabel ).to.equal( '' );
 	} );
 
 	it( 'should toggle selected class', () => {
-		setModelData( doc, '<paragraph>foo</paragraph>[<widget>foo</widget>]' );
+		setModelData( model, '<paragraph>foo</paragraph>[<widget>foo</widget>]' );
 
-		expect( getViewData( viewDocument ) ).to.equal(
+		expect( getViewData( view ) ).to.equal(
 			'<p>foo</p>[<div class="ck-widget ck-widget_selected" contenteditable="false">foo<b></b></div>]'
 		);
 
-		doc.enqueueChanges( () => {
-			doc.selection.removeAllRanges();
+		model.change( writer => {
+			writer.setSelection( null );
 		} );
 
-		expect( getViewData( viewDocument ) ).to.equal(
+		expect( getViewData( view ) ).to.equal(
 			'<p>{}foo</p><div class="ck-widget" contenteditable="false">foo<b></b></div>'
 		);
 	} );
 
 	it( 'should do nothing when selection is placed in other editable', () => {
-		setModelData( doc, '<widget><editable>foo bar</editable></widget><editable>[baz]</editable>' );
+		setModelData( model, '<widget><editable>foo bar</editable></widget><editable>[baz]</editable>' );
 
-		expect( getViewData( viewDocument ) ).to.equal(
+		expect( getViewData( view ) ).to.equal(
 			'<div class="ck-widget" contenteditable="false">' +
 				'<figcaption contenteditable="true">foo bar</figcaption>' +
 				'<b></b>' +
@@ -244,431 +243,6 @@ describe( 'Widget', () => {
 	} );
 
 	describe( 'keys handling', () => {
-		describe( 'delete and backspace', () => {
-			test(
-				'should select widget when backspace is pressed',
-				'<widget></widget><paragraph>[]foo</paragraph>',
-				keyCodes.backspace,
-				'[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should remove empty element after selecting widget when backspace is pressed',
-				'<widget></widget><paragraph>[]</paragraph>',
-				keyCodes.backspace,
-				'[<widget></widget>]'
-			);
-
-			test(
-				'should select widget when delete is pressed',
-				'<paragraph>foo[]</paragraph><widget></widget>',
-				keyCodes.delete,
-				'<paragraph>foo</paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should remove empty element after selecting widget when delete is pressed',
-				'<paragraph>[]</paragraph><widget></widget>',
-				keyCodes.delete,
-				'[<widget></widget>]'
-			);
-
-			test(
-				'should not respond to other keys',
-				'<widget></widget><paragraph>[]foo</paragraph>',
-				65,
-				'<widget></widget><paragraph>[]foo</paragraph>'
-			);
-
-			test(
-				'should do nothing on non-collapsed selection',
-				'<widget></widget><paragraph>[f]oo</paragraph>',
-				keyCodes.backspace,
-				'<widget></widget><paragraph>[f]oo</paragraph>'
-			);
-
-			test(
-				'should do nothing on non-object elements',
-				'<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
-				keyCodes.backspace,
-				'<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
-			);
-
-			test(
-				'should work correctly with modifier key: backspace + ctrl',
-				'<widget></widget><paragraph>[]foo</paragraph>',
-				{ keyCode: keyCodes.backspace, ctrlKey: true },
-				'[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should work correctly with modifier key: backspace + alt',
-				'<widget></widget><paragraph>[]foo</paragraph>',
-				{ keyCode: keyCodes.backspace, altKey: true },
-				'[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should work correctly with modifier key: backspace + shift',
-				'<widget></widget><paragraph>[]foo</paragraph>',
-				{ keyCode: keyCodes.backspace, shiftKey: true },
-				'[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should work correctly with modifier key: delete + ctrl',
-				'<paragraph>foo[]</paragraph><widget></widget>',
-				{ keyCode: keyCodes.delete, ctrlKey: true },
-				'<paragraph>foo</paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should work correctly with modifier key: delete + alt',
-				'<paragraph>foo[]</paragraph><widget></widget>',
-				{ keyCode: keyCodes.delete, altKey: true },
-				'<paragraph>foo</paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should work correctly with modifier key: delete + shift',
-				'<paragraph>foo[]</paragraph><widget></widget>',
-				{ keyCode: keyCodes.delete, shiftKey: true },
-				'<paragraph>foo</paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should not modify backspace default behaviour in single paragraph boundaries',
-				'<paragraph>[]foo</paragraph>',
-				keyCodes.backspace,
-				'<paragraph>[]foo</paragraph>'
-			);
-
-			test(
-				'should not modify delete default behaviour in single paragraph boundaries',
-				'<paragraph>foo[]</paragraph>',
-				keyCodes.delete,
-				'<paragraph>foo[]</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected widget preceded by a paragraph - backspace',
-				'<paragraph>foo</paragraph>[<widget></widget>]',
-				keyCodes.backspace,
-				'<paragraph>foo</paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should do nothing on selected widget preceded by another widget - backspace',
-				'<widget></widget>[<widget></widget>]',
-				keyCodes.backspace,
-				'<widget></widget>[<widget></widget>]'
-			);
-
-			test(
-				'should do nothing on selected widget before paragraph - backspace',
-				'[<widget></widget>]<paragraph>foo</paragraph>',
-				keyCodes.backspace,
-				'[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected widget before another widget - backspace',
-				'[<widget></widget>]<widget></widget>',
-				keyCodes.backspace,
-				'[<widget></widget>]<widget></widget>'
-			);
-
-			test(
-				'should do nothing on selected widget between paragraphs - backspace',
-				'<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
-				keyCodes.backspace,
-				'<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected widget between other widgets - backspace',
-				'<widget></widget>[<widget></widget>]<widget></widget>',
-				keyCodes.backspace,
-				'<widget></widget>[<widget></widget>]<widget></widget>'
-			);
-
-			test(
-				'should do nothing on selected widget preceded by a paragraph - delete',
-				'<paragraph>foo</paragraph>[<widget></widget>]',
-				keyCodes.delete,
-				'<paragraph>foo</paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should do nothing on selected widget preceded by another widget - delete',
-				'<widget></widget>[<widget></widget>]',
-				keyCodes.delete,
-				'<widget></widget>[<widget></widget>]'
-			);
-
-			test(
-				'should do nothing on selected widget before paragraph - delete',
-				'[<widget></widget>]<paragraph>foo</paragraph>',
-				keyCodes.delete,
-				'[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected widget before another widget - delete',
-				'[<widget></widget>]<widget></widget>',
-				keyCodes.delete,
-				'[<widget></widget>]<widget></widget>'
-			);
-
-			test(
-				'should do nothing on selected widget between paragraphs - delete',
-				'<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
-				keyCodes.delete,
-				'<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected widget between other widgets - delete',
-				'<widget></widget>[<widget></widget>]<widget></widget>',
-				keyCodes.delete,
-				'<widget></widget>[<widget></widget>]<widget></widget>'
-			);
-
-			test(
-				'should select inline objects - backspace',
-				'<paragraph>foo<inline></inline>[]bar</paragraph>',
-				keyCodes.backspace,
-				'<paragraph>foo[<inline></inline>]bar</paragraph>'
-			);
-
-			test(
-				'should select inline objects - delete',
-				'<paragraph>foo[]<inline></inline>bar</paragraph>',
-				keyCodes.delete,
-				'<paragraph>foo[<inline></inline>]bar</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected inline objects - backspace',
-				'<paragraph>foo[<inline></inline>]bar</paragraph>',
-				keyCodes.backspace,
-				'<paragraph>foo[<inline></inline>]bar</paragraph>'
-			);
-
-			test(
-				'should do nothing on selected inline objects - delete',
-				'<paragraph>foo[<inline></inline>]bar</paragraph>',
-				keyCodes.delete,
-				'<paragraph>foo[<inline></inline>]bar</paragraph>'
-			);
-
-			test(
-				'should do nothing if selection is placed after first letter - backspace',
-				'<paragraph>a[]</paragraph>',
-				keyCodes.backspace,
-				'<paragraph>a[]</paragraph>'
-			);
-
-			test(
-				'should do nothing if selection is placed before first letter - delete',
-				'<paragraph>[]a</paragraph>',
-				keyCodes.delete,
-				'<paragraph>[]a</paragraph>'
-			);
-
-			it( 'should prevent default behaviour and stop event propagation', () => {
-				const keydownHandler = sinon.spy();
-				const domEventDataMock = {
-					keyCode: keyCodes.delete,
-					preventDefault: sinon.spy(),
-				};
-				setModelData( doc, '<paragraph>foo[]</paragraph><widget></widget>' );
-				viewDocument.on( 'keydown', keydownHandler );
-
-				viewDocument.fire( 'keydown', domEventDataMock );
-
-				expect( getModelData( doc ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
-				sinon.assert.calledOnce( domEventDataMock.preventDefault );
-				sinon.assert.notCalled( keydownHandler );
-			} );
-
-			test(
-				'should remove the entire empty element if it is next to a widget',
-
-				'<paragraph>foo</paragraph>' +
-				'<image></image>' +
-				'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.backspace,
-
-				'<paragraph>foo</paragraph>[<image></image>]<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should remove the entire empty element (deeper structure) if it is next to a widget',
-
-				'<paragraph>foo</paragraph>' +
-				'<image></image>' +
-				'<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.backspace,
-
-				'<paragraph>foo</paragraph>' +
-				'[<image></image>]' +
-				'<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should remove the entire empty element (deeper structure) if it is next to a widget (forward delete)',
-
-				'<paragraph>foo</paragraph>' +
-				'<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
-				'<image></image>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.delete,
-
-				'<paragraph>foo</paragraph>' +
-				'[<image></image>]' +
-				'<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should not remove the entire element which is not empty and the element is next to a widget',
-
-				'<paragraph>foo</paragraph>' +
-				'<image></image>' +
-				'<blockQuote><paragraph>[]</paragraph><paragraph></paragraph></blockQuote>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.backspace,
-
-				'<paragraph>foo</paragraph>' +
-				'[<image></image>]' +
-				'<blockQuote><paragraph></paragraph></blockQuote>' +
-				'<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should not remove the entire element which is not empty and the element is next to a widget (forward delete)',
-
-				'<paragraph>foo</paragraph>' +
-				'<blockQuote><paragraph>Foo</paragraph><paragraph>[]</paragraph></blockQuote>' +
-				'<image></image>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.delete,
-
-				'<paragraph>foo</paragraph>' +
-				'<blockQuote><paragraph>Foo</paragraph></blockQuote>' +
-				'[<image></image>]' +
-				'<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should not remove the entire element (deeper structure) which is not empty and the element is next to a widget',
-
-				'<paragraph>foo</paragraph>' +
-				'<image></image>' +
-				'<blockQuote>' +
-					'<div>' +
-						'<div>' +
-							'<paragraph>[]</paragraph>' +
-						'</div>' +
-					'</div>' +
-					'<paragraph></paragraph>' +
-				'</blockQuote>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.backspace,
-
-				'<paragraph>foo</paragraph>' +
-				'[<image></image>]' +
-				'<blockQuote>' +
-					'<paragraph></paragraph>' +
-				'</blockQuote>' +
-				'<paragraph>foo</paragraph>'
-			);
-
-			test(
-				'should do nothing if the nested element is not empty and the element is next to a widget',
-
-				'<paragraph>foo</paragraph>' +
-				'<image></image>' +
-				'<blockQuote>' +
-					'<div>' +
-						'<div>' +
-							'<paragraph>Foo[]</paragraph>' +
-						'</div>' +
-					'</div>' +
-				'</blockQuote>' +
-				'<paragraph>foo</paragraph>',
-
-				keyCodes.backspace,
-
-				'<paragraph>foo</paragraph>' +
-				'<image></image>' +
-				'<blockQuote>' +
-					'<div>' +
-						'<div>' +
-							'<paragraph>Foo[]</paragraph>' +
-						'</div>' +
-					'</div>' +
-				'</blockQuote>' +
-				'<paragraph>foo</paragraph>'
-			);
-
-			it( 'does nothing when editor when read only mode is enabled (delete)', () => {
-				setModelData( doc,
-					'<paragraph>foo</paragraph>' +
-					'<image></image>' +
-					'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
-					'<paragraph>foo</paragraph>'
-				);
-
-				editor.isReadOnly = true;
-
-				viewDocument.fire( 'keydown', new DomEventData(
-					viewDocument,
-					{ target: document.createElement( 'div' ), preventDefault: () => {} },
-					{ keyCode: keyCodes.backspace }
-				) );
-
-				expect( getModelData( doc ) ).to.equal(
-					'<paragraph>foo</paragraph>' +
-					'<image></image>' +
-					'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
-					'<paragraph>foo</paragraph>'
-				);
-			} );
-
-			it( 'does nothing when editor when read only mode is enabled (forward delete)', () => {
-				setModelData( doc,
-					'<paragraph>foo</paragraph>' +
-					'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
-					'<image></image>' +
-					'<paragraph>foo</paragraph>'
-				);
-
-				editor.isReadOnly = true;
-
-				viewDocument.fire( 'keydown', new DomEventData(
-					viewDocument,
-					{ target: document.createElement( 'div' ), preventDefault: () => {} },
-					{ keyCode: keyCodes.delete }
-				) );
-
-				expect( getModelData( doc ) ).to.equal(
-					'<paragraph>foo</paragraph>' +
-					'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
-					'<image></image>' +
-					'<paragraph>foo</paragraph>'
-				);
-			} );
-		} );
-
 		describe( 'arrows', () => {
 			test(
 				'should move selection forward from selected object - right arrow',
@@ -782,18 +356,26 @@ describe( 'Widget', () => {
 				'[<widget></widget>]<paragraph>foo</paragraph>'
 			);
 
+			test(
+				'should do nothing if other key is pressed',
+				'[<widget></widget>]<paragraph>foo</paragraph>',
+				// Use a safe key (alt) to not trigger the Input features "unsafe keys" handler.
+				18,
+				'[<widget></widget>]<paragraph>foo</paragraph>'
+			);
+
 			it( 'should prevent default behaviour when there is no correct location - document end', () => {
 				const keydownHandler = sinon.spy();
 				const domEventDataMock = {
 					keyCode: keyCodes.arrowright,
 					preventDefault: sinon.spy(),
 				};
-				setModelData( doc, '<paragraph>foo</paragraph>[<widget></widget>]' );
+				setModelData( model, '<paragraph>foo</paragraph>[<widget></widget>]' );
 				viewDocument.on( 'keydown', keydownHandler );
 
 				viewDocument.fire( 'keydown', domEventDataMock );
 
-				expect( getModelData( doc ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
+				expect( getModelData( model ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
 				sinon.assert.calledOnce( domEventDataMock.preventDefault );
 				sinon.assert.notCalled( keydownHandler );
 			} );
@@ -804,12 +386,12 @@ describe( 'Widget', () => {
 					keyCode: keyCodes.arrowleft,
 					preventDefault: sinon.spy(),
 				};
-				setModelData( doc, '[<widget></widget>]<paragraph>foo</paragraph>' );
+				setModelData( model, '[<widget></widget>]<paragraph>foo</paragraph>' );
 				viewDocument.on( 'keydown', keydownHandler );
 
 				viewDocument.fire( 'keydown', domEventDataMock );
 
-				expect( getModelData( doc ) ).to.equal( '[<widget></widget>]<paragraph>foo</paragraph>' );
+				expect( getModelData( model ) ).to.equal( '[<widget></widget>]<paragraph>foo</paragraph>' );
 				sinon.assert.calledOnce( domEventDataMock.preventDefault );
 				sinon.assert.notCalled( keydownHandler );
 			} );
@@ -1105,19 +687,432 @@ describe( 'Widget', () => {
 					keyCode: keyCodeOrMock
 				};
 
-				setModelData( doc, data );
+				setModelData( model, data );
 				viewDocument.fire( 'keydown', new DomEventData(
 					viewDocument,
-					{ target: document.createElement( 'div' ), preventDefault: () => {} },
+					{ target: document.createElement( 'div' ), preventDefault() {} },
 					domEventDataMock
 				) );
 
-				expect( getModelData( doc ) ).to.equal( expected );
+				expect( getModelData( model ) ).to.equal( expected );
 
 				if ( expectedView ) {
-					expect( getViewData( viewDocument ) ).to.equal( expectedView );
+					expect( getViewData( view ) ).to.equal( expectedView );
 				}
 			} );
 		}
 	} );
+
+	describe( 'delete integration', () => {
+		function test( name, input, direction, expected ) {
+			it( name, () => {
+				setModelData( model, input );
+				const scrollStub = sinon.stub( view, 'scrollToTheSelection' );
+				const domEventDataMock = {
+					keyCode: direction == 'backward' ? keyCodes.backspace : keyCodes.delete,
+				};
+
+				viewDocument.fire( 'keydown', new DomEventData(
+					viewDocument,
+					{ target: document.createElement( 'div' ), preventDefault() {} },
+					domEventDataMock
+				) );
+
+				expect( getModelData( model ) ).to.equal( expected );
+				scrollStub.restore();
+			} );
+		}
+
+		// Let's make this integration tests real which will help covering
+		// cases like https://github.com/ckeditor/ckeditor5/issues/753.
+		// Originally, this test file used the Delete feature only which was not "integrational" enough.
+		it( 'tests are executed with the Typing feature', () => {
+			expect( editor.plugins.get( 'Typing' ) ).to.not.be.undefined;
+		} );
+
+		test(
+			'should select widget when backspace is pressed',
+			'<widget></widget><paragraph>[]foo</paragraph>',
+			'backward',
+			'[<widget></widget>]<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should remove empty element after selecting widget when backspace is pressed',
+			'<widget></widget><paragraph>[]</paragraph>',
+			'backward',
+			'[<widget></widget>]'
+		);
+
+		test(
+			'should select widget when delete is pressed',
+			'<paragraph>foo[]</paragraph><widget></widget>',
+			'forward',
+			'<paragraph>foo</paragraph>[<widget></widget>]'
+		);
+
+		test(
+			'should remove empty element after selecting widget when delete is pressed',
+			'<paragraph>[]</paragraph><widget></widget>',
+			'forward',
+			'[<widget></widget>]'
+		);
+
+		test(
+			'should not select widget on non-collapsed selection',
+			'<widget></widget><paragraph>[f]oo</paragraph>',
+			'backward',
+			'<widget></widget><paragraph>[]oo</paragraph>'
+		);
+
+		test(
+			'should not affect non-object elements',
+			'<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
+			'backward',
+			'<paragraph>foo[]bar</paragraph>'
+		);
+
+		test(
+			'should not modify backward delete default behaviour in single paragraph boundaries',
+			'<paragraph>[]foo</paragraph>',
+			'backward',
+			'<paragraph>[]foo</paragraph>'
+		);
+
+		test(
+			'should not modify forward delete default behaviour in single paragraph boundaries',
+			'<paragraph>foo[]</paragraph>',
+			'forward',
+			'<paragraph>foo[]</paragraph>'
+		);
+
+		test(
+			'should delete selected widget with paragraph before - backward',
+			'<paragraph>foo</paragraph>[<widget></widget>]',
+			'backward',
+			'<paragraph>foo</paragraph><paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should delete selected widget with paragraph before - forward',
+			'<paragraph>foo</paragraph>[<widget></widget>]',
+			'forward',
+			'<paragraph>foo</paragraph><paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should delete selected widget with paragraph after - backward',
+			'[<widget></widget>]<paragraph>foo</paragraph>',
+			'backward',
+			'<paragraph>[]</paragraph><paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should delete selected widget with paragraph after - forward',
+			'[<widget></widget>]<paragraph>foo</paragraph>',
+			'forward',
+			'<paragraph>[]</paragraph><paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should delete selected widget between paragraphs - backward',
+			'<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
+			'backward',
+			'<paragraph>bar</paragraph><paragraph>[]</paragraph><paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should delete selected widget between paragraphs - forward',
+			'<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
+			'forward',
+			'<paragraph>bar</paragraph><paragraph>[]</paragraph><paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should delete selected widget preceded by another widget - backward',
+			'<widget></widget>[<widget></widget>]',
+			'backward',
+			'<widget></widget><paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should delete selected widget preceded by another widget - forward',
+			'<widget></widget>[<widget></widget>]',
+			'forward',
+			'<widget></widget><paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should delete selected widget before another widget - forward',
+			'[<widget></widget>]<widget></widget>',
+			'forward',
+			'<paragraph>[]</paragraph><widget></widget>'
+		);
+
+		test(
+			'should delete selected widget before another widget - backward',
+			'[<widget></widget>]<widget></widget>',
+			'backward',
+			'<paragraph>[]</paragraph><widget></widget>'
+		);
+
+		test(
+			'should delete selected widget between other widgets - forward',
+			'<widget></widget>[<widget></widget>]<widget></widget>',
+			'forward',
+			'<widget></widget><paragraph>[]</paragraph><widget></widget>'
+		);
+
+		test(
+			'should delete selected widget between other widgets - backward',
+			'<widget></widget>[<widget></widget>]<widget></widget>',
+			'backward',
+			'<widget></widget><paragraph>[]</paragraph><widget></widget>'
+		);
+
+		test(
+			'should select inline objects - backward',
+			'<paragraph>foo<inline></inline>[]bar</paragraph>',
+			'backward',
+			'<paragraph>foo[<inline></inline>]bar</paragraph>'
+		);
+
+		test(
+			'should select inline objects - forward',
+			'<paragraph>foo[]<inline></inline>bar</paragraph>',
+			'forward',
+			'<paragraph>foo[<inline></inline>]bar</paragraph>'
+		);
+
+		test(
+			'should delete selected inline objects - backward',
+			'<paragraph>foo[<inline></inline>]bar</paragraph>',
+			'backward',
+			'<paragraph>foo[]bar</paragraph>'
+		);
+
+		test(
+			'should delete selected inline objects - forward',
+			'<paragraph>foo[<inline></inline>]bar</paragraph>',
+			'forward',
+			'<paragraph>foo[]bar</paragraph>'
+		);
+
+		test(
+			'should use standard delete behaviour when after first letter - backward',
+			'<paragraph>a[]</paragraph>',
+			'backward',
+			'<paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should use standard delete behaviour when before first letter - forward',
+			'<paragraph>[]a</paragraph>',
+			'forward',
+			'<paragraph>[]</paragraph>'
+		);
+
+		it( 'should prevent default behaviour and stop event propagation', () => {
+			setModelData( model, '<paragraph>foo[]</paragraph><widget></widget>' );
+			const scrollStub = sinon.stub( view, 'scrollToTheSelection' );
+			const deleteSpy = sinon.spy();
+
+			viewDocument.on( 'delete', deleteSpy );
+			const domEventDataMock = { target: document.createElement( 'div' ), preventDefault: sinon.spy() };
+
+			viewDocument.fire( 'delete', new DomEventData(
+				viewDocument,
+				domEventDataMock,
+				{ direction: 'forward', unit: 'character', sequence: 0 }
+			) );
+
+			sinon.assert.calledOnce( domEventDataMock.preventDefault );
+			sinon.assert.notCalled( deleteSpy );
+			scrollStub.restore();
+		} );
+
+		test(
+			'should remove the entire empty element if it is next to a widget',
+
+			'<paragraph>foo</paragraph>' +
+			'<image></image>' +
+			'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
+			'<paragraph>foo</paragraph>',
+
+			'backward',
+
+			'<paragraph>foo</paragraph>[<image></image>]<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should remove the entire empty element (deeper structure) if it is next to a widget',
+
+			'<paragraph>foo</paragraph>' +
+			'<image></image>' +
+			'<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
+			'<paragraph>foo</paragraph>',
+
+			'backward',
+
+			'<paragraph>foo</paragraph>' +
+			'[<image></image>]' +
+			'<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should remove the entire empty element (deeper structure) if it is next to a widget (forward delete)',
+
+			'<paragraph>foo</paragraph>' +
+			'<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
+			'<image></image>' +
+			'<paragraph>foo</paragraph>',
+
+			'forward',
+
+			'<paragraph>foo</paragraph>' +
+			'[<image></image>]' +
+			'<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should not remove the entire element which is not empty and the element is next to a widget',
+
+			'<paragraph>foo</paragraph>' +
+			'<image></image>' +
+			'<blockQuote><paragraph>[]</paragraph><paragraph></paragraph></blockQuote>' +
+			'<paragraph>foo</paragraph>',
+
+			'backward',
+
+			'<paragraph>foo</paragraph>' +
+			'[<image></image>]' +
+			'<blockQuote><paragraph></paragraph></blockQuote>' +
+			'<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should not remove the entire element which is not empty and the element is next to a widget (forward delete)',
+
+			'<paragraph>foo</paragraph>' +
+			'<blockQuote><paragraph>Foo</paragraph><paragraph>[]</paragraph></blockQuote>' +
+			'<image></image>' +
+			'<paragraph>foo</paragraph>',
+
+			'forward',
+
+			'<paragraph>foo</paragraph>' +
+			'<blockQuote><paragraph>Foo</paragraph></blockQuote>' +
+			'[<image></image>]' +
+			'<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should not remove the entire element (deeper structure) which is not empty and the element is next to a widget',
+
+			'<paragraph>foo</paragraph>' +
+			'<image></image>' +
+			'<blockQuote>' +
+			'<div>' +
+			'<div>' +
+			'<paragraph>[]</paragraph>' +
+			'</div>' +
+			'</div>' +
+			'<paragraph></paragraph>' +
+			'</blockQuote>' +
+			'<paragraph>foo</paragraph>',
+
+			'backward',
+
+			'<paragraph>foo</paragraph>' +
+			'[<image></image>]' +
+			'<blockQuote>' +
+			'<paragraph></paragraph>' +
+			'</blockQuote>' +
+			'<paragraph>foo</paragraph>'
+		);
+
+		test(
+			'should do nothing if the nested element is not empty and the element is next to a widget',
+
+			'<paragraph>foo</paragraph>' +
+			'<image></image>' +
+			'<blockQuote>' +
+			'<div>' +
+			'<div>' +
+			'<paragraph>Foo[]</paragraph>' +
+			'</div>' +
+			'</div>' +
+			'</blockQuote>' +
+			'<paragraph>foo</paragraph>',
+
+			'backward',
+
+			'<paragraph>foo</paragraph>' +
+			'<image></image>' +
+			'<blockQuote>' +
+			'<div>' +
+			'<div>' +
+			'<paragraph>Fo[]</paragraph>' +
+			'</div>' +
+			'</div>' +
+			'</blockQuote>' +
+			'<paragraph>foo</paragraph>'
+		);
+
+		it( 'does nothing when editor when read only mode is enabled (delete)', () => {
+			const scrollStub = sinon.stub( view, 'scrollToTheSelection' );
+			setModelData( model,
+				'<paragraph>foo</paragraph>' +
+				'<image></image>' +
+				'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
+				'<paragraph>foo</paragraph>'
+			);
+
+			editor.isReadOnly = true;
+
+			const domEventDataMock = { target: document.createElement( 'div' ), preventDefault: sinon.spy() };
+
+			viewDocument.fire( 'delete', new DomEventData(
+				viewDocument,
+				domEventDataMock,
+				{ direction: 'backward', unit: 'character', sequence: 0 }
+			) );
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>foo</paragraph>' +
+				'<image></image>' +
+				'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
+				'<paragraph>foo</paragraph>'
+			);
+			scrollStub.restore();
+		} );
+
+		it( 'does nothing when editor when read only mode is enabled (forward delete)', () => {
+			const scrollStub = sinon.stub( view, 'scrollToTheSelection' );
+			setModelData( model,
+				'<paragraph>foo</paragraph>' +
+				'<image></image>' +
+				'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
+				'<paragraph>foo</paragraph>'
+			);
+
+			editor.isReadOnly = true;
+
+			const domEventDataMock = { target: document.createElement( 'div' ), preventDefault: sinon.spy() };
+
+			viewDocument.fire( 'delete', new DomEventData(
+				viewDocument,
+				domEventDataMock,
+				{ direction: 'forward', unit: 'character', sequence: 0 }
+			) );
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>foo</paragraph>' +
+				'<image></image>' +
+				'<blockQuote><paragraph>[]</paragraph></blockQuote>' +
+				'<paragraph>foo</paragraph>'
+			);
+			scrollStub.restore();
+		} );
+	} );
 } );

+ 0 - 45
packages/ckeditor5-widget/theme/theme.scss

@@ -1,45 +0,0 @@
-// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/colors';
-@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/shadow';
-@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/states';
-@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/spacing';
-
-$widget-outline-thickness: 3px;
-
-@include ck-color-add( (
-	'widget-border-blurred': #ddd,
-	'widget-border-hover': #FFD25C,
-	'widget-editable-focused-background': $ck-color-background,
-) );
-
-.ck-widget {
-	margin: ck-spacing() 0;
-	padding: 0;
-
-	&.ck-widget_selected, &.ck-widget_selected:hover {
-		outline: $widget-outline-thickness solid ck-color( 'border-focus' );
-	}
-
-	.ck-editor__editable.ck-blurred &.ck-widget_selected {
-		outline: $widget-outline-thickness solid ck-color( 'widget-border-blurred' );
-	}
-
-	&:hover {
-		outline: $widget-outline-thickness solid ck-color( 'widget-border-hover' );
-	}
-
-	.ck-editable {
-		border: 1px solid transparent;
-
-		// The `:focus` style is applied before `.ck-editable_focused` class is rendered in the view.
-		// These styles show a different border for a blink of an eye, so `:focus` need to have same styles applied.
-		&.ck-editable_focused, &:focus {
-			@include ck-focus-ring();
-			@include ck-box-shadow( $ck-inner-shadow );
-
-			background-color: ck-color( 'widget-editable-focused-background' );
-		}
-	}
-}

+ 10 - 0
packages/ckeditor5-widget/theme/widget.css

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/*
+ * Note: This file should contain the wireframe styles only. But since there are no such styles,
+ * it acts as a message to the builder telling that it should look for the corresponding styles
+ * **in the theme** when compiling the editor.
+ */