Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #291 from ckeditor/i/6357
Browse files Browse the repository at this point in the history
Other: The position of table cell properties balloon should be in relation to multiple selected cells. Closes ckeditor/ckeditor5#6357.
  • Loading branch information
jodator authored Mar 30, 2020
2 parents d239f69 + d5ddad1 commit e2dff56
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 10 deletions.
51 changes: 47 additions & 4 deletions src/ui/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ColorInputView from './colorinputview';
import { isColor, isLength, isPercentage } from '@ckeditor/ckeditor5-engine/src/view/styles/utils';
import { getTableWidgetAncestor } from '../utils';
import { findAncestor } from '../commands/utils';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';

const DEFAULT_BALLOON_POSITIONS = BalloonPanelView.defaultPositions;
const BALLOON_POSITIONS = [
Expand Down Expand Up @@ -81,12 +82,26 @@ export function getBalloonTablePositionData( editor ) {
* @returns {module:utils/dom/position~Options}
*/
export function getBalloonCellPositionData( editor ) {
// This is a bit naive. See https://github.com/ckeditor/ckeditor5/issues/6357.
const modelTableCell = getTableCellAtPosition( editor.model.document.selection.getFirstPosition() );
const viewTableCell = editor.editing.mapper.toViewElement( modelTableCell );
const mapper = editor.editing.mapper;
const domConverter = editor.editing.view.domConverter;
const selection = editor.model.document.selection;

if ( selection.rangeCount > 1 ) {
return {
target: () => createBoundingRect( selection.getRanges(), modelRange => {
const modelTableCell = getTableCellAtPosition( modelRange.start );
const viewTableCell = mapper.toViewElement( modelTableCell );
return new Rect( domConverter.viewToDom( viewTableCell ) );
} ),
positions: BALLOON_POSITIONS
};
}

const modelTableCell = getTableCellAtPosition( selection.getFirstPosition() );
const viewTableCell = mapper.toViewElement( modelTableCell );

return {
target: editor.editing.view.domConverter.viewToDom( viewTableCell ),
target: domConverter.viewToDom( viewTableCell ),
positions: BALLOON_POSITIONS
};
}
Expand Down Expand Up @@ -478,3 +493,31 @@ function getTableCellAtPosition( position ) {

return isTableCellSelected ? position.nodeAfter : findAncestor( 'tableCell', position );
}

// Returns bounding rect for list of rects.
//
// @param {Array.<module:utils/dom/rect~Rect>|Array.<*>} list List of `Rect`s or any list to map by `mapFn`.
// @param {Function} mapFn Mapping function for list elements.
// @returns {module:utils/dom/rect~Rect}
function createBoundingRect( list, mapFn ) {
const rectData = {
left: Number.POSITIVE_INFINITY,
top: Number.POSITIVE_INFINITY,
right: Number.NEGATIVE_INFINITY,
bottom: Number.NEGATIVE_INFINITY
};

for ( const item of list ) {
const rect = mapFn( item );

rectData.left = Math.min( rectData.left, rect.left );
rectData.top = Math.min( rectData.top, rect.top );
rectData.right = Math.max( rectData.right, rect.right );
rectData.bottom = Math.max( rectData.bottom, rect.bottom );
}

rectData.width = rectData.right - rectData.left;
rectData.height = rectData.bottom - rectData.top;

return new Rect( rectData );
}
109 changes: 103 additions & 6 deletions tests/ui/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ import {
fillToolbar
} from '../../src/ui/utils';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { modelTable } from '../_utils/utils';

describe( 'UI Utils', () => {
let editor, editingView, balloon, editorElement;

testUtils.createSinonSandbox();

beforeEach( () => {
editorElement = global.document.createElement( 'div' );
global.document.body.appendChild( editorElement );
Expand Down Expand Up @@ -152,14 +156,35 @@ describe( 'UI Utils', () => {
} );

describe( 'getBalloonCellPositionData()', () => {
it( 'returns the position data', () => {
const defaultPositions = BalloonPanelView.defaultPositions;
let modelRoot;

setData( editor.model, '<table><tableRow>' +
'<tableCell><paragraph>foo</paragraph></tableCell>' +
'<tableCell><paragraph>[bar]</paragraph></tableCell>' +
'</tableRow></table>' );
beforeEach( () => {
setData( editor.model, modelTable( [
[ '11[]', '12', '13' ],
[ '21', '22', '23' ],
[ '31', '32', '33' ]
] ) );

modelRoot = editor.model.document.getRoot();

for ( let row = 0; row < 3; row++ ) {
for ( let col = 0; col < 3; col++ ) {
const modelCell = modelRoot.getNodeByPath( [ 0, row, col ] );
const viewCell = editor.editing.mapper.toViewElement( modelCell );
const cellDomElement = editingView.domConverter.viewToDom( viewCell );

mockBoundingBox( cellDomElement, {
top: 100 + row * 10,
left: 100 + col * 10,
height: 10,
width: 10
} );
}
}
} );

it( 'returns the position data', () => {
const defaultPositions = BalloonPanelView.defaultPositions;
const data = getBalloonCellPositionData( editor );
const modelCell = getTableCellsContainingSelection( editor.model.document.selection )[ 0 ];
const viewCell = editor.editing.mapper.toViewElement( modelCell );
Expand All @@ -176,6 +201,78 @@ describe( 'UI Utils', () => {
]
} );
} );

it( 'returns the position data for multiple cells selected horizontally', () => {
selectTableCells( [
[ 0, 0 ],
[ 0, 1 ]
] );

const data = getBalloonCellPositionData( editor );
const targetData = data.target();

expect( targetData ).to.deep.equal( {
top: 100,
left: 100,
right: 120,
bottom: 110,
width: 20,
height: 10
} );
} );

it( 'returns the position data for multiple cells selected vertically', () => {
selectTableCells( [
[ 0, 1 ],
[ 1, 1 ]
] );

const data = getBalloonCellPositionData( editor );
const targetData = data.target();

expect( targetData ).to.deep.equal( {
top: 100,
left: 110,
right: 120,
bottom: 120,
width: 10,
height: 20
} );
} );

it( 'returns the position data for multiple cells selected', () => {
selectTableCells( [
[ 0, 1 ],
[ 1, 0 ],
[ 1, 1 ]
] );

const data = getBalloonCellPositionData( editor );
const targetData = data.target();

expect( targetData ).to.deep.equal( {
top: 100,
left: 100,
right: 120,
bottom: 120,
width: 20,
height: 20
} );
} );

function selectTableCells( paths ) {
editor.model.change( writer => {
writer.setSelection( paths.map( path => writer.createRangeOn( modelRoot.getNodeByPath( [ 0, ...path ] ) ) ) );
} );
}

function mockBoundingBox( element, data ) {
testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
...data,
right: data.left + data.width,
bottom: data.top + data.height
} );
}
} );

describe( 'getBorderStyleLabels()', () => {
Expand Down

0 comments on commit e2dff56

Please sign in to comment.