@ckeditor/ckeditor5-table 36.0.0 → 37.0.0-alpha.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.
Files changed (166) hide show
  1. package/build/table.js +1 -1
  2. package/package.json +34 -29
  3. package/src/commands/insertcolumncommand.d.ts +61 -0
  4. package/src/commands/insertcolumncommand.js +45 -60
  5. package/src/commands/insertrowcommand.d.ts +60 -0
  6. package/src/commands/insertrowcommand.js +44 -59
  7. package/src/commands/inserttablecommand.d.ts +50 -0
  8. package/src/commands/inserttablecommand.js +51 -68
  9. package/src/commands/mergecellcommand.d.ts +76 -0
  10. package/src/commands/mergecellcommand.js +169 -244
  11. package/src/commands/mergecellscommand.d.ts +33 -0
  12. package/src/commands/mergecellscommand.js +72 -101
  13. package/src/commands/removecolumncommand.d.ts +34 -0
  14. package/src/commands/removecolumncommand.js +88 -102
  15. package/src/commands/removerowcommand.d.ts +34 -0
  16. package/src/commands/removerowcommand.js +63 -80
  17. package/src/commands/selectcolumncommand.d.ts +38 -0
  18. package/src/commands/selectcolumncommand.js +41 -54
  19. package/src/commands/selectrowcommand.d.ts +38 -0
  20. package/src/commands/selectrowcommand.js +38 -48
  21. package/src/commands/setheadercolumncommand.d.ts +55 -0
  22. package/src/commands/setheadercolumncommand.js +48 -73
  23. package/src/commands/setheaderrowcommand.d.ts +58 -0
  24. package/src/commands/setheaderrowcommand.js +56 -85
  25. package/src/commands/splitcellcommand.d.ts +49 -0
  26. package/src/commands/splitcellcommand.js +35 -49
  27. package/src/converters/downcast.d.ts +63 -0
  28. package/src/converters/downcast.js +98 -130
  29. package/src/converters/table-caption-post-fixer.d.ts +20 -0
  30. package/src/converters/table-caption-post-fixer.js +36 -52
  31. package/src/converters/table-cell-paragraph-post-fixer.d.ts +32 -0
  32. package/src/converters/table-cell-paragraph-post-fixer.js +88 -119
  33. package/src/converters/table-cell-refresh-handler.d.ts +18 -0
  34. package/src/converters/table-cell-refresh-handler.js +29 -48
  35. package/src/converters/table-headings-refresh-handler.d.ts +17 -0
  36. package/src/converters/table-headings-refresh-handler.js +35 -54
  37. package/src/converters/table-layout-post-fixer.d.ts +226 -0
  38. package/src/converters/table-layout-post-fixer.js +276 -313
  39. package/src/converters/tableproperties.d.ts +54 -0
  40. package/src/converters/tableproperties.js +136 -168
  41. package/src/converters/upcasttable.d.ts +49 -0
  42. package/src/converters/upcasttable.js +196 -251
  43. package/src/index.d.ts +29 -0
  44. package/src/index.js +0 -2
  45. package/src/plaintableoutput.d.ts +30 -0
  46. package/src/plaintableoutput.js +107 -135
  47. package/src/table.d.ts +38 -0
  48. package/src/table.js +12 -88
  49. package/src/tablecaption/tablecaptionediting.d.ts +68 -0
  50. package/src/tablecaption/tablecaptionediting.js +104 -135
  51. package/src/tablecaption/tablecaptionui.d.ts +26 -0
  52. package/src/tablecaption/tablecaptionui.js +42 -58
  53. package/src/tablecaption/toggletablecaptioncommand.d.ts +73 -0
  54. package/src/tablecaption/toggletablecaptioncommand.js +77 -92
  55. package/src/tablecaption/utils.d.ts +42 -0
  56. package/src/tablecaption/utils.js +35 -61
  57. package/src/tablecaption.d.ts +27 -0
  58. package/src/tablecaption.js +12 -19
  59. package/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.d.ts +37 -0
  60. package/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.js +14 -20
  61. package/src/tablecellproperties/commands/tablecellbordercolorcommand.d.ts +42 -0
  62. package/src/tablecellproperties/commands/tablecellbordercolorcommand.js +27 -37
  63. package/src/tablecellproperties/commands/tablecellborderstylecommand.d.ts +42 -0
  64. package/src/tablecellproperties/commands/tablecellborderstylecommand.js +27 -37
  65. package/src/tablecellproperties/commands/tablecellborderwidthcommand.d.ts +56 -0
  66. package/src/tablecellproperties/commands/tablecellborderwidthcommand.js +42 -53
  67. package/src/tablecellproperties/commands/tablecellheightcommand.d.ts +51 -0
  68. package/src/tablecellproperties/commands/tablecellheightcommand.js +29 -36
  69. package/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.d.ts +37 -0
  70. package/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.js +14 -20
  71. package/src/tablecellproperties/commands/tablecellpaddingcommand.d.ts +56 -0
  72. package/src/tablecellproperties/commands/tablecellpaddingcommand.js +42 -53
  73. package/src/tablecellproperties/commands/tablecellpropertycommand.d.ts +62 -0
  74. package/src/tablecellproperties/commands/tablecellpropertycommand.js +77 -122
  75. package/src/tablecellproperties/commands/tablecellverticalalignmentcommand.d.ts +45 -0
  76. package/src/tablecellproperties/commands/tablecellverticalalignmentcommand.js +14 -20
  77. package/src/tablecellproperties/tablecellpropertiesediting.d.ts +47 -0
  78. package/src/tablecellproperties/tablecellpropertiesediting.js +194 -236
  79. package/src/tablecellproperties/tablecellpropertiesui.d.ts +117 -0
  80. package/src/tablecellproperties/tablecellpropertiesui.js +303 -456
  81. package/src/tablecellproperties/ui/tablecellpropertiesview.d.ts +227 -0
  82. package/src/tablecellproperties/ui/tablecellpropertiesview.js +509 -844
  83. package/src/tablecellproperties.d.ts +33 -0
  84. package/src/tablecellproperties.js +12 -98
  85. package/src/tablecellwidth/commands/tablecellwidthcommand.d.ts +51 -0
  86. package/src/tablecellwidth/commands/tablecellwidthcommand.js +29 -35
  87. package/src/tablecellwidth/tablecellwidthediting.d.ts +34 -0
  88. package/src/tablecellwidth/tablecellwidthediting.js +26 -38
  89. package/src/tableclipboard.d.ts +68 -0
  90. package/src/tableclipboard.js +429 -568
  91. package/src/tablecolumnresize/constants.d.ts +20 -0
  92. package/src/tablecolumnresize/constants.js +0 -10
  93. package/src/tablecolumnresize/converters.d.ts +18 -0
  94. package/src/tablecolumnresize/converters.js +35 -119
  95. package/src/tablecolumnresize/tablecolumnresizeediting.d.ts +142 -0
  96. package/src/tablecolumnresize/tablecolumnresizeediting.js +545 -711
  97. package/src/tablecolumnresize/tablewidthscommand.d.ts +38 -0
  98. package/src/tablecolumnresize/tablewidthscommand.js +61 -0
  99. package/src/tablecolumnresize/utils.d.ts +141 -0
  100. package/src/tablecolumnresize/utils.js +256 -233
  101. package/src/tablecolumnresize.d.ts +29 -0
  102. package/src/tablecolumnresize.js +12 -19
  103. package/src/tableconfig.d.ts +341 -0
  104. package/src/tableconfig.js +5 -0
  105. package/src/tableediting.d.ts +102 -0
  106. package/src/tableediting.js +157 -176
  107. package/src/tablekeyboard.d.ts +68 -0
  108. package/src/tablekeyboard.js +261 -344
  109. package/src/tablemouse/mouseeventsobserver.d.ts +62 -0
  110. package/src/tablemouse/mouseeventsobserver.js +12 -49
  111. package/src/tablemouse.d.ts +51 -0
  112. package/src/tablemouse.js +154 -202
  113. package/src/tableproperties/commands/tablealignmentcommand.d.ts +37 -0
  114. package/src/tableproperties/commands/tablealignmentcommand.js +14 -20
  115. package/src/tableproperties/commands/tablebackgroundcolorcommand.d.ts +37 -0
  116. package/src/tableproperties/commands/tablebackgroundcolorcommand.js +14 -20
  117. package/src/tableproperties/commands/tablebordercolorcommand.d.ts +42 -0
  118. package/src/tableproperties/commands/tablebordercolorcommand.js +27 -37
  119. package/src/tableproperties/commands/tableborderstylecommand.d.ts +42 -0
  120. package/src/tableproperties/commands/tableborderstylecommand.js +27 -37
  121. package/src/tableproperties/commands/tableborderwidthcommand.d.ts +56 -0
  122. package/src/tableproperties/commands/tableborderwidthcommand.js +42 -53
  123. package/src/tableproperties/commands/tableheightcommand.d.ts +51 -0
  124. package/src/tableproperties/commands/tableheightcommand.js +29 -33
  125. package/src/tableproperties/commands/tablepropertycommand.d.ts +61 -0
  126. package/src/tableproperties/commands/tablepropertycommand.js +68 -112
  127. package/src/tableproperties/commands/tablewidthcommand.d.ts +51 -0
  128. package/src/tableproperties/commands/tablewidthcommand.js +29 -33
  129. package/src/tableproperties/tablepropertiesediting.d.ts +45 -0
  130. package/src/tableproperties/tablepropertiesediting.js +164 -210
  131. package/src/tableproperties/tablepropertiesui.d.ts +119 -0
  132. package/src/tableproperties/tablepropertiesui.js +294 -439
  133. package/src/tableproperties/ui/tablepropertiesview.d.ts +203 -0
  134. package/src/tableproperties/ui/tablepropertiesview.js +427 -718
  135. package/src/tableproperties.d.ts +33 -0
  136. package/src/tableproperties.js +12 -95
  137. package/src/tableselection.d.ts +111 -0
  138. package/src/tableselection.js +279 -376
  139. package/src/tabletoolbar.d.ts +37 -0
  140. package/src/tabletoolbar.js +39 -92
  141. package/src/tableui.d.ts +58 -0
  142. package/src/tableui.js +281 -338
  143. package/src/tableutils.d.ts +453 -0
  144. package/src/tableutils.js +1015 -1229
  145. package/src/tablewalker.d.ts +323 -0
  146. package/src/tablewalker.js +308 -548
  147. package/src/ui/colorinputview.d.ts +143 -0
  148. package/src/ui/colorinputview.js +229 -366
  149. package/src/ui/formrowview.d.ts +61 -0
  150. package/src/ui/formrowview.js +38 -84
  151. package/src/ui/inserttableview.d.ts +77 -0
  152. package/src/ui/inserttableview.js +152 -242
  153. package/src/utils/common.d.ts +42 -0
  154. package/src/utils/common.js +33 -57
  155. package/src/utils/structure.d.ts +245 -0
  156. package/src/utils/structure.js +261 -379
  157. package/src/utils/table-properties.d.ts +67 -0
  158. package/src/utils/table-properties.js +60 -81
  159. package/src/utils/ui/contextualballoon.d.ts +34 -0
  160. package/src/utils/ui/contextualballoon.js +70 -89
  161. package/src/utils/ui/table-properties.d.ts +193 -0
  162. package/src/utils/ui/table-properties.js +259 -319
  163. package/src/utils/ui/widget.d.ts +16 -0
  164. package/src/utils/ui/widget.js +24 -46
  165. package/src/tablecolumnresize/tablecolumnwidthscommand.js +0 -55
  166. package/src/tablecolumnresize/tablewidthresizecommand.js +0 -65
@@ -2,394 +2,297 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module table/tableselection
8
7
  */
9
-
10
8
  import { Plugin } from 'ckeditor5/src/core';
11
9
  import { first } from 'ckeditor5/src/utils';
12
-
13
10
  import TableWalker from './tablewalker';
14
11
  import TableUtils from './tableutils';
15
-
16
12
  import { cropTableToDimensions, adjustLastRowIndex, adjustLastColumnIndex } from './utils/structure';
17
-
18
13
  import '../theme/tableselection.css';
19
-
20
14
  /**
21
15
  * This plugin enables the advanced table cells, rows and columns selection.
22
16
  * It is loaded automatically by the {@link module:table/table~Table} plugin.
23
- *
24
- * @extends module:core/plugin~Plugin
25
17
  */
26
18
  export default class TableSelection extends Plugin {
27
- /**
28
- * @inheritDoc
29
- */
30
- static get pluginName() {
31
- return 'TableSelection';
32
- }
33
-
34
- /**
35
- * @inheritDoc
36
- */
37
- static get requires() {
38
- return [ TableUtils, TableUtils ];
39
- }
40
-
41
- /**
42
- * @inheritDoc
43
- */
44
- init() {
45
- const editor = this.editor;
46
- const model = editor.model;
47
- const view = editor.editing.view;
48
-
49
- this.listenTo( model, 'deleteContent', ( evt, args ) => this._handleDeleteContent( evt, args ), { priority: 'high' } );
50
- this.listenTo( view.document, 'insertText', ( evt, data ) => this._handleInsertTextEvent( evt, data ), { priority: 'high' } );
51
-
52
- this._defineSelectionConverter();
53
- this._enablePluginDisabling(); // sic!
54
- }
55
-
56
- /**
57
- * Returns the currently selected table cells or `null` if it is not a table cells selection.
58
- *
59
- * @returns {Array.<module:engine/model/element~Element>|null}
60
- */
61
- getSelectedTableCells() {
62
- const tableUtils = this.editor.plugins.get( TableUtils );
63
- const selection = this.editor.model.document.selection;
64
-
65
- const selectedCells = tableUtils.getSelectedTableCells( selection );
66
-
67
- if ( selectedCells.length == 0 ) {
68
- return null;
69
- }
70
-
71
- // This should never happen, but let's know if it ever happens.
72
- // @if CK_DEBUG // /* istanbul ignore next */
73
- // @if CK_DEBUG // if ( selectedCells.length != selection.rangeCount ) {
74
- // @if CK_DEBUG // console.warn( 'Mixed selection warning. The selection contains table cells and some other ranges.' );
75
- // @if CK_DEBUG // }
76
-
77
- return selectedCells;
78
- }
79
-
80
- /**
81
- * Returns the selected table fragment as a document fragment.
82
- *
83
- * @returns {module:engine/model/documentfragment~DocumentFragment|null}
84
- */
85
- getSelectionAsFragment() {
86
- const tableUtils = this.editor.plugins.get( TableUtils );
87
- const selectedCells = this.getSelectedTableCells();
88
-
89
- if ( !selectedCells ) {
90
- return null;
91
- }
92
-
93
- return this.editor.model.change( writer => {
94
- const documentFragment = writer.createDocumentFragment();
95
-
96
- const { first: firstColumn, last: lastColumn } = tableUtils.getColumnIndexes( selectedCells );
97
- const { first: firstRow, last: lastRow } = tableUtils.getRowIndexes( selectedCells );
98
-
99
- const sourceTable = selectedCells[ 0 ].findAncestor( 'table' );
100
-
101
- let adjustedLastRow = lastRow;
102
- let adjustedLastColumn = lastColumn;
103
-
104
- // If the selection is rectangular there could be a case of all cells in the last row/column spanned over
105
- // next row/column so the real lastRow/lastColumn should be updated.
106
- if ( tableUtils.isSelectionRectangular( selectedCells ) ) {
107
- const dimensions = {
108
- firstColumn,
109
- lastColumn,
110
- firstRow,
111
- lastRow
112
- };
113
-
114
- adjustedLastRow = adjustLastRowIndex( sourceTable, dimensions );
115
- adjustedLastColumn = adjustLastColumnIndex( sourceTable, dimensions );
116
- }
117
-
118
- const cropDimensions = {
119
- startRow: firstRow,
120
- startColumn: firstColumn,
121
- endRow: adjustedLastRow,
122
- endColumn: adjustedLastColumn
123
- };
124
-
125
- const table = cropTableToDimensions( sourceTable, cropDimensions, writer );
126
-
127
- writer.insert( table, documentFragment, 0 );
128
-
129
- return documentFragment;
130
- } );
131
- }
132
-
133
- /**
134
- * Sets the model selection based on given anchor and target cells (can be the same cell).
135
- * Takes care of setting the backward flag.
136
- *
137
- * const modelRoot = editor.model.document.getRoot();
138
- * const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
139
- * const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
140
- *
141
- * const tableSelection = editor.plugins.get( 'TableSelection' );
142
- * tableSelection.setCellSelection( firstCell, lastCell );
143
- *
144
- * @param {module:engine/model/element~Element} anchorCell
145
- * @param {module:engine/model/element~Element} targetCell
146
- */
147
- setCellSelection( anchorCell, targetCell ) {
148
- const cellsToSelect = this._getCellsToSelect( anchorCell, targetCell );
149
-
150
- this.editor.model.change( writer => {
151
- writer.setSelection(
152
- cellsToSelect.cells.map( cell => writer.createRangeOn( cell ) ),
153
- { backward: cellsToSelect.backward }
154
- );
155
- } );
156
- }
157
-
158
- /**
159
- * Returns the focus cell from the current selection.
160
- *
161
- * @returns {module:engine/model/element~Element}
162
- */
163
- getFocusCell() {
164
- const selection = this.editor.model.document.selection;
165
- const focusCellRange = [ ...selection.getRanges() ].pop();
166
- const element = focusCellRange.getContainedElement();
167
-
168
- if ( element && element.is( 'element', 'tableCell' ) ) {
169
- return element;
170
- }
171
-
172
- return null;
173
- }
174
-
175
- /**
176
- * Returns the anchor cell from the current selection.
177
- *
178
- * @returns {module:engine/model/element~Element} anchorCell
179
- */
180
- getAnchorCell() {
181
- const selection = this.editor.model.document.selection;
182
- const anchorCellRange = first( selection.getRanges() );
183
- const element = anchorCellRange.getContainedElement();
184
-
185
- if ( element && element.is( 'element', 'tableCell' ) ) {
186
- return element;
187
- }
188
-
189
- return null;
190
- }
191
-
192
- /**
193
- * Defines a selection converter which marks the selected cells with a specific class.
194
- *
195
- * The real DOM selection is put in the last cell. Since the order of ranges is dependent on whether the
196
- * selection is backward or not, the last cell will usually be close to the "focus" end of the selection
197
- * (a selection has anchor and focus).
198
- *
199
- * The real DOM selection is then hidden with CSS.
200
- *
201
- * @private
202
- */
203
- _defineSelectionConverter() {
204
- const editor = this.editor;
205
- const highlighted = new Set();
206
-
207
- editor.conversion.for( 'editingDowncast' ).add( dispatcher => dispatcher.on( 'selection', ( evt, data, conversionApi ) => {
208
- const viewWriter = conversionApi.writer;
209
-
210
- clearHighlightedTableCells( viewWriter );
211
-
212
- const selectedCells = this.getSelectedTableCells();
213
-
214
- if ( !selectedCells ) {
215
- return;
216
- }
217
-
218
- for ( const tableCell of selectedCells ) {
219
- const viewElement = conversionApi.mapper.toViewElement( tableCell );
220
-
221
- viewWriter.addClass( 'ck-editor__editable_selected', viewElement );
222
- highlighted.add( viewElement );
223
- }
224
-
225
- const lastViewCell = conversionApi.mapper.toViewElement( selectedCells[ selectedCells.length - 1 ] );
226
- viewWriter.setSelection( lastViewCell, 0 );
227
- }, { priority: 'lowest' } ) );
228
-
229
- function clearHighlightedTableCells( writer ) {
230
- for ( const previouslyHighlighted of highlighted ) {
231
- writer.removeClass( 'ck-editor__editable_selected', previouslyHighlighted );
232
- }
233
-
234
- highlighted.clear();
235
- }
236
- }
237
-
238
- /**
239
- * Creates a listener that reacts to changes in {@link #isEnabled} and, if the plugin was disabled,
240
- * it collapses the multi-cell selection to a regular selection placed inside a table cell.
241
- *
242
- * This listener helps features that disable the table selection plugin bring the selection
243
- * to a clear state they can work with (for instance, because they don't support multiple cell selection).
244
- */
245
- _enablePluginDisabling() {
246
- const editor = this.editor;
247
-
248
- this.on( 'change:isEnabled', () => {
249
- if ( !this.isEnabled ) {
250
- const selectedCells = this.getSelectedTableCells();
251
-
252
- if ( !selectedCells ) {
253
- return;
254
- }
255
-
256
- editor.model.change( writer => {
257
- const position = writer.createPositionAt( selectedCells[ 0 ], 0 );
258
- const range = editor.model.schema.getNearestSelectionRange( position );
259
-
260
- writer.setSelection( range );
261
- } );
262
- }
263
- } );
264
- }
265
-
266
- /**
267
- * Overrides the default `model.deleteContent()` behavior over a selected table fragment.
268
- *
269
- * @private
270
- * @param {module:utils/eventinfo~EventInfo} event
271
- * @param {Array.<*>} args Delete content method arguments.
272
- */
273
- _handleDeleteContent( event, args ) {
274
- const tableUtils = this.editor.plugins.get( TableUtils );
275
- const [ selection, options ] = args;
276
- const model = this.editor.model;
277
- const isBackward = !options || options.direction == 'backward';
278
- const selectedTableCells = tableUtils.getSelectedTableCells( selection );
279
-
280
- if ( !selectedTableCells.length ) {
281
- return;
282
- }
283
-
284
- event.stop();
285
-
286
- model.change( writer => {
287
- const tableCellToSelect = selectedTableCells[ isBackward ? selectedTableCells.length - 1 : 0 ];
288
-
289
- model.change( writer => {
290
- for ( const tableCell of selectedTableCells ) {
291
- model.deleteContent( writer.createSelection( tableCell, 'in' ) );
292
- }
293
- } );
294
-
295
- const rangeToSelect = model.schema.getNearestSelectionRange( writer.createPositionAt( tableCellToSelect, 0 ) );
296
-
297
- // Note: we ignore the case where rangeToSelect may be null because deleteContent() will always (unless someone broke it)
298
- // create an empty paragraph to accommodate the selection.
299
-
300
- if ( selection.is( 'documentSelection' ) ) {
301
- writer.setSelection( rangeToSelect );
302
- } else {
303
- selection.setTo( rangeToSelect );
304
- }
305
- } );
306
- }
307
-
308
- /**
309
- * This handler makes it possible to remove the content of all selected cells by starting to type.
310
- * If you take a look at {@link #_defineSelectionConverter} you will find out that despite the multi-cell selection being set
311
- * in the model, the view selection is collapsed in the last cell (because most browsers are unable to render multi-cell selections;
312
- * yes, it's a hack).
313
- *
314
- * When multiple cells are selected in the model and the user starts to type, the
315
- * {@link module:engine/view/document~Document#event:insertText} event carries information provided by the
316
- * beforeinput DOM event, that in turn only knows about this collapsed DOM selection in the last cell.
317
- *
318
- * As a result, the selected cells have no chance to be cleaned up. To fix this, this listener intercepts
319
- * the event and injects the custom view selection in the data that translates correctly to the actual state
320
- * of the multi-cell selection in the model.
321
- *
322
- * @private
323
- * @param {module:utils/eventinfo~EventInfo} event
324
- * @param {module:engine/view/observer/domeventdata~DomEventData} data Insert text event data.
325
- */
326
- _handleInsertTextEvent( evt, data ) {
327
- const editor = this.editor;
328
- const model = editor.model;
329
- const modelSelection = model.document.selection;
330
- const selectedCells = this.getSelectedTableCells( modelSelection );
331
-
332
- if ( !selectedCells ) {
333
- return;
334
- }
335
-
336
- const view = editor.editing.view;
337
- const mapper = editor.editing.mapper;
338
- const viewRanges = selectedCells.map( tableCell => view.createRangeOn( mapper.toViewElement( tableCell ) ) );
339
-
340
- data.selection = view.createSelection( viewRanges );
341
- }
342
-
343
- /**
344
- * Returns an array of table cells that should be selected based on the
345
- * given anchor cell and target (focus) cell.
346
- *
347
- * The cells are returned in a reverse direction if the selection is backward.
348
- *
349
- * @private
350
- * @param {module:engine/model/element~Element} anchorCell
351
- * @param {module:engine/model/element~Element} targetCell
352
- * @returns {Array.<module:engine/model/element~Element>}
353
- */
354
- _getCellsToSelect( anchorCell, targetCell ) {
355
- const tableUtils = this.editor.plugins.get( 'TableUtils' );
356
- const startLocation = tableUtils.getCellLocation( anchorCell );
357
- const endLocation = tableUtils.getCellLocation( targetCell );
358
-
359
- const startRow = Math.min( startLocation.row, endLocation.row );
360
- const endRow = Math.max( startLocation.row, endLocation.row );
361
-
362
- const startColumn = Math.min( startLocation.column, endLocation.column );
363
- const endColumn = Math.max( startLocation.column, endLocation.column );
364
-
365
- // 2-dimensional array of the selected cells to ease flipping the order of cells for backward selections.
366
- const selectionMap = new Array( endRow - startRow + 1 ).fill( null ).map( () => [] );
367
-
368
- const walkerOptions = {
369
- startRow,
370
- endRow,
371
- startColumn,
372
- endColumn
373
- };
374
-
375
- for ( const { row, cell } of new TableWalker( anchorCell.findAncestor( 'table' ), walkerOptions ) ) {
376
- selectionMap[ row - startRow ].push( cell );
377
- }
378
-
379
- const flipVertically = endLocation.row < startLocation.row;
380
- const flipHorizontally = endLocation.column < startLocation.column;
381
-
382
- if ( flipVertically ) {
383
- selectionMap.reverse();
384
- }
385
-
386
- if ( flipHorizontally ) {
387
- selectionMap.forEach( row => row.reverse() );
388
- }
389
-
390
- return {
391
- cells: selectionMap.flat(),
392
- backward: flipVertically || flipHorizontally
393
- };
394
- }
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ static get pluginName() {
23
+ return 'TableSelection';
24
+ }
25
+ /**
26
+ * @inheritDoc
27
+ */
28
+ static get requires() {
29
+ return [TableUtils, TableUtils];
30
+ }
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ init() {
35
+ const editor = this.editor;
36
+ const model = editor.model;
37
+ const view = editor.editing.view;
38
+ this.listenTo(model, 'deleteContent', (evt, args) => this._handleDeleteContent(evt, args), { priority: 'high' });
39
+ this.listenTo(view.document, 'insertText', (evt, data) => this._handleInsertTextEvent(evt, data), { priority: 'high' });
40
+ this._defineSelectionConverter();
41
+ this._enablePluginDisabling(); // sic!
42
+ }
43
+ /**
44
+ * Returns the currently selected table cells or `null` if it is not a table cells selection.
45
+ */
46
+ getSelectedTableCells() {
47
+ const tableUtils = this.editor.plugins.get(TableUtils);
48
+ const selection = this.editor.model.document.selection;
49
+ const selectedCells = tableUtils.getSelectedTableCells(selection);
50
+ if (selectedCells.length == 0) {
51
+ return null;
52
+ }
53
+ // This should never happen, but let's know if it ever happens.
54
+ // @if CK_DEBUG // /* istanbul ignore next */
55
+ // @if CK_DEBUG // if ( selectedCells.length != selection.rangeCount ) {
56
+ // @if CK_DEBUG // console.warn( 'Mixed selection warning. The selection contains table cells and some other ranges.' );
57
+ // @if CK_DEBUG // }
58
+ return selectedCells;
59
+ }
60
+ /**
61
+ * Returns the selected table fragment as a document fragment.
62
+ */
63
+ getSelectionAsFragment() {
64
+ const tableUtils = this.editor.plugins.get(TableUtils);
65
+ const selectedCells = this.getSelectedTableCells();
66
+ if (!selectedCells) {
67
+ return null;
68
+ }
69
+ return this.editor.model.change(writer => {
70
+ const documentFragment = writer.createDocumentFragment();
71
+ const { first: firstColumn, last: lastColumn } = tableUtils.getColumnIndexes(selectedCells);
72
+ const { first: firstRow, last: lastRow } = tableUtils.getRowIndexes(selectedCells);
73
+ const sourceTable = selectedCells[0].findAncestor('table');
74
+ let adjustedLastRow = lastRow;
75
+ let adjustedLastColumn = lastColumn;
76
+ // If the selection is rectangular there could be a case of all cells in the last row/column spanned over
77
+ // next row/column so the real lastRow/lastColumn should be updated.
78
+ if (tableUtils.isSelectionRectangular(selectedCells)) {
79
+ const dimensions = {
80
+ firstColumn,
81
+ lastColumn,
82
+ firstRow,
83
+ lastRow
84
+ };
85
+ adjustedLastRow = adjustLastRowIndex(sourceTable, dimensions);
86
+ adjustedLastColumn = adjustLastColumnIndex(sourceTable, dimensions);
87
+ }
88
+ const cropDimensions = {
89
+ startRow: firstRow,
90
+ startColumn: firstColumn,
91
+ endRow: adjustedLastRow,
92
+ endColumn: adjustedLastColumn
93
+ };
94
+ const table = cropTableToDimensions(sourceTable, cropDimensions, writer);
95
+ writer.insert(table, documentFragment, 0);
96
+ return documentFragment;
97
+ });
98
+ }
99
+ /**
100
+ * Sets the model selection based on given anchor and target cells (can be the same cell).
101
+ * Takes care of setting the backward flag.
102
+ *
103
+ * ```ts
104
+ * const modelRoot = editor.model.document.getRoot();
105
+ * const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
106
+ * const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
107
+ *
108
+ * const tableSelection = editor.plugins.get( 'TableSelection' );
109
+ * tableSelection.setCellSelection( firstCell, lastCell );
110
+ * ```
111
+ */
112
+ setCellSelection(anchorCell, targetCell) {
113
+ const cellsToSelect = this._getCellsToSelect(anchorCell, targetCell);
114
+ this.editor.model.change(writer => {
115
+ writer.setSelection(cellsToSelect.cells.map(cell => writer.createRangeOn(cell)), { backward: cellsToSelect.backward });
116
+ });
117
+ }
118
+ /**
119
+ * Returns the focus cell from the current selection.
120
+ */
121
+ getFocusCell() {
122
+ const selection = this.editor.model.document.selection;
123
+ const focusCellRange = [...selection.getRanges()].pop();
124
+ const element = focusCellRange.getContainedElement();
125
+ if (element && element.is('element', 'tableCell')) {
126
+ return element;
127
+ }
128
+ return null;
129
+ }
130
+ /**
131
+ * Returns the anchor cell from the current selection.
132
+ */
133
+ getAnchorCell() {
134
+ const selection = this.editor.model.document.selection;
135
+ const anchorCellRange = first(selection.getRanges());
136
+ const element = anchorCellRange.getContainedElement();
137
+ if (element && element.is('element', 'tableCell')) {
138
+ return element;
139
+ }
140
+ return null;
141
+ }
142
+ /**
143
+ * Defines a selection converter which marks the selected cells with a specific class.
144
+ *
145
+ * The real DOM selection is put in the last cell. Since the order of ranges is dependent on whether the
146
+ * selection is backward or not, the last cell will usually be close to the "focus" end of the selection
147
+ * (a selection has anchor and focus).
148
+ *
149
+ * The real DOM selection is then hidden with CSS.
150
+ */
151
+ _defineSelectionConverter() {
152
+ const editor = this.editor;
153
+ const highlighted = new Set();
154
+ editor.conversion.for('editingDowncast').add(dispatcher => dispatcher.on('selection', (evt, data, conversionApi) => {
155
+ const viewWriter = conversionApi.writer;
156
+ clearHighlightedTableCells(viewWriter);
157
+ const selectedCells = this.getSelectedTableCells();
158
+ if (!selectedCells) {
159
+ return;
160
+ }
161
+ for (const tableCell of selectedCells) {
162
+ const viewElement = conversionApi.mapper.toViewElement(tableCell);
163
+ viewWriter.addClass('ck-editor__editable_selected', viewElement);
164
+ highlighted.add(viewElement);
165
+ }
166
+ const lastViewCell = conversionApi.mapper.toViewElement(selectedCells[selectedCells.length - 1]);
167
+ viewWriter.setSelection(lastViewCell, 0);
168
+ }, { priority: 'lowest' }));
169
+ function clearHighlightedTableCells(viewWriter) {
170
+ for (const previouslyHighlighted of highlighted) {
171
+ viewWriter.removeClass('ck-editor__editable_selected', previouslyHighlighted);
172
+ }
173
+ highlighted.clear();
174
+ }
175
+ }
176
+ /**
177
+ * Creates a listener that reacts to changes in {@link #isEnabled} and, if the plugin was disabled,
178
+ * it collapses the multi-cell selection to a regular selection placed inside a table cell.
179
+ *
180
+ * This listener helps features that disable the table selection plugin bring the selection
181
+ * to a clear state they can work with (for instance, because they don't support multiple cell selection).
182
+ */
183
+ _enablePluginDisabling() {
184
+ const editor = this.editor;
185
+ this.on('change:isEnabled', () => {
186
+ if (!this.isEnabled) {
187
+ const selectedCells = this.getSelectedTableCells();
188
+ if (!selectedCells) {
189
+ return;
190
+ }
191
+ editor.model.change(writer => {
192
+ const position = writer.createPositionAt(selectedCells[0], 0);
193
+ const range = editor.model.schema.getNearestSelectionRange(position);
194
+ writer.setSelection(range);
195
+ });
196
+ }
197
+ });
198
+ }
199
+ /**
200
+ * Overrides the default `model.deleteContent()` behavior over a selected table fragment.
201
+ *
202
+ * @param args Delete content method arguments.
203
+ */
204
+ _handleDeleteContent(event, args) {
205
+ const tableUtils = this.editor.plugins.get(TableUtils);
206
+ const selection = args[0];
207
+ const options = args[1];
208
+ const model = this.editor.model;
209
+ const isBackward = !options || options.direction == 'backward';
210
+ const selectedTableCells = tableUtils.getSelectedTableCells(selection);
211
+ if (!selectedTableCells.length) {
212
+ return;
213
+ }
214
+ event.stop();
215
+ model.change(writer => {
216
+ const tableCellToSelect = selectedTableCells[isBackward ? selectedTableCells.length - 1 : 0];
217
+ model.change(writer => {
218
+ for (const tableCell of selectedTableCells) {
219
+ model.deleteContent(writer.createSelection(tableCell, 'in'));
220
+ }
221
+ });
222
+ const rangeToSelect = model.schema.getNearestSelectionRange(writer.createPositionAt(tableCellToSelect, 0));
223
+ // Note: we ignore the case where rangeToSelect may be null because deleteContent() will always (unless someone broke it)
224
+ // create an empty paragraph to accommodate the selection.
225
+ if (selection.is('documentSelection')) {
226
+ writer.setSelection(rangeToSelect);
227
+ }
228
+ else {
229
+ selection.setTo(rangeToSelect);
230
+ }
231
+ });
232
+ }
233
+ /**
234
+ * This handler makes it possible to remove the content of all selected cells by starting to type.
235
+ * If you take a look at {@link #_defineSelectionConverter} you will find out that despite the multi-cell selection being set
236
+ * in the model, the view selection is collapsed in the last cell (because most browsers are unable to render multi-cell selections;
237
+ * yes, it's a hack).
238
+ *
239
+ * When multiple cells are selected in the model and the user starts to type, the
240
+ * {@link module:engine/view/document~Document#event:insertText} event carries information provided by the
241
+ * beforeinput DOM event, that in turn only knows about this collapsed DOM selection in the last cell.
242
+ *
243
+ * As a result, the selected cells have no chance to be cleaned up. To fix this, this listener intercepts
244
+ * the event and injects the custom view selection in the data that translates correctly to the actual state
245
+ * of the multi-cell selection in the model.
246
+ *
247
+ * @param data Insert text event data.
248
+ */
249
+ _handleInsertTextEvent(evt, data) {
250
+ const editor = this.editor;
251
+ const selectedCells = this.getSelectedTableCells();
252
+ if (!selectedCells) {
253
+ return;
254
+ }
255
+ const view = editor.editing.view;
256
+ const mapper = editor.editing.mapper;
257
+ const viewRanges = selectedCells.map(tableCell => view.createRangeOn(mapper.toViewElement(tableCell)));
258
+ data.selection = view.createSelection(viewRanges);
259
+ }
260
+ /**
261
+ * Returns an array of table cells that should be selected based on the
262
+ * given anchor cell and target (focus) cell.
263
+ *
264
+ * The cells are returned in a reverse direction if the selection is backward.
265
+ */
266
+ _getCellsToSelect(anchorCell, targetCell) {
267
+ const tableUtils = this.editor.plugins.get('TableUtils');
268
+ const startLocation = tableUtils.getCellLocation(anchorCell);
269
+ const endLocation = tableUtils.getCellLocation(targetCell);
270
+ const startRow = Math.min(startLocation.row, endLocation.row);
271
+ const endRow = Math.max(startLocation.row, endLocation.row);
272
+ const startColumn = Math.min(startLocation.column, endLocation.column);
273
+ const endColumn = Math.max(startLocation.column, endLocation.column);
274
+ // 2-dimensional array of the selected cells to ease flipping the order of cells for backward selections.
275
+ const selectionMap = new Array(endRow - startRow + 1).fill(null).map(() => []);
276
+ const walkerOptions = {
277
+ startRow,
278
+ endRow,
279
+ startColumn,
280
+ endColumn
281
+ };
282
+ for (const { row, cell } of new TableWalker(anchorCell.findAncestor('table'), walkerOptions)) {
283
+ selectionMap[row - startRow].push(cell);
284
+ }
285
+ const flipVertically = endLocation.row < startLocation.row;
286
+ const flipHorizontally = endLocation.column < startLocation.column;
287
+ if (flipVertically) {
288
+ selectionMap.reverse();
289
+ }
290
+ if (flipHorizontally) {
291
+ selectionMap.forEach(row => row.reverse());
292
+ }
293
+ return {
294
+ cells: selectionMap.flat(),
295
+ backward: flipVertically || flipHorizontally
296
+ };
297
+ }
395
298
  }