Skip to content

Commit

Permalink
Merge pull request #7355 from ckeditor/i/6757
Browse files Browse the repository at this point in the history
Other (table): Extracted `TableMouse` plugin from `TableSelection` plugin. Closes #6757.

MINOR BREAKING CHANGE (table): The `TableNavigation` plugin renamed to `TableKeyboard`.
  • Loading branch information
jodator authored Jun 2, 2020
2 parents 893b873 + 7adbaf3 commit 4d2f5f9
Show file tree
Hide file tree
Showing 10 changed files with 878 additions and 754 deletions.
8 changes: 5 additions & 3 deletions packages/ckeditor5-table/src/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import TableEditing from './tableediting';
import TableUI from './tableui';
import TableSelection from './tableselection';
import TableClipboard from './tableclipboard';
import TableNavigation from './tablenavigation';
import TableKeyboard from './tablekeyboard';
import TableMouse from './tablemouse';
import Widget from '@ckeditor/ckeditor5-widget/src/widget';

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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

/**
* @module table/tablenavigation
* @module table/tablekeyboard
*/

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

/**
Expand Down
223 changes: 223 additions & 0 deletions packages/ckeditor5-table/src/tablemouse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module table/tablemouse
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import TableSelection from './tableselection';
import MouseEventsObserver from './tablemouse/mouseeventsobserver';

import { findAncestor } from './utils/common';
import { getTableCellsContainingSelection } from './utils/selection';

/**
* This plugin enables a table cells' selection with the mouse.
* It is loaded automatically by the {@link module:table/table~Table} plugin.
*
* @extends module:core/plugin~Plugin
*/
export default class TableMouse extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableMouse';
}

/**
* @inheritDoc
*/
static get requires() {
return [ TableSelection ];
}

/**
* @inheritDoc
*/
init() {
const editor = this.editor;

// Currently the MouseObserver only handles `mouseup` events.
// TODO move to the engine?
editor.editing.view.addObserver( MouseEventsObserver );

this._enableShiftClickSelection();
this._enableMouseDragSelection();
}

/**
* Enables making cells selection by <kbd>Shift</kbd>+click. Creates a selection from the cell which previously held
* the selection to the cell which was clicked. It can be the same cell, in which case it selects a single cell.
*
* @private
*/
_enableShiftClickSelection() {
const editor = this.editor;
let blockSelectionChange = false;

const tableSelection = editor.plugins.get( TableSelection );

this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
if ( !this.isEnabled || !tableSelection.isEnabled ) {
return;
}

if ( !domEventData.domEvent.shiftKey ) {
return;
}

const anchorCell = tableSelection.getAnchorCell() || getTableCellsContainingSelection( editor.model.document.selection )[ 0 ];

if ( !anchorCell ) {
return;
}

const targetCell = this._getModelTableCellFromDomEvent( domEventData );

if ( targetCell && haveSameTableParent( anchorCell, targetCell ) ) {
blockSelectionChange = true;
tableSelection.setCellSelection( anchorCell, targetCell );

domEventData.preventDefault();
}
} );

this.listenTo( editor.editing.view.document, 'mouseup', () => {
blockSelectionChange = false;
} );

// We need to ignore a `selectionChange` event that is fired after we render our new table cells selection.
// When downcasting table cells selection to the view, we put the view selection in the last selected cell
// in a place that may not be natively a "correct" location. This is – we put it directly in the `<td>` element.
// All browsers fire the native `selectionchange` event.
// However, all browsers except Safari return the selection in the exact place where we put it
// (even though it's visually normalized). Safari returns `<td><p>^foo` that makes our selection observer
// fire our `selectionChange` event (because the view selection that we set in the first step differs from the DOM selection).
// Since `selectionChange` is fired, we automatically update the model selection that moves it that paragraph.
// This breaks our dear cells selection.
//
// Theoretically this issue concerns only Safari that is the only browser that do normalize the selection.
// However, to avoid code branching and to have a good coverage for this event blocker, I enabled it for all browsers.
//
// Note: I'm keeping the `blockSelectionChange` state separately for shift+click and mouse drag (exact same logic)
// so I don't have to try to analyze whether they don't overlap in some weird cases. Probably they don't.
// But I have other things to do, like writing this comment.
this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
if ( blockSelectionChange ) {
// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );

evt.stop();
}
}, { priority: 'highest' } );
}

/**
* Enables making cells selection by dragging.
*
* The selection is made only on mousemove. Mouse tracking is started on mousedown.
* However, the cells selection is enabled only after the mouse cursor left the anchor cell.
* Thanks to that normal text selection within one cell works just fine. However, you can still select
* just one cell by leaving the anchor cell and moving back to it.
*
* @private
*/
_enableMouseDragSelection() {
const editor = this.editor;
let anchorCell, targetCell;
let beganCellSelection = false;
let blockSelectionChange = false;

const tableSelection = editor.plugins.get( TableSelection );

this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
if ( !this.isEnabled || !tableSelection.isEnabled ) {
return;
}

// Make sure to not conflict with the shift+click listener and any other possible handler.
if ( domEventData.domEvent.shiftKey || domEventData.domEvent.ctrlKey || domEventData.domEvent.altKey ) {
return;
}

anchorCell = this._getModelTableCellFromDomEvent( domEventData );
} );

this.listenTo( editor.editing.view.document, 'mousemove', ( evt, domEventData ) => {
if ( !domEventData.domEvent.buttons ) {
return;
}

if ( !anchorCell ) {
return;
}

const newTargetCell = this._getModelTableCellFromDomEvent( domEventData );

if ( newTargetCell && haveSameTableParent( anchorCell, newTargetCell ) ) {
targetCell = newTargetCell;

// Switch to the cell selection mode after the mouse cursor left the anchor cell.
// Switch off only on mouseup (makes selecting a single cell possible).
if ( !beganCellSelection && targetCell != anchorCell ) {
beganCellSelection = true;
}
}

// Yep, not making a cell selection yet. See method docs.
if ( !beganCellSelection ) {
return;
}

blockSelectionChange = true;
tableSelection.setCellSelection( anchorCell, targetCell );

domEventData.preventDefault();
} );

this.listenTo( editor.editing.view.document, 'mouseup', () => {
beganCellSelection = false;
blockSelectionChange = false;
anchorCell = null;
targetCell = null;
} );

// See the explanation in `_enableShiftClickSelection()`.
this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
if ( blockSelectionChange ) {
// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );

evt.stop();
}
}, { priority: 'highest' } );
}

/**
* Returns the model table cell element based on the target element of the passed DOM event.
*
* @private
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
* @returns {module:engine/model/element~Element|undefined} Returns the table cell or `undefined`.
*/
_getModelTableCellFromDomEvent( domEventData ) {
// Note: Work with positions (not element mapping) because the target element can be an attribute or other non-mapped element.
const viewTargetElement = domEventData.target;
const viewPosition = this.editor.editing.view.createPositionAt( viewTargetElement, 0 );
const modelPosition = this.editor.editing.mapper.toModelPosition( viewPosition );
const modelElement = modelPosition.parent;

if ( modelElement.is( 'tableCell' ) ) {
return modelElement;
}

return findAncestor( 'tableCell', modelElement );
}
}

function haveSameTableParent( cellA, cellB ) {
return cellA.parent.parent == cellB.parent.parent;
}
Loading

0 comments on commit 4d2f5f9

Please sign in to comment.