Преглед на файлове

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

Kuba Niegowski преди 5 години
родител
ревизия
72179046ae
променени са 2 файла, в които са добавени 153 реда и са изтрити 13 реда
  1. 50 7
      packages/ckeditor5-table/src/ui/utils.js
  2. 103 6
      packages/ckeditor5-table/tests/ui/utils.js

+ 50 - 7
packages/ckeditor5-table/src/ui/utils.js

@@ -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 = [
@@ -81,14 +82,28 @@ 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 ranges = Array.from( editor.model.document.selection.getRanges() );
+
+	if ( ranges.length > 1 ) {
+		return {
+			target: () => createBoundingRect( ranges, modelRange => {
+				const modelTableCell = getTableCellAtPosition( modelRange.start );
+				const viewTableCell = mapper.toViewElement( modelTableCell );
+				return new Rect( domConverter.viewToDom( viewTableCell ) );
+			} ),
+			positions: BALLOON_POSITIONS
+		};
+	} else {
+		const modelTableCell = getTableCellAtPosition( ranges[ 0 ].start );
+		const viewTableCell = mapper.toViewElement( modelTableCell );
 
-	return {
-		target: editor.editing.view.domConverter.viewToDom( viewTableCell ),
-		positions: BALLOON_POSITIONS
-	};
+		return {
+			target: domConverter.viewToDom( viewTableCell ),
+			positions: BALLOON_POSITIONS
+		};
+	}
 }
 
 /**
@@ -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 = null ) {
+	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 ? mapFn( item ) : 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 );
+}

+ 103 - 6
packages/ckeditor5-table/tests/ui/utils.js

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