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