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

Merge pull request #7355 from ckeditor/i/6757

Other (table): Extracted `TableMouse` plugin from `TableSelection` plugin. Closes #6757.

MINOR BREAKING CHANGE (table): The `TableNavigation` plugin renamed to `TableKeyboard`.
Maciej 5 лет назад
Родитель
Сommit
4d2f5f9b9f

+ 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', () => {