@ckeditor/ckeditor5-table 33.0.0 → 34.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,367 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module table/tablecolumnresize/utils
8
+ */
9
+
10
+ /* istanbul ignore file */
11
+
12
+ import { global } from 'ckeditor5/src/utils';
13
+ import {
14
+ COLUMN_WIDTH_PRECISION,
15
+ COLUMN_MIN_WIDTH_AS_PERCENTAGE,
16
+ COLUMN_MIN_WIDTH_IN_PIXELS
17
+ } from './constants';
18
+
19
+ /**
20
+ * Collects all affected by the differ table model elements. The returned set may be empty.
21
+ *
22
+ * @param {Array.<module:engine/model/differ~DiffItem>} changes
23
+ * @param {module:engine/model/model~Model} model
24
+ * @returns {Set.<module:engine/model/element~Element>}
25
+ */
26
+ export function getAffectedTables( changes, model ) {
27
+ const tablesToProcess = new Set();
28
+
29
+ for ( const change of changes ) {
30
+ let referencePosition = null;
31
+
32
+ // Checks if the particular change from the differ is:
33
+ // - an insertion or removal of a table, a row or a cell,
34
+ // - an attribute change on a table, a row or a cell.
35
+ switch ( change.type ) {
36
+ case 'insert':
37
+ case 'remove':
38
+ referencePosition = [ 'table', 'tableRow', 'tableCell' ].includes( change.name ) ?
39
+ change.position :
40
+ null;
41
+
42
+ break;
43
+
44
+ case 'attribute':
45
+ if ( change.range.start.nodeAfter ) {
46
+ referencePosition = [ 'table', 'tableRow', 'tableCell' ].includes( change.range.start.nodeAfter.name ) ?
47
+ change.range.start :
48
+ null;
49
+ }
50
+
51
+ break;
52
+ }
53
+
54
+ const affectedTables = [];
55
+
56
+ if ( referencePosition ) {
57
+ const tableNode = ( referencePosition.nodeAfter && referencePosition.nodeAfter.name === 'table' ) ?
58
+ referencePosition.nodeAfter : referencePosition.findAncestor( 'table' );
59
+
60
+ if ( tableNode ) {
61
+ const range = model.createRangeOn( tableNode );
62
+
63
+ for ( const node of range.getItems() ) {
64
+ if ( node.is( 'element' ) && node.name === 'table' ) {
65
+ affectedTables.push( node );
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ const table = affectedTables;
72
+
73
+ if ( table ) {
74
+ for ( const tableItem of table ) {
75
+ tablesToProcess.add( tableItem );
76
+ }
77
+ }
78
+ }
79
+
80
+ return tablesToProcess;
81
+ }
82
+
83
+ /**
84
+ * Returns the computed width (in pixels) of the DOM element.
85
+ *
86
+ * @param {HTMLElement} domElement
87
+ * @returns {Number}
88
+ */
89
+ export function getElementWidthInPixels( domElement ) {
90
+ return parseFloat( global.window.getComputedStyle( domElement ).width );
91
+ }
92
+
93
+ /**
94
+ * Calculates the table width in pixels.
95
+ *
96
+ * @param {module:engine/model/element~Element} table
97
+ * @param {module:core/editor/editor~Editor} editor
98
+ * @returns {Number}
99
+ */
100
+ export function getTableWidthInPixels( table, editor ) {
101
+ const viewTbody = getTbodyViewElement( table, editor );
102
+ const domTbody = editor.editing.view.domConverter.mapViewToDom( viewTbody );
103
+
104
+ return getElementWidthInPixels( domTbody );
105
+ }
106
+
107
+ /**
108
+ * Calculates the column widths in pixels basing on the `columnWidths` table attribute:
109
+ * - If the value for a given column is provided in pixels then it is just converted to a number and returned.
110
+ * - Otherwise, it is assumed that unit is percentage and the column width is calculated proportionally to the whole table width.
111
+ *
112
+ * @param {module:engine/model/element~Element} table
113
+ * @param {module:core/editor/editor~Editor} editor
114
+ * @returns {Array.<Number>}
115
+ */
116
+ export function getColumnWidthsInPixels( table, editor ) {
117
+ const tableWidthInPixels = getTableWidthInPixels( table, editor );
118
+
119
+ return table.getAttribute( 'columnWidths' )
120
+ .split( ',' )
121
+ .map( columnWidth => columnWidth.trim() )
122
+ .map( columnWidth => {
123
+ return columnWidth.endsWith( 'px' ) ?
124
+ parseFloat( columnWidth ) :
125
+ parseFloat( columnWidth ) * tableWidthInPixels / 100;
126
+ } );
127
+ }
128
+
129
+ /**
130
+ * Calculates the percentage of the minimum column width given in pixels for a given table.
131
+ *
132
+ * @param {module:engine/model/element~Element} table
133
+ * @param {module:core/editor/editor~Editor} editor
134
+ * @returns {Number}
135
+ */
136
+ export function getColumnMinWidthAsPercentage( table, editor ) {
137
+ const tableWidthInPixels = getTableWidthInPixels( table, editor );
138
+
139
+ return COLUMN_MIN_WIDTH_IN_PIXELS * 100 / tableWidthInPixels;
140
+ }
141
+
142
+ /**
143
+ * Returns the column indexes on the left and right edges of a cell.
144
+ *
145
+ * @param {module:engine/model/element~Element} cell
146
+ * @returns {Object}
147
+ */
148
+ export function getColumnIndex( cell, columnIndexMap ) {
149
+ const cellColumnIndex = columnIndexMap.get( cell );
150
+ const cellWidth = cell.getAttribute( 'colspan' ) || 1;
151
+
152
+ return {
153
+ leftEdge: cellColumnIndex,
154
+ rightEdge: cellColumnIndex + cellWidth - 1
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Returns the total number of columns in a table.
160
+ *
161
+ * @param {module:engine/model/element~Element} table
162
+ * @param {module:core/editor/editor~Editor} editor
163
+ * @returns {Number}
164
+ */
165
+ export function getNumberOfColumn( table, editor ) {
166
+ return editor.plugins.get( 'TableUtils' ).getColumns( table );
167
+ }
168
+
169
+ /**
170
+ * Checks if the table is already fully rendered, with the `<colgroup>` element that defines the widths for each column.
171
+ *
172
+ * @param {module:engine/model/element~Element} table
173
+ * @param {module:core/editor/editor~Editor} editor
174
+ * @returns {Number}
175
+ */
176
+ export function isTableRendered( table, editor ) {
177
+ return !!getColgroupViewElement( table, editor );
178
+ }
179
+
180
+ // Returns the `<colgroup>` view element, if it exists in a table. Returns `undefined` otherwise.
181
+ //
182
+ // @private
183
+ // @param {module:engine/model/element~Element} table
184
+ // @param {module:core/editor/editor~Editor} editor
185
+ // @returns {module:engine/view/element~Element|undefined}
186
+ function getColgroupViewElement( table, editor ) {
187
+ const viewFigure = editor.editing.mapper.toViewElement( table );
188
+ const viewTable = [ ...viewFigure.getChildren() ].find( viewChild => viewChild.is( 'element', 'table' ) );
189
+
190
+ return [ ...viewTable.getChildren() ].find( viewChild => viewChild.is( 'element', 'colgroup' ) );
191
+ }
192
+
193
+ // Returns the `<tbody>` view element, if it exists in a table. Returns `undefined` otherwise.
194
+ //
195
+ // @private
196
+ // @param {module:engine/model/element~Element} table
197
+ // @param {module:core/editor/editor~Editor} editor
198
+ // @returns {module:engine/view/element~Element|undefined}
199
+ function getTbodyViewElement( table, editor ) {
200
+ const viewFigure = editor.editing.mapper.toViewElement( table );
201
+ const viewTable = [ ...viewFigure.getChildren() ].find( viewChild => viewChild.is( 'element', 'table' ) );
202
+
203
+ return [ ...viewTable.getChildren() ].find( viewChild => viewChild.is( 'element', 'tbody' ) );
204
+ }
205
+
206
+ /**
207
+ * Rounds the provided value to a fixed-point number with defined number of digits after the decimal point.
208
+ *
209
+ * @param {Number|String} value
210
+ * @returns {Number}
211
+ */
212
+ export function toPrecision( value ) {
213
+ const multiplier = Math.pow( 10, COLUMN_WIDTH_PRECISION );
214
+ const number = parseFloat( value );
215
+
216
+ return Math.round( number * multiplier ) / multiplier;
217
+ }
218
+
219
+ /**
220
+ * Clamps the number within the inclusive lower (min) and upper (max) bounds. Returned number is rounded using the
221
+ * {@link ~toPrecision `toPrecision()`} function.
222
+ *
223
+ * @param {Number} number
224
+ * @param {Number} min
225
+ * @param {Number} max
226
+ * @returns {Number}
227
+ */
228
+ export function clamp( number, min, max ) {
229
+ if ( number <= min ) {
230
+ return toPrecision( min );
231
+ }
232
+
233
+ if ( number >= max ) {
234
+ return toPrecision( max );
235
+ }
236
+
237
+ return toPrecision( number );
238
+ }
239
+
240
+ /**
241
+ * Creates an array with defined length and fills all elements with defined value.
242
+ *
243
+ * @param {Number} length
244
+ * @param {*} value
245
+ * @returns {Array.<*>}
246
+ */
247
+ export function fillArray( length, value ) {
248
+ return Array( length ).fill( value );
249
+ }
250
+
251
+ /**
252
+ * Sums all array values that can be parsed to a float.
253
+ *
254
+ * @param {Array.<Number>} array
255
+ * @returns {Number}
256
+ */
257
+ export function sumArray( array ) {
258
+ return array
259
+ .map( value => parseFloat( value ) )
260
+ .filter( value => !Number.isNaN( value ) )
261
+ .reduce( ( result, item ) => result + item, 0 );
262
+ }
263
+
264
+ /**
265
+ * Makes sure that the sum of the widths from all columns is 100%. If the sum of all the widths is not equal 100%, all the widths are
266
+ * changed proportionally so that they all sum back to 100%.
267
+ *
268
+ * Currently, only widths provided as percentage values are supported.
269
+ *
270
+ * @param {String} columnWidthsAttribute
271
+ * @returns {Array.<Number>}
272
+ */
273
+ export function normalizeColumnWidthsAttribute( columnWidthsAttribute ) {
274
+ const columnWidths = prepareColumnWidths( columnWidthsAttribute );
275
+ const totalWidth = sumArray( columnWidths );
276
+
277
+ if ( totalWidth === 100 ) {
278
+ return columnWidths;
279
+ }
280
+
281
+ return columnWidths
282
+ // Adjust all the columns proportionally.
283
+ .map( columnWidth => toPrecision( columnWidth * 100 / totalWidth ) )
284
+ // Due to rounding of numbers it may happen that the sum of the widths of all columns will not be exactly 100%. Therefore, the width
285
+ // of the last column is explicitly adjusted (narrowed or expanded), since all the columns have been proportionally changed already.
286
+ .map( ( columnWidth, columnIndex, columnWidths ) => {
287
+ const isLastColumn = columnIndex === columnWidths.length - 1;
288
+
289
+ if ( !isLastColumn ) {
290
+ return columnWidth;
291
+ }
292
+
293
+ const totalWidth = sumArray( columnWidths );
294
+
295
+ return toPrecision( columnWidth + 100 - totalWidth );
296
+ } );
297
+ }
298
+
299
+ // Initializes the column widths by parsing the attribute value and calculating the uninitialized column widths. The special value 'auto'
300
+ // indicates that width for the column must be calculated. The width of such uninitialized column is calculated as follows:
301
+ // - If there is enough free space in the table for all uninitialized columns to have at least the minimum allowed width for all of them,
302
+ // then set this width equally for all uninitialized columns.
303
+ // - Otherwise, just set the minimum allowed width for all uninitialized columns. The sum of all column widths will be greater than 100%,
304
+ // but then it will be adjusted proportionally to 100% in {@link #normalizeColumnWidthsAttribute `normalizeColumnWidthsAttribute()`}.
305
+ //
306
+ // @private
307
+ // @param {String} columnWidthsAttribute
308
+ // @returns {Array.<Number>}
309
+ function prepareColumnWidths( columnWidthsAttribute ) {
310
+ const columnWidths = columnWidthsAttribute
311
+ .split( ',' )
312
+ .map( columnWidth => columnWidth.trim() );
313
+
314
+ const numberOfUninitializedColumns = columnWidths.filter( columnWidth => columnWidth === 'auto' ).length;
315
+
316
+ if ( numberOfUninitializedColumns === 0 ) {
317
+ return columnWidths.map( columnWidth => toPrecision( columnWidth ) );
318
+ }
319
+
320
+ const totalWidthOfInitializedColumns = sumArray( columnWidths );
321
+
322
+ const widthForUninitializedColumn = Math.max(
323
+ ( 100 - totalWidthOfInitializedColumns ) / numberOfUninitializedColumns,
324
+ COLUMN_MIN_WIDTH_AS_PERCENTAGE
325
+ );
326
+
327
+ return columnWidths
328
+ .map( columnWidth => columnWidth === 'auto' ? widthForUninitializedColumn : columnWidth )
329
+ .map( columnWidth => toPrecision( columnWidth ) );
330
+ }
331
+
332
+ // Inserts column resizer element into a view cell.
333
+ //
334
+ // @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter View writer instance.
335
+ // @param {module:engine/view/element~Element} viewCell View cell.
336
+ export function insertColumnResizerElements( viewWriter, viewCell ) {
337
+ let viewTableColumnResizerElement = [ ...viewCell.getChildren() ]
338
+ .find( viewElement => viewElement.hasClass( 'table-column-resizer' ) );
339
+
340
+ if ( viewTableColumnResizerElement ) {
341
+ return;
342
+ }
343
+
344
+ viewTableColumnResizerElement = viewWriter.createUIElement( 'div', {
345
+ class: 'table-column-resizer'
346
+ } );
347
+
348
+ viewWriter.insert(
349
+ viewWriter.createPositionAt( viewCell, 'end' ),
350
+ viewTableColumnResizerElement
351
+ );
352
+ }
353
+
354
+ // Removes column resizer element from a view cell.
355
+ //
356
+ // @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter View writer instance.
357
+ // @param {module:engine/view/element~Element} viewCell View cell.
358
+ export function removeColumnResizerElements( viewWriter, viewCell ) {
359
+ const viewTableColumnResizerElement = [ ...viewCell.getChildren() ]
360
+ .find( viewElement => viewElement.hasClass( 'table-column-resizer' ) );
361
+
362
+ if ( !viewTableColumnResizerElement ) {
363
+ return;
364
+ }
365
+
366
+ viewWriter.remove( viewTableColumnResizerElement );
367
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module table/tablecolumnresize
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+ import TableColumnResizeEditing from './tablecolumnresize/tablecolumnresizeediting';
12
+
13
+ import '../theme/tablecolumnresize.css';
14
+
15
+ /**
16
+ * The table column resizer feature.
17
+ *
18
+ * It provides the possibility to set the width of each column in a table using a resize handle.
19
+ *
20
+ * @extends module:core/plugin~Plugin
21
+ */
22
+ export default class TableColumnResize extends Plugin {
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ static get requires() {
27
+ return [ TableColumnResizeEditing ];
28
+ }
29
+
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ static get pluginName() {
34
+ return 'TableColumnResize';
35
+ }
36
+ }
@@ -65,10 +65,8 @@ export default class TableEditing extends Plugin {
65
65
  const tableUtils = editor.plugins.get( TableUtils );
66
66
 
67
67
  schema.register( 'table', {
68
- allowWhere: '$block',
69
- allowAttributes: [ 'headingRows', 'headingColumns' ],
70
- isObject: true,
71
- isBlock: true
68
+ inheritAllFrom: '$blockObject',
69
+ allowAttributes: [ 'headingRows', 'headingColumns' ]
72
70
  } );
73
71
 
74
72
  schema.register( 'tableRow', {
@@ -77,8 +75,8 @@ export default class TableEditing extends Plugin {
77
75
  } );
78
76
 
79
77
  schema.register( 'tableCell', {
78
+ allowContentOf: '$container',
80
79
  allowIn: 'tableRow',
81
- allowChildren: '$block',
82
80
  allowAttributes: [ 'colspan', 'rowspan' ],
83
81
  isLimit: true,
84
82
  isSelectable: true
@@ -42,23 +42,20 @@ export default class TableKeyboard extends Plugin {
42
42
  const view = this.editor.editing.view;
43
43
  const viewDocument = view.document;
44
44
 
45
- // Handle Tab key navigation.
46
- this.editor.keystrokes.set( 'Tab', ( ...args ) => this._handleTabOnSelectedTable( ...args ), { priority: 'low' } );
47
- this.editor.keystrokes.set( 'Tab', this._getTabHandler( true ), { priority: 'low' } );
48
- this.editor.keystrokes.set( 'Shift+Tab', this._getTabHandler( false ), { priority: 'low' } );
49
-
50
45
  this.listenTo( viewDocument, 'arrowKey', ( ...args ) => this._onArrowKey( ...args ), { context: 'table' } );
46
+ this.listenTo( viewDocument, 'tab', ( ...args ) => this._handleTabOnSelectedTable( ...args ), { context: 'figure' } );
47
+ this.listenTo( viewDocument, 'tab', ( ...args ) => this._handleTab( ...args ), { context: [ 'th', 'td' ] } );
51
48
  }
52
49
 
53
50
  /**
54
- * Handles {@link module:engine/view/document~Document#event:keydown keydown} events for the <kbd>Tab</kbd> key executed
51
+ * Handles {@link module:engine/view/document~Document#event:tab tab} events for the <kbd>Tab</kbd> key executed
55
52
  * when the table widget is selected.
56
53
  *
57
54
  * @private
58
- * @param {module:engine/view/observer/keyobserver~KeyEventData} data Key event data.
59
- * @param {Function} cancel The stop/stopPropagation/preventDefault function.
55
+ * @param {module:engine/view/observer/bubblingeventinfo~BubblingEventInfo} bubblingEventInfo
56
+ * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
60
57
  */
61
- _handleTabOnSelectedTable( data, cancel ) {
58
+ _handleTabOnSelectedTable( bubblingEventInfo, domEventData ) {
62
59
  const editor = this.editor;
63
60
  const selection = editor.model.document.selection;
64
61
  const selectedElement = selection.getSelectedElement();
@@ -67,7 +64,9 @@ export default class TableKeyboard extends Plugin {
67
64
  return;
68
65
  }
69
66
 
70
- cancel();
67
+ domEventData.preventDefault();
68
+ domEventData.stopPropagation();
69
+ bubblingEventInfo.stop();
71
70
 
72
71
  editor.model.change( writer => {
73
72
  writer.setSelection( writer.createRangeIn( selectedElement.getChild( 0 ).getChild( 0 ) ) );
@@ -75,87 +74,90 @@ export default class TableKeyboard extends Plugin {
75
74
  }
76
75
 
77
76
  /**
78
- * Returns a handler for {@link module:engine/view/document~Document#event:keydown keydown} events for the <kbd>Tab</kbd> key executed
77
+ * Handles {@link module:engine/view/document~Document#event:tab tab} events for the <kbd>Tab</kbd> key executed
79
78
  * inside table cells.
80
79
  *
81
80
  * @private
82
- * @param {Boolean} isForward Whether this handler will move the selection to the next or the previous cell.
81
+ * @param {module:engine/view/observer/bubblingeventinfo~BubblingEventInfo} bubblingEventInfo
82
+ * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
83
83
  */
84
- _getTabHandler( isForward ) {
84
+ _handleTab( bubblingEventInfo, domEventData ) {
85
85
  const editor = this.editor;
86
86
  const tableUtils = this.editor.plugins.get( TableUtils );
87
87
 
88
- return ( domEventData, cancel ) => {
89
- const selection = editor.model.document.selection;
90
- let tableCell = tableUtils.getTableCellsContainingSelection( selection )[ 0 ];
88
+ const selection = editor.model.document.selection;
89
+ const isForward = !domEventData.shiftKey;
91
90
 
92
- if ( !tableCell ) {
93
- tableCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
94
- }
91
+ let tableCell = tableUtils.getTableCellsContainingSelection( selection )[ 0 ];
95
92
 
96
- if ( !tableCell ) {
97
- return;
98
- }
93
+ if ( !tableCell ) {
94
+ tableCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
95
+ }
99
96
 
100
- cancel();
97
+ if ( !tableCell ) {
98
+ return;
99
+ }
101
100
 
102
- const tableRow = tableCell.parent;
103
- const table = tableRow.parent;
101
+ domEventData.preventDefault();
102
+ domEventData.stopPropagation();
103
+ bubblingEventInfo.stop();
104
104
 
105
- const currentRowIndex = table.getChildIndex( tableRow );
106
- const currentCellIndex = tableRow.getChildIndex( tableCell );
105
+ const tableRow = tableCell.parent;
106
+ const table = tableRow.parent;
107
107
 
108
- const isFirstCellInRow = currentCellIndex === 0;
108
+ const currentRowIndex = table.getChildIndex( tableRow );
109
+ const currentCellIndex = tableRow.getChildIndex( tableCell );
109
110
 
110
- if ( !isForward && isFirstCellInRow && currentRowIndex === 0 ) {
111
- // Set the selection over the whole table if the selection was in the first table cell.
112
- editor.model.change( writer => {
113
- writer.setSelection( writer.createRangeOn( table ) );
114
- } );
111
+ const isFirstCellInRow = currentCellIndex === 0;
115
112
 
116
- return;
117
- }
113
+ if ( !isForward && isFirstCellInRow && currentRowIndex === 0 ) {
114
+ // Set the selection over the whole table if the selection was in the first table cell.
115
+ editor.model.change( writer => {
116
+ writer.setSelection( writer.createRangeOn( table ) );
117
+ } );
118
118
 
119
- const isLastCellInRow = currentCellIndex === tableRow.childCount - 1;
120
- const isLastRow = currentRowIndex === tableUtils.getRows( table ) - 1;
119
+ return;
120
+ }
121
121
 
122
- if ( isForward && isLastRow && isLastCellInRow ) {
123
- editor.execute( 'insertTableRowBelow' );
122
+ const isLastCellInRow = currentCellIndex === tableRow.childCount - 1;
123
+ const isLastRow = currentRowIndex === tableUtils.getRows( table ) - 1;
124
124
 
125
- // Check if the command actually added a row. If `insertTableRowBelow` execution didn't add a row (because it was disabled
126
- // or it got overwritten) set the selection over the whole table to mirror the first cell case.
127
- if ( currentRowIndex === tableUtils.getRows( table ) - 1 ) {
128
- editor.model.change( writer => {
129
- writer.setSelection( writer.createRangeOn( table ) );
130
- } );
125
+ if ( isForward && isLastRow && isLastCellInRow ) {
126
+ editor.execute( 'insertTableRowBelow' );
131
127
 
132
- return;
133
- }
128
+ // Check if the command actually added a row. If `insertTableRowBelow` execution didn't add a row (because it was disabled
129
+ // or it got overwritten) set the selection over the whole table to mirror the first cell case.
130
+ if ( currentRowIndex === tableUtils.getRows( table ) - 1 ) {
131
+ editor.model.change( writer => {
132
+ writer.setSelection( writer.createRangeOn( table ) );
133
+ } );
134
+
135
+ return;
134
136
  }
137
+ }
135
138
 
136
- let cellToFocus;
139
+ let cellToFocus;
137
140
 
138
- // Move to the first cell in the next row.
139
- if ( isForward && isLastCellInRow ) {
140
- const nextRow = table.getChild( currentRowIndex + 1 );
141
+ // Move to the first cell in the next row.
142
+ if ( isForward && isLastCellInRow ) {
143
+ const nextRow = table.getChild( currentRowIndex + 1 );
141
144
 
142
- cellToFocus = nextRow.getChild( 0 );
143
- }
144
- // Move to the last cell in the previous row.
145
- else if ( !isForward && isFirstCellInRow ) {
146
- const previousRow = table.getChild( currentRowIndex - 1 );
145
+ cellToFocus = nextRow.getChild( 0 );
146
+ }
147
+ // Move to the last cell in the previous row.
148
+ else if ( !isForward && isFirstCellInRow ) {
149
+ const previousRow = table.getChild( currentRowIndex - 1 );
147
150
 
148
- cellToFocus = previousRow.getChild( previousRow.childCount - 1 );
149
- }
150
- // Move to the next/previous cell.
151
- else {
152
- cellToFocus = tableRow.getChild( currentCellIndex + ( isForward ? 1 : -1 ) );
153
- }
151
+ cellToFocus = previousRow.getChild( previousRow.childCount - 1 );
152
+ }
153
+ // Move to the next/previous cell.
154
+ else {
155
+ cellToFocus = tableRow.getChild( currentCellIndex + ( isForward ? 1 : -1 ) );
156
+ }
154
157
 
155
- editor.model.change( writer => {
156
- writer.setSelection( writer.createRangeIn( cellToFocus ) );
157
- } );
158
- };
158
+ editor.model.change( writer => {
159
+ writer.setSelection( writer.createRangeIn( cellToFocus ) );
160
+ } );
159
161
  }
160
162
 
161
163
  /**
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ :root {
7
+ --ck-color-table-column-resizer-hover: var(--ck-color-base-active);
8
+ --ck-table-column-resizer-width: 7px;
9
+
10
+ /* The offset used for absolute positioning of the resizer element, so that it is placed exactly above the cell border.
11
+ The value is: minus half the width of the resizer decreased additionaly by the half the width of the border (0.5px). */
12
+ --ck-table-column-resizer-position-offset: calc(var(--ck-table-column-resizer-width) * -0.5 - 0.5px);
13
+ }
14
+
15
+ .ck-content .table table {
16
+ overflow: hidden;
17
+ table-layout: fixed;
18
+ }
19
+
20
+ .ck-content .table td,
21
+ .ck-content .table th {
22
+ position: relative;
23
+ }
24
+
25
+ .ck-content .table .table-column-resizer {
26
+ position: absolute;
27
+ /* The resizer element resides in each cell so to occupy the entire height of the table, which is unknown from a CSS point of view,
28
+ it is extended to an extremely high height. Even for screens with a very high pixel density, the resizer will fulfill its role as
29
+ it should, i.e. for a screen of 476 ppi the total height of the resizer will take over 350 sheets of A4 format, which is totally
30
+ unrealistic height for a single table. */
31
+ top: -999999px;
32
+ bottom: -999999px;
33
+ right: var(--ck-table-column-resizer-position-offset);
34
+ width: var(--ck-table-column-resizer-width);
35
+ cursor: col-resize;
36
+ user-select: none;
37
+ z-index: var(--ck-z-default);
38
+ }
39
+
40
+ /* The resizer elements, which are extended to an extremely high height, break the drag & drop feature in Chrome. To make it work again,
41
+ all resizers must be hidden while the table is dragged. */
42
+ .ck-content .table[draggable] .table-column-resizer {
43
+ display: none;
44
+ }
45
+
46
+ .ck-content .table .table-column-resizer:hover,
47
+ .ck-content .table .table-column-resizer__active {
48
+ background-color: var(--ck-color-table-column-resizer-hover);
49
+ opacity: 0.25;
50
+ }
51
+
52
+ .ck-content[dir=rtl] .table .table-column-resizer {
53
+ left: var(--ck-table-column-resizer-position-offset);
54
+ right: unset;
55
+ }
56
+
57
+ .ck-content.ck-read-only .table .table-column-resizer {
58
+ display: none;
59
+ }