1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import TableSelection from './tableselection';
|
9 | import TableWalker from './tablewalker';
|
10 | import TableUtils from './tableutils';
|
11 | import { Plugin } from 'ckeditor5/src/core';
|
12 | import { getLocalizedArrowKeyCodeDirection } from 'ckeditor5/src/utils';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | export default class TableKeyboard extends Plugin {
|
18 | |
19 |
|
20 |
|
21 | static get pluginName() {
|
22 | return 'TableKeyboard';
|
23 | }
|
24 | |
25 |
|
26 |
|
27 | static get requires() {
|
28 | return [TableSelection, TableUtils];
|
29 | }
|
30 | |
31 |
|
32 |
|
33 | init() {
|
34 | const view = this.editor.editing.view;
|
35 | const viewDocument = view.document;
|
36 | this.listenTo(viewDocument, 'arrowKey', (...args) => this._onArrowKey(...args), { context: 'table' });
|
37 | this.listenTo(viewDocument, 'tab', (...args) => this._handleTabOnSelectedTable(...args), { context: 'figure' });
|
38 | this.listenTo(viewDocument, 'tab', (...args) => this._handleTab(...args), { context: ['th', 'td'] });
|
39 | }
|
40 | |
41 |
|
42 |
|
43 |
|
44 | _handleTabOnSelectedTable(bubblingEventInfo, domEventData) {
|
45 | const editor = this.editor;
|
46 | const selection = editor.model.document.selection;
|
47 | const selectedElement = selection.getSelectedElement();
|
48 | if (!selectedElement || !selectedElement.is('element', 'table')) {
|
49 | return;
|
50 | }
|
51 | domEventData.preventDefault();
|
52 | domEventData.stopPropagation();
|
53 | bubblingEventInfo.stop();
|
54 | editor.model.change(writer => {
|
55 | writer.setSelection(writer.createRangeIn(selectedElement.getChild(0).getChild(0)));
|
56 | });
|
57 | }
|
58 | |
59 |
|
60 |
|
61 |
|
62 | _handleTab(bubblingEventInfo, domEventData) {
|
63 | const editor = this.editor;
|
64 | const tableUtils = this.editor.plugins.get(TableUtils);
|
65 | const tableSelection = this.editor.plugins.get('TableSelection');
|
66 | const selection = editor.model.document.selection;
|
67 | const isForward = !domEventData.shiftKey;
|
68 | let tableCell = tableUtils.getTableCellsContainingSelection(selection)[0];
|
69 | if (!tableCell) {
|
70 | tableCell = tableSelection.getFocusCell();
|
71 | }
|
72 | if (!tableCell) {
|
73 | return;
|
74 | }
|
75 | domEventData.preventDefault();
|
76 | domEventData.stopPropagation();
|
77 | bubblingEventInfo.stop();
|
78 | const tableRow = tableCell.parent;
|
79 | const table = tableRow.parent;
|
80 | const currentRowIndex = table.getChildIndex(tableRow);
|
81 | const currentCellIndex = tableRow.getChildIndex(tableCell);
|
82 | const isFirstCellInRow = currentCellIndex === 0;
|
83 | if (!isForward && isFirstCellInRow && currentRowIndex === 0) {
|
84 |
|
85 | editor.model.change(writer => {
|
86 | writer.setSelection(writer.createRangeOn(table));
|
87 | });
|
88 | return;
|
89 | }
|
90 | const isLastCellInRow = currentCellIndex === tableRow.childCount - 1;
|
91 | const isLastRow = currentRowIndex === tableUtils.getRows(table) - 1;
|
92 | if (isForward && isLastRow && isLastCellInRow) {
|
93 | editor.execute('insertTableRowBelow');
|
94 |
|
95 |
|
96 | if (currentRowIndex === tableUtils.getRows(table) - 1) {
|
97 | editor.model.change(writer => {
|
98 | writer.setSelection(writer.createRangeOn(table));
|
99 | });
|
100 | return;
|
101 | }
|
102 | }
|
103 | let cellToFocus;
|
104 |
|
105 | if (isForward && isLastCellInRow) {
|
106 | const nextRow = table.getChild(currentRowIndex + 1);
|
107 | cellToFocus = nextRow.getChild(0);
|
108 | }
|
109 |
|
110 | else if (!isForward && isFirstCellInRow) {
|
111 | const previousRow = table.getChild(currentRowIndex - 1);
|
112 | cellToFocus = previousRow.getChild(previousRow.childCount - 1);
|
113 | }
|
114 |
|
115 | else {
|
116 | cellToFocus = tableRow.getChild(currentCellIndex + (isForward ? 1 : -1));
|
117 | }
|
118 | editor.model.change(writer => {
|
119 | writer.setSelection(writer.createRangeIn(cellToFocus));
|
120 | });
|
121 | }
|
122 | |
123 |
|
124 |
|
125 | _onArrowKey(eventInfo, domEventData) {
|
126 | const editor = this.editor;
|
127 | const keyCode = domEventData.keyCode;
|
128 | const direction = getLocalizedArrowKeyCodeDirection(keyCode, editor.locale.contentLanguageDirection);
|
129 | const wasHandled = this._handleArrowKeys(direction, domEventData.shiftKey);
|
130 | if (wasHandled) {
|
131 | domEventData.preventDefault();
|
132 | domEventData.stopPropagation();
|
133 | eventInfo.stop();
|
134 | }
|
135 | }
|
136 | |
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | _handleArrowKeys(direction, expandSelection) {
|
144 | const tableUtils = this.editor.plugins.get(TableUtils);
|
145 | const tableSelection = this.editor.plugins.get('TableSelection');
|
146 | const model = this.editor.model;
|
147 | const selection = model.document.selection;
|
148 | const isForward = ['right', 'down'].includes(direction);
|
149 |
|
150 |
|
151 | const selectedCells = tableUtils.getSelectedTableCells(selection);
|
152 | if (selectedCells.length) {
|
153 | let focusCell;
|
154 | if (expandSelection) {
|
155 | focusCell = tableSelection.getFocusCell();
|
156 | }
|
157 | else {
|
158 | focusCell = isForward ? selectedCells[selectedCells.length - 1] : selectedCells[0];
|
159 | }
|
160 | this._navigateFromCellInDirection(focusCell, direction, expandSelection);
|
161 | return true;
|
162 | }
|
163 |
|
164 | const tableCell = selection.focus.findAncestor('tableCell');
|
165 |
|
166 | if (!tableCell) {
|
167 | return false;
|
168 | }
|
169 |
|
170 | if (!selection.isCollapsed) {
|
171 | if (expandSelection) {
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | if (selection.isBackward == isForward && !selection.containsEntireContent(tableCell)) {
|
178 | return false;
|
179 | }
|
180 | }
|
181 | else {
|
182 | const selectedElement = selection.getSelectedElement();
|
183 |
|
184 | if (!selectedElement || !model.schema.isObject(selectedElement)) {
|
185 | return false;
|
186 | }
|
187 | }
|
188 | }
|
189 |
|
190 | if (this._isSelectionAtCellEdge(selection, tableCell, isForward)) {
|
191 | this._navigateFromCellInDirection(tableCell, direction, expandSelection);
|
192 | return true;
|
193 | }
|
194 | return false;
|
195 | }
|
196 | |
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | _isSelectionAtCellEdge(selection, tableCell, isForward) {
|
204 | const model = this.editor.model;
|
205 | const schema = this.editor.model.schema;
|
206 | const focus = isForward ? selection.getLastPosition() : selection.getFirstPosition();
|
207 |
|
208 |
|
209 | if (!schema.getLimitElement(focus).is('element', 'tableCell')) {
|
210 | const boundaryPosition = model.createPositionAt(tableCell, isForward ? 'end' : 0);
|
211 | return boundaryPosition.isTouching(focus);
|
212 | }
|
213 | const probe = model.createSelection(focus);
|
214 | model.modifySelection(probe, { direction: isForward ? 'forward' : 'backward' });
|
215 |
|
216 | return focus.isEqual(probe.focus);
|
217 | }
|
218 | |
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | _navigateFromCellInDirection(focusCell, direction, expandSelection = false) {
|
226 | const model = this.editor.model;
|
227 | const table = focusCell.findAncestor('table');
|
228 | const tableMap = [...new TableWalker(table, { includeAllSlots: true })];
|
229 | const { row: lastRow, column: lastColumn } = tableMap[tableMap.length - 1];
|
230 | const currentCellInfo = tableMap.find(({ cell }) => cell == focusCell);
|
231 | let { row, column } = currentCellInfo;
|
232 | switch (direction) {
|
233 | case 'left':
|
234 | column--;
|
235 | break;
|
236 | case 'up':
|
237 | row--;
|
238 | break;
|
239 | case 'right':
|
240 | column += currentCellInfo.cellWidth;
|
241 | break;
|
242 | case 'down':
|
243 | row += currentCellInfo.cellHeight;
|
244 | break;
|
245 | }
|
246 | const isOutsideVertically = row < 0 || row > lastRow;
|
247 | const isBeforeFirstCell = column < 0 && row <= 0;
|
248 | const isAfterLastCell = column > lastColumn && row >= lastRow;
|
249 |
|
250 |
|
251 | if (isOutsideVertically || isBeforeFirstCell || isAfterLastCell) {
|
252 | model.change(writer => {
|
253 | writer.setSelection(writer.createRangeOn(table));
|
254 | });
|
255 | return;
|
256 | }
|
257 | if (column < 0) {
|
258 | column = expandSelection ? 0 : lastColumn;
|
259 | row--;
|
260 | }
|
261 | else if (column > lastColumn) {
|
262 | column = expandSelection ? lastColumn : 0;
|
263 | row++;
|
264 | }
|
265 | const cellToSelect = tableMap.find(cellInfo => cellInfo.row == row && cellInfo.column == column).cell;
|
266 | const isForward = ['right', 'down'].includes(direction);
|
267 | const tableSelection = this.editor.plugins.get('TableSelection');
|
268 | if (expandSelection && tableSelection.isEnabled) {
|
269 | const anchorCell = tableSelection.getAnchorCell() || focusCell;
|
270 | tableSelection.setCellSelection(anchorCell, cellToSelect);
|
271 | }
|
272 | else {
|
273 | const positionToSelect = model.createPositionAt(cellToSelect, isForward ? 0 : 'end');
|
274 | model.change(writer => {
|
275 | writer.setSelection(positionToSelect);
|
276 | });
|
277 | }
|
278 | }
|
279 | }
|