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

Position of table cell properties balloon should be in relation to multiple selected cells. #291

Merged
merged 3 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ) {
jodator marked this conversation as resolved.
Show resolved Hide resolved
const rectData = {
jodator marked this conversation as resolved.
Show resolved Hide resolved
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