1 | /**
|
2 | * @license Copyright (c) 2003-2023, 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 | import { Plugin } from 'ckeditor5/src/core';
|
6 | import type { DocumentSelection, Element, Range, Selection, Writer } from 'ckeditor5/src/engine';
|
7 | import TableWalker, { type TableWalkerOptions } from './tablewalker';
|
8 | type IndexesObject = {
|
9 | first: number;
|
10 | last: number;
|
11 | };
|
12 | /**
|
13 | * The table utilities plugin.
|
14 | */
|
15 | export default class TableUtils extends Plugin {
|
16 | /**
|
17 | * @inheritDoc
|
18 | */
|
19 | static get pluginName(): 'TableUtils';
|
20 | /**
|
21 | * @inheritDoc
|
22 | */
|
23 | init(): void;
|
24 | /**
|
25 | * Returns the table cell location as an object with table row and table column indexes.
|
26 | *
|
27 | * For instance, in the table below:
|
28 | *
|
29 | * 0 1 2 3
|
30 | * +---+---+---+---+
|
31 | * 0 | a | b | c |
|
32 | * + + +---+
|
33 | * 1 | | | d |
|
34 | * +---+---+ +---+
|
35 | * 2 | e | | f |
|
36 | * +---+---+---+---+
|
37 | *
|
38 | * the method will return:
|
39 | *
|
40 | * ```ts
|
41 | * const cellA = table.getNodeByPath( [ 0, 0 ] );
|
42 | * editor.plugins.get( 'TableUtils' ).getCellLocation( cellA );
|
43 | * // will return { row: 0, column: 0 }
|
44 | *
|
45 | * const cellD = table.getNodeByPath( [ 1, 0 ] );
|
46 | * editor.plugins.get( 'TableUtils' ).getCellLocation( cellD );
|
47 | * // will return { row: 1, column: 3 }
|
48 | * ```
|
49 | *
|
50 | * @returns Returns a `{row, column}` object.
|
51 | */
|
52 | getCellLocation(tableCell: Element): {
|
53 | row: number;
|
54 | column: number;
|
55 | };
|
56 | /**
|
57 | * Creates an empty table with a proper structure. The table needs to be inserted into the model,
|
58 | * for example, by using the {@link module:engine/model/model~Model#insertContent} function.
|
59 | *
|
60 | * ```ts
|
61 | * model.change( ( writer ) => {
|
62 | * // Create a table of 2 rows and 7 columns:
|
63 | * const table = tableUtils.createTable( writer, { rows: 2, columns: 7 } );
|
64 | *
|
65 | * // Insert a table to the model at the best position taking the current selection:
|
66 | * model.insertContent( table );
|
67 | * }
|
68 | * ```
|
69 | *
|
70 | * @param writer The model writer.
|
71 | * @param options.rows The number of rows to create. Default value is 2.
|
72 | * @param options.columns The number of columns to create. Default value is 2.
|
73 | * @param options.headingRows The number of heading rows. Default value is 0.
|
74 | * @param options.headingColumns The number of heading columns. Default value is 0.
|
75 | * @returns The created table element.
|
76 | */
|
77 | createTable(writer: Writer, options: {
|
78 | rows?: number;
|
79 | columns?: number;
|
80 | headingRows?: number;
|
81 | headingColumns?: number;
|
82 | }): Element;
|
83 | /**
|
84 | * Inserts rows into a table.
|
85 | *
|
86 | * ```ts
|
87 | * editor.plugins.get( 'TableUtils' ).insertRows( table, { at: 1, rows: 2 } );
|
88 | * ```
|
89 | *
|
90 | * Assuming the table on the left, the above code will transform it to the table on the right:
|
91 | *
|
92 | * row index
|
93 | * 0 +---+---+---+ `at` = 1, +---+---+---+ 0
|
94 | * | a | b | c | `rows` = 2, | a | b | c |
|
95 | * 1 + +---+---+ <-- insert here + +---+---+ 1
|
96 | * | | d | e | | | | |
|
97 | * 2 + +---+---+ will give: + +---+---+ 2
|
98 | * | | f | g | | | | |
|
99 | * 3 +---+---+---+ + +---+---+ 3
|
100 | * | | d | e |
|
101 | * + +---+---+ 4
|
102 | * + + f | g |
|
103 | * +---+---+---+ 5
|
104 | *
|
105 | * @param table The table model element where the rows will be inserted.
|
106 | * @param options.at The row index at which the rows will be inserted. Default value is 0.
|
107 | * @param options.rows The number of rows to insert. Default value is 1.
|
108 | * @param options.copyStructureFromAbove The flag for copying row structure. Note that
|
109 | * the row structure will not be copied if this option is not provided.
|
110 | */
|
111 | insertRows(table: Element, options?: {
|
112 | at?: number;
|
113 | rows?: number;
|
114 | copyStructureFromAbove?: boolean;
|
115 | }): void;
|
116 | /**
|
117 | * Inserts columns into a table.
|
118 | *
|
119 | * ```ts
|
120 | * editor.plugins.get( 'TableUtils' ).insertColumns( table, { at: 1, columns: 2 } );
|
121 | * ```
|
122 | *
|
123 | * Assuming the table on the left, the above code will transform it to the table on the right:
|
124 | *
|
125 | * 0 1 2 3 0 1 2 3 4 5
|
126 | * +---+---+---+ +---+---+---+---+---+
|
127 | * | a | b | | a | b |
|
128 | * + +---+ + +---+
|
129 | * | | c | | | c |
|
130 | * +---+---+---+ will give: +---+---+---+---+---+
|
131 | * | d | e | f | | d | | | e | f |
|
132 | * +---+ +---+ +---+---+---+ +---+
|
133 | * | g | | h | | g | | | | h |
|
134 | * +---+---+---+ +---+---+---+---+---+
|
135 | * | i | | i |
|
136 | * +---+---+---+ +---+---+---+---+---+
|
137 | * ^---- insert here, `at` = 1, `columns` = 2
|
138 | *
|
139 | * @param table The table model element where the columns will be inserted.
|
140 | * @param options.at The column index at which the columns will be inserted. Default value is 0.
|
141 | * @param options.columns The number of columns to insert. Default value is 1.
|
142 | */
|
143 | insertColumns(table: Element, options?: {
|
144 | at?: number;
|
145 | columns?: number;
|
146 | }): void;
|
147 | /**
|
148 | * Removes rows from the given `table`.
|
149 | *
|
150 | * This method re-calculates the table geometry including `rowspan` attribute of table cells overlapping removed rows
|
151 | * and table headings values.
|
152 | *
|
153 | * ```ts
|
154 | * editor.plugins.get( 'TableUtils' ).removeRows( table, { at: 1, rows: 2 } );
|
155 | * ```
|
156 | *
|
157 | * Executing the above code in the context of the table on the left will transform its structure as presented on the right:
|
158 | *
|
159 | * row index
|
160 | * ┌───┬───┬───┐ `at` = 1 ┌───┬───┬───┐
|
161 | * 0 │ a │ b │ c │ `rows` = 2 │ a │ b │ c │ 0
|
162 | * │ ├───┼───┤ │ ├───┼───┤
|
163 | * 1 │ │ d │ e │ <-- remove from here │ │ d │ g │ 1
|
164 | * │ │ ├───┤ will give: ├───┼───┼───┤
|
165 | * 2 │ │ │ f │ │ h │ i │ j │ 2
|
166 | * │ │ ├───┤ └───┴───┴───┘
|
167 | * 3 │ │ │ g │
|
168 | * ├───┼───┼───┤
|
169 | * 4 │ h │ i │ j │
|
170 | * └───┴───┴───┘
|
171 | *
|
172 | * @param options.at The row index at which the removing rows will start.
|
173 | * @param options.rows The number of rows to remove. Default value is 1.
|
174 | */
|
175 | removeRows(table: Element, options: {
|
176 | at: number;
|
177 | rows?: number;
|
178 | }): void;
|
179 | /**
|
180 | * Removes columns from the given `table`.
|
181 | *
|
182 | * This method re-calculates the table geometry including the `colspan` attribute of table cells overlapping removed columns
|
183 | * and table headings values.
|
184 | *
|
185 | * ```ts
|
186 | * editor.plugins.get( 'TableUtils' ).removeColumns( table, { at: 1, columns: 2 } );
|
187 | * ```
|
188 | *
|
189 | * Executing the above code in the context of the table on the left will transform its structure as presented on the right:
|
190 | *
|
191 | * 0 1 2 3 4 0 1 2
|
192 | * ┌───────────────┬───┐ ┌───────┬───┐
|
193 | * │ a │ b │ │ a │ b │
|
194 | * │ ├───┤ │ ├───┤
|
195 | * │ │ c │ │ │ c │
|
196 | * ├───┬───┬───┬───┼───┤ will give: ├───┬───┼───┤
|
197 | * │ d │ e │ f │ g │ h │ │ d │ g │ h │
|
198 | * ├───┼───┼───┤ ├───┤ ├───┤ ├───┤
|
199 | * │ i │ j │ k │ │ l │ │ i │ │ l │
|
200 | * ├───┴───┴───┴───┴───┤ ├───┴───┴───┤
|
201 | * │ m │ │ m │
|
202 | * └───────────────────┘ └───────────┘
|
203 | * ^---- remove from here, `at` = 1, `columns` = 2
|
204 | *
|
205 | * @param options.at The row index at which the removing columns will start.
|
206 | * @param options.columns The number of columns to remove.
|
207 | */
|
208 | removeColumns(table: Element, options: {
|
209 | at: number;
|
210 | columns?: number;
|
211 | }): void;
|
212 | /**
|
213 | * Divides a table cell vertically into several ones.
|
214 | *
|
215 | * The cell will be visually split into more cells by updating colspans of other cells in a column
|
216 | * and inserting cells (columns) after that cell.
|
217 | *
|
218 | * In the table below, if cell "a" is split into 3 cells:
|
219 | *
|
220 | * +---+---+---+
|
221 | * | a | b | c |
|
222 | * +---+---+---+
|
223 | * | d | e | f |
|
224 | * +---+---+---+
|
225 | *
|
226 | * it will result in the table below:
|
227 | *
|
228 | * +---+---+---+---+---+
|
229 | * | a | | | b | c |
|
230 | * +---+---+---+---+---+
|
231 | * | d | e | f |
|
232 | * +---+---+---+---+---+
|
233 | *
|
234 | * So cell "d" will get its `colspan` updated to `3` and 2 cells will be added (2 columns will be created).
|
235 | *
|
236 | * Splitting a cell that already has a `colspan` attribute set will distribute the cell `colspan` evenly and the remainder
|
237 | * will be left to the original cell:
|
238 | *
|
239 | * +---+---+---+
|
240 | * | a |
|
241 | * +---+---+---+
|
242 | * | b | c | d |
|
243 | * +---+---+---+
|
244 | *
|
245 | * Splitting cell "a" with `colspan=3` into 2 cells will create 1 cell with a `colspan=a` and cell "a" that will have `colspan=2`:
|
246 | *
|
247 | * +---+---+---+
|
248 | * | a | |
|
249 | * +---+---+---+
|
250 | * | b | c | d |
|
251 | * +---+---+---+
|
252 | */
|
253 | splitCellVertically(tableCell: Element, numberOfCells?: number): void;
|
254 | /**
|
255 | * Divides a table cell horizontally into several ones.
|
256 | *
|
257 | * The cell will be visually split into more cells by updating rowspans of other cells in the row and inserting rows with a single cell
|
258 | * below.
|
259 | *
|
260 | * If in the table below cell "b" is split into 3 cells:
|
261 | *
|
262 | * +---+---+---+
|
263 | * | a | b | c |
|
264 | * +---+---+---+
|
265 | * | d | e | f |
|
266 | * +---+---+---+
|
267 | *
|
268 | * It will result in the table below:
|
269 | *
|
270 | * +---+---+---+
|
271 | * | a | b | c |
|
272 | * + +---+ +
|
273 | * | | | |
|
274 | * + +---+ +
|
275 | * | | | |
|
276 | * +---+---+---+
|
277 | * | d | e | f |
|
278 | * +---+---+---+
|
279 | *
|
280 | * So cells "a" and "b" will get their `rowspan` updated to `3` and 2 rows with a single cell will be added.
|
281 | *
|
282 | * Splitting a cell that already has a `rowspan` attribute set will distribute the cell `rowspan` evenly and the remainder
|
283 | * will be left to the original cell:
|
284 | *
|
285 | * +---+---+---+
|
286 | * | a | b | c |
|
287 | * + +---+---+
|
288 | * | | d | e |
|
289 | * + +---+---+
|
290 | * | | f | g |
|
291 | * + +---+---+
|
292 | * | | h | i |
|
293 | * +---+---+---+
|
294 | *
|
295 | * Splitting cell "a" with `rowspan=4` into 3 cells will create 2 cells with a `rowspan=1` and cell "a" will have `rowspan=2`:
|
296 | *
|
297 | * +---+---+---+
|
298 | * | a | b | c |
|
299 | * + +---+---+
|
300 | * | | d | e |
|
301 | * +---+---+---+
|
302 | * | | f | g |
|
303 | * +---+---+---+
|
304 | * | | h | i |
|
305 | * +---+---+---+
|
306 | */
|
307 | splitCellHorizontally(tableCell: Element, numberOfCells?: number): void;
|
308 | /**
|
309 | * Returns the number of columns for a given table.
|
310 | *
|
311 | * ```ts
|
312 | * editor.plugins.get( 'TableUtils' ).getColumns( table );
|
313 | * ```
|
314 | *
|
315 | * @param table The table to analyze.
|
316 | */
|
317 | getColumns(table: Element): number;
|
318 | /**
|
319 | * Returns the number of rows for a given table. Any other element present in the table model is omitted.
|
320 | *
|
321 | * ```ts
|
322 | * editor.plugins.get( 'TableUtils' ).getRows( table );
|
323 | * ```
|
324 | *
|
325 | * @param table The table to analyze.
|
326 | */
|
327 | getRows(table: Element): number;
|
328 | /**
|
329 | * Creates an instance of the table walker.
|
330 | *
|
331 | * The table walker iterates internally by traversing the table from row index = 0 and column index = 0.
|
332 | * It walks row by row and column by column in order to output values defined in the options.
|
333 | * By default it will output only the locations that are occupied by a cell. To include also spanned rows and columns,
|
334 | * pass the `includeAllSlots` option.
|
335 | *
|
336 | * @internal
|
337 | * @param table A table over which the walker iterates.
|
338 | * @param options An object with configuration.
|
339 | */
|
340 | createTableWalker(table: Element, options?: TableWalkerOptions): TableWalker;
|
341 | /**
|
342 | * Returns all model table cells that are fully selected (from the outside)
|
343 | * within the provided model selection's ranges.
|
344 | *
|
345 | * To obtain the cells selected from the inside, use
|
346 | * {@link #getTableCellsContainingSelection}.
|
347 | */
|
348 | getSelectedTableCells(selection: Selection | DocumentSelection): Array<Element>;
|
349 | /**
|
350 | * Returns all model table cells that the provided model selection's ranges
|
351 | * {@link module:engine/model/range~Range#start} inside.
|
352 | *
|
353 | * To obtain the cells selected from the outside, use
|
354 | * {@link #getSelectedTableCells}.
|
355 | */
|
356 | getTableCellsContainingSelection(selection: Selection | DocumentSelection): Array<Element>;
|
357 | /**
|
358 | * Returns all model table cells that are either completely selected
|
359 | * by selection ranges or host selection range
|
360 | * {@link module:engine/model/range~Range#start start positions} inside them.
|
361 | *
|
362 | * Combines {@link #getTableCellsContainingSelection} and
|
363 | * {@link #getSelectedTableCells}.
|
364 | */
|
365 | getSelectionAffectedTableCells(selection: Selection | DocumentSelection): Array<Element>;
|
366 | /**
|
367 | * Returns an object with the `first` and `last` row index contained in the given `tableCells`.
|
368 | *
|
369 | * ```ts
|
370 | * const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
|
371 | *
|
372 | * const { first, last } = getRowIndexes( selectedTableCells );
|
373 | *
|
374 | * console.log( `Selected rows: ${ first } to ${ last }` );
|
375 | * ```
|
376 | *
|
377 | * @returns Returns an object with the `first` and `last` table row indexes.
|
378 | */
|
379 | getRowIndexes(tableCells: Array<Element>): IndexesObject;
|
380 | /**
|
381 | * Returns an object with the `first` and `last` column index contained in the given `tableCells`.
|
382 | *
|
383 | * ```ts
|
384 | * const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
|
385 | *
|
386 | * const { first, last } = getColumnIndexes( selectedTableCells );
|
387 | *
|
388 | * console.log( `Selected columns: ${ first } to ${ last }` );
|
389 | * ```
|
390 | *
|
391 | * @returns Returns an object with the `first` and `last` table column indexes.
|
392 | */
|
393 | getColumnIndexes(tableCells: Array<Element>): IndexesObject;
|
394 | /**
|
395 | * Checks if the selection contains cells that do not exceed rectangular selection.
|
396 | *
|
397 | * In a table below:
|
398 | *
|
399 | * ┌───┬───┬───┬───┐
|
400 | * │ a │ b │ c │ d │
|
401 | * ├───┴───┼───┤ │
|
402 | * │ e │ f │ │
|
403 | * │ ├───┼───┤
|
404 | * │ │ g │ h │
|
405 | * └───────┴───┴───┘
|
406 | *
|
407 | * Valid selections are these which create a solid rectangle (without gaps), such as:
|
408 | * - a, b (two horizontal cells)
|
409 | * - c, f (two vertical cells)
|
410 | * - a, b, e (cell "e" spans over four cells)
|
411 | * - c, d, f (cell d spans over a cell in the row below)
|
412 | *
|
413 | * While an invalid selection would be:
|
414 | * - a, c (the unselected cell "b" creates a gap)
|
415 | * - f, g, h (cell "d" spans over a cell from the row of "f" cell - thus creates a gap)
|
416 | */
|
417 | isSelectionRectangular(selectedTableCells: Array<Element>): boolean;
|
418 | /**
|
419 | * Returns array of sorted ranges.
|
420 | */
|
421 | sortRanges(ranges: Iterable<Range>): Array<Range>;
|
422 | /**
|
423 | * Helper method to get an object with `first` and `last` indexes from an unsorted array of indexes.
|
424 | */
|
425 | private _getFirstLastIndexesObject;
|
426 | /**
|
427 | * Checks if the selection does not mix a header (column or row) with other cells.
|
428 | *
|
429 | * For instance, in the table below valid selections consist of cells with the same letter only.
|
430 | * So, a-a (same heading row and column) or d-d (body cells) are valid while c-d or a-b are not.
|
431 | *
|
432 | * header columns
|
433 | * ↓ ↓
|
434 | * ┌───┬───┬───┬───┐
|
435 | * │ a │ a │ b │ b │ ← header row
|
436 | * ├───┼───┼───┼───┤
|
437 | * │ c │ c │ d │ d │
|
438 | * ├───┼───┼───┼───┤
|
439 | * │ c │ c │ d │ d │
|
440 | * └───┴───┴───┴───┘
|
441 | */
|
442 | private _areCellInTheSameTableSection;
|
443 | /**
|
444 | * Unified check if table rows/columns indexes are in the same heading/body section.
|
445 | */
|
446 | private _areIndexesInSameSection;
|
447 | }
|
448 | export {};
|