@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.
- package/LICENSE.md +2 -2
- package/README.md +2 -1
- package/build/table.js +1 -1
- package/build/translations/en-au.js +1 -1
- package/build/translations/hr.js +1 -1
- package/build/translations/lv.js +1 -1
- package/build/translations/pt.js +1 -0
- package/build/translations/sk.js +1 -1
- package/build/translations/ur.js +1 -0
- package/build/translations/zh-cn.js +1 -1
- package/ckeditor5-metadata.json +19 -0
- package/lang/translations/en-au.po +3 -3
- package/lang/translations/es.po +1 -1
- package/lang/translations/fr.po +1 -1
- package/lang/translations/hr.po +3 -3
- package/lang/translations/it.po +1 -1
- package/lang/translations/lv.po +40 -40
- package/lang/translations/pt-br.po +1 -1
- package/lang/translations/pt.po +261 -0
- package/lang/translations/sk.po +3 -3
- package/lang/translations/ur.po +261 -0
- package/lang/translations/zh-cn.po +3 -3
- package/package.json +26 -23
- package/src/commands/inserttablecommand.js +1 -5
- package/src/index.js +3 -0
- package/src/tablecolumnresize/constants.js +32 -0
- package/src/tablecolumnresize/converters.js +126 -0
- package/src/tablecolumnresize/tablecolumnresizeediting.js +758 -0
- package/src/tablecolumnresize/utils.js +367 -0
- package/src/tablecolumnresize.js +36 -0
- package/src/tableediting.js +3 -5
- package/src/tablekeyboard.js +68 -66
- package/theme/tablecolumnresize.css +59 -0
- package/build/table.js.map +0 -1
|
@@ -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
|
+
}
|
package/src/tableediting.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/src/tablekeyboard.js
CHANGED
|
@@ -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:
|
|
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/
|
|
59
|
-
* @param {
|
|
55
|
+
* @param {module:engine/view/observer/bubblingeventinfo~BubblingEventInfo} bubblingEventInfo
|
|
56
|
+
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
|
|
60
57
|
*/
|
|
61
|
-
_handleTabOnSelectedTable(
|
|
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
|
-
|
|
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
|
-
*
|
|
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 {
|
|
81
|
+
* @param {module:engine/view/observer/bubblingeventinfo~BubblingEventInfo} bubblingEventInfo
|
|
82
|
+
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
|
|
83
83
|
*/
|
|
84
|
-
|
|
84
|
+
_handleTab( bubblingEventInfo, domEventData ) {
|
|
85
85
|
const editor = this.editor;
|
|
86
86
|
const tableUtils = this.editor.plugins.get( TableUtils );
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
let tableCell = tableUtils.getTableCellsContainingSelection( selection )[ 0 ];
|
|
88
|
+
const selection = editor.model.document.selection;
|
|
89
|
+
const isForward = !domEventData.shiftKey;
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
tableCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
|
|
94
|
-
}
|
|
91
|
+
let tableCell = tableUtils.getTableCellsContainingSelection( selection )[ 0 ];
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
if ( !tableCell ) {
|
|
94
|
+
tableCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
|
|
95
|
+
}
|
|
99
96
|
|
|
100
|
-
|
|
97
|
+
if ( !tableCell ) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
domEventData.preventDefault();
|
|
102
|
+
domEventData.stopPropagation();
|
|
103
|
+
bubblingEventInfo.stop();
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
const tableRow = tableCell.parent;
|
|
106
|
+
const table = tableRow.parent;
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
const currentRowIndex = table.getChildIndex( tableRow );
|
|
109
|
+
const currentCellIndex = tableRow.getChildIndex( tableCell );
|
|
109
110
|
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
const isLastCellInRow = currentCellIndex === tableRow.childCount - 1;
|
|
123
|
+
const isLastRow = currentRowIndex === tableUtils.getRows( table ) - 1;
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
+
let cellToFocus;
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
// Move to the first cell in the next row.
|
|
142
|
+
if ( isForward && isLastCellInRow ) {
|
|
143
|
+
const nextRow = table.getChild( currentRowIndex + 1 );
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
+
}
|