Преглед на файлове

Merge branch 'master' into i/4858

panr преди 5 години
родител
ревизия
27e0df87b1

+ 5 - 3
packages/ckeditor5-table/src/table.js

@@ -13,7 +13,8 @@ import TableEditing from './tableediting';
 import TableUI from './tableui';
 import TableSelection from './tableselection';
 import TableClipboard from './tableclipboard';
-import TableNavigation from './tablenavigation';
+import TableKeyboard from './tablekeyboard';
+import TableMouse from './tablemouse';
 import Widget from '@ckeditor/ckeditor5-widget/src/widget';
 
 import '../theme/table.css';
@@ -27,7 +28,8 @@ import '../theme/table.css';
  *
  * * {@link module:table/tableediting~TableEditing editing feature},
  * * {@link module:table/tableselection~TableSelection selection feature},
- * * {@link module:table/tablenavigation~TableNavigation keyboard navigation feature},
+ * * {@link module:table/tablekeyboard~TableKeyboard keyboard navigation feature},
+ * * {@link module:table/tablemouse~TableMouse mouse selection feature},
  * * {@link module:table/tableclipboard~TableClipboard clipboard feature},
  * * {@link module:table/tableui~TableUI UI feature}.
  *
@@ -38,7 +40,7 @@ export default class Table extends Plugin {
 	 * @inheritDoc
 	 */
 	static get requires() {
-		return [ TableEditing, TableUI, TableSelection, TableClipboard, TableNavigation, Widget ];
+		return [ TableEditing, TableUI, TableSelection, TableMouse, TableKeyboard, TableClipboard, Widget ];
 	}
 
 	/**

+ 3 - 3
packages/ckeditor5-table/src/tablenavigation.js

@@ -4,7 +4,7 @@
  */
 
 /**
- * @module table/tablenavigation
+ * @module table/tablekeyboard
  */
 
 import TableSelection from './tableselection';
@@ -23,12 +23,12 @@ import { findAncestor } from './utils/common';
  *
  * @extends module:core/plugin~Plugin
  */
-export default class TableNavigation extends Plugin {
+export default class TableKeyboard extends Plugin {
 	/**
 	 * @inheritDoc
 	 */
 	static get pluginName() {
-		return 'TableNavigation';
+		return 'TableKeyboard';
 	}
 
 	/**

+ 223 - 0
packages/ckeditor5-table/src/tablemouse.js

@@ -0,0 +1,223 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module table/tablemouse
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import TableSelection from './tableselection';
+import MouseEventsObserver from './tablemouse/mouseeventsobserver';
+
+import { findAncestor } from './utils/common';
+import { getTableCellsContainingSelection } from './utils/selection';
+
+/**
+ * This plugin enables a table cells' selection with the mouse.
+ * It is loaded automatically by the {@link module:table/table~Table} plugin.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class TableMouse extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'TableMouse';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ TableSelection ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+
+		// Currently the MouseObserver only handles `mouseup` events.
+		// TODO move to the engine?
+		editor.editing.view.addObserver( MouseEventsObserver );
+
+		this._enableShiftClickSelection();
+		this._enableMouseDragSelection();
+	}
+
+	/**
+	 * Enables making cells selection by <kbd>Shift</kbd>+click. Creates a selection from the cell which previously held
+	 * the selection to the cell which was clicked. It can be the same cell, in which case it selects a single cell.
+	 *
+	 * @private
+	 */
+	_enableShiftClickSelection() {
+		const editor = this.editor;
+		let blockSelectionChange = false;
+
+		const tableSelection = editor.plugins.get( TableSelection );
+
+		this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
+			if ( !this.isEnabled || !tableSelection.isEnabled ) {
+				return;
+			}
+
+			if ( !domEventData.domEvent.shiftKey ) {
+				return;
+			}
+
+			const anchorCell = tableSelection.getAnchorCell() || getTableCellsContainingSelection( editor.model.document.selection )[ 0 ];
+
+			if ( !anchorCell ) {
+				return;
+			}
+
+			const targetCell = this._getModelTableCellFromDomEvent( domEventData );
+
+			if ( targetCell && haveSameTableParent( anchorCell, targetCell ) ) {
+				blockSelectionChange = true;
+				tableSelection.setCellSelection( anchorCell, targetCell );
+
+				domEventData.preventDefault();
+			}
+		} );
+
+		this.listenTo( editor.editing.view.document, 'mouseup', () => {
+			blockSelectionChange = false;
+		} );
+
+		// We need to ignore a `selectionChange` event that is fired after we render our new table cells selection.
+		// When downcasting table cells selection to the view, we put the view selection in the last selected cell
+		// in a place that may not be natively a "correct" location. This is – we put it directly in the `<td>` element.
+		// All browsers fire the native `selectionchange` event.
+		// However, all browsers except Safari return the selection in the exact place where we put it
+		// (even though it's visually normalized). Safari returns `<td><p>^foo` that makes our selection observer
+		// fire our `selectionChange` event (because the view selection that we set in the first step differs from the DOM selection).
+		// Since `selectionChange` is fired, we automatically update the model selection that moves it that paragraph.
+		// This breaks our dear cells selection.
+		//
+		// Theoretically this issue concerns only Safari that is the only browser that do normalize the selection.
+		// However, to avoid code branching and to have a good coverage for this event blocker, I enabled it for all browsers.
+		//
+		// Note: I'm keeping the `blockSelectionChange` state separately for shift+click and mouse drag (exact same logic)
+		// so I don't have to try to analyze whether they don't overlap in some weird cases. Probably they don't.
+		// But I have other things to do, like writing this comment.
+		this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
+			if ( blockSelectionChange ) {
+				// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
+
+				evt.stop();
+			}
+		}, { priority: 'highest' } );
+	}
+
+	/**
+	 * Enables making cells selection by dragging.
+	 *
+	 * The selection is made only on mousemove. Mouse tracking is started on mousedown.
+	 * However, the cells selection is enabled only after the mouse cursor left the anchor cell.
+	 * Thanks to that normal text selection within one cell works just fine. However, you can still select
+	 * just one cell by leaving the anchor cell and moving back to it.
+	 *
+	 * @private
+	 */
+	_enableMouseDragSelection() {
+		const editor = this.editor;
+		let anchorCell, targetCell;
+		let beganCellSelection = false;
+		let blockSelectionChange = false;
+
+		const tableSelection = editor.plugins.get( TableSelection );
+
+		this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
+			if ( !this.isEnabled || !tableSelection.isEnabled ) {
+				return;
+			}
+
+			// Make sure to not conflict with the shift+click listener and any other possible handler.
+			if ( domEventData.domEvent.shiftKey || domEventData.domEvent.ctrlKey || domEventData.domEvent.altKey ) {
+				return;
+			}
+
+			anchorCell = this._getModelTableCellFromDomEvent( domEventData );
+		} );
+
+		this.listenTo( editor.editing.view.document, 'mousemove', ( evt, domEventData ) => {
+			if ( !domEventData.domEvent.buttons ) {
+				return;
+			}
+
+			if ( !anchorCell ) {
+				return;
+			}
+
+			const newTargetCell = this._getModelTableCellFromDomEvent( domEventData );
+
+			if ( newTargetCell && haveSameTableParent( anchorCell, newTargetCell ) ) {
+				targetCell = newTargetCell;
+
+				// Switch to the cell selection mode after the mouse cursor left the anchor cell.
+				// Switch off only on mouseup (makes selecting a single cell possible).
+				if ( !beganCellSelection && targetCell != anchorCell ) {
+					beganCellSelection = true;
+				}
+			}
+
+			// Yep, not making a cell selection yet. See method docs.
+			if ( !beganCellSelection ) {
+				return;
+			}
+
+			blockSelectionChange = true;
+			tableSelection.setCellSelection( anchorCell, targetCell );
+
+			domEventData.preventDefault();
+		} );
+
+		this.listenTo( editor.editing.view.document, 'mouseup', () => {
+			beganCellSelection = false;
+			blockSelectionChange = false;
+			anchorCell = null;
+			targetCell = null;
+		} );
+
+		// See the explanation in `_enableShiftClickSelection()`.
+		this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
+			if ( blockSelectionChange ) {
+				// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
+
+				evt.stop();
+			}
+		}, { priority: 'highest' } );
+	}
+
+	/**
+	 * Returns the model table cell element based on the target element of the passed DOM event.
+	 *
+	 * @private
+	 * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
+	 * @returns {module:engine/model/element~Element|undefined} Returns the table cell or `undefined`.
+	 */
+	_getModelTableCellFromDomEvent( domEventData ) {
+		// Note: Work with positions (not element mapping) because the target element can be an attribute or other non-mapped element.
+		const viewTargetElement = domEventData.target;
+		const viewPosition = this.editor.editing.view.createPositionAt( viewTargetElement, 0 );
+		const modelPosition = this.editor.editing.mapper.toModelPosition( viewPosition );
+		const modelElement = modelPosition.parent;
+
+		if ( modelElement.is( 'tableCell' ) ) {
+			return modelElement;
+		}
+
+		return findAncestor( 'tableCell', modelElement );
+	}
+}
+
+function haveSameTableParent( cellA, cellB ) {
+	return cellA.parent.parent == cellB.parent.parent;
+}

packages/ckeditor5-table/src/tableselection/mouseeventsobserver.js → packages/ckeditor5-table/src/tablemouse/mouseeventsobserver.js


+ 1 - 175
packages/ckeditor5-table/src/tableselection.js

@@ -12,11 +12,10 @@ import first from '@ckeditor/ckeditor5-utils/src/first';
 
 import TableWalker from './tablewalker';
 import TableUtils from './tableutils';
-import MouseEventsObserver from './tableselection/mouseeventsobserver';
 
 import { findAncestor } from './utils/common';
 import { cropTableToDimensions } from './utils/structure';
-import { getColumnIndexes, getRowIndexes, getSelectedTableCells, getTableCellsContainingSelection } from './utils/selection';
+import { getColumnIndexes, getRowIndexes, getSelectedTableCells } from './utils/selection';
 
 import '../theme/tableselection.css';
 
@@ -50,13 +49,7 @@ export default class TableSelection extends Plugin {
 
 		this.listenTo( model, 'deleteContent', ( evt, args ) => this._handleDeleteContent( evt, args ), { priority: 'high' } );
 
-		// Currently the MouseObserver only handles `mouseup` events.
-		// TODO move to the engine?
-		editor.editing.view.addObserver( MouseEventsObserver );
-
 		this._defineSelectionConverter();
-		this._enableShiftClickSelection();
-		this._enableMouseDragSelection();
 		this._enablePluginDisabling(); // sic!
 	}
 
@@ -224,148 +217,6 @@ export default class TableSelection extends Plugin {
 	}
 
 	/**
-	 * Enables making cells selection by <kbd>Shift</kbd>+click. Creates a selection from the cell which previously held
-	 * the selection to the cell which was clicked. It can be the same cell, in which case it selects a single cell.
-	 *
-	 * @private
-	 */
-	_enableShiftClickSelection() {
-		const editor = this.editor;
-		let blockSelectionChange = false;
-
-		this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
-			if ( !this.isEnabled ) {
-				return;
-			}
-
-			if ( !domEventData.domEvent.shiftKey ) {
-				return;
-			}
-
-			const anchorCell = this.getAnchorCell() || getTableCellsContainingSelection( editor.model.document.selection )[ 0 ];
-
-			if ( !anchorCell ) {
-				return;
-			}
-
-			const targetCell = this._getModelTableCellFromDomEvent( domEventData );
-
-			if ( targetCell && haveSameTableParent( anchorCell, targetCell ) ) {
-				blockSelectionChange = true;
-				this.setCellSelection( anchorCell, targetCell );
-
-				domEventData.preventDefault();
-			}
-		} );
-
-		this.listenTo( editor.editing.view.document, 'mouseup', () => {
-			blockSelectionChange = false;
-		} );
-
-		// We need to ignore a `selectionChange` event that is fired after we render our new table cells selection.
-		// When downcasting table cells selection to the view, we put the view selection in the last selected cell
-		// in a place that may not be natively a "correct" location. This is – we put it directly in the `<td>` element.
-		// All browsers fire the native `selectionchange` event.
-		// However, all browsers except Safari return the selection in the exact place where we put it
-		// (even though it's visually normalized). Safari returns `<td><p>^foo` that makes our selection observer
-		// fire our `selectionChange` event (because the view selection that we set in the first step differs from the DOM selection).
-		// Since `selectionChange` is fired, we automatically update the model selection that moves it that paragraph.
-		// This breaks our dear cells selection.
-		//
-		// Theoretically this issue concerns only Safari that is the only browser that do normalize the selection.
-		// However, to avoid code branching and to have a good coverage for this event blocker, I enabled it for all browsers.
-		//
-		// Note: I'm keeping the `blockSelectionChange` state separately for shift+click and mouse drag (exact same logic)
-		// so I don't have to try to analyze whether they don't overlap in some weird cases. Probably they don't.
-		// But I have other things to do, like writing this comment.
-		this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
-			if ( blockSelectionChange ) {
-				// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
-
-				evt.stop();
-			}
-		}, { priority: 'highest' } );
-	}
-
-	/**
-	 * Enables making cells selection by dragging.
-	 *
-	 * The selection is made only on mousemove. Mouse tracking is started on mousedown.
-	 * However, the cells selection is enabled only after the mouse cursor left the anchor cell.
-	 * Thanks to that normal text selection within one cell works just fine. However, you can still select
-	 * just one cell by leaving the anchor cell and moving back to it.
-	 *
-	 * @private
-	 */
-	_enableMouseDragSelection() {
-		const editor = this.editor;
-		let anchorCell, targetCell;
-		let beganCellSelection = false;
-		let blockSelectionChange = false;
-
-		this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
-			if ( !this.isEnabled ) {
-				return;
-			}
-
-			// Make sure to not conflict with the shift+click listener and any other possible handler.
-			if ( domEventData.domEvent.shiftKey || domEventData.domEvent.ctrlKey || domEventData.domEvent.altKey ) {
-				return;
-			}
-
-			anchorCell = this._getModelTableCellFromDomEvent( domEventData );
-		} );
-
-		this.listenTo( editor.editing.view.document, 'mousemove', ( evt, domEventData ) => {
-			if ( !domEventData.domEvent.buttons ) {
-				return;
-			}
-
-			if ( !anchorCell ) {
-				return;
-			}
-
-			const newTargetCell = this._getModelTableCellFromDomEvent( domEventData );
-
-			if ( newTargetCell && haveSameTableParent( anchorCell, newTargetCell ) ) {
-				targetCell = newTargetCell;
-
-				// Switch to the cell selection mode after the mouse cursor left the anchor cell.
-				// Switch off only on mouseup (makes selecting a single cell possible).
-				if ( !beganCellSelection && targetCell != anchorCell ) {
-					beganCellSelection = true;
-				}
-			}
-
-			// Yep, not making a cell selection yet. See method docs.
-			if ( !beganCellSelection ) {
-				return;
-			}
-
-			blockSelectionChange = true;
-			this.setCellSelection( anchorCell, targetCell );
-
-			domEventData.preventDefault();
-		} );
-
-		this.listenTo( editor.editing.view.document, 'mouseup', () => {
-			beganCellSelection = false;
-			blockSelectionChange = false;
-			anchorCell = null;
-			targetCell = null;
-		} );
-
-		// See the explanation in `_enableShiftClickSelection()`.
-		this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
-			if ( blockSelectionChange ) {
-				// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
-
-				evt.stop();
-			}
-		}, { priority: 'highest' } );
-	}
-
-	/**
 	 * Creates a listener that reacts to changes in {@link #isEnabled} and, if the plugin was disabled,
 	 * it collapses the multi-cell selection to a regular selection placed inside a table cell.
 	 *
@@ -435,27 +286,6 @@ export default class TableSelection extends Plugin {
 	}
 
 	/**
-	 * Returns the model table cell element based on the target element of the passed DOM event.
-	 *
-	 * @private
-	 * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
-	 * @returns {module:engine/model/element~Element|undefined} Returns the table cell or `undefined`.
-	 */
-	_getModelTableCellFromDomEvent( domEventData ) {
-		// Note: Work with positions (not element mapping) because the target element can be an attribute or other non-mapped element.
-		const viewTargetElement = domEventData.target;
-		const viewPosition = this.editor.editing.view.createPositionAt( viewTargetElement, 0 );
-		const modelPosition = this.editor.editing.mapper.toModelPosition( viewPosition );
-		const modelElement = modelPosition.parent;
-
-		if ( modelElement.is( 'tableCell' ) ) {
-			return modelElement;
-		}
-
-		return findAncestor( 'tableCell', modelElement );
-	}
-
-	/**
 	 * Returns an array of table cells that should be selected based on the
 	 * given anchor cell and target (focus) cell.
 	 *
@@ -508,7 +338,3 @@ export default class TableSelection extends Plugin {
 		};
 	}
 }
-
-function haveSameTableParent( cellA, cellB ) {
-	return cellA.parent.parent == cellB.parent.parent;
-}

+ 6 - 3
packages/ckeditor5-table/tests/table.js

@@ -8,12 +8,15 @@ import TableEditing from '../src/tableediting';
 import TableUI from '../src/tableui';
 import TableSelection from '../src/tableselection';
 import TableClipboard from '../src/tableclipboard';
-import TableNavigation from '../src/tablenavigation';
+import TableKeyboard from '../src/tablekeyboard';
 import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+import TableMouse from '../src/tablemouse';
 
 describe( 'Table', () => {
-	it( 'requires TableEditing, TableUI, TableSelection, TableClipboard, TableNavigation and Widget', () => {
-		expect( Table.requires ).to.deep.equal( [ TableEditing, TableUI, TableSelection, TableClipboard, TableNavigation, Widget ] );
+	it( 'requires TableEditing, TableUI, TableSelection, TableMouse, TableKeyboard, TableClipboard and Widget', () => {
+		expect( Table.requires ).to.deep.equal( [
+			TableEditing, TableUI, TableSelection, TableMouse, TableKeyboard, TableClipboard, Widget
+		] );
 	} );
 
 	it( 'has proper name', () => {

+ 63 - 63
packages/ckeditor5-table/tests/tablenavigation.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-import TableNavigation from '../src/tablenavigation';
+import TableKeyboard from '../src/tablekeyboard';
 import Table from '../src/table';
 import TableEditing from '../src/tableediting';
 import TableSelection from '../src/tableselection';
@@ -27,8 +27,8 @@ import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
 import env from '@ckeditor/ckeditor5-utils/src/env';
 
-describe( 'TableNavigation', () => {
-	let editor, model, modelRoot, tableSelection, tableNavigation, selection;
+describe( 'TableKeyboard', () => {
+	let editor, model, modelRoot, tableSelection, tableKeyboard, selection;
 
 	const imageUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAUCAQAAADRyVAeAAAAKklEQVR42u3PAQ0AAAwCI' +
 		'O0f+u/hoAHNZUJFRERERERERERERERERLYiD9N4FAFj2iK6AAAAAElFTkSuQmCC';
@@ -36,7 +36,7 @@ describe( 'TableNavigation', () => {
 	beforeEach( () => {
 		return VirtualTestEditor
 			.create( {
-				plugins: [ TableEditing, TableNavigation, TableSelection, Paragraph, ImageEditing, ImageCaptionEditing, MediaEmbedEditing,
+				plugins: [ TableEditing, TableKeyboard, TableSelection, Paragraph, ImageEditing, ImageCaptionEditing, MediaEmbedEditing,
 					HorizontalLineEditing ]
 			} )
 			.then( newEditor => {
@@ -46,7 +46,7 @@ describe( 'TableNavigation', () => {
 				selection = model.document.selection;
 				modelRoot = model.document.getRoot();
 				tableSelection = editor.plugins.get( TableSelection );
-				tableNavigation = editor.plugins.get( TableNavigation );
+				tableKeyboard = editor.plugins.get( TableKeyboard );
 			} );
 	} );
 
@@ -55,7 +55,7 @@ describe( 'TableNavigation', () => {
 	} );
 
 	it( 'should have pluginName', () => {
-		expect( TableNavigation.pluginName ).to.equal( 'TableNavigation' );
+		expect( TableKeyboard.pluginName ).to.equal( 'TableKeyboard' );
 	} );
 
 	describe( 'Tab key handling', () => {
@@ -465,7 +465,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the start position of the cell on the right when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '[]01', '02' ],
@@ -475,7 +475,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the start position of the cell below when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -485,7 +485,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -495,7 +495,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -513,7 +513,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the end position of the cell on the left when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -523,7 +523,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the end position of the cell above when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -533,7 +533,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -543,7 +543,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -561,7 +561,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to start position of the cell on the right when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -571,7 +571,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the end position of the cell above when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00[]', '01', '02' ],
@@ -581,7 +581,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the start position of the cell below when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -591,7 +591,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the end position of the last cell in the previous row when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02[]' ],
@@ -609,7 +609,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the end position of the cell on the left when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -619,7 +619,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the end position the cell above when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02[]' ],
@@ -629,7 +629,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the start position of the cell below when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -639,7 +639,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should navigate to the start position of the first cell in the next row when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), '<paragraph>foo</paragraph>' + modelTable( [
 							[ '00', '01', '02' ],
@@ -676,7 +676,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-col-spanned cell when approaching from the upper-spanned row', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 0 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -690,7 +690,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-col-spanned cell when approaching from the lower-spanned row', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 2, 0 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -704,7 +704,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell when approaching from the other row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -718,7 +718,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the cell in the upper-spanned row when approaching from the row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 2 ] ); // Cell 13.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -732,7 +732,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 3, 0 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -746,7 +746,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate from the col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 3, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -762,7 +762,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell when approaching from the upper-spanned row', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 3 ] ); // Cell 14.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -776,7 +776,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell when approaching from the lower-spanned row', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] ); // Cell 24.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -790,7 +790,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell when approaching from the other row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 2 ] ); // Cell 13.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -804,7 +804,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the cell in the upper-spanned row when approaching from the row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] ); // Cell 11.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -818,7 +818,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 3, 2 ] ); // Cell 33.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -832,7 +832,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate from the col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 3, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -848,7 +848,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-col-spanned cell when approaching from the first spanned column', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -862,7 +862,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-col-spanned cell when approaching from the last spanned column', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -876,7 +876,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell when approaching from the other col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -890,7 +890,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the cell in the first spanned column when approaching from the col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] ); // Cell 11.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -904,7 +904,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 0, 3 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -918,7 +918,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate from the row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 2 ] ); // Cell 13.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -934,7 +934,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the col-spanned cell when approaching from the first spanned column', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 4, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -948,7 +948,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the col-spanned cell when approaching from the last spanned column', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 4, 2 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -962,7 +962,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-col-spanned cell when approaching from the other col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 3, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -976,7 +976,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the cell in the first spanned column when approaching from the col-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01[]', '02', '03', '04' ],
@@ -990,7 +990,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate to the row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 3, 2 ] ); // Cell 33.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03', '04' ],
@@ -1004,7 +1004,7 @@ describe( 'TableNavigation', () => {
 					it( 'should navigate from the row-spanned cell', () => {
 						const tableCell = modelRoot.getNodeByPath( [ 0, 1, 2 ] ); // Cell 13.
 
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up' );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up' );
 
 						assertEqualMarkup( getModelData( model ), modelTable( [
 							[ '00', '01', '02', '03[]', '04' ],
@@ -1034,7 +1034,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell on the right when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
@@ -1042,7 +1042,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell below when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 0 ] ) );
@@ -1050,7 +1050,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up', true );
 
 						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -1060,7 +1060,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left', true );
 
 						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -1079,7 +1079,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell on the left when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
@@ -1087,7 +1087,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell above when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
@@ -1095,7 +1095,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down', true );
 
 						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -1105,7 +1105,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should select a whole table when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right', true );
 
 						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
 							[ '00', '01', '02' ],
@@ -1124,7 +1124,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell on the right when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
@@ -1132,7 +1132,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell above when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
@@ -1140,7 +1140,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell below when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 0 ] ) );
@@ -1148,7 +1148,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell above when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
@@ -1165,7 +1165,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell on the left when the direction is "left"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'left', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
@@ -1173,7 +1173,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell above when the direction is "up"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'up', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 2 ] ) );
@@ -1181,7 +1181,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell below when the direction is "down"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'down', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
@@ -1189,7 +1189,7 @@ describe( 'TableNavigation', () => {
 					} );
 
 					it( 'should expand the selection to the cell below when the direction is "right"', () => {
-						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+						tableKeyboard._navigateFromCellInDirection( tableCell, 'right', true );
 
 						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
 						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
@@ -2979,7 +2979,7 @@ describe( 'TableNavigation', () => {
 			beforeEach( () => {
 				return VirtualTestEditor
 					.create( {
-						plugins: [ TableEditing, TableNavigation, TableSelection, Paragraph, ImageEditing, MediaEmbedEditing ],
+						plugins: [ TableEditing, TableKeyboard, TableSelection, Paragraph, ImageEditing, MediaEmbedEditing ],
 						language: 'ar'
 					} )
 					.then( newEditor => {

+ 569 - 0
packages/ckeditor5-table/tests/tablemouse.js

@@ -0,0 +1,569 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals document, console */
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+import TableEditing from '../src/tableediting';
+import TableSelection from '../src/tableselection';
+import TableMouse from '../src/tablemouse';
+import { assertSelectedCells, modelTable } from './_utils/utils';
+import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
+
+describe( 'TableMouse', () => {
+	let editorElement, editor, model, tableMouse, modelRoot, view, viewDocument;
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+	} );
+
+	afterEach( async () => {
+		editorElement.remove();
+		await editor.destroy();
+	} );
+
+	describe( 'plugin', () => {
+		beforeEach( async () => {
+			editor = await createEditor();
+		} );
+
+		it( 'should have pluginName', () => {
+			expect( TableMouse.pluginName ).to.equal( 'TableMouse' );
+		} );
+	} );
+
+	describe( 'selection by Shift+click', () => {
+		beforeEach( async () => {
+			editor = await createEditor();
+			model = editor.model;
+			modelRoot = model.document.getRoot();
+			view = editor.editing.view;
+			viewDocument = view.document;
+			tableMouse = editor.plugins.get( TableMouse );
+
+			setModelData( model, modelTable( [
+				[ '11[]', '12', '13' ],
+				[ '21', '22', '23' ],
+				[ '31', '32', '33' ]
+			] ) );
+		} );
+
+		it( 'should do nothing if the plugin is disabled', () => {
+			tableMouse.isEnabled = false;
+
+			viewDocument.fire( 'mousedown', new DomEventData( view, {} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if the TableSelection plugin is disabled', () => {
+			editor.plugins.get( 'TableSelection' ).isEnabled = false;
+
+			viewDocument.fire( 'mousedown', new DomEventData( view, {} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should abort if Shift key was not pressed', () => {
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				shiftKey: false,
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
+				)
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should abort if Shift+clicked an element outside a table', () => {
+			const preventDefault = sinon.spy();
+
+			model.change( writer => {
+				const paragraph = writer.createElement( 'paragraph' );
+				const text = writer.createText( 'foo' );
+
+				writer.insert( text, paragraph );
+				writer.insert( paragraph, model.document.getRoot(), 'end' );
+				writer.setSelection( paragraph, 'end' );
+			} );
+
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				shiftKey: true,
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 1 )
+				),
+				preventDefault
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+
+			expect( preventDefault.called ).to.equal( false );
+		} );
+
+		it( 'should abort if clicked a cell that belongs to another table', () => {
+			const preventDefault = sinon.spy();
+
+			setModelData( model, [
+				modelTable( [
+					[ '1.11[]', '1.12' ],
+					[ '1.21', '1.22' ]
+				] ),
+				modelTable( [
+					[ '2.11', '2.12' ],
+					[ '2.21', '2.22' ]
+				] )
+			].join( '' ) );
+
+			const domEventDataMock = new DomEventData( view, {
+				shiftKey: true,
+				target: view.domConverter.mapViewToDom(
+					// The second table: figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 1 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 )
+				),
+				preventDefault
+			} );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 0, 0 ],
+				[ 0, 0 ]
+			] );
+
+			expect( preventDefault.called ).to.equal( false );
+		} );
+
+		it( 'should select all cells in first row', () => {
+			const preventDefault = sinon.spy();
+
+			const domEventDataMock = new DomEventData( view, {
+				shiftKey: true,
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
+				),
+				preventDefault
+			} );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 1, 1, 1 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+
+			expect( preventDefault.called ).to.equal( true );
+		} );
+
+		it( 'should use the anchor cell from the selection if possible', () => {
+			const preventDefault = sinon.spy();
+
+			const domEventDataMock = new DomEventData( view, {
+				shiftKey: true,
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
+				),
+				preventDefault
+			} );
+
+			editor.plugins.get( 'TableSelection' ).setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 2, 1 ] )
+			);
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 1, 1, 0 ],
+				[ 1, 1, 0 ]
+			] );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 1, 1, 1 ],
+				[ 1, 1, 1 ],
+				[ 0, 0, 0 ]
+			] );
+
+			expect( preventDefault.called ).to.equal( true );
+		} );
+
+		it( 'should ignore `selectionChange` event when selecting cells', () => {
+			const consoleLog = sinon.stub( console, 'log' );
+			const preventDefault = sinon.spy();
+			const selectionChangeCallback = sinon.spy();
+
+			// Adding a new callback to check whether it will be executed (whether `evt.stop()` is being called).
+			viewDocument.on( 'selectionChange', selectionChangeCallback );
+
+			// Shift+click a cell to create a selection. Should disable listening to `selectionChange`.
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				shiftKey: true,
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
+				),
+				preventDefault
+			} ) );
+
+			// Due to browsers "fixing" the selection (e.g. moving it to text nodes), after we set a selection
+			// the browser fill fire native selectionchange, which triggers our selectionChange. We need to ignore it.
+			// See a broader explanation in tablemouse.js.
+			viewDocument.fire( 'selectionChange' );
+
+			// The callback shouldn't be executed because
+			// `selectionChange` event should be canceled.
+			expect( selectionChangeCallback.called ).to.equal( false );
+			expect( consoleLog.called ).to.equal( true );
+			expect( consoleLog.firstCall.args[ 0 ] ).to.equal( 'Blocked selectionChange to avoid breaking table cells selection.' );
+
+			// Enables listening to `selectionChange` event.
+			viewDocument.fire( 'mouseup' );
+
+			viewDocument.fire( 'selectionChange', {
+				newSelection: view.document.selection
+			} );
+
+			expect( selectionChangeCallback.called ).to.equal( true );
+
+			consoleLog.restore();
+		} );
+	} );
+
+	describe( 'selection by mouse drag', () => {
+		let preventDefault;
+
+		beforeEach( async () => {
+			editor = await createEditor();
+			model = editor.model;
+			modelRoot = model.document.getRoot();
+			view = editor.editing.view;
+			viewDocument = view.document;
+			tableMouse = editor.plugins.get( TableMouse );
+
+			setModelData( model, modelTable( [
+				[ '11[]', '12', '13' ],
+				[ '21', '22', '23' ],
+				[ '31', '32', '33' ]
+			] ) );
+
+			preventDefault = sinon.spy();
+		} );
+
+		it( 'should do nothing if the plugin is disabled', () => {
+			tableMouse.isEnabled = false;
+
+			const domEventDataMock = new DomEventData( view, {} );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if the TableSelection plugin is disabled', () => {
+			editor.plugins.get( 'TableSelection' ).isEnabled = false;
+
+			const domEventDataMock = new DomEventData( view, {} );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should abort if Ctrl is pressed', () => {
+			const domEventDataMock = new DomEventData( view, {
+				ctrlKey: true
+			} );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should abort if Alt is pressed', () => {
+			const domEventDataMock = new DomEventData( view, {
+				altKey: true
+			} );
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if any of mouse buttons was not clicked', () => {
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				buttons: 0
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if started dragging outside of table', () => {
+			model.change( writer => {
+				const paragraph = writer.createElement( 'paragraph' );
+				const text = writer.createText( 'foo' );
+
+				writer.insert( text, paragraph );
+				writer.insert( paragraph, model.document.getRoot(), 'end' );
+				writer.setSelection( paragraph, 'end' );
+			} );
+
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 1 )
+				)
+			} ) );
+
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				buttons: 1
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if ended dragging outside of table', () => {
+			model.change( writer => {
+				const paragraph = writer.createElement( 'paragraph' );
+				const text = writer.createText( 'foo' );
+
+				writer.insert( text, paragraph );
+				writer.insert( paragraph, model.document.getRoot(), 'end' );
+				writer.setSelection( paragraph, 'end' );
+			} );
+
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				)
+			} ) );
+
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 1 )
+				),
+				buttons: 1
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if ended dragging inside another table', () => {
+			setModelData( model, [
+				modelTable( [
+					[ '1.11[]', '1.12' ],
+					[ '1.21', '1.22' ]
+				] ),
+				modelTable( [
+					[ '2.11', '2.12' ],
+					[ '2.21', '2.22' ]
+				] )
+			].join( '' ) );
+
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				)
+			} ) );
+
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 1 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 )
+				),
+				buttons: 1
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0 ],
+				[ 0, 0 ]
+			] );
+		} );
+
+		it( 'should do nothing if ended in the same cell', () => {
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				)
+			} ) );
+
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				),
+				buttons: 1
+			} ) );
+
+			assertSelectedCells( model, [
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should select started and ended dragging in the same cell but went over its border', () => {
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				)
+			} ) );
+
+			// Select the next one.
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 1 )
+				),
+				buttons: 1,
+				preventDefault: sinon.spy()
+			} ) );
+
+			// And back to the "started" cell.
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				),
+				buttons: 1,
+				preventDefault: sinon.spy()
+			} ) );
+
+			viewDocument.fire( 'mouseup' );
+
+			assertSelectedCells( model, [
+				[ 1, 0, 0 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+		} );
+
+		it( 'should select all cells in first row', () => {
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
+				)
+			} ) );
+
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
+				),
+				buttons: 1,
+				preventDefault
+			} ) );
+
+			viewDocument.fire( 'mouseup' );
+
+			assertSelectedCells( model, [
+				[ 1, 1, 1 ],
+				[ 0, 0, 0 ],
+				[ 0, 0, 0 ]
+			] );
+
+			expect( preventDefault.called ).to.equal( true );
+		} );
+
+		it( 'should ignore `selectionChange` event when selecting cells ', () => {
+			const consoleLog = sinon.stub( console, 'log' );
+			const preventDefault = sinon.spy();
+			const selectionChangeCallback = sinon.spy();
+
+			// Adding a new callback to check whether it will be executed (whether `evt.stop()` is being called).
+			viewDocument.on( 'selectionChange', selectionChangeCallback );
+
+			// Click on a cell.
+			viewDocument.fire( 'mousedown', new DomEventData( view, {
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 1 )
+				)
+			} ) );
+
+			// Then move the mouse to another cell. Disables listening to `selectionChange`.
+			viewDocument.fire( 'mousemove', new DomEventData( view, {
+				buttons: 1,
+				target: view.domConverter.mapViewToDom(
+					// figure > table > tbody > tr > td
+					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
+				),
+				preventDefault
+			} ) );
+
+			// See explanation why do we fire it in the similar test for Shift+click.
+			viewDocument.fire( 'selectionChange' );
+
+			// `selectionChange` event should be canceled.
+			expect( selectionChangeCallback.called ).to.equal( false );
+			expect( consoleLog.called ).to.equal( true );
+			expect( consoleLog.firstCall.args[ 0 ] ).to.equal( 'Blocked selectionChange to avoid breaking table cells selection.' );
+
+			// Enables listening to `selectionChange` event.
+			viewDocument.fire( 'mouseup' );
+
+			viewDocument.fire( 'selectionChange', {
+				newSelection: view.document.selection
+			} );
+
+			expect( selectionChangeCallback.called ).to.equal( true );
+
+			consoleLog.restore();
+		} );
+	} );
+
+	function createEditor() {
+		return ClassicTestEditor.create( editorElement, {
+			plugins: [ TableEditing, TableSelection, TableMouse, Paragraph, Typing ]
+		} );
+	}
+} );

+ 7 - 506
packages/ckeditor5-table/tests/tableselection.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-/* globals document, console */
+/* globals document */
 
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
@@ -17,12 +17,11 @@ import TableEditing from '../src/tableediting';
 import TableSelection from '../src/tableselection';
 import { assertSelectedCells, modelTable } from './_utils/utils';
 import DocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment';
-import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
 import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
-describe( 'table selection', () => {
-	let editorElement, editor, model, tableSelection, modelRoot, view, viewDocument;
+describe( 'TableSelection', () => {
+	let editorElement, editor, model, tableSelection, modelRoot;
 
 	beforeEach( () => {
 		editorElement = document.createElement( 'div' );
@@ -48,6 +47,10 @@ describe( 'table selection', () => {
 			] ) );
 		} );
 
+		it( 'should have pluginName', () => {
+			expect( TableSelection.pluginName ).to.equal( 'TableSelection' );
+		} );
+
 		describe( 'plugin disabling support', () => {
 			it( 'should collapse multi-cell selection when the plugin gets disabled', () => {
 				const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
@@ -98,509 +101,11 @@ describe( 'table selection', () => {
 		} );
 	} );
 
-	describe( 'selection by Shift+click', () => {
-		beforeEach( async () => {
-			editor = await createEditor();
-			model = editor.model;
-			modelRoot = model.document.getRoot();
-			view = editor.editing.view;
-			viewDocument = view.document;
-			tableSelection = editor.plugins.get( TableSelection );
-
-			setModelData( model, modelTable( [
-				[ '11[]', '12', '13' ],
-				[ '21', '22', '23' ],
-				[ '31', '32', '33' ]
-			] ) );
-		} );
-
-		it( 'should do nothing if the plugin is disabled', () => {
-			tableSelection.isEnabled = false;
-
-			viewDocument.fire( 'mousedown', new DomEventData( view, {} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should abort if Shift key was not pressed', () => {
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				shiftKey: false,
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
-				)
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should abort if Shift+clicked an element outside a table', () => {
-			const preventDefault = sinon.spy();
-
-			model.change( writer => {
-				const paragraph = writer.createElement( 'paragraph' );
-				const text = writer.createText( 'foo' );
-
-				writer.insert( text, paragraph );
-				writer.insert( paragraph, model.document.getRoot(), 'end' );
-				writer.setSelection( paragraph, 'end' );
-			} );
-
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				shiftKey: true,
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 1 )
-				),
-				preventDefault
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-
-			expect( preventDefault.called ).to.equal( false );
-		} );
-
-		it( 'should abort if clicked a cell that belongs to another table', () => {
-			const preventDefault = sinon.spy();
-
-			setModelData( model, [
-				modelTable( [
-					[ '1.11[]', '1.12' ],
-					[ '1.21', '1.22' ]
-				] ),
-				modelTable( [
-					[ '2.11', '2.12' ],
-					[ '2.21', '2.22' ]
-				] )
-			].join( '' ) );
-
-			const domEventDataMock = new DomEventData( view, {
-				shiftKey: true,
-				target: view.domConverter.mapViewToDom(
-					// The second table: figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 1 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 )
-				),
-				preventDefault
-			} );
-
-			viewDocument.fire( 'mousedown', domEventDataMock );
-
-			assertSelectedCells( model, [
-				[ 0, 0 ],
-				[ 0, 0 ]
-			] );
-
-			expect( preventDefault.called ).to.equal( false );
-		} );
-
-		it( 'should select all cells in first row', () => {
-			const preventDefault = sinon.spy();
-
-			const domEventDataMock = new DomEventData( view, {
-				shiftKey: true,
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
-				),
-				preventDefault
-			} );
-
-			viewDocument.fire( 'mousedown', domEventDataMock );
-
-			assertSelectedCells( model, [
-				[ 1, 1, 1 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-
-			expect( preventDefault.called ).to.equal( true );
-		} );
-
-		it( 'should use the anchor cell from the selection if possible', () => {
-			const preventDefault = sinon.spy();
-
-			const domEventDataMock = new DomEventData( view, {
-				shiftKey: true,
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
-				),
-				preventDefault
-			} );
-
-			tableSelection.setCellSelection(
-				modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
-				modelRoot.getNodeByPath( [ 0, 2, 1 ] )
-			);
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 1, 1, 0 ],
-				[ 1, 1, 0 ]
-			] );
-
-			viewDocument.fire( 'mousedown', domEventDataMock );
-
-			assertSelectedCells( model, [
-				[ 1, 1, 1 ],
-				[ 1, 1, 1 ],
-				[ 0, 0, 0 ]
-			] );
-
-			expect( preventDefault.called ).to.equal( true );
-		} );
-
-		it( 'should ignore `selectionChange` event when selecting cells', () => {
-			const consoleLog = sinon.stub( console, 'log' );
-			const preventDefault = sinon.spy();
-			const selectionChangeCallback = sinon.spy();
-
-			// Adding a new callback to check whether it will be executed (whether `evt.stop()` is being called).
-			viewDocument.on( 'selectionChange', selectionChangeCallback );
-
-			// Shift+click a cell to create a selection. Should disable listening to `selectionChange`.
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				shiftKey: true,
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
-				),
-				preventDefault
-			} ) );
-
-			// Due to browsers "fixing" the selection (e.g. moving it to text nodes), after we set a selection
-			// the browser fill fire native selectionchange, which triggers our selectionChange. We need to ignore it.
-			// See a broader explanation in tableselection.js.
-			viewDocument.fire( 'selectionChange' );
-
-			// The callback shouldn't be executed because
-			// `selectionChange` event should be canceled.
-			expect( selectionChangeCallback.called ).to.equal( false );
-			expect( consoleLog.called ).to.equal( true );
-			expect( consoleLog.firstCall.args[ 0 ] ).to.equal( 'Blocked selectionChange to avoid breaking table cells selection.' );
-
-			// Enables listening to `selectionChange` event.
-			viewDocument.fire( 'mouseup' );
-
-			viewDocument.fire( 'selectionChange', {
-				newSelection: view.document.selection
-			} );
-
-			expect( selectionChangeCallback.called ).to.equal( true );
-
-			consoleLog.restore();
-		} );
-	} );
-
-	describe( 'selection by mouse drag', () => {
-		let preventDefault;
-
-		beforeEach( async () => {
-			editor = await createEditor();
-			model = editor.model;
-			modelRoot = model.document.getRoot();
-			view = editor.editing.view;
-			viewDocument = view.document;
-			tableSelection = editor.plugins.get( TableSelection );
-
-			setModelData( model, modelTable( [
-				[ '11[]', '12', '13' ],
-				[ '21', '22', '23' ],
-				[ '31', '32', '33' ]
-			] ) );
-
-			preventDefault = sinon.spy();
-		} );
-
-		it( 'should do nothing if the plugin is disabled', () => {
-			tableSelection.isEnabled = false;
-
-			const domEventDataMock = new DomEventData( view, {} );
-
-			viewDocument.fire( 'mousedown', domEventDataMock );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should abort if Ctrl is pressed', () => {
-			const domEventDataMock = new DomEventData( view, {
-				ctrlKey: true
-			} );
-
-			viewDocument.fire( 'mousedown', domEventDataMock );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should abort if Alt is pressed', () => {
-			const domEventDataMock = new DomEventData( view, {
-				altKey: true
-			} );
-
-			viewDocument.fire( 'mousedown', domEventDataMock );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should do nothing if any of mouse buttons was not clicked', () => {
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				buttons: 0
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should do nothing if started dragging outside of table', () => {
-			model.change( writer => {
-				const paragraph = writer.createElement( 'paragraph' );
-				const text = writer.createText( 'foo' );
-
-				writer.insert( text, paragraph );
-				writer.insert( paragraph, model.document.getRoot(), 'end' );
-				writer.setSelection( paragraph, 'end' );
-			} );
-
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 1 )
-				)
-			} ) );
-
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				buttons: 1
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should do nothing if ended dragging outside of table', () => {
-			model.change( writer => {
-				const paragraph = writer.createElement( 'paragraph' );
-				const text = writer.createText( 'foo' );
-
-				writer.insert( text, paragraph );
-				writer.insert( paragraph, model.document.getRoot(), 'end' );
-				writer.setSelection( paragraph, 'end' );
-			} );
-
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				)
-			} ) );
-
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 1 )
-				),
-				buttons: 1
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should do nothing if ended dragging inside another table', () => {
-			setModelData( model, [
-				modelTable( [
-					[ '1.11[]', '1.12' ],
-					[ '1.21', '1.22' ]
-				] ),
-				modelTable( [
-					[ '2.11', '2.12' ],
-					[ '2.21', '2.22' ]
-				] )
-			].join( '' ) );
-
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				)
-			} ) );
-
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 1 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 )
-				),
-				buttons: 1
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0 ],
-				[ 0, 0 ]
-			] );
-		} );
-
-		it( 'should do nothing if ended in the same cell', () => {
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				)
-			} ) );
-
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				),
-				buttons: 1
-			} ) );
-
-			assertSelectedCells( model, [
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should select started and ended dragging in the same cell but went over its border', () => {
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				)
-			} ) );
-
-			// Select the next one.
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 1 )
-				),
-				buttons: 1,
-				preventDefault: sinon.spy()
-			} ) );
-
-			// And back to the "started" cell.
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				),
-				buttons: 1,
-				preventDefault: sinon.spy()
-			} ) );
-
-			viewDocument.fire( 'mouseup' );
-
-			assertSelectedCells( model, [
-				[ 1, 0, 0 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-		} );
-
-		it( 'should select all cells in first row', () => {
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
-				)
-			} ) );
-
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
-				),
-				buttons: 1,
-				preventDefault
-			} ) );
-
-			viewDocument.fire( 'mouseup' );
-
-			assertSelectedCells( model, [
-				[ 1, 1, 1 ],
-				[ 0, 0, 0 ],
-				[ 0, 0, 0 ]
-			] );
-
-			expect( preventDefault.called ).to.equal( true );
-		} );
-
-		it( 'should ignore `selectionChange` event when selecting cells ', () => {
-			const consoleLog = sinon.stub( console, 'log' );
-			const preventDefault = sinon.spy();
-			const selectionChangeCallback = sinon.spy();
-
-			// Adding a new callback to check whether it will be executed (whether `evt.stop()` is being called).
-			viewDocument.on( 'selectionChange', selectionChangeCallback );
-
-			// Click on a cell.
-			viewDocument.fire( 'mousedown', new DomEventData( view, {
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 1 )
-				)
-			} ) );
-
-			// Then move the mouse to another cell. Disables listening to `selectionChange`.
-			viewDocument.fire( 'mousemove', new DomEventData( view, {
-				buttons: 1,
-				target: view.domConverter.mapViewToDom(
-					// figure > table > tbody > tr > td
-					viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
-				),
-				preventDefault
-			} ) );
-
-			// See explanation why do we fire it in the similar test for Shift+click.
-			viewDocument.fire( 'selectionChange' );
-
-			// `selectionChange` event should be canceled.
-			expect( selectionChangeCallback.called ).to.equal( false );
-			expect( consoleLog.called ).to.equal( true );
-			expect( consoleLog.firstCall.args[ 0 ] ).to.equal( 'Blocked selectionChange to avoid breaking table cells selection.' );
-
-			// Enables listening to `selectionChange` event.
-			viewDocument.fire( 'mouseup' );
-
-			viewDocument.fire( 'selectionChange', {
-				newSelection: view.document.selection
-			} );
-
-			expect( selectionChangeCallback.called ).to.equal( true );
-
-			consoleLog.restore();
-		} );
-	} );
-
 	describe( 'getSelectedTableCells()', () => {
 		beforeEach( async () => {
 			editor = await createEditor();
 			model = editor.model;
 			modelRoot = model.document.getRoot();
-			view = editor.editing.view;
-			viewDocument = view.document;
 			tableSelection = editor.plugins.get( TableSelection );
 
 			setModelData( model, modelTable( [
@@ -684,8 +189,6 @@ describe( 'table selection', () => {
 			editor = await createEditor();
 			model = editor.model;
 			modelRoot = model.document.getRoot();
-			view = editor.editing.view;
-			viewDocument = view.document;
 			tableSelection = editor.plugins.get( TableSelection );
 
 			setModelData( model, modelTable( [
@@ -740,8 +243,6 @@ describe( 'table selection', () => {
 			editor = await createEditor();
 			model = editor.model;
 			modelRoot = model.document.getRoot();
-			view = editor.editing.view;
-			viewDocument = view.document;
 			tableSelection = editor.plugins.get( TableSelection );
 
 			setModelData( model, modelTable( [

+ 1 - 1
packages/ckeditor5-table/tests/tableselection/mouseeventsobserver.js

@@ -6,7 +6,7 @@
 /* globals document */
 
 import View from '@ckeditor/ckeditor5-engine/src/view/view';
-import MouseEventsObserver from '../../src/tableselection/mouseeventsobserver';
+import MouseEventsObserver from '../../src/tablemouse/mouseeventsobserver';
 
 describe( 'table selection', () => {
 	describe( 'MouseEventsObserver', () => {

+ 42 - 42
yarn.lock

@@ -1080,6 +1080,13 @@
     "@nodelib/fs.scandir" "2.1.3"
     fastq "^1.6.0"
 
+"@npmcli/move-file@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464"
+  integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==
+  dependencies:
+    mkdirp "^1.0.4"
+
 "@octokit/auth-token@^2.4.0":
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.1.tgz#375d79eebd03750e6a9b0299e80b8167c7c85655"
@@ -1178,9 +1185,9 @@
     "@types/node" ">= 8"
 
 "@octokit/types@^4.0.1":
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-4.1.3.tgz#9a90f2c2dd2d42105c4dbf5cabcb31e4ac960835"
-  integrity sha512-MMBEO1k+fMa44gATPamxdpZmya9ugPBdcxwBIPgnH8/uD/1FWO3hiQFMGJT8diUk7E5UnnkraTFx00oHfUIFAA==
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-4.1.5.tgz#465872f9f5f5e6bb85c6ed763053486bba9b251b"
+  integrity sha512-/MKeipxtwMorckj1bfP+SKhbzKhqQimT5JuXKGtwnLazqDwj/noYYSPChpLzstVAwF8JVPygJ7L75cKCK47Ikg==
   dependencies:
     "@types/node" ">= 8"
 
@@ -1340,9 +1347,9 @@
   integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
 
 "@types/node@*", "@types/node@>= 8":
-  version "14.0.6"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.6.tgz#f9e178b2da31a4b0ec60b64649e244c31ce18daf"
-  integrity sha512-FbNmu4F67d3oZMWBV6Y4MaPER+0EpE9eIYf2yaHhCWovc1dlXCZkqGX4NLHfVVr6umt20TNBdRzrNJIzIKfdbw==
+  version "14.0.9"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.9.tgz#43896ab87fc82bda1dfd600cdf44a0c8a64e11d2"
+  integrity sha512-0sCTiXKXELOBxvZLN4krQ0FPOAA7ij+6WwvD0k/PHd9/KAkr4dXel5J9fh6F4x1FwAQILqAWkmpeuS6mjf1iKA==
 
 "@types/node@^13.7.0":
   version "13.13.9"
@@ -2528,10 +2535,11 @@ cacache@^12.0.2:
     y18n "^4.0.0"
 
 cacache@^15.0.3:
-  version "15.0.3"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.3.tgz#2225c2d1dd8e872339950d6a39c051e0e9334392"
-  integrity sha512-bc3jKYjqv7k4pWh7I/ixIjfcjPul4V4jme/WbjvwGS5LzoPL/GzXr4C5EgPNLO/QEZl9Oi61iGitYEdwcrwLCQ==
+  version "15.0.4"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.4.tgz#b2c23cf4ac4f5ead004fb15a0efb0a20340741f1"
+  integrity sha512-YlnKQqTbD/6iyoJvEY3KJftjrdBYroCbxxYXzhOzsFLWlp6KX4BOlEf4mTx0cMUfVaTS3ENL2QtDWeRYoGLkkw==
   dependencies:
+    "@npmcli/move-file" "^1.0.1"
     chownr "^2.0.0"
     fs-minipass "^2.0.0"
     glob "^7.1.4"
@@ -2542,7 +2550,6 @@ cacache@^15.0.3:
     minipass-flush "^1.0.5"
     minipass-pipeline "^1.2.2"
     mkdirp "^1.0.3"
-    move-file "^2.0.0"
     p-map "^4.0.0"
     promise-inflight "^1.0.1"
     rimraf "^3.0.2"
@@ -2684,9 +2691,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061:
-  version "1.0.30001066"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz#0a8a58a10108f2b9bf38e7b65c237b12fd9c5f04"
-  integrity sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==
+  version "1.0.30001077"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001077.tgz#5d7da6a120b08d9f4fd94823786ecb454aaa5626"
+  integrity sha512-AEzsGvjBJL0lby/87W96PyEvwN0GsYvk5LHsglLg9tW37K4BqvAvoSCdWIE13OZQ8afupqZ73+oL/1LkedN8hA==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -4218,9 +4225,9 @@ ee-first@1.1.1:
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
 electron-to-chromium@^1.3.413:
-  version "1.3.455"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.455.tgz#fd65a3f5db6ffa83eb7c84f16ea9b1b7396f537d"
-  integrity sha512-4lwnxp+ArqOX9hiLwLpwhfqvwzUHFuDgLz4NTiU3lhygUzWtocIJ/5Vix+mWVNE2HQ9aI1k2ncGe5H/0OktMvA==
+  version "1.3.458"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.458.tgz#4ef179f9a0f1d8a658180c09b21bf73edddfc5eb"
+  integrity sha512-OjRkb0igW0oKE2QbzS7vBYrm7xjW/KRTtIj0OGGx57jr/YhBiKb7oZvdbaojqjfCb/7LbnwsbMbdsYjthdJbAw==
 
 elegant-spinner@^2.0.0:
   version "2.0.0"
@@ -5490,9 +5497,9 @@ globby@^10.0.1:
     slash "^3.0.0"
 
 globby@^11.0.0:
-  version "11.0.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.0.tgz#56fd0e9f0d4f8fb0c456f1ab0dee96e1380bc154"
-  integrity sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==
+  version "11.0.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
+  integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==
   dependencies:
     array-union "^2.1.0"
     dir-glob "^3.0.1"
@@ -6201,9 +6208,9 @@ interpret@1.2.0:
   integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
 
 interpret@^1.0.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.3.0.tgz#6f637617cf307760be422ab9f4d13cc8a35eca1a"
-  integrity sha512-RDVhhDkycLoSQtE9o0vpK/vOccVDsCbWVzRxArGYnlQLcihPl2loFbPyiH7CM0m2/ijOJU3+PZbnBPaB6NJ1MA==
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
+  integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
 
 invariant@^2.2.2, invariant@^2.2.4:
   version "2.2.4"
@@ -6297,9 +6304,9 @@ is-buffer@^2.0.0, is-buffer@~2.0.3:
   integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
 
 is-callable@^1.1.4, is-callable@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
-  integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
+  integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
 
 is-color-stop@^1.0.0:
   version "1.1.0"
@@ -7878,9 +7885,9 @@ merge-stream@^2.0.0:
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
 merge2@^1.2.3, merge2@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
-  integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
 mergesort@0.0.1:
   version "0.0.1"
@@ -8106,7 +8113,7 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkd
   dependencies:
     minimist "^1.2.5"
 
-mkdirp@^1.0.3, mkdirp@~1.0.3:
+mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -8169,13 +8176,6 @@ move-concurrently@^1.0.1:
     rimraf "^2.5.4"
     run-queue "^1.0.3"
 
-move-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/move-file/-/move-file-2.0.0.tgz#83ffa309b5d7f69d518b28e1333e2ffadf331e3e"
-  integrity sha512-cdkdhNCgbP5dvS4tlGxZbD+nloio9GIimP57EjqFhwLcMjnU+XJKAZzlmg/TN/AK1LuNAdTSvm3CPPP4Xkv0iQ==
-  dependencies:
-    path-exists "^4.0.0"
-
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -8348,9 +8348,9 @@ node-preload@^0.2.1:
     process-on-spawn "^1.0.0"
 
 node-releases@^1.1.53:
-  version "1.1.57"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.57.tgz#f6754ce225fad0611e61228df3e09232e017ea19"
-  integrity sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw==
+  version "1.1.58"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935"
+  integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==
 
 node-sass-tilde-importer@^1.0.2:
   version "1.0.2"
@@ -9583,9 +9583,9 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
   integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
 
 postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.30, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7:
-  version "7.0.31"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.31.tgz#332af45cb73e26c0ee2614d7c7fb02dfcc2bd6dd"
-  integrity sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==
+  version "7.0.32"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
+  integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
   dependencies:
     chalk "^2.4.2"
     source-map "^0.6.1"