diff --git a/packages/ckeditor5-table/src/tableselection.ts b/packages/ckeditor5-table/src/tableselection.ts index fa8ee4ee3ab..c4eaf6819a1 100644 --- a/packages/ckeditor5-table/src/tableselection.ts +++ b/packages/ckeditor5-table/src/tableselection.ts @@ -368,9 +368,10 @@ export default class TableSelection extends Plugin { const startColumn = Math.min( startLocation.column, endLocation.column ); const endColumn = Math.max( startLocation.column, endLocation.column ); + const table = anchorCell.findAncestor( 'table' )!; - // 2-dimensional array of the selected cells to ease flipping the order of cells for backward selections. - const selectionMap: Array> = new Array( endRow - startRow + 1 ).fill( null ).map( () => [] ); + // First collect cells based on initial selection + let selectionMap: Array> = createSelectionMap( endRow - startRow + 1 ); const walkerOptions = { startRow, @@ -379,10 +380,41 @@ export default class TableSelection extends Plugin { endColumn }; - for ( const { row, cell } of new TableWalker( anchorCell.findAncestor( 'table' )!, walkerOptions ) ) { + for ( const { row, cell } of new TableWalker( table, walkerOptions ) ) { selectionMap[ row - startRow ].push( cell ); } + // If the entire last row is selected, extend the selection to include all columns in the rows above for better UX. + // This prevents a scenario where selecting the entire last row (which may contain colspans) results in only one column + // being selected in the row above, instead of all columns (when colspan is equal to the total amount of columns in the table). + // This adjustment is only active for top-left to bottom-right selections. + // See: https://github.com/ckeditor/ckeditor5/issues/17538 + if ( !startColumn && startLocation.row <= endLocation.row ) { + // Pick total width of the last selection row. It includes colspan values and not-fully selected cells. + const totalRowWidth = getTotalColumnsInRow( table, endRow ); + + // Pick width of all selected cells in the last selection row. + const selectedCellsWidth = getTotalCellsWidth( selectionMap[ selectionMap.length - 1 ] ); + + // If last row is fully selected, adjust selection to include all columns in all rows above + if ( selectedCellsWidth === totalRowWidth ) { + const totalTableColumns = tableUtils.getColumns( table ); + const fullSelectorWalker = new TableWalker( table, { + startRow, + endRow, + startColumn: 0, + endColumn: totalTableColumns + } ); + + // Let's reset the selection map and fill it with the full row selection + selectionMap = createSelectionMap( endRow - startRow + 1 ); + + for ( const { row, cell } of fullSelectorWalker ) { + selectionMap[ row - startRow ].push( cell ); + } + } + } + const flipVertically = endLocation.row < startLocation.row; const flipHorizontally = endLocation.column < startLocation.column; @@ -400,3 +432,42 @@ export default class TableSelection extends Plugin { }; } } + +/** + * Creates a 2D array of selections based on the given size. + * + * @param size The size of the map + * @returns An array of arrays of elements + */ +function createSelectionMap( size: number ): Array> { + return new Array( size ).fill( null ).map( () => [] ); +} + +/** + * Calculates the total width of columns in a table row by getting the sum of colspan values + * of all cells in that row. + * + * @param table The table element to calculate the row width for + * @param rowIndex The index of the row to process + * @returns The total width of the row (sum of colspan values) + */ +function getTotalColumnsInRow( table: Element, rowIndex: number ): number { + const cells = Array + .from( new TableWalker( table, { row: rowIndex } ) ) + .map( ( { cell } ) => cell ); + + return getTotalCellsWidth( cells ); +} + +/** + * Calculates the total width of the given cells by summing up their colspan values. + * + * @param cells An array of table cell elements to process + * @returns The total width (sum of colspan values) of all provided cells + */ +function getTotalCellsWidth( cells: Array ): number { + return cells.reduce( + ( width, cell ) => width + ( parseInt( cell.getAttribute( 'colspan' ) as string ) || 1 ), + 0 + ); +} diff --git a/packages/ckeditor5-table/tests/commands/mergecellscommand.js b/packages/ckeditor5-table/tests/commands/mergecellscommand.js index 0e63d4e6a2b..956d15bcbc6 100644 --- a/packages/ckeditor5-table/tests/commands/mergecellscommand.js +++ b/packages/ckeditor5-table/tests/commands/mergecellscommand.js @@ -745,10 +745,9 @@ describe( 'MergeCellsCommand', () => { expect( getData( model ) ).to.equalMarkup( modelTable( [ [ - '[0001' + + '[000102' + '1011' + - '2021]', - '02' + '2021]' ] ] ) ); } ); diff --git a/packages/ckeditor5-table/tests/manual/table.html b/packages/ckeditor5-table/tests/manual/table.html index 88d8a6442fa..30f8b136d21 100644 --- a/packages/ckeditor5-table/tests/manual/table.html +++ b/packages/ckeditor5-table/tests/manual/table.html @@ -6,229 +6,325 @@
-

Complex table:

- -
-
Data about the planets of our solar system (Planetary facts taken from Nasa's Planetary Fact Sheet - Metric. -
- - - - - - - - - - - - - - - - +
+
 NameMass (1024kg)Diameter (km)Density (kg/m3)Gravity (m/s2)Length of day (hours)Distance from Sun (106km)Mean temperature (°C)Number of moonsNotes
+ + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Terrestrial planetsMercury0.3304,87954273.74222.657.91670Closest to the Sun
Venus4.8712,10452438.92802.0108.24640 
Earth5.9712,75655149.824.0149.6151Our world
Mars0.6426,79239333.724.7227.9-652The red planet
Jovian planetsGas giantsJupiter1898142,984132623.19.9778.6-11067The largest planet
Saturn568120,5366879.010.71433.5-14062 
Ice giantsUranus86.851,11812718.717.22872.5-19527 
Neptune10249,528163811.016.14495.1-20014 
Dwarf planetsPluto0.01462,37020950.7153.35906.4-2255Declassified as a planet in 2006, but this remains controversial. -
lorem
lorem   lorem loremlorem     
lorem  lorem  lorem     
loremlorem lorem      
lorem        
lorem loremlorem     
lorem lorem      
loremlorem + lorem + loremloremloremlorem +
lorem + loremloremloremloremloremlorem
lorem
loremlorem        
loremlorem        
loremlorem        
loremloremloremloremloremloremloremlorem
loremloremloremloremloremloremloremlorem
loremloremloremlorem  loremlorem
loremloremloremloremloremloremloremlorem
loremloremloremlorem    
loremloremloremlorem    
loremloremloremlorem    
loremloremloremlorem    
loremloremloremlorem    
loremloremloremlorem    
loremloremloremlorem    
loremloremloremlorem    
loremloremlorem + lorem      
loremlorem      
lorem + lorem
lorem
lorem
lorem
lorem
- -

Table with 2 tbody:

- - - - - - - - - - - - - - - - -
abc
abc
- -

Table with no tbody:

- - - - - - - - - - - - -
abc
abc
- -

Table with thead section between two tbody sections

- - - - - - - - - - - - - - - - - -
2
1
3
- -

Nested tables

- - - - - - - - - - - - - - - - - -
   
  -

Paragraph before table

- - - - -
- Nested table -
-

Paragraph after table

-
 
   
diff --git a/packages/ckeditor5-table/tests/tableclipboard-paste.js b/packages/ckeditor5-table/tests/tableclipboard-paste.js index d943350691a..0e7b3f284f2 100644 --- a/packages/ckeditor5-table/tests/tableclipboard-paste.js +++ b/packages/ckeditor5-table/tests/tableclipboard-paste.js @@ -3082,23 +3082,23 @@ describe( 'table clipboard', () => { // +----+----+----+ +----+----+ // | 10 | 11 | 12 | | 14 | 15 | // +----+----+----+----+----+----+ - // | aa | ab | aa | ab | aa | 25 | + // | aa | ab | aa | ab | aa | ab | // +----+----+----+----+----+----+ - // | ba | bb | ba | bb | ba | 35 | + // | ba | bb | ba | bb | ba | bb | // +----+----+----+----+----+----+ - // | aa | ab | aa | ab | aa | | - // +----+----+----+----+----+ + - // | 50 | 51 | 52 | | | + // | aa | ab | aa | ab | aa | ab | + // +----+----+----+----+----+----+ + // | 50 | 51 | 52 | | // +----+----+----+----+----+----+ // | 60 | 61 | 62 | 63 | 64 | 65 | // +----+----+----+----+----+----+ expect( getModelData( model, { withoutSelection: true } ) ).to.equalMarkup( modelTable( [ [ '00', '01', '02', { contents: '03', rowspan: 2 }, '04', '05' ], [ '10', '11', '12', '14', '15' ], - [ 'aa', 'ab', 'aa', 'ab', 'aa', '25' ], - [ 'ba', 'bb', 'ba', 'bb', 'ba', '35' ], - [ 'aa', 'ab', 'aa', 'ab', 'aa', { contents: '', rowspan: 2 } ], - [ '50', '51', { contents: '52', colspan: 2 }, '' ], + [ 'aa', 'ab', 'aa', 'ab', 'aa', 'ab' ], + [ 'ba', 'bb', 'ba', 'bb', 'ba', 'bb' ], + [ 'aa', 'ab', 'aa', 'ab', 'aa', 'ab' ], + [ '50', '51', { contents: '52', colspan: 2 }, { contents: '', colspan: 2 } ], [ '60', '61', '62', '63', '64', '65' ] ] ) ); } );