瀏覽代碼

Merge pull request #6728 from ckeditor/i/6115

Feature (table): Introduce table cells selection using keyboard. Closes #6115. Closes #3203.
Maciej 5 年之前
父節點
當前提交
b567de402d

+ 43 - 19
packages/ckeditor5-table/src/tablenavigation.js

@@ -7,13 +7,15 @@
  * @module table/tablenavigation
  */
 
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import { getSelectedTableCells, getTableCellsContainingSelection } from './utils';
-import { findAncestor } from './commands/utils';
+import TableSelection from './tableselection';
 import TableWalker from './tablewalker';
+import { findAncestor } from './commands/utils';
+import { getSelectedTableCells, getTableCellsContainingSelection } from './utils';
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
-import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
 import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
+import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
 
 /**
  * This plugin enables keyboard navigation for tables.
@@ -32,6 +34,13 @@ export default class TableNavigation extends Plugin {
 	/**
 	 * @inheritDoc
 	 */
+	static get requires() {
+		return [ TableSelection ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
 	init() {
 		const view = this.editor.editing.view;
 		const viewDocument = view.document;
@@ -188,9 +197,15 @@ export default class TableNavigation extends Plugin {
 		const selectedCells = getSelectedTableCells( selection );
 
 		if ( selectedCells.length ) {
-			const tableCell = isForward ? selectedCells[ selectedCells.length - 1 ] : selectedCells[ 0 ];
+			let focusCell;
+
+			if ( expandSelection ) {
+				focusCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
+			} else {
+				focusCell = isForward ? selectedCells[ selectedCells.length - 1 ] : selectedCells[ 0 ];
+			}
 
-			this._navigateFromCellInDirection( tableCell, direction );
+			this._navigateFromCellInDirection( focusCell, direction, expandSelection );
 
 			return true;
 		}
@@ -206,7 +221,7 @@ export default class TableNavigation extends Plugin {
 
 		// Let's check if the selection is at the beginning/end of the cell.
 		if ( this._isSelectionAtCellEdge( selection, isForward ) ) {
-			this._navigateFromCellInDirection( tableCell, direction );
+			this._navigateFromCellInDirection( tableCell, direction, expandSelection );
 
 			return true;
 		}
@@ -228,7 +243,7 @@ export default class TableNavigation extends Plugin {
 		const textRange = this._findTextRangeFromSelection( cellRange, selection, isForward );
 
 		if ( !textRange ) {
-			this._navigateFromCellInDirection( tableCell, direction );
+			this._navigateFromCellInDirection( tableCell, direction, expandSelection );
 
 			return true;
 		}
@@ -426,18 +441,19 @@ export default class TableNavigation extends Plugin {
 	/**
 	 * Moves the selection from the given table cell in the specified direction.
 	 *
-	 * @private
-	 * @param {module:engine/model/element~Element} tableCell The table cell to start the selection navigation.
+	 * @protected
+	 * @param {module:engine/model/element~Element} focusCell The table cell that is current multi-cell selection focus.
 	 * @param {'left'|'up'|'right'|'down'} direction Direction in which selection should move.
+	 * @param {Boolean} [expandSelection=false] If the current selection should be expanded.
 	 */
-	_navigateFromCellInDirection( tableCell, direction ) {
+	_navigateFromCellInDirection( focusCell, direction, expandSelection = false ) {
 		const model = this.editor.model;
 
-		const table = findAncestor( 'table', tableCell );
+		const table = findAncestor( 'table', focusCell );
 		const tableMap = [ ...new TableWalker( table, { includeSpanned: true } ) ];
 		const { row: lastRow, column: lastColumn } = tableMap[ tableMap.length - 1 ];
 
-		const currentCellInfo = tableMap.find( ( { cell } ) => cell == tableCell );
+		const currentCellInfo = tableMap.find( ( { cell } ) => cell == focusCell );
 		let { row, column } = currentCellInfo;
 
 		switch ( direction ) {
@@ -474,20 +490,28 @@ export default class TableNavigation extends Plugin {
 		}
 
 		if ( column < 0 ) {
-			column = lastColumn;
+			column = expandSelection ? 0 : lastColumn;
 			row--;
 		} else if ( column > lastColumn ) {
-			column = 0;
+			column = expandSelection ? lastColumn : 0;
 			row++;
 		}
 
 		const cellToSelect = tableMap.find( cellInfo => cellInfo.row == row && cellInfo.column == column ).cell;
 		const isForward = [ 'right', 'down' ].includes( direction );
-		const positionToSelect = model.createPositionAt( cellToSelect, isForward ? 0 : 'end' );
 
-		model.change( writer => {
-			writer.setSelection( positionToSelect );
-		} );
+		if ( expandSelection ) {
+			const tableSelection = this.editor.plugins.get( 'TableSelection' );
+			const anchorCell = tableSelection.getAnchorCell() || focusCell;
+
+			tableSelection.setCellSelection( anchorCell, cellToSelect );
+		} else {
+			const positionToSelect = model.createPositionAt( cellToSelect, isForward ? 0 : 'end' );
+
+			model.change( writer => {
+				writer.setSelection( positionToSelect );
+			} );
+		}
 	}
 }
 

+ 77 - 44
packages/ckeditor5-table/src/tableselection.js

@@ -8,6 +8,7 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import first from '@ckeditor/ckeditor5-utils/src/first';
 
 import TableWalker from './tablewalker';
 import TableUtils from './tableutils';
@@ -107,6 +108,65 @@ export default class TableSelection extends Plugin {
 	}
 
 	/**
+	 * Sets the model selection based on given anchor and target cells (can be the same cell).
+	 * Takes care of setting the backward flag.
+	 *
+	 *		const modelRoot = editor.model.document.getRoot();
+	 *		const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+	 *		const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
+	 *
+	 *		const tableSelection = editor.plugins.get( 'TableSelection' );
+	 *		tableSelection.setCellSelection( firstCell, lastCell );
+	 *
+	 * @param {module:engine/model/element~Element} anchorCell
+	 * @param {module:engine/model/element~Element} targetCell
+	 */
+	setCellSelection( anchorCell, targetCell ) {
+		const cellsToSelect = this._getCellsToSelect( anchorCell, targetCell );
+
+		this.editor.model.change( writer => {
+			writer.setSelection(
+				cellsToSelect.cells.map( cell => writer.createRangeOn( cell ) ),
+				{ backward: cellsToSelect.backward }
+			);
+		} );
+	}
+
+	/**
+	 * Returns the focus cell from the current selection.
+	 *
+	 * @returns {module:engine/model/element~Element}
+	 */
+	getFocusCell() {
+		const selection = this.editor.model.document.selection;
+		const focusCellRange = [ ...selection.getRanges() ].pop();
+		const element = focusCellRange.getContainedElement();
+
+		if ( element && element.is( 'tableCell' ) ) {
+			return element;
+		}
+
+		return null;
+	}
+
+	/**
+	 * Returns the anchor cell from the current selection.
+	 *
+	 * @returns {module:engine/model/element~Element} anchorCell
+	 */
+	getAnchorCell() {
+		const selection = this.editor.model.document.selection;
+		const anchorCellRange = first( selection.getRanges() );
+		const element = anchorCellRange.getContainedElement();
+
+		if ( element && element.is( 'tableCell' ) ) {
+			return element;
+		}
+
+		return null;
+	}
+
+	/**
 	 * Defines a selection converter which marks the selected cells with a specific class.
 	 *
 	 * The real DOM selection is put in the last cell. Since the order of ranges is dependent on whether the
@@ -181,7 +241,7 @@ export default class TableSelection extends Plugin {
 
 			if ( targetCell && haveSameTableParent( anchorCell, targetCell ) ) {
 				blockSelectionChange = true;
-				this._setCellSelection( anchorCell, targetCell );
+				this.setCellSelection( anchorCell, targetCell );
 
 				domEventData.preventDefault();
 			}
@@ -272,7 +332,7 @@ export default class TableSelection extends Plugin {
 			}
 
 			blockSelectionChange = true;
-			this._setCellSelection( anchorCell, targetCell );
+			this.setCellSelection( anchorCell, targetCell );
 
 			domEventData.preventDefault();
 		} );
@@ -364,25 +424,6 @@ export default class TableSelection extends Plugin {
 	}
 
 	/**
-	 * Sets the model selection based on given anchor and target cells (can be the same cell).
-	 * Takes care of setting the backward flag.
-	 *
-	 * @protected
-	 * @param {module:engine/model/element~Element} anchorCell
-	 * @param {module:engine/model/element~Element} targetCell
-	 */
-	_setCellSelection( anchorCell, targetCell ) {
-		const cellsToSelect = this._getCellsToSelect( anchorCell, targetCell );
-
-		this.editor.model.change( writer => {
-			writer.setSelection(
-				cellsToSelect.cells.map( cell => writer.createRangeOn( cell ) ),
-				{ backward: cellsToSelect.backward }
-			);
-		} );
-	}
-
-	/**
 	 * Returns the model table cell element based on the target element of the passed DOM event.
 	 *
 	 * @private
@@ -425,39 +466,31 @@ export default class TableSelection extends Plugin {
 		const startColumn = Math.min( startLocation.column, endLocation.column );
 		const endColumn = Math.max( startLocation.column, endLocation.column );
 
-		const cells = [];
+		// 2-dimensional array of the selected cells to ease flipping the order of cells for backward selections.
+		const selectionMap = new Array( endRow - startRow + 1 ).fill( null ).map( () => [] );
 
 		for ( const cellInfo of new TableWalker( findAncestor( 'table', anchorCell ), { startRow, endRow } ) ) {
 			if ( cellInfo.column >= startColumn && cellInfo.column <= endColumn ) {
-				cells.push( cellInfo.cell );
+				selectionMap[ cellInfo.row - startRow ].push( cellInfo.cell );
 			}
 		}
 
-		// A selection started in the bottom right corner and finished in the upper left corner
-		// should have it ranges returned in the reverse order.
-		// However, this is only half of the story because the selection could be made to the left (then the left cell is a focus)
-		// or to the right (then the right cell is a focus), while being a forward selection in both cases
-		// (because it was made from top to bottom). This isn't handled.
-		// This method would need to be smarter, but the ROI is microscopic, so I skip this.
-		if ( checkIsBackward( startLocation, endLocation ) ) {
-			return { cells: cells.reverse(), backward: true };
-		}
+		const flipVertically = endLocation.row < startLocation.row;
+		const flipHorizontally = endLocation.column < startLocation.column;
 
-		return { cells, backward: false };
-	}
-}
+		if ( flipVertically ) {
+			selectionMap.reverse();
+		}
 
-// Naively check whether the selection should be backward or not. See the longer explanation where this function is used.
-function checkIsBackward( startLocation, endLocation ) {
-	if ( startLocation.row > endLocation.row ) {
-		return true;
-	}
+		if ( flipHorizontally ) {
+			selectionMap.forEach( row => row.reverse() );
+		}
 
-	if ( startLocation.row == endLocation.row && startLocation.column > endLocation.column ) {
-		return true;
+		return {
+			cells: selectionMap.flat(),
+			backward: flipVertically || flipHorizontally
+		};
 	}
-
-	return false;
 }
 
 function haveSameTableParent( cellA, cellB ) {

+ 2 - 2
packages/ckeditor5-table/tests/commands/insertcolumncommand.js

@@ -100,7 +100,7 @@ describe( 'InsertColumnCommand', () => {
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -251,7 +251,7 @@ describe( 'InsertColumnCommand', () => {
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);

+ 2 - 2
packages/ckeditor5-table/tests/commands/insertrowcommand.js

@@ -193,7 +193,7 @@ describe( 'InsertRowCommand', () => {
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -328,7 +328,7 @@ describe( 'InsertRowCommand', () => {
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);

+ 11 - 11
packages/ckeditor5-table/tests/commands/mergecellscommand.js

@@ -201,7 +201,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '10', '11' ]
 			], { headingRows: 1 } ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 1, 0 ] )
 			);
@@ -230,7 +230,7 @@ describe( 'MergeCellsCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 12, 0 ] )
 			);
@@ -244,7 +244,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '10', '11', '12', '13' ]
 			], { headingColumns: 2 } ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -258,7 +258,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '10', '11', '12', '13' ]
 			], { headingColumns: 2 } ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 1, 2 ] )
 			);
@@ -272,7 +272,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '10', '11', '12', '13' ]
 			], { headingColumns: 2, headingRows: 1 } ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -286,7 +286,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '10', '11', '12', '13' ]
 			], { headingColumns: 2, headingRows: 1 } ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 0, 2 ] )
 			);
@@ -300,7 +300,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '10', '11', '12', '13' ]
 			], { headingColumns: 2, headingRows: 1 } ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 1, 2 ] )
 			);
@@ -315,7 +315,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '[]00', '01' ]
 			] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 0, 0 ] ),
 				root.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -334,7 +334,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '20', '22' ]
 			] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 1, 0 ] ),
 				root.getNodeByPath( [ 0, 2, 1 ] )
 			);
@@ -358,7 +358,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '20', '22' ]
 			] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 2, 1 ] ),
 				root.getNodeByPath( [ 0, 1, 0 ] )
 			);
@@ -383,7 +383,7 @@ describe( 'MergeCellsCommand', () => {
 				[ '30', '31', '32', '33' ]
 			] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				root.getNodeByPath( [ 0, 2, 1 ] ),
 				root.getNodeByPath( [ 0, 1, 2 ] )
 			);

+ 12 - 12
packages/ckeditor5-table/tests/commands/removecolumncommand.js

@@ -52,7 +52,7 @@ describe( 'RemoveColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -78,7 +78,7 @@ describe( 'RemoveColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 			);
@@ -93,7 +93,7 @@ describe( 'RemoveColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 12 ] )
 			);
@@ -152,7 +152,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);
@@ -177,7 +177,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -202,7 +202,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -227,7 +227,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -252,7 +252,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 2 ] )
 				);
@@ -277,7 +277,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -303,7 +303,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 2 ] )
 				);
@@ -327,7 +327,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 3 ] )
 				);
@@ -349,7 +349,7 @@ describe( 'RemoveColumnCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);

+ 16 - 16
packages/ckeditor5-table/tests/commands/removerowcommand.js

@@ -47,7 +47,7 @@ describe( 'RemoveRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -71,7 +71,7 @@ describe( 'RemoveRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 			);
@@ -95,7 +95,7 @@ describe( 'RemoveRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 			);
@@ -122,7 +122,7 @@ describe( 'RemoveRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 12, 0 ] )
 			);
@@ -158,7 +158,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);
@@ -181,7 +181,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -204,7 +204,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 3, 0 ] )
 				);
@@ -227,7 +227,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -250,7 +250,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -275,7 +275,7 @@ describe( 'RemoveRowCommand', () => {
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 3, 0 ] )
 				);
@@ -303,7 +303,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -324,7 +324,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -354,7 +354,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -385,7 +385,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 12, 0 ] )
 				);
@@ -410,7 +410,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -431,7 +431,7 @@ describe( 'RemoveRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] )
 				);

+ 14 - 14
packages/ckeditor5-table/tests/commands/selectcolumncommand.js

@@ -48,7 +48,7 @@ describe( 'SelectColumnCommand', () => {
 				[ '20', '21' ]
 			] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -150,7 +150,7 @@ describe( 'SelectColumnCommand', () => {
 
 			it( 'should select only one column if only one cell is selected', () => {
 				// Selection in cell 10.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -180,7 +180,7 @@ describe( 'SelectColumnCommand', () => {
 
 			it( 'should not select col-spanned columns that start in other column', () => {
 				// Selection in cell 42.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 4, 2 ] ),
 					modelRoot.getNodeByPath( [ 0, 4, 2 ] )
 				);
@@ -210,7 +210,7 @@ describe( 'SelectColumnCommand', () => {
 
 			it( 'should not select col-spanned columns that start in other column but include those that start in selected column', () => {
 				// Selection in cell 41.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 4, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 4, 1 ] )
 				);
@@ -240,7 +240,7 @@ describe( 'SelectColumnCommand', () => {
 
 			it( 'should select properly for multiple not spanned cells selected', () => {
 				// Selection in cells 40 - 41.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 4, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 4, 1 ] )
 				);
@@ -270,7 +270,7 @@ describe( 'SelectColumnCommand', () => {
 
 			it( 'should select properly for multiple cells selected including spanned one', () => {
 				// Selection in cells 31 - 33.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 3, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 3, 2 ] )
 				);
@@ -309,7 +309,7 @@ describe( 'SelectColumnCommand', () => {
 			} );
 
 			it( 'should properly select middle columns', () => {
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 				);
@@ -324,7 +324,7 @@ describe( 'SelectColumnCommand', () => {
 			} );
 
 			it( 'should properly select middle columns in reversed order', () => {
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 				);
@@ -339,7 +339,7 @@ describe( 'SelectColumnCommand', () => {
 			} );
 
 			it( 'should properly select tailing columns', () => {
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 3 ] )
 				);
@@ -354,7 +354,7 @@ describe( 'SelectColumnCommand', () => {
 			} );
 
 			it( 'should properly select beginning columns', () => {
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 				);
@@ -369,7 +369,7 @@ describe( 'SelectColumnCommand', () => {
 			} );
 
 			it( 'should properly select multiple columns from square selection', () => {
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 2 ] )
 				);
@@ -390,7 +390,7 @@ describe( 'SelectColumnCommand', () => {
 					[ '20', '21', '22', '23' ]
 				], { headingRows: 1 } ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 				);
@@ -413,7 +413,7 @@ describe( 'SelectColumnCommand', () => {
 					[ '20', '21', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -433,7 +433,7 @@ describe( 'SelectColumnCommand', () => {
 					[ '10', '11' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] )
 				);

+ 15 - 15
packages/ckeditor5-table/tests/commands/selectrowcommand.js

@@ -48,7 +48,7 @@ describe( 'SelectRowCommand', () => {
 				[ '20', '21' ]
 			] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -147,7 +147,7 @@ describe( 'SelectRowCommand', () => {
 
 			it( 'should select only one row if only one cell is selected', () => {
 				// Selection in cell 01.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 				);
@@ -175,7 +175,7 @@ describe( 'SelectRowCommand', () => {
 
 			it( 'should not select row-spanned rows that start in other row', () => {
 				// Selection in cell 24.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -203,7 +203,7 @@ describe( 'SelectRowCommand', () => {
 
 			it( 'should not select row-spanned rows that start in other row but include those that start in selected row', () => {
 				// Selection in cell 14.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -231,7 +231,7 @@ describe( 'SelectRowCommand', () => {
 
 			it( 'should select properly for multiple not spanned cells selected', () => {
 				// Selection in cells 04 - 14.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 4 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -259,7 +259,7 @@ describe( 'SelectRowCommand', () => {
 
 			it( 'should select properly for multiple cells selected including spanned one', () => {
 				// Selection in cells 13 - 33.
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 3, 2 ] )
 				);
@@ -295,7 +295,7 @@ describe( 'SelectRowCommand', () => {
 					[ '30', '31' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);
@@ -318,7 +318,7 @@ describe( 'SelectRowCommand', () => {
 					[ '30', '31' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -341,7 +341,7 @@ describe( 'SelectRowCommand', () => {
 					[ '30', '31' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 3, 0 ] )
 				);
@@ -364,7 +364,7 @@ describe( 'SelectRowCommand', () => {
 					[ '30', '31' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -387,7 +387,7 @@ describe( 'SelectRowCommand', () => {
 					[ '30', '31', '32' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -409,7 +409,7 @@ describe( 'SelectRowCommand', () => {
 					[ '20', '21' ]
 				], { headingColumns: 1 } ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -444,7 +444,7 @@ describe( 'SelectRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 12, 0 ] )
 				);
@@ -479,7 +479,7 @@ describe( 'SelectRowCommand', () => {
 					[ '20', '21' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -499,7 +499,7 @@ describe( 'SelectRowCommand', () => {
 					[ '10', '11' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] )
 				);

+ 13 - 13
packages/ckeditor5-table/tests/commands/setheadercolumncommand.js

@@ -52,7 +52,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -67,7 +67,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -102,7 +102,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -117,7 +117,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] )
 			);
@@ -141,7 +141,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 			);
@@ -170,7 +170,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -207,7 +207,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 					);
@@ -233,7 +233,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 					);
@@ -260,7 +260,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 					);
@@ -287,7 +287,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
 						modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 					);
@@ -316,7 +316,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 					);
@@ -345,7 +345,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
 					);
@@ -372,7 +372,7 @@ describe( 'SetHeaderColumnCommand', () => {
 
 					const tableSelection = editor.plugins.get( TableSelection );
 					const modelRoot = model.document.getRoot();
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
 						modelRoot.getNodeByPath( [ 0, 0, 13 ] )
 					);

+ 13 - 13
packages/ckeditor5-table/tests/commands/setheaderrowcommand.js

@@ -51,7 +51,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -66,7 +66,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -120,7 +120,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -136,7 +136,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);
@@ -152,7 +152,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 			const tableSelection = editor.plugins.get( TableSelection );
 			const modelRoot = model.document.getRoot();
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 			);
@@ -254,7 +254,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);
@@ -288,7 +288,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
 				);
@@ -322,7 +322,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 2 ] )
 				);
@@ -367,7 +367,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 13, 0 ] )
 				);
@@ -414,7 +414,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 13, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -450,7 +450,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);
@@ -486,7 +486,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);
@@ -524,7 +524,7 @@ describe( 'SetHeaderRowCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
 				);

+ 2 - 2
packages/ckeditor5-table/tests/commands/splitcellcommand.js

@@ -55,7 +55,7 @@ describe( 'SplitCellCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] )
 				);
@@ -70,7 +70,7 @@ describe( 'SplitCellCommand', () => {
 
 				const tableSelection = editor.plugins.get( TableSelection );
 				const modelRoot = model.document.getRoot();
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 				);

+ 14 - 14
packages/ckeditor5-table/tests/tableclipboard.js

@@ -56,7 +56,7 @@ describe( 'table clipboard', () => {
 			it( 'should copy selected table cells as a standalone table', () => {
 				const preventDefaultSpy = sinon.spy();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 2 ] )
 				);
@@ -81,7 +81,7 @@ describe( 'table clipboard', () => {
 					[ '20', '21', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -100,7 +100,7 @@ describe( 'table clipboard', () => {
 					[ '20', '21', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] )
 				);
@@ -119,7 +119,7 @@ describe( 'table clipboard', () => {
 					[ '20', '21', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 2 ] )
 				);
@@ -137,7 +137,7 @@ describe( 'table clipboard', () => {
 					[ '20', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -155,7 +155,7 @@ describe( 'table clipboard', () => {
 					[ '20', '21', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 2 ] )
 				);
@@ -174,7 +174,7 @@ describe( 'table clipboard', () => {
 					[ '20', '21', '22' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 2, 2 ] )
 				);
@@ -223,7 +223,7 @@ describe( 'table clipboard', () => {
 					[ '80', '82', '83', '84', '85', '87', '88' ]
 				] ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 2, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 7, 6 ] )
 				);
@@ -247,7 +247,7 @@ describe( 'table clipboard', () => {
 					[ '40', '41', '42', '43', '44' ]
 				], { headingRows: 3, headingColumns: 2 } ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 3, 3 ] )
 				);
@@ -268,7 +268,7 @@ describe( 'table clipboard', () => {
 					[ '40', '41', '42', '43', '44' ]
 				], { headingRows: 3, headingColumns: 2 } ) );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 3, 2 ] ),
 					modelRoot.getNodeByPath( [ 0, 4, 4 ] )
 				);
@@ -299,7 +299,7 @@ describe( 'table clipboard', () => {
 			} );
 
 			it( 'should be preventable', () => {
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -323,7 +323,7 @@ describe( 'table clipboard', () => {
 
 				viewDocument.on( 'clipboardOutput', spy );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 				);
@@ -343,7 +343,7 @@ describe( 'table clipboard', () => {
 			it( 'should copy selected table cells as a standalone table', () => {
 				const preventDefaultSpy = sinon.spy();
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 2 ] )
 				);
@@ -366,7 +366,7 @@ describe( 'table clipboard', () => {
 
 				editor.isReadOnly = true;
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 					modelRoot.getNodeByPath( [ 0, 1, 2 ] )
 				);

+ 614 - 139
packages/ckeditor5-table/tests/tablenavigation.js

@@ -8,6 +8,7 @@ import Table from '../src/table';
 import TableEditing from '../src/tableediting';
 import TableSelection from '../src/tableselection';
 import { modelTable } from './_utils/utils';
+import { getTableCellsContainingSelection } from '../src/utils';
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
@@ -27,7 +28,7 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global';
 import env from '@ckeditor/ckeditor5-utils/src/env';
 
 describe( 'TableNavigation', () => {
-	let editor, model, modelRoot, tableSelection;
+	let editor, model, modelRoot, tableSelection, tableNavigation, selection;
 
 	const imageUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAUCAQAAADRyVAeAAAAKklEQVR42u3PAQ0AAAwCI' +
 		'O0f+u/hoAHNZUJFRERERERERERERERERLYiD9N4FAFj2iK6AAAAAElFTkSuQmCC';
@@ -42,8 +43,10 @@ describe( 'TableNavigation', () => {
 				editor = newEditor;
 
 				model = editor.model;
+				selection = model.document.selection;
 				modelRoot = model.document.getRoot();
 				tableSelection = editor.plugins.get( TableSelection );
+				tableNavigation = editor.plugins.get( TableNavigation );
 			} );
 	} );
 
@@ -445,12 +448,6 @@ describe( 'TableNavigation', () => {
 		} );
 
 		describe( '#_navigateFromCellInDirection (finding a proper cell to move the selection to)', () => {
-			let tableNavigation;
-
-			beforeEach( () => {
-				tableNavigation = editor.plugins.get( TableNavigation );
-			} );
-
 			describe( 'with no col/row-spanned cells', () => {
 				beforeEach( () => {
 					setModelData( model, '<paragraph>foo</paragraph>' + modelTable( [
@@ -1019,77 +1016,316 @@ describe( 'TableNavigation', () => {
 					} );
 				} );
 			} );
-		} );
 
-		describe( 'with the table cells selected from outside', () => {
-			describe( 'on a single table cell selected', () => {
+			describe( 'when expanding selection', () => {
 				beforeEach( () => {
 					setModelData( model, modelTable( [
-						[ '00', '01', '02' ],
+						[ '00[]', '01', '02' ],
 						[ '10', '11', '12' ],
 						[ '20', '21', '22' ]
 					] ) );
+				} );
 
-					tableSelection._setCellSelection(
-						modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
-						modelRoot.getNodeByPath( [ 0, 1, 1 ] )
-					);
+				describe( 'from the first table cell', () => {
+					let tableCell;
+
+					beforeEach( () => {
+						tableCell = getTableCellsContainingSelection( selection )[ 0 ];
+					} );
+
+					it( 'should expand the selection to the cell on the right when the direction is "right"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell below when the direction is "down"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should select a whole table when the direction is "up"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+
+						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
+							[ '00', '01', '02' ],
+							[ '10', '11', '12' ],
+							[ '20', '21', '22' ]
+						] ) + ']' );
+					} );
+
+					it( 'should select a whole table when the direction is "left"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+
+						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
+							[ '00', '01', '02' ],
+							[ '10', '11', '12' ],
+							[ '20', '21', '22' ]
+						] ) + ']' );
+					} );
 				} );
 
-				it( 'should move to the cell on the left', () => {
-					editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
+				describe( 'from the last table cell', () => {
+					let tableCell;
 
-					sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+					beforeEach( () => {
+						tableCell = modelRoot.getNodeByPath( [ 0, 2, 2 ] );
+						tableSelection.setCellSelection( tableCell, tableCell );
+					} );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02' ],
-						[ '10[]', '11', '12' ],
-						[ '20', '21', '22' ]
-					] ) );
+					it( 'should expand the selection to the cell on the left when the direction is "left"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell above when the direction is "up"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should select a whole table when the direction is "down"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+
+						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
+							[ '00', '01', '02' ],
+							[ '10', '11', '12' ],
+							[ '20', '21', '22' ]
+						] ) + ']' );
+					} );
+
+					it( 'should select a whole table when the direction is "right"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+
+						assertEqualMarkup( getModelData( model ), '[' + modelTable( [
+							[ '00', '01', '02' ],
+							[ '10', '11', '12' ],
+							[ '20', '21', '22' ]
+						] ) + ']' );
+					} );
 				} );
 
-				it( 'should move to the cell on the right', () => {
-					editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+				describe( 'from a cell in the first column (but not first row)', () => {
+					let tableCell;
 
-					sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+					beforeEach( () => {
+						tableCell = modelRoot.getNodeByPath( [ 0, 1, 0 ] );
+						tableSelection.setCellSelection( tableCell, tableCell );
+					} );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02' ],
-						[ '10', '11', '[]12' ],
-						[ '20', '21', '22' ]
-					] ) );
+					it( 'should expand the selection to the cell on the right when the direction is "right"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell above when the direction is "up"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell below when the direction is "down"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell above when the direction is "left"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
 				} );
 
-				it( 'should move to the cell above the selection', () => {
-					editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+				describe( 'from a cell in the last column (but not the last row)', () => {
+					let tableCell;
 
-					sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+					beforeEach( () => {
+						tableCell = modelRoot.getNodeByPath( [ 0, 1, 2 ] );
+						tableSelection.setCellSelection( tableCell, tableCell );
+					} );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01[]', '02' ],
+					it( 'should expand the selection to the cell on the left when the direction is "left"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'left', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell above when the direction is "up"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'up', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell below when the direction is "down"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'down', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell below when the direction is "right"', () => {
+						tableNavigation._navigateFromCellInDirection( tableCell, 'right', true );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( tableCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+				} );
+			} );
+		} );
+
+		describe( 'with the table cells selected from outside', () => {
+			describe( 'on a single table cell selected', () => {
+				let anchorCell, focusCell;
+
+				beforeEach( () => {
+					setModelData( model, modelTable( [
+						[ '00', '01', '02' ],
 						[ '10', '11', '12' ],
 						[ '20', '21', '22' ]
 					] ) );
+
+					anchorCell = focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
+
+					tableSelection.setCellSelection( anchorCell, focusCell );
 				} );
 
-				it( 'should move to the cell below the selection', () => {
-					editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+				describe( 'without shift pressed', () => {
+					it( 'should move to the cell on the left', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
 
-					sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02' ],
-						[ '10', '11', '12' ],
-						[ '20', '[]21', '22' ]
-					] ) );
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02' ],
+							[ '10[]', '11', '12' ],
+							[ '20', '21', '22' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell on the right', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02' ],
+							[ '10', '11', '[]12' ],
+							[ '20', '21', '22' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell above the selection', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01[]', '02' ],
+							[ '10', '11', '12' ],
+							[ '20', '21', '22' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell below the selection', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02' ],
+							[ '10', '11', '12' ],
+							[ '20', '[]21', '22' ]
+						] ) );
+					} );
+				} );
+
+				describe( 'with shift pressed', () => {
+					beforeEach( () => {
+						leftArrowDomEvtDataStub.shiftKey = true;
+						rightArrowDomEvtDataStub.shiftKey = true;
+						upArrowDomEvtDataStub.shiftKey = true;
+						downArrowDomEvtDataStub.shiftKey = true;
+					} );
+
+					it( 'should expand the selection to the cell on the left', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell on the right', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell above the selection', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
+
+					it( 'should expand the selection to the cell below the selection', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 2 );
+					} );
 				} );
 			} );
 
-			describe( 'on multiple table cells selected vertically', () => {
+			describe( 'on multiple table cells selected vertically (the anchor cell above the focus cell)', () => {
+				let anchorCell, focusCell;
+
 				beforeEach( () => {
 					setModelData( model, modelTable( [
 						[ '00', '01', '02', '03' ],
@@ -1098,70 +1334,127 @@ describe( 'TableNavigation', () => {
 						[ '30', '31', '32', '33' ]
 					] ) );
 
-					tableSelection._setCellSelection(
-						modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
-						modelRoot.getNodeByPath( [ 0, 2, 1 ] )
-					);
+					anchorCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
+					focusCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
+
+					tableSelection.setCellSelection( anchorCell, focusCell );
 				} );
 
-				it( 'should move to the cell on the top left of the selection', () => {
-					editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
+				describe( 'without shift pressed', () => {
+					it( 'should move to the cell on the top left of the selection', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
 
-					sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02', '03' ],
-						[ '10[]', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
-				} );
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10[]', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
 
-				it( 'should move to the cell on the bottom right of the selection', () => {
-					editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+					it( 'should move to the cell on the bottom right of the selection', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
 
-					sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '[]22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
-				} );
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '[]22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
 
-				it( 'should move to the cell above the selection', () => {
-					editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+					it( 'should move to the cell above the selection', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
 
-					sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01[]', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01[]', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell below the selection', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '[]31', '32', '33' ]
+						] ) );
+					} );
 				} );
 
-				it( 'should move to the cell below the selection', () => {
-					editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+				describe( 'with shift pressed', () => {
+					beforeEach( () => {
+						leftArrowDomEvtDataStub.shiftKey = true;
+						rightArrowDomEvtDataStub.shiftKey = true;
+						upArrowDomEvtDataStub.shiftKey = true;
+						downArrowDomEvtDataStub.shiftKey = true;
+					} );
 
-					sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+					it( 'should expand the selection to the cell on the left from the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '[]31', '32', '33' ]
-					] ) );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 4 );
+					} );
+
+					it( 'should expand the selection to the cell on the right from the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 4 );
+					} );
+
+					it( 'should shrink the selection to the anchor cell', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( anchorCell );
+						expect( selection.rangeCount ).to.equal( 1 );
+					} );
+
+					it( 'should expand the selection to the cell below the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 3, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 3 );
+					} );
 				} );
 			} );
 
-			describe( 'on multiple table cell selected horizontally', () => {
+			describe( 'on multiple table cells selected vertically (the anchor cell below the focus cell)', () => {
+				let anchorCell, focusCell;
+
 				beforeEach( () => {
 					setModelData( model, modelTable( [
 						[ '00', '01', '02', '03' ],
@@ -1170,68 +1463,250 @@ describe( 'TableNavigation', () => {
 						[ '30', '31', '32', '33' ]
 					] ) );
 
-					// Note that this also tests that selection direction doesn't matter.
+					anchorCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
+					focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-					tableSelection._setCellSelection(
-						modelRoot.getNodeByPath( [ 0, 1, 2 ] ),
-						modelRoot.getNodeByPath( [ 0, 1, 1 ] )
-					);
+					tableSelection.setCellSelection( anchorCell, focusCell );
 				} );
 
-				it( 'should move to the cell on the top left of the selection', () => {
-					editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
+				describe( 'without shift pressed', () => {
+					it( 'should move to the cell on the top left of the selection', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
 
-					sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02', '03' ],
-						[ '10[]', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10[]', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell on the bottom right of the selection', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '[]22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell above the selection', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01[]', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell below the selection', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '[]31', '32', '33' ]
+						] ) );
+					} );
 				} );
 
-				it( 'should move to the cell on the bottom right of the selection', () => {
-					editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+				describe( 'with shift pressed', () => {
+					beforeEach( () => {
+						leftArrowDomEvtDataStub.shiftKey = true;
+						rightArrowDomEvtDataStub.shiftKey = true;
+						upArrowDomEvtDataStub.shiftKey = true;
+						downArrowDomEvtDataStub.shiftKey = true;
+					} );
 
-					sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+					it( 'should expand the selection to the cell on the left from the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 0 ] ) );
+						expect( selection.rangeCount ).to.equal( 4 );
+					} );
+
+					it( 'should expand the selection to the cell on the right from the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 4 );
+					} );
+
+					it( 'should shrink the selection to the anchor cell', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( anchorCell );
+						expect( selection.rangeCount ).to.equal( 1 );
+					} );
+
+					it( 'should expand the selection to the cell below the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
+						expect( selection.rangeCount ).to.equal( 3 );
+					} );
+				} );
+			} );
+
+			describe( 'on multiple table cell selected horizontally (the anchor cell is to the left of the focus cell ', () => {
+				let anchorCell, focusCell;
+
+				beforeEach( () => {
+					setModelData( model, modelTable( [
 						[ '00', '01', '02', '03' ],
-						[ '10', '11', '12', '[]13' ],
+						[ '10', '11', '12', '13' ],
 						[ '20', '21', '22', '23' ],
 						[ '30', '31', '32', '33' ]
 					] ) );
+
+					anchorCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
+					focusCell = modelRoot.getNodeByPath( [ 0, 1, 2 ] );
+
+					tableSelection.setCellSelection( anchorCell, focusCell );
 				} );
 
-				it( 'should move to the cell above the selection', () => {
-					editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+				describe( 'without shift pressed', () => {
+					it( 'should move to the cell on the top left of the selection', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
 
-					sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01[]', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10[]', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell on the bottom right of the selection', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '[]13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell above the selection', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01[]', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
+
+					it( 'should move to the cell below the selection', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						assertEqualMarkup( getModelData( model ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', '[]22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+					} );
 				} );
 
-				it( 'should move to the cell below the selection', () => {
-					editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+				describe( 'with shift pressed', () => {
+					beforeEach( () => {
+						leftArrowDomEvtDataStub.shiftKey = true;
+						rightArrowDomEvtDataStub.shiftKey = true;
+						upArrowDomEvtDataStub.shiftKey = true;
+						downArrowDomEvtDataStub.shiftKey = true;
+					} );
 
-					sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
-					sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+					it( 'should expand the selection to the cell above the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
 
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00', '01', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '[]22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 4 );
+					} );
+
+					it( 'should expand the selection to the cell below the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
+						expect( selection.rangeCount ).to.equal( 4 );
+					} );
+
+					it( 'should shrink the selection to the anchor cell', () => {
+						editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( leftArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( anchorCell );
+						expect( selection.rangeCount ).to.equal( 1 );
+					} );
+
+					it( 'should expand the selection to the cell on the right to the focus cell', () => {
+						editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
+
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.preventDefault );
+						sinon.assert.calledOnce( rightArrowDomEvtDataStub.stopPropagation );
+
+						expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+						expect( tableSelection.getFocusCell() ).to.equal( modelRoot.getNodeByPath( [ 0, 1, 3 ] ) );
+						expect( selection.rangeCount ).to.equal( 3 );
+					} );
 				} );
 			} );
 
@@ -1244,7 +1719,7 @@ describe( 'TableNavigation', () => {
 						[ '30', '31', '32', '33' ]
 					] ) );
 
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 2, 2 ] )
 					);
@@ -2524,7 +2999,7 @@ describe( 'TableNavigation', () => {
 						[ '20', '21', '22' ]
 					] ) );
 
-					tableSelection._setCellSelection(
+					tableSelection.setCellSelection(
 						modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 						modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 					);

+ 6 - 6
packages/ckeditor5-table/tests/tableselection-integration.js

@@ -40,7 +40,7 @@ describe( 'TableSelection - integration', () => {
 		} );
 
 		it( 'should clear contents of the selected table cells and put selection in last cell on backward delete', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -62,7 +62,7 @@ describe( 'TableSelection - integration', () => {
 		} );
 
 		it( 'should clear contents of the selected table cells and put selection in last cell on forward delete', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -113,7 +113,7 @@ describe( 'TableSelection - integration', () => {
 		} );
 
 		it( 'should clear contents of the selected table cells and put selection in last cell on user input', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -170,7 +170,7 @@ describe( 'TableSelection - integration', () => {
 		} );
 
 		it( 'allows pasting over multi-cell selection', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -192,7 +192,7 @@ describe( 'TableSelection - integration', () => {
 		} );
 
 		it( 'allows inserting a horizontal line over a multi-range selection', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -231,7 +231,7 @@ describe( 'TableSelection - integration', () => {
 		it( 'works with merge cells command', () => {
 			setModelData( editor.model, modelTable( [ [ '00', '01' ] ] ) );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
 			);

+ 128 - 14
packages/ckeditor5-table/tests/tableselection.js

@@ -53,7 +53,7 @@ describe( 'table selection', () => {
 				const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 				const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					firstCell,
 					lastCell
 				);
@@ -74,7 +74,7 @@ describe( 'table selection', () => {
 				const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 				const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-				tableSelection._setCellSelection(
+				tableSelection.setCellSelection(
 					firstCell,
 					lastCell
 				);
@@ -585,7 +585,7 @@ describe( 'table selection', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				firstCell,
 				lastCell
 			);
@@ -599,7 +599,7 @@ describe( 'table selection', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				firstCell,
 				lastCell
 			);
@@ -613,7 +613,7 @@ describe( 'table selection', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
 
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				firstCell,
 				lastCell
 			);
@@ -627,7 +627,7 @@ describe( 'table selection', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
 				firstCell, modelRoot.getNodeByPath( [ 0, 1, 1 ] ), lastCell
@@ -638,7 +638,7 @@ describe( 'table selection', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
 				lastCell, firstCell
@@ -667,7 +667,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should return document fragment for selected table cells', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -676,7 +676,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should return cells in the source order in case of forward selection', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -688,7 +688,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should return cells in the source order in case of backward selection', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] )
 			);
@@ -719,7 +719,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should put selection in the last selected cell after removing content (backward delete)', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
 				modelRoot.getChild( 0 ).getChild( 1 ).getChild( 1 )
 			);
@@ -734,7 +734,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should put selection in the last selected cell after removing content (forward delete)', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
 				modelRoot.getChild( 0 ).getChild( 1 ).getChild( 1 )
 			);
@@ -749,7 +749,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should clear single cell if selected', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
 				modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
 			);
@@ -764,7 +764,7 @@ describe( 'table selection', () => {
 		} );
 
 		it( 'should work with document selection passed to Model#deleteContent()', () => {
-			tableSelection._setCellSelection(
+			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
 			);
@@ -831,6 +831,120 @@ describe( 'table selection', () => {
 		} );
 	} );
 
+	describe( 'getAnchorCell() and getFocusCell()', () => {
+		beforeEach( async () => {
+			editor = await createEditor();
+			model = editor.model;
+			modelRoot = model.document.getRoot();
+			tableSelection = editor.plugins.get( TableSelection );
+
+			setModelData( model, modelTable( [
+				[ '[]00', '01', '02' ],
+				[ '10', '11', '12' ],
+				[ '20', '21', '22' ]
+			] ) );
+		} );
+
+		it( 'should return null if no table cell is selected', () => {
+			expect( tableSelection.getAnchorCell() ).to.be.null;
+			expect( tableSelection.getFocusCell() ).to.be.null;
+		} );
+
+		it( 'getAnchorCell() should return the table cell from the first range in the selection', () => {
+			const anchorCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
+
+			tableSelection.setCellSelection( anchorCell, focusCell );
+
+			expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+		} );
+
+		it( 'getFocusCell() should return the table cell from the last range in the selection', () => {
+			const anchorCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
+
+			tableSelection.setCellSelection( anchorCell, focusCell );
+
+			expect( tableSelection.getFocusCell() ).to.equal( focusCell );
+		} );
+	} );
+
+	describe( 'the selection ranges order', () => {
+		let selection, table;
+
+		beforeEach( async () => {
+			editor = await createEditor();
+			model = editor.model;
+			selection = model.document.selection;
+			modelRoot = model.document.getRoot();
+			tableSelection = editor.plugins.get( TableSelection );
+
+			setModelData( model, modelTable( [
+				[ '00', '01', '02' ],
+				[ '10', '11', '12' ],
+				[ '20', '21', '22' ]
+			] ) );
+
+			table = modelRoot.getChild( 0 );
+		} );
+
+		it( 'should be to below right', () => {
+			const anchorCell = table.getChild( 1 ).getChild( 1 );
+			const focusCell = table.getChild( 2 ).getChild( 2 );
+
+			tableSelection.setCellSelection( anchorCell, focusCell );
+
+			assertSelection( anchorCell, focusCell, 4 );
+			expect( tableSelection.getFocusCell() ).to.equal( focusCell );
+			expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+			expect( selection.isBackward ).to.be.false;
+		} );
+
+		it( 'should be to below left', () => {
+			const anchorCell = table.getChild( 1 ).getChild( 1 );
+			const focusCell = table.getChild( 2 ).getChild( 0 );
+
+			tableSelection.setCellSelection( anchorCell, focusCell );
+
+			assertSelection( anchorCell, focusCell, 4 );
+			expect( tableSelection.getFocusCell() ).to.equal( focusCell );
+			expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+			expect( selection.isBackward ).to.be.true;
+		} );
+
+		it( 'should be to above left', () => {
+			const anchorCell = table.getChild( 1 ).getChild( 1 );
+			const focusCell = table.getChild( 0 ).getChild( 0 );
+
+			tableSelection.setCellSelection( anchorCell, focusCell );
+
+			assertSelection( anchorCell, focusCell, 4 );
+			expect( tableSelection.getFocusCell() ).to.equal( focusCell );
+			expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+			expect( selection.isBackward ).to.be.true;
+		} );
+
+		it( 'should be to above right', () => {
+			const anchorCell = table.getChild( 1 ).getChild( 1 );
+			const focusCell = table.getChild( 0 ).getChild( 2 );
+
+			tableSelection.setCellSelection( anchorCell, focusCell );
+
+			assertSelection( anchorCell, focusCell, 4 );
+			expect( tableSelection.getFocusCell() ).to.equal( focusCell );
+			expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
+			expect( selection.isBackward ).to.be.true;
+		} );
+
+		function assertSelection( anchorCell, focusCell, count ) {
+			const cells = [ ...selection.getRanges() ].map( range => range.getContainedElement() );
+
+			expect( selection.rangeCount ).to.equal( count );
+			expect( cells[ 0 ] ).to.equal( anchorCell );
+			expect( cells[ cells.length - 1 ] ).to.equal( focusCell );
+		}
+	} );
+
 	function createEditor() {
 		return ClassicTestEditor.create( editorElement, {
 			plugins: [ TableEditing, TableSelection, Paragraph, Typing ]

+ 5 - 5
packages/ckeditor5-table/tests/utils.js

@@ -92,7 +92,7 @@ describe( 'table utils', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( getSelectedTableCells( selection ) ).to.have.ordered.members( [
 				firstCell, lastCell
@@ -103,7 +103,7 @@ describe( 'table utils', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( getSelectedTableCells( selection ) ).to.have.ordered.members( [
 				firstCell,
@@ -117,7 +117,7 @@ describe( 'table utils', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( getSelectedTableCells( selection ) ).to.have.ordered.members( [
 				firstCell,
@@ -130,7 +130,7 @@ describe( 'table utils', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( getSelectedTableCells( selection ) ).to.have.ordered.members( [
 				firstCell,
@@ -315,7 +315,7 @@ describe( 'table utils', () => {
 			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
 			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
 
-			tableSelection._setCellSelection( firstCell, lastCell );
+			tableSelection.setCellSelection( firstCell, lastCell );
 
 			expect( Array.from( getSelectionAffectedTableCells( selection ) ) ).to.have.ordered.members( [
 				firstCell, lastCell