UNPKG

18 kBTypeScriptView Raw
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 */
5import { Plugin } from 'ckeditor5/src/core';
6import type { DocumentSelection, Element, Range, Selection, Writer } from 'ckeditor5/src/engine';
7import TableWalker, { type TableWalkerOptions } from './tablewalker';
8type IndexesObject = {
9 first: number;
10 last: number;
11};
12/**
13 * The table utilities plugin.
14 */
15export 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}
448export {};