@lexical/table 0.20.1-nightly.20241121.0 → 0.20.1-nightly.20241125.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/LexicalTable.dev.js +663 -413
- package/LexicalTable.dev.mjs +664 -414
- package/LexicalTable.js.flow +5 -5
- package/LexicalTable.prod.js +106 -96
- package/LexicalTable.prod.mjs +1 -1
- package/LexicalTableCellNode.d.ts +2 -2
- package/LexicalTableObserver.d.ts +35 -8
- package/LexicalTableSelection.d.ts +16 -1
- package/LexicalTableSelectionHelpers.d.ts +3 -1
- package/LexicalTableUtils.d.ts +20 -2
- package/package.json +4 -4
package/LexicalTable.dev.mjs
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
*/
|
8
8
|
|
9
9
|
import { addClassNamesToElement, $findMatchingParent, removeClassNamesFromElement, objectKlassEquals, isHTMLElement } from '@lexical/utils';
|
10
|
-
import { ElementNode, $createParagraphNode, $isElementNode, $isLineBreakNode, $isTextNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $createPoint, $isParagraphNode, $normalizeSelection__EXPERIMENTAL,
|
10
|
+
import { ElementNode, $createParagraphNode, $isElementNode, $isLineBreakNode, $isTextNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $createPoint, $isParagraphNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getNodeByKey, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $getNearestNodeFromDOMNode, $createRangeSelection, $getRoot, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_CRITICAL, CUT_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $getPreviousSelection, $createRangeSelectionFromDom, INSERT_PARAGRAPH_COMMAND, $isRootOrShadowRoot, $isDecoratorNode, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, setDOMUnmanaged } from 'lexical';
|
11
11
|
import { copyToClipboard, $getClipboardDataFromSelection } from '@lexical/clipboard';
|
12
12
|
|
13
13
|
/**
|
@@ -104,31 +104,24 @@ class TableCellNode extends ElementNode {
|
|
104
104
|
return element;
|
105
105
|
}
|
106
106
|
exportDOM(editor) {
|
107
|
-
const
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
const element_ = element;
|
112
|
-
element_.style.border = '1px solid black';
|
107
|
+
const output = super.exportDOM(editor);
|
108
|
+
if (output.element) {
|
109
|
+
const element = output.element;
|
110
|
+
element.style.border = '1px solid black';
|
113
111
|
if (this.__colSpan > 1) {
|
114
|
-
|
112
|
+
element.colSpan = this.__colSpan;
|
115
113
|
}
|
116
114
|
if (this.__rowSpan > 1) {
|
117
|
-
|
115
|
+
element.rowSpan = this.__rowSpan;
|
118
116
|
}
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
element_.style.backgroundColor = backgroundColor;
|
125
|
-
} else if (this.hasHeader()) {
|
126
|
-
element_.style.backgroundColor = '#f2f3f5';
|
117
|
+
element.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;
|
118
|
+
element.style.verticalAlign = 'top';
|
119
|
+
element.style.textAlign = 'start';
|
120
|
+
if (this.__backgroundColor === null && this.hasHeader()) {
|
121
|
+
element.style.backgroundColor = '#f2f3f5';
|
127
122
|
}
|
128
123
|
}
|
129
|
-
return
|
130
|
-
element
|
131
|
-
};
|
124
|
+
return output;
|
132
125
|
}
|
133
126
|
exportJSON() {
|
134
127
|
return {
|
@@ -296,6 +289,18 @@ const INSERT_TABLE_COMMAND = createCommand('INSERT_TABLE_COMMAND');
|
|
296
289
|
|
297
290
|
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
298
291
|
|
292
|
+
/**
|
293
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
294
|
+
*
|
295
|
+
* This source code is licensed under the MIT license found in the
|
296
|
+
* LICENSE file in the root directory of this source tree.
|
297
|
+
*
|
298
|
+
*/
|
299
|
+
|
300
|
+
const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
|
301
|
+
const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
302
|
+
CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
|
303
|
+
|
299
304
|
/**
|
300
305
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
301
306
|
*
|
@@ -980,8 +985,8 @@ function $unmergeCell() {
|
|
980
985
|
cell.setRowSpan(1);
|
981
986
|
}
|
982
987
|
}
|
983
|
-
function $computeTableMap(
|
984
|
-
const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(
|
988
|
+
function $computeTableMap(tableNode, cellA, cellB) {
|
989
|
+
const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(tableNode, cellA, cellB);
|
985
990
|
if (!(cellAValue !== null)) {
|
986
991
|
throw Error(`Anchor not found in Table`);
|
987
992
|
}
|
@@ -990,7 +995,7 @@ function $computeTableMap(grid, cellA, cellB) {
|
|
990
995
|
}
|
991
996
|
return [tableMap, cellAValue, cellBValue];
|
992
997
|
}
|
993
|
-
function $computeTableMapSkipCellCheck(
|
998
|
+
function $computeTableMapSkipCellCheck(tableNode, cellA, cellB) {
|
994
999
|
const tableMap = [];
|
995
1000
|
let cellAValue = null;
|
996
1001
|
let cellBValue = null;
|
@@ -1001,7 +1006,7 @@ function $computeTableMapSkipCellCheck(grid, cellA, cellB) {
|
|
1001
1006
|
}
|
1002
1007
|
return row;
|
1003
1008
|
}
|
1004
|
-
const gridChildren =
|
1009
|
+
const gridChildren = tableNode.getChildren();
|
1005
1010
|
for (let rowIdx = 0; rowIdx < gridChildren.length; rowIdx++) {
|
1006
1011
|
const row = gridChildren[rowIdx];
|
1007
1012
|
if (!$isTableRowNode(row)) {
|
@@ -1077,6 +1082,99 @@ function $getNodeTriplet(source) {
|
|
1077
1082
|
}
|
1078
1083
|
return [cell, row, grid];
|
1079
1084
|
}
|
1085
|
+
function $computeTableCellRectSpans(map, boundary) {
|
1086
|
+
const {
|
1087
|
+
minColumn,
|
1088
|
+
maxColumn,
|
1089
|
+
minRow,
|
1090
|
+
maxRow
|
1091
|
+
} = boundary;
|
1092
|
+
let topSpan = 1;
|
1093
|
+
let leftSpan = 1;
|
1094
|
+
let rightSpan = 1;
|
1095
|
+
let bottomSpan = 1;
|
1096
|
+
const topRow = map[minRow];
|
1097
|
+
const bottomRow = map[maxRow];
|
1098
|
+
for (let col = minColumn; col <= maxColumn; col++) {
|
1099
|
+
topSpan = Math.max(topSpan, topRow[col].cell.__rowSpan);
|
1100
|
+
bottomSpan = Math.max(bottomSpan, bottomRow[col].cell.__rowSpan);
|
1101
|
+
}
|
1102
|
+
for (let row = minRow; row <= maxRow; row++) {
|
1103
|
+
leftSpan = Math.max(leftSpan, map[row][minColumn].cell.__colSpan);
|
1104
|
+
rightSpan = Math.max(rightSpan, map[row][maxColumn].cell.__colSpan);
|
1105
|
+
}
|
1106
|
+
return {
|
1107
|
+
bottomSpan,
|
1108
|
+
leftSpan,
|
1109
|
+
rightSpan,
|
1110
|
+
topSpan
|
1111
|
+
};
|
1112
|
+
}
|
1113
|
+
function $computeTableCellRectBoundary(map, cellAMap, cellBMap) {
|
1114
|
+
let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
|
1115
|
+
let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
|
1116
|
+
let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
|
1117
|
+
let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
|
1118
|
+
let exploredMinColumn = minColumn;
|
1119
|
+
let exploredMinRow = minRow;
|
1120
|
+
let exploredMaxColumn = minColumn;
|
1121
|
+
let exploredMaxRow = minRow;
|
1122
|
+
function expandBoundary(mapValue) {
|
1123
|
+
const {
|
1124
|
+
cell,
|
1125
|
+
startColumn: cellStartColumn,
|
1126
|
+
startRow: cellStartRow
|
1127
|
+
} = mapValue;
|
1128
|
+
minColumn = Math.min(minColumn, cellStartColumn);
|
1129
|
+
minRow = Math.min(minRow, cellStartRow);
|
1130
|
+
maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
|
1131
|
+
maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
|
1132
|
+
}
|
1133
|
+
while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
|
1134
|
+
if (minColumn < exploredMinColumn) {
|
1135
|
+
// Expand on the left
|
1136
|
+
const rowDiff = exploredMaxRow - exploredMinRow;
|
1137
|
+
const previousColumn = exploredMinColumn - 1;
|
1138
|
+
for (let i = 0; i <= rowDiff; i++) {
|
1139
|
+
expandBoundary(map[exploredMinRow + i][previousColumn]);
|
1140
|
+
}
|
1141
|
+
exploredMinColumn = previousColumn;
|
1142
|
+
}
|
1143
|
+
if (minRow < exploredMinRow) {
|
1144
|
+
// Expand on top
|
1145
|
+
const columnDiff = exploredMaxColumn - exploredMinColumn;
|
1146
|
+
const previousRow = exploredMinRow - 1;
|
1147
|
+
for (let i = 0; i <= columnDiff; i++) {
|
1148
|
+
expandBoundary(map[previousRow][exploredMinColumn + i]);
|
1149
|
+
}
|
1150
|
+
exploredMinRow = previousRow;
|
1151
|
+
}
|
1152
|
+
if (maxColumn > exploredMaxColumn) {
|
1153
|
+
// Expand on the right
|
1154
|
+
const rowDiff = exploredMaxRow - exploredMinRow;
|
1155
|
+
const nextColumn = exploredMaxColumn + 1;
|
1156
|
+
for (let i = 0; i <= rowDiff; i++) {
|
1157
|
+
expandBoundary(map[exploredMinRow + i][nextColumn]);
|
1158
|
+
}
|
1159
|
+
exploredMaxColumn = nextColumn;
|
1160
|
+
}
|
1161
|
+
if (maxRow > exploredMaxRow) {
|
1162
|
+
// Expand on the bottom
|
1163
|
+
const columnDiff = exploredMaxColumn - exploredMinColumn;
|
1164
|
+
const nextRow = exploredMaxRow + 1;
|
1165
|
+
for (let i = 0; i <= columnDiff; i++) {
|
1166
|
+
expandBoundary(map[nextRow][exploredMinColumn + i]);
|
1167
|
+
}
|
1168
|
+
exploredMaxRow = nextRow;
|
1169
|
+
}
|
1170
|
+
}
|
1171
|
+
return {
|
1172
|
+
maxColumn,
|
1173
|
+
maxRow,
|
1174
|
+
minColumn,
|
1175
|
+
minRow
|
1176
|
+
};
|
1177
|
+
}
|
1080
1178
|
function $getTableCellNodeRect(tableCellNode) {
|
1081
1179
|
const [cellNode,, gridNode] = $getNodeTriplet(tableCellNode);
|
1082
1180
|
const rows = gridNode.getChildren();
|
@@ -1131,6 +1229,38 @@ function $getTableCellNodeRect(tableCellNode) {
|
|
1131
1229
|
*
|
1132
1230
|
*/
|
1133
1231
|
|
1232
|
+
function $getCellNodes(tableSelection) {
|
1233
|
+
const [[anchorNode, anchorCell, anchorRow, anchorTable], [focusNode, focusCell, focusRow, focusTable]] = ['anchor', 'focus'].map(k => {
|
1234
|
+
const node = tableSelection[k].getNode();
|
1235
|
+
const cellNode = $findMatchingParent(node, $isTableCellNode);
|
1236
|
+
if (!$isTableCellNode(cellNode)) {
|
1237
|
+
throw Error(`Expected TableSelection ${k} to be (or a child of) TableCellNode, got key ${node.getKey()} of type ${node.getType()}`);
|
1238
|
+
}
|
1239
|
+
const rowNode = cellNode.getParent();
|
1240
|
+
if (!$isTableRowNode(rowNode)) {
|
1241
|
+
throw Error(`Expected TableSelection ${k} cell parent to be a TableRowNode`);
|
1242
|
+
}
|
1243
|
+
const tableNode = rowNode.getParent();
|
1244
|
+
if (!$isTableNode(tableNode)) {
|
1245
|
+
throw Error(`Expected TableSelection ${k} row parent to be a TableNode`);
|
1246
|
+
}
|
1247
|
+
return [node, cellNode, rowNode, tableNode];
|
1248
|
+
});
|
1249
|
+
// TODO: nested tables may violate this
|
1250
|
+
if (!anchorTable.is(focusTable)) {
|
1251
|
+
throw Error(`Expected TableSelection anchor and focus to be in the same table`);
|
1252
|
+
}
|
1253
|
+
return {
|
1254
|
+
anchorCell,
|
1255
|
+
anchorNode,
|
1256
|
+
anchorRow,
|
1257
|
+
anchorTable,
|
1258
|
+
focusCell,
|
1259
|
+
focusNode,
|
1260
|
+
focusRow,
|
1261
|
+
focusTable
|
1262
|
+
};
|
1263
|
+
}
|
1134
1264
|
class TableSelection {
|
1135
1265
|
constructor(tableKey, anchor, focus) {
|
1136
1266
|
this.anchor = anchor;
|
@@ -1145,6 +1275,17 @@ class TableSelection {
|
|
1145
1275
|
return [this.anchor, this.focus];
|
1146
1276
|
}
|
1147
1277
|
|
1278
|
+
/**
|
1279
|
+
* {@link $createTableSelection} unfortunately makes it very easy to create
|
1280
|
+
* nonsense selections, so we have a method to see if the selection probably
|
1281
|
+
* makes sense.
|
1282
|
+
*
|
1283
|
+
* @returns true if the TableSelection is (probably) valid
|
1284
|
+
*/
|
1285
|
+
isValid() {
|
1286
|
+
return this.tableKey !== 'root' && this.anchor.key !== 'root' && this.anchor.type === 'element' && this.focus.key !== 'root' && this.focus.type === 'element';
|
1287
|
+
}
|
1288
|
+
|
1148
1289
|
/**
|
1149
1290
|
* Returns whether the Selection is "backwards", meaning the focus
|
1150
1291
|
* logically precedes the anchor in the EditorState.
|
@@ -1160,20 +1301,18 @@ class TableSelection {
|
|
1160
1301
|
this._cachedNodes = nodes;
|
1161
1302
|
}
|
1162
1303
|
is(selection) {
|
1163
|
-
|
1164
|
-
return false;
|
1165
|
-
}
|
1166
|
-
return this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
|
1304
|
+
return $isTableSelection(selection) && this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
|
1167
1305
|
}
|
1168
1306
|
set(tableKey, anchorCellKey, focusCellKey) {
|
1169
|
-
|
1307
|
+
// note: closure compiler's acorn does not support ||=
|
1308
|
+
this.dirty = this.dirty || tableKey !== this.tableKey || anchorCellKey !== this.anchor.key || focusCellKey !== this.focus.key;
|
1170
1309
|
this.tableKey = tableKey;
|
1171
1310
|
this.anchor.key = anchorCellKey;
|
1172
1311
|
this.focus.key = focusCellKey;
|
1173
1312
|
this._cachedNodes = null;
|
1174
1313
|
}
|
1175
1314
|
clone() {
|
1176
|
-
return new TableSelection(this.tableKey, this.anchor, this.focus);
|
1315
|
+
return new TableSelection(this.tableKey, $createPoint(this.anchor.key, this.anchor.offset, this.anchor.type), $createPoint(this.focus.key, this.focus.offset, this.focus.type));
|
1177
1316
|
}
|
1178
1317
|
isCollapsed() {
|
1179
1318
|
return false;
|
@@ -1218,19 +1357,15 @@ class TableSelection {
|
|
1218
1357
|
|
1219
1358
|
// TODO Deprecate this method. It's confusing when used with colspan|rowspan
|
1220
1359
|
getShape() {
|
1221
|
-
const
|
1222
|
-
|
1223
|
-
|
1224
|
-
}
|
1225
|
-
const anchorCellNodeRect = $getTableCellNodeRect(
|
1360
|
+
const {
|
1361
|
+
anchorCell,
|
1362
|
+
focusCell
|
1363
|
+
} = $getCellNodes(this);
|
1364
|
+
const anchorCellNodeRect = $getTableCellNodeRect(anchorCell);
|
1226
1365
|
if (!(anchorCellNodeRect !== null)) {
|
1227
1366
|
throw Error(`getCellRect: expected to find AnchorNode`);
|
1228
1367
|
}
|
1229
|
-
const
|
1230
|
-
if (!$isTableCellNode(focusCellNode)) {
|
1231
|
-
throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
|
1232
|
-
}
|
1233
|
-
const focusCellNodeRect = $getTableCellNodeRect(focusCellNode);
|
1368
|
+
const focusCellNodeRect = $getTableCellNodeRect(focusCell);
|
1234
1369
|
if (!(focusCellNodeRect !== null)) {
|
1235
1370
|
throw Error(`getCellRect: expected to find focusCellNode`);
|
1236
1371
|
}
|
@@ -1246,29 +1381,18 @@ class TableSelection {
|
|
1246
1381
|
};
|
1247
1382
|
}
|
1248
1383
|
getNodes() {
|
1384
|
+
if (!this.isValid()) {
|
1385
|
+
return [];
|
1386
|
+
}
|
1249
1387
|
const cachedNodes = this._cachedNodes;
|
1250
1388
|
if (cachedNodes !== null) {
|
1251
1389
|
return cachedNodes;
|
1252
1390
|
}
|
1253
|
-
const
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
if (!$isTableCellNode(anchorCell)) {
|
1259
|
-
throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
|
1260
|
-
}
|
1261
|
-
if (!$isTableCellNode(focusCell)) {
|
1262
|
-
throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
|
1263
|
-
}
|
1264
|
-
const anchorRow = anchorCell.getParent();
|
1265
|
-
if (!$isTableRowNode(anchorRow)) {
|
1266
|
-
throw Error(`Expected anchorCell to have a parent TableRowNode`);
|
1267
|
-
}
|
1268
|
-
const tableNode = anchorRow.getParent();
|
1269
|
-
if (!$isTableNode(tableNode)) {
|
1270
|
-
throw Error(`Expected tableNode to have a parent TableNode`);
|
1271
|
-
}
|
1391
|
+
const {
|
1392
|
+
anchorTable: tableNode,
|
1393
|
+
anchorCell,
|
1394
|
+
focusCell
|
1395
|
+
} = $getCellNodes(this);
|
1272
1396
|
const focusCellGrid = focusCell.getParents()[1];
|
1273
1397
|
if (focusCellGrid !== tableNode) {
|
1274
1398
|
if (!tableNode.isParentOf(focusCell)) {
|
@@ -1294,63 +1418,12 @@ class TableSelection {
|
|
1294
1418
|
// ability to store a state. Killing TableSelection and moving the logic to the plugin would make
|
1295
1419
|
// this possible.
|
1296
1420
|
const [map, cellAMap, cellBMap] = $computeTableMap(tableNode, anchorCell, focusCell);
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
let exploredMaxColumn = minColumn;
|
1304
|
-
let exploredMaxRow = minRow;
|
1305
|
-
function expandBoundary(mapValue) {
|
1306
|
-
const {
|
1307
|
-
cell,
|
1308
|
-
startColumn: cellStartColumn,
|
1309
|
-
startRow: cellStartRow
|
1310
|
-
} = mapValue;
|
1311
|
-
minColumn = Math.min(minColumn, cellStartColumn);
|
1312
|
-
minRow = Math.min(minRow, cellStartRow);
|
1313
|
-
maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
|
1314
|
-
maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
|
1315
|
-
}
|
1316
|
-
while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
|
1317
|
-
if (minColumn < exploredMinColumn) {
|
1318
|
-
// Expand on the left
|
1319
|
-
const rowDiff = exploredMaxRow - exploredMinRow;
|
1320
|
-
const previousColumn = exploredMinColumn - 1;
|
1321
|
-
for (let i = 0; i <= rowDiff; i++) {
|
1322
|
-
expandBoundary(map[exploredMinRow + i][previousColumn]);
|
1323
|
-
}
|
1324
|
-
exploredMinColumn = previousColumn;
|
1325
|
-
}
|
1326
|
-
if (minRow < exploredMinRow) {
|
1327
|
-
// Expand on top
|
1328
|
-
const columnDiff = exploredMaxColumn - exploredMinColumn;
|
1329
|
-
const previousRow = exploredMinRow - 1;
|
1330
|
-
for (let i = 0; i <= columnDiff; i++) {
|
1331
|
-
expandBoundary(map[previousRow][exploredMinColumn + i]);
|
1332
|
-
}
|
1333
|
-
exploredMinRow = previousRow;
|
1334
|
-
}
|
1335
|
-
if (maxColumn > exploredMaxColumn) {
|
1336
|
-
// Expand on the right
|
1337
|
-
const rowDiff = exploredMaxRow - exploredMinRow;
|
1338
|
-
const nextColumn = exploredMaxColumn + 1;
|
1339
|
-
for (let i = 0; i <= rowDiff; i++) {
|
1340
|
-
expandBoundary(map[exploredMinRow + i][nextColumn]);
|
1341
|
-
}
|
1342
|
-
exploredMaxColumn = nextColumn;
|
1343
|
-
}
|
1344
|
-
if (maxRow > exploredMaxRow) {
|
1345
|
-
// Expand on the bottom
|
1346
|
-
const columnDiff = exploredMaxColumn - exploredMinColumn;
|
1347
|
-
const nextRow = exploredMaxRow + 1;
|
1348
|
-
for (let i = 0; i <= columnDiff; i++) {
|
1349
|
-
expandBoundary(map[nextRow][exploredMinColumn + i]);
|
1350
|
-
}
|
1351
|
-
exploredMaxRow = nextRow;
|
1352
|
-
}
|
1353
|
-
}
|
1421
|
+
const {
|
1422
|
+
minColumn,
|
1423
|
+
maxColumn,
|
1424
|
+
minRow,
|
1425
|
+
maxRow
|
1426
|
+
} = $computeTableCellRectBoundary(map, cellAMap, cellBMap);
|
1354
1427
|
|
1355
1428
|
// We use a Map here because merged cells in the grid would otherwise
|
1356
1429
|
// show up multiple times in the nodes array
|
@@ -1367,12 +1440,13 @@ class TableSelection {
|
|
1367
1440
|
}
|
1368
1441
|
if (currentRow !== lastRow) {
|
1369
1442
|
nodeMap.set(currentRow.getKey(), currentRow);
|
1443
|
+
lastRow = currentRow;
|
1370
1444
|
}
|
1371
|
-
nodeMap.
|
1372
|
-
|
1373
|
-
|
1445
|
+
if (!nodeMap.has(cell.getKey())) {
|
1446
|
+
$visitRecursively(cell, childNode => {
|
1447
|
+
nodeMap.set(childNode.getKey(), childNode);
|
1448
|
+
});
|
1374
1449
|
}
|
1375
|
-
lastRow = currentRow;
|
1376
1450
|
}
|
1377
1451
|
}
|
1378
1452
|
const nodes = Array.from(nodeMap.values());
|
@@ -1397,26 +1471,51 @@ function $isTableSelection(x) {
|
|
1397
1471
|
return x instanceof TableSelection;
|
1398
1472
|
}
|
1399
1473
|
function $createTableSelection() {
|
1474
|
+
// TODO this is a suboptimal design, it doesn't make sense to have
|
1475
|
+
// a table selection that isn't associated with a table. This
|
1476
|
+
// constructor should have required argumnets and in true we
|
1477
|
+
// should check that they point to a table and are element points to
|
1478
|
+
// cell nodes of that table.
|
1400
1479
|
const anchor = $createPoint('root', 0, 'element');
|
1401
1480
|
const focus = $createPoint('root', 0, 'element');
|
1402
1481
|
return new TableSelection('root', anchor, focus);
|
1403
1482
|
}
|
1404
|
-
function $
|
1405
|
-
const
|
1406
|
-
const
|
1407
|
-
|
1408
|
-
|
1409
|
-
if (!(
|
1410
|
-
throw Error(
|
1483
|
+
function $createTableSelectionFrom(tableNode, anchorCell, focusCell) {
|
1484
|
+
const tableNodeKey = tableNode.getKey();
|
1485
|
+
const anchorCellKey = anchorCell.getKey();
|
1486
|
+
const focusCellKey = focusCell.getKey();
|
1487
|
+
{
|
1488
|
+
if (!tableNode.isAttached()) {
|
1489
|
+
throw Error(`$createTableSelectionFrom: tableNode ${tableNodeKey} is not attached`);
|
1411
1490
|
}
|
1412
|
-
if ($
|
1413
|
-
|
1491
|
+
if (!tableNode.is($findTableNode(anchorCell))) {
|
1492
|
+
throw Error(`$createTableSelectionFrom: anchorCell ${anchorCellKey} is not in table ${tableNodeKey}`);
|
1414
1493
|
}
|
1415
|
-
if (
|
1416
|
-
|
1494
|
+
if (!tableNode.is($findTableNode(focusCell))) {
|
1495
|
+
throw Error(`$createTableSelectionFrom: focusCell ${focusCellKey} is not in table ${tableNodeKey}`);
|
1496
|
+
} // TODO: Check for rectangular grid
|
1497
|
+
}
|
1498
|
+
const prevSelection = $getSelection();
|
1499
|
+
const nextSelection = $isTableSelection(prevSelection) ? prevSelection.clone() : $createTableSelection();
|
1500
|
+
nextSelection.set(tableNode.getKey(), anchorCell.getKey(), focusCell.getKey());
|
1501
|
+
return nextSelection;
|
1502
|
+
}
|
1503
|
+
|
1504
|
+
/**
|
1505
|
+
* Depth first visitor
|
1506
|
+
* @param node The starting node
|
1507
|
+
* @param $visit The function to call for each node. If the function returns false, then children of this node will not be explored
|
1508
|
+
*/
|
1509
|
+
function $visitRecursively(node, $visit) {
|
1510
|
+
const stack = [[node]];
|
1511
|
+
for (let currentArray = stack.at(-1); currentArray !== undefined && stack.length > 0; currentArray = stack.at(-1)) {
|
1512
|
+
const currentNode = currentArray.pop();
|
1513
|
+
if (currentNode === undefined) {
|
1514
|
+
stack.pop();
|
1515
|
+
} else if ($visit(currentNode) !== false && $isElementNode(currentNode)) {
|
1516
|
+
stack.push(currentNode.getChildren());
|
1417
1517
|
}
|
1418
1518
|
}
|
1419
|
-
return nodes;
|
1420
1519
|
}
|
1421
1520
|
|
1422
1521
|
/**
|
@@ -1468,6 +1567,7 @@ class TableObserver {
|
|
1468
1567
|
this.listenerOptions = {
|
1469
1568
|
signal: this.abortController.signal
|
1470
1569
|
};
|
1570
|
+
this.nextFocus = null;
|
1471
1571
|
this.trackTable();
|
1472
1572
|
}
|
1473
1573
|
getTable() {
|
@@ -1521,7 +1621,7 @@ class TableObserver {
|
|
1521
1621
|
editor: this.editor
|
1522
1622
|
});
|
1523
1623
|
}
|
1524
|
-
clearHighlight() {
|
1624
|
+
$clearHighlight() {
|
1525
1625
|
const editor = this.editor;
|
1526
1626
|
this.isHighlightingCells = false;
|
1527
1627
|
this.anchorX = -1;
|
@@ -1534,55 +1634,47 @@ class TableObserver {
|
|
1534
1634
|
this.anchorCell = null;
|
1535
1635
|
this.focusCell = null;
|
1536
1636
|
this.hasHijackedSelectionStyles = false;
|
1537
|
-
this
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1637
|
+
this.$enableHighlightStyle();
|
1638
|
+
const {
|
1639
|
+
tableNode,
|
1640
|
+
tableElement
|
1641
|
+
} = this.$lookup();
|
1642
|
+
const grid = getTable(tableNode, tableElement);
|
1643
|
+
$updateDOMForSelection(editor, grid, null);
|
1644
|
+
if ($getSelection() !== null) {
|
1545
1645
|
$setSelection(null);
|
1546
1646
|
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1547
|
-
}
|
1647
|
+
}
|
1548
1648
|
}
|
1549
|
-
enableHighlightStyle() {
|
1649
|
+
$enableHighlightStyle() {
|
1550
1650
|
const editor = this.editor;
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
this.hasHijackedSelectionStyles = false;
|
1558
|
-
}, {
|
1559
|
-
editor
|
1560
|
-
});
|
1651
|
+
const {
|
1652
|
+
tableElement
|
1653
|
+
} = this.$lookup();
|
1654
|
+
removeClassNamesFromElement(tableElement, editor._config.theme.tableSelection);
|
1655
|
+
tableElement.classList.remove('disable-selection');
|
1656
|
+
this.hasHijackedSelectionStyles = false;
|
1561
1657
|
}
|
1562
|
-
disableHighlightStyle() {
|
1563
|
-
const
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
addClassNamesToElement(tableElement, editor._config.theme.tableSelection);
|
1569
|
-
this.hasHijackedSelectionStyles = true;
|
1570
|
-
}, {
|
1571
|
-
editor
|
1572
|
-
});
|
1658
|
+
$disableHighlightStyle() {
|
1659
|
+
const {
|
1660
|
+
tableElement
|
1661
|
+
} = this.$lookup();
|
1662
|
+
addClassNamesToElement(tableElement, this.editor._config.theme.tableSelection);
|
1663
|
+
this.hasHijackedSelectionStyles = true;
|
1573
1664
|
}
|
1574
|
-
updateTableTableSelection(selection) {
|
1575
|
-
if (selection !== null
|
1665
|
+
$updateTableTableSelection(selection) {
|
1666
|
+
if (selection !== null) {
|
1667
|
+
if (!(selection.tableKey === this.tableNodeKey)) {
|
1668
|
+
throw Error(`TableObserver.$updateTableTableSelection: selection.tableKey !== this.tableNodeKey ('${selection.tableKey}' !== '${this.tableNodeKey}')`);
|
1669
|
+
}
|
1576
1670
|
const editor = this.editor;
|
1577
1671
|
this.tableSelection = selection;
|
1578
1672
|
this.isHighlightingCells = true;
|
1579
|
-
this
|
1673
|
+
this.$disableHighlightStyle();
|
1674
|
+
this.updateDOMSelection();
|
1580
1675
|
$updateDOMForSelection(editor, this.table, this.tableSelection);
|
1581
|
-
} else if (selection == null) {
|
1582
|
-
this.clearHighlight();
|
1583
1676
|
} else {
|
1584
|
-
this
|
1585
|
-
this.updateTableTableSelection(selection);
|
1677
|
+
this.$clearHighlight();
|
1586
1678
|
}
|
1587
1679
|
}
|
1588
1680
|
|
@@ -1608,120 +1700,165 @@ class TableObserver {
|
|
1608
1700
|
}
|
1609
1701
|
return false;
|
1610
1702
|
}
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1703
|
+
|
1704
|
+
/**
|
1705
|
+
* @internal
|
1706
|
+
* When handling mousemove events we track what the focus cell should be, but
|
1707
|
+
* the DOM selection may end up somewhere else entirely. We don't have an elegant
|
1708
|
+
* way to handle this after the DOM selection has been resolved in a
|
1709
|
+
* SELECTION_CHANGE_COMMAND callback.
|
1710
|
+
*/
|
1711
|
+
setNextFocus(nextFocus) {
|
1712
|
+
this.nextFocus = nextFocus;
|
1713
|
+
}
|
1714
|
+
|
1715
|
+
/** @internal */
|
1716
|
+
getAndClearNextFocus() {
|
1717
|
+
const {
|
1718
|
+
nextFocus
|
1719
|
+
} = this;
|
1720
|
+
if (nextFocus !== null) {
|
1721
|
+
this.nextFocus = null;
|
1722
|
+
}
|
1723
|
+
return nextFocus;
|
1724
|
+
}
|
1725
|
+
|
1726
|
+
/** @internal */
|
1727
|
+
updateDOMSelection() {
|
1728
|
+
if (this.anchorCell !== null && this.focusCell !== null) {
|
1729
|
+
const domSelection = getDOMSelection(this.editor._window);
|
1730
|
+
// We are not using a native selection for tables, and if we
|
1731
|
+
// set one then the reconciler will undo it.
|
1732
|
+
// TODO - it would make sense to have one so that native
|
1733
|
+
// copy/paste worked. Right now we have to emulate with
|
1734
|
+
// keyboard events but it won't fire if trigged from the menu
|
1735
|
+
if (domSelection && domSelection.rangeCount > 0) {
|
1736
|
+
domSelection.removeAllRanges();
|
1632
1737
|
}
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1738
|
+
}
|
1739
|
+
}
|
1740
|
+
$setFocusCellForSelection(cell, ignoreStart = false) {
|
1741
|
+
const editor = this.editor;
|
1742
|
+
const {
|
1743
|
+
tableNode
|
1744
|
+
} = this.$lookup();
|
1745
|
+
const cellX = cell.x;
|
1746
|
+
const cellY = cell.y;
|
1747
|
+
this.focusCell = cell;
|
1748
|
+
if (!this.isHighlightingCells && (this.anchorX !== cellX || this.anchorY !== cellY || ignoreStart)) {
|
1749
|
+
this.isHighlightingCells = true;
|
1750
|
+
this.$disableHighlightStyle();
|
1751
|
+
} else if (cellX === this.focusX && cellY === this.focusY) {
|
1752
|
+
return false;
|
1753
|
+
}
|
1754
|
+
this.focusX = cellX;
|
1755
|
+
this.focusY = cellY;
|
1756
|
+
if (this.isHighlightingCells) {
|
1757
|
+
const focusTableCellNode = $getNearestNodeFromDOMNode(cell.elem);
|
1758
|
+
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode) && tableNode.is($findTableNode(focusTableCellNode))) {
|
1759
|
+
this.focusCellNodeKey = focusTableCellNode.getKey();
|
1760
|
+
this.tableSelection = $createTableSelectionFrom(tableNode, this.$getAnchorTableCellOrThrow(), focusTableCellNode);
|
1761
|
+
$setSelection(this.tableSelection);
|
1762
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1763
|
+
$updateDOMForSelection(editor, this.table, this.tableSelection);
|
1764
|
+
return true;
|
1646
1765
|
}
|
1647
|
-
}
|
1766
|
+
}
|
1767
|
+
return false;
|
1768
|
+
}
|
1769
|
+
$getAnchorTableCell() {
|
1770
|
+
return this.anchorCellNodeKey ? $getNodeByKey(this.anchorCellNodeKey) : null;
|
1771
|
+
}
|
1772
|
+
$getAnchorTableCellOrThrow() {
|
1773
|
+
const anchorTableCell = this.$getAnchorTableCell();
|
1774
|
+
if (!(anchorTableCell !== null)) {
|
1775
|
+
throw Error(`TableObserver anchorTableCell is null`);
|
1776
|
+
}
|
1777
|
+
return anchorTableCell;
|
1778
|
+
}
|
1779
|
+
$getFocusTableCell() {
|
1780
|
+
return this.focusCellNodeKey ? $getNodeByKey(this.focusCellNodeKey) : null;
|
1781
|
+
}
|
1782
|
+
$getFocusTableCellOrThrow() {
|
1783
|
+
const focusTableCell = this.$getFocusTableCell();
|
1784
|
+
if (!(focusTableCell !== null)) {
|
1785
|
+
throw Error(`TableObserver focusTableCell is null`);
|
1786
|
+
}
|
1787
|
+
return focusTableCell;
|
1648
1788
|
}
|
1649
|
-
setAnchorCellForSelection(cell) {
|
1789
|
+
$setAnchorCellForSelection(cell) {
|
1650
1790
|
this.isHighlightingCells = false;
|
1651
1791
|
this.anchorCell = cell;
|
1652
1792
|
this.anchorX = cell.x;
|
1653
1793
|
this.anchorY = cell.y;
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
}
|
1661
|
-
});
|
1794
|
+
const anchorTableCellNode = $getNearestNodeFromDOMNode(cell.elem);
|
1795
|
+
if ($isTableCellNode(anchorTableCellNode)) {
|
1796
|
+
const anchorNodeKey = anchorTableCellNode.getKey();
|
1797
|
+
this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
|
1798
|
+
this.anchorCellNodeKey = anchorNodeKey;
|
1799
|
+
}
|
1662
1800
|
}
|
1663
|
-
formatCells(type) {
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
});
|
1682
|
-
$setSelection(selection);
|
1683
|
-
this.editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1801
|
+
$formatCells(type) {
|
1802
|
+
const selection = $getSelection();
|
1803
|
+
if (!$isTableSelection(selection)) {
|
1804
|
+
throw Error(`Expected Table selection`);
|
1805
|
+
}
|
1806
|
+
const formatSelection = $createRangeSelection();
|
1807
|
+
const anchor = formatSelection.anchor;
|
1808
|
+
const focus = formatSelection.focus;
|
1809
|
+
const cellNodes = selection.getNodes().filter($isTableCellNode);
|
1810
|
+
if (!(cellNodes.length > 0)) {
|
1811
|
+
throw Error(`No table cells present`);
|
1812
|
+
}
|
1813
|
+
const paragraph = cellNodes[0].getFirstChild();
|
1814
|
+
const alignFormatWith = $isParagraphNode(paragraph) ? paragraph.getFormatFlags(type, null) : null;
|
1815
|
+
cellNodes.forEach(cellNode => {
|
1816
|
+
anchor.set(cellNode.getKey(), 0, 'element');
|
1817
|
+
focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
|
1818
|
+
formatSelection.formatText(type, alignFormatWith);
|
1684
1819
|
});
|
1820
|
+
$setSelection(selection);
|
1821
|
+
this.editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1685
1822
|
}
|
1686
|
-
clearText() {
|
1687
|
-
const
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1823
|
+
$clearText() {
|
1824
|
+
const {
|
1825
|
+
editor
|
1826
|
+
} = this;
|
1827
|
+
const tableNode = $getNodeByKey(this.tableNodeKey);
|
1828
|
+
if (!$isTableNode(tableNode)) {
|
1829
|
+
throw new Error('Expected TableNode.');
|
1830
|
+
}
|
1831
|
+
const selection = $getSelection();
|
1832
|
+
if (!$isTableSelection(selection)) {
|
1833
|
+
{
|
1834
|
+
throw Error(`Expected grid selection`);
|
1698
1835
|
}
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1836
|
+
}
|
1837
|
+
const selectedNodes = selection.getNodes().filter($isTableCellNode);
|
1838
|
+
if (selectedNodes.length === this.table.columns * this.table.rows) {
|
1839
|
+
tableNode.selectPrevious();
|
1840
|
+
// Delete entire table
|
1841
|
+
tableNode.remove();
|
1842
|
+
const rootNode = $getRoot();
|
1843
|
+
rootNode.selectStart();
|
1844
|
+
return;
|
1845
|
+
}
|
1846
|
+
selectedNodes.forEach(cellNode => {
|
1847
|
+
if ($isElementNode(cellNode)) {
|
1848
|
+
const paragraphNode = $createParagraphNode();
|
1849
|
+
const textNode = $createTextNode();
|
1850
|
+
paragraphNode.append(textNode);
|
1851
|
+
cellNode.append(paragraphNode);
|
1852
|
+
cellNode.getChildren().forEach(child => {
|
1853
|
+
if (child !== paragraphNode) {
|
1854
|
+
child.remove();
|
1855
|
+
}
|
1856
|
+
});
|
1707
1857
|
}
|
1708
|
-
selectedNodes.forEach(cellNode => {
|
1709
|
-
if ($isElementNode(cellNode)) {
|
1710
|
-
const paragraphNode = $createParagraphNode();
|
1711
|
-
const textNode = $createTextNode();
|
1712
|
-
paragraphNode.append(textNode);
|
1713
|
-
cellNode.append(paragraphNode);
|
1714
|
-
cellNode.getChildren().forEach(child => {
|
1715
|
-
if (child !== paragraphNode) {
|
1716
|
-
child.remove();
|
1717
|
-
}
|
1718
|
-
});
|
1719
|
-
}
|
1720
|
-
});
|
1721
|
-
$updateDOMForSelection(editor, this.table, null);
|
1722
|
-
$setSelection(null);
|
1723
|
-
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1724
1858
|
});
|
1859
|
+
$updateDOMForSelection(editor, this.table, null);
|
1860
|
+
$setSelection(null);
|
1861
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1725
1862
|
}
|
1726
1863
|
}
|
1727
1864
|
|
@@ -1734,7 +1871,6 @@ class TableObserver {
|
|
1734
1871
|
*/
|
1735
1872
|
|
1736
1873
|
const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
|
1737
|
-
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
1738
1874
|
const isMouseDownOnEvent = event => {
|
1739
1875
|
return (event.buttons & 1) === 1;
|
1740
1876
|
};
|
@@ -1748,64 +1884,103 @@ function getTableElement(tableNode, dom) {
|
|
1748
1884
|
}
|
1749
1885
|
return element;
|
1750
1886
|
}
|
1887
|
+
function getEditorWindow(editor) {
|
1888
|
+
return editor._window;
|
1889
|
+
}
|
1890
|
+
function $findParentTableCellNodeInTable(tableNode, node) {
|
1891
|
+
for (let currentNode = node, lastTableCellNode = null; currentNode !== null; currentNode = currentNode.getParent()) {
|
1892
|
+
if (tableNode.is(currentNode)) {
|
1893
|
+
return lastTableCellNode;
|
1894
|
+
} else if ($isTableCellNode(currentNode)) {
|
1895
|
+
lastTableCellNode = currentNode;
|
1896
|
+
}
|
1897
|
+
}
|
1898
|
+
return null;
|
1899
|
+
}
|
1900
|
+
const ARROW_KEY_COMMANDS_WITH_DIRECTION = [[KEY_ARROW_DOWN_COMMAND, 'down'], [KEY_ARROW_UP_COMMAND, 'up'], [KEY_ARROW_LEFT_COMMAND, 'backward'], [KEY_ARROW_RIGHT_COMMAND, 'forward']];
|
1901
|
+
const DELETE_TEXT_COMMANDS = [DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND];
|
1902
|
+
const DELETE_KEY_COMMANDS = [KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND];
|
1751
1903
|
function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
1752
1904
|
const rootElement = editor.getRootElement();
|
1753
|
-
|
1754
|
-
|
1905
|
+
const editorWindow = getEditorWindow(editor);
|
1906
|
+
if (!(rootElement !== null && editorWindow !== null)) {
|
1907
|
+
throw Error(`applyTableHandlers: editor has no root element set`);
|
1755
1908
|
}
|
1756
1909
|
const tableObserver = new TableObserver(editor, tableNode.getKey());
|
1757
|
-
const editorWindow = editor._window || window;
|
1758
1910
|
const tableElement = getTableElement(tableNode, element);
|
1759
1911
|
attachTableObserverToTableElement(tableElement, tableObserver);
|
1760
1912
|
tableObserver.listenersToRemove.add(() => detatchTableObserverFromTableElement(tableElement, tableObserver));
|
1761
1913
|
const createMouseHandlers = () => {
|
1914
|
+
if (tableObserver.isSelecting) {
|
1915
|
+
return;
|
1916
|
+
}
|
1762
1917
|
const onMouseUp = () => {
|
1763
1918
|
tableObserver.isSelecting = false;
|
1764
1919
|
editorWindow.removeEventListener('mouseup', onMouseUp);
|
1765
1920
|
editorWindow.removeEventListener('mousemove', onMouseMove);
|
1766
1921
|
};
|
1767
1922
|
const onMouseMove = moveEvent => {
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
editorWindow.removeEventListener('mouseup', onMouseUp);
|
1773
|
-
editorWindow.removeEventListener('mousemove', onMouseMove);
|
1774
|
-
return;
|
1775
|
-
}
|
1776
|
-
const focusCell = getDOMCellFromTarget(moveEvent.target);
|
1777
|
-
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
|
1778
|
-
moveEvent.preventDefault();
|
1779
|
-
tableObserver.setFocusCellForSelection(focusCell);
|
1780
|
-
}
|
1781
|
-
}, 0);
|
1782
|
-
};
|
1783
|
-
return {
|
1784
|
-
onMouseMove,
|
1785
|
-
onMouseUp
|
1786
|
-
};
|
1787
|
-
};
|
1788
|
-
const onMouseDown = event => {
|
1789
|
-
setTimeout(() => {
|
1790
|
-
if (event.button !== 0) {
|
1923
|
+
if (!isMouseDownOnEvent(moveEvent) && tableObserver.isSelecting) {
|
1924
|
+
tableObserver.isSelecting = false;
|
1925
|
+
editorWindow.removeEventListener('mouseup', onMouseUp);
|
1926
|
+
editorWindow.removeEventListener('mousemove', onMouseMove);
|
1791
1927
|
return;
|
1792
1928
|
}
|
1793
|
-
|
1794
|
-
|
1929
|
+
const override = !tableElement.contains(moveEvent.target);
|
1930
|
+
let focusCell = null;
|
1931
|
+
if (!override) {
|
1932
|
+
focusCell = getDOMCellFromTarget(moveEvent.target);
|
1933
|
+
} else {
|
1934
|
+
for (const el of document.elementsFromPoint(moveEvent.clientX, moveEvent.clientY)) {
|
1935
|
+
focusCell = tableElement.contains(el) ? getDOMCellFromTarget(el) : null;
|
1936
|
+
if (focusCell) {
|
1937
|
+
break;
|
1938
|
+
}
|
1939
|
+
}
|
1795
1940
|
}
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1941
|
+
if (focusCell && (tableObserver.focusCell === null || focusCell.elem !== tableObserver.focusCell.elem)) {
|
1942
|
+
tableObserver.setNextFocus({
|
1943
|
+
focusCell,
|
1944
|
+
override
|
1945
|
+
});
|
1946
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1800
1947
|
}
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1948
|
+
};
|
1949
|
+
tableObserver.isSelecting = true;
|
1950
|
+
editorWindow.addEventListener('mouseup', onMouseUp, tableObserver.listenerOptions);
|
1951
|
+
editorWindow.addEventListener('mousemove', onMouseMove, tableObserver.listenerOptions);
|
1952
|
+
};
|
1953
|
+
const onMouseDown = event => {
|
1954
|
+
if (event.button !== 0) {
|
1955
|
+
return;
|
1956
|
+
}
|
1957
|
+
if (!editorWindow) {
|
1958
|
+
return;
|
1959
|
+
}
|
1960
|
+
const targetCell = getDOMCellFromTarget(event.target);
|
1961
|
+
if (targetCell !== null) {
|
1962
|
+
editor.update(() => {
|
1963
|
+
const prevSelection = $getPreviousSelection();
|
1964
|
+
// We can't trust Firefox to do the right thing with the selection and
|
1965
|
+
// we don't have a proper state machine to do this "correctly" but
|
1966
|
+
// if we go ahead and make the table selection now it will work
|
1967
|
+
if (IS_FIREFOX && event.shiftKey && $isSelectionInTable(prevSelection, tableNode) && ($isRangeSelection(prevSelection) || $isTableSelection(prevSelection))) {
|
1968
|
+
const prevAnchorNode = prevSelection.anchor.getNode();
|
1969
|
+
const prevAnchorCell = $findParentTableCellNodeInTable(tableNode, prevSelection.anchor.getNode());
|
1970
|
+
if (prevAnchorCell) {
|
1971
|
+
tableObserver.$setAnchorCellForSelection($getObserverCellFromCellNodeOrThrow(tableObserver, prevAnchorCell));
|
1972
|
+
tableObserver.$setFocusCellForSelection(targetCell);
|
1973
|
+
stopEvent(event);
|
1974
|
+
} else {
|
1975
|
+
const newSelection = tableNode.isBefore(prevAnchorNode) ? tableNode.selectStart() : tableNode.selectEnd();
|
1976
|
+
newSelection.anchor.set(prevSelection.anchor.key, prevSelection.anchor.offset, prevSelection.anchor.type);
|
1977
|
+
}
|
1978
|
+
} else {
|
1979
|
+
tableObserver.$setAnchorCellForSelection(targetCell);
|
1980
|
+
}
|
1981
|
+
});
|
1982
|
+
}
|
1983
|
+
createMouseHandlers();
|
1809
1984
|
};
|
1810
1985
|
tableElement.addEventListener('mousedown', onMouseDown, tableObserver.listenerOptions);
|
1811
1986
|
|
@@ -1818,20 +1993,19 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1818
1993
|
const selection = $getSelection();
|
1819
1994
|
const target = event.target;
|
1820
1995
|
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey && rootElement.contains(target)) {
|
1821
|
-
tableObserver
|
1996
|
+
tableObserver.$clearHighlight();
|
1822
1997
|
}
|
1823
1998
|
});
|
1824
1999
|
};
|
1825
2000
|
editorWindow.addEventListener('mousedown', mouseDownCallback, tableObserver.listenerOptions);
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, event => $handleArrowKey(editor, event, 'forward', tableNode, tableObserver), COMMAND_PRIORITY_HIGH));
|
2001
|
+
for (const [command, direction] of ARROW_KEY_COMMANDS_WITH_DIRECTION) {
|
2002
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(command, event => $handleArrowKey(editor, event, direction, tableNode, tableObserver), COMMAND_PRIORITY_HIGH));
|
2003
|
+
}
|
1830
2004
|
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ESCAPE_COMMAND, event => {
|
1831
2005
|
const selection = $getSelection();
|
1832
2006
|
if ($isTableSelection(selection)) {
|
1833
|
-
const focusCellNode = $
|
1834
|
-
if (
|
2007
|
+
const focusCellNode = $findParentTableCellNodeInTable(tableNode, selection.focus.getNode());
|
2008
|
+
if (focusCellNode !== null) {
|
1835
2009
|
stopEvent(event);
|
1836
2010
|
focusCellNode.selectEnd();
|
1837
2011
|
return true;
|
@@ -1845,10 +2019,10 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1845
2019
|
return false;
|
1846
2020
|
}
|
1847
2021
|
if ($isTableSelection(selection)) {
|
1848
|
-
tableObserver
|
2022
|
+
tableObserver.$clearText();
|
1849
2023
|
return true;
|
1850
2024
|
} else if ($isRangeSelection(selection)) {
|
1851
|
-
const tableCellNode = $
|
2025
|
+
const tableCellNode = $findParentTableCellNodeInTable(tableNode, selection.anchor.getNode());
|
1852
2026
|
if (!$isTableCellNode(tableCellNode)) {
|
1853
2027
|
return false;
|
1854
2028
|
}
|
@@ -1858,7 +2032,7 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1858
2032
|
const isFocusInside = tableNode.isParentOf(focusNode);
|
1859
2033
|
const selectionContainsPartialTable = isAnchorInside && !isFocusInside || isFocusInside && !isAnchorInside;
|
1860
2034
|
if (selectionContainsPartialTable) {
|
1861
|
-
tableObserver
|
2035
|
+
tableObserver.$clearText();
|
1862
2036
|
return true;
|
1863
2037
|
}
|
1864
2038
|
const nearestElementNode = $findMatchingParent(selection.anchor.getNode(), n => $isElementNode(n));
|
@@ -1873,9 +2047,9 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1873
2047
|
}
|
1874
2048
|
return false;
|
1875
2049
|
};
|
1876
|
-
|
2050
|
+
for (const command of DELETE_TEXT_COMMANDS) {
|
1877
2051
|
tableObserver.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), COMMAND_PRIORITY_CRITICAL));
|
1878
|
-
}
|
2052
|
+
}
|
1879
2053
|
const $deleteCellHandler = event => {
|
1880
2054
|
const selection = $getSelection();
|
1881
2055
|
if (!($isTableSelection(selection) || $isRangeSelection(selection))) {
|
@@ -1908,13 +2082,14 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1908
2082
|
event.preventDefault();
|
1909
2083
|
event.stopPropagation();
|
1910
2084
|
}
|
1911
|
-
tableObserver
|
2085
|
+
tableObserver.$clearText();
|
1912
2086
|
return true;
|
1913
2087
|
}
|
1914
2088
|
return false;
|
1915
2089
|
};
|
1916
|
-
|
1917
|
-
|
2090
|
+
for (const command of DELETE_KEY_COMMANDS) {
|
2091
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(command, $deleteCellHandler, COMMAND_PRIORITY_CRITICAL));
|
2092
|
+
}
|
1918
2093
|
tableObserver.listenersToRemove.add(editor.registerCommand(CUT_COMMAND, event => {
|
1919
2094
|
const selection = $getSelection();
|
1920
2095
|
if (selection) {
|
@@ -1939,7 +2114,7 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1939
2114
|
return false;
|
1940
2115
|
}
|
1941
2116
|
if ($isTableSelection(selection)) {
|
1942
|
-
tableObserver
|
2117
|
+
tableObserver.$formatCells(payload);
|
1943
2118
|
return true;
|
1944
2119
|
} else if ($isRangeSelection(selection)) {
|
1945
2120
|
const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
@@ -1960,13 +2135,18 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1960
2135
|
return false;
|
1961
2136
|
}
|
1962
2137
|
const [tableMap, anchorCell, focusCell] = $computeTableMap(tableNode, anchorNode, focusNode);
|
1963
|
-
const maxRow = Math.max(anchorCell.startRow, focusCell.startRow);
|
1964
|
-
const maxColumn = Math.max(anchorCell.startColumn, focusCell.startColumn);
|
2138
|
+
const maxRow = Math.max(anchorCell.startRow + anchorCell.cell.__rowSpan - 1, focusCell.startRow + focusCell.cell.__rowSpan - 1);
|
2139
|
+
const maxColumn = Math.max(anchorCell.startColumn + anchorCell.cell.__colSpan - 1, focusCell.startColumn + focusCell.cell.__colSpan - 1);
|
1965
2140
|
const minRow = Math.min(anchorCell.startRow, focusCell.startRow);
|
1966
2141
|
const minColumn = Math.min(anchorCell.startColumn, focusCell.startColumn);
|
2142
|
+
const visited = new Set();
|
1967
2143
|
for (let i = minRow; i <= maxRow; i++) {
|
1968
2144
|
for (let j = minColumn; j <= maxColumn; j++) {
|
1969
2145
|
const cell = tableMap[i][j].cell;
|
2146
|
+
if (visited.has(cell)) {
|
2147
|
+
continue;
|
2148
|
+
}
|
2149
|
+
visited.add(cell);
|
1970
2150
|
cell.setFormat(formatType);
|
1971
2151
|
const cellChildren = cell.getChildren();
|
1972
2152
|
for (let k = 0; k < cellChildren.length; k++) {
|
@@ -1985,7 +2165,7 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
1985
2165
|
return false;
|
1986
2166
|
}
|
1987
2167
|
if ($isTableSelection(selection)) {
|
1988
|
-
tableObserver
|
2168
|
+
tableObserver.$clearHighlight();
|
1989
2169
|
return false;
|
1990
2170
|
} else if ($isRangeSelection(selection)) {
|
1991
2171
|
const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
@@ -2021,10 +2201,6 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
2021
2201
|
tableObserver.listenersToRemove.add(editor.registerCommand(FOCUS_COMMAND, payload => {
|
2022
2202
|
return tableNode.isSelected();
|
2023
2203
|
}, COMMAND_PRIORITY_HIGH));
|
2024
|
-
function getObserverCellFromCellNode(tableCellNode) {
|
2025
|
-
const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
|
2026
|
-
return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
|
2027
|
-
}
|
2028
2204
|
tableObserver.listenersToRemove.add(editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, selectionPayload => {
|
2029
2205
|
const {
|
2030
2206
|
nodes,
|
@@ -2099,10 +2275,30 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
2099
2275
|
tableObserver.listenersToRemove.add(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
2100
2276
|
const selection = $getSelection();
|
2101
2277
|
const prevSelection = $getPreviousSelection();
|
2278
|
+
const nextFocus = tableObserver.getAndClearNextFocus();
|
2279
|
+
if (nextFocus !== null) {
|
2280
|
+
const {
|
2281
|
+
focusCell
|
2282
|
+
} = nextFocus;
|
2283
|
+
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
|
2284
|
+
if (focusCell.x === tableObserver.focusX && focusCell.y === tableObserver.focusY) {
|
2285
|
+
// The selection is already the correct table selection
|
2286
|
+
return false;
|
2287
|
+
} else {
|
2288
|
+
tableObserver.$setFocusCellForSelection(focusCell);
|
2289
|
+
return true;
|
2290
|
+
}
|
2291
|
+
} else if (focusCell !== tableObserver.anchorCell && $isSelectionInTable(selection, tableNode)) {
|
2292
|
+
// The selection has crossed cells
|
2293
|
+
tableObserver.$setFocusCellForSelection(focusCell);
|
2294
|
+
return true;
|
2295
|
+
}
|
2296
|
+
}
|
2297
|
+
const shouldCheckSelection = tableObserver.getAndClearShouldCheckSelection();
|
2102
2298
|
// If they pressed the down arrow with the selection outside of the
|
2103
2299
|
// table, and then the selection ends up in the table but not in the
|
2104
2300
|
// first cell, then move the selection to the first cell.
|
2105
|
-
if (
|
2301
|
+
if (shouldCheckSelection && $isRangeSelection(prevSelection) && $isRangeSelection(selection) && selection.isCollapsed()) {
|
2106
2302
|
const anchor = selection.anchor.getNode();
|
2107
2303
|
const firstRow = tableNode.getFirstChild();
|
2108
2304
|
const anchorCell = $findCellNode(anchor);
|
@@ -2128,10 +2324,10 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
2128
2324
|
const focusCellNode = $findCellNode(focusNode);
|
2129
2325
|
const isAnchorInside = !!(anchorCellNode && tableNode.is($findTableNode(anchorCellNode)));
|
2130
2326
|
const isFocusInside = !!(focusCellNode && tableNode.is($findTableNode(focusCellNode)));
|
2131
|
-
const
|
2327
|
+
const isPartiallyWithinTable = isAnchorInside !== isFocusInside;
|
2132
2328
|
const isWithinTable = isAnchorInside && isFocusInside;
|
2133
2329
|
const isBackward = selection.isBackward();
|
2134
|
-
if (
|
2330
|
+
if (isPartiallyWithinTable) {
|
2135
2331
|
const newSelection = selection.clone();
|
2136
2332
|
if (isFocusInside) {
|
2137
2333
|
const [tableMap] = $computeTableMap(tableNode, focusCellNode, focusCellNode);
|
@@ -2154,26 +2350,15 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
2154
2350
|
$addHighlightStyleToTable(editor, tableObserver);
|
2155
2351
|
} else if (isWithinTable) {
|
2156
2352
|
// Handle case when selection spans across multiple cells but still
|
2157
|
-
// has range selection, then we convert it into
|
2353
|
+
// has range selection, then we convert it into table selection
|
2158
2354
|
if (!anchorCellNode.is(focusCellNode)) {
|
2159
|
-
tableObserver
|
2160
|
-
tableObserver
|
2161
|
-
if (!tableObserver.isSelecting) {
|
2162
|
-
setTimeout(() => {
|
2163
|
-
const {
|
2164
|
-
onMouseUp,
|
2165
|
-
onMouseMove
|
2166
|
-
} = createMouseHandlers();
|
2167
|
-
tableObserver.isSelecting = true;
|
2168
|
-
editorWindow.addEventListener('mouseup', onMouseUp);
|
2169
|
-
editorWindow.addEventListener('mousemove', onMouseMove);
|
2170
|
-
}, 0);
|
2171
|
-
}
|
2355
|
+
tableObserver.$setAnchorCellForSelection($getObserverCellFromCellNodeOrThrow(tableObserver, anchorCellNode));
|
2356
|
+
tableObserver.$setFocusCellForSelection($getObserverCellFromCellNodeOrThrow(tableObserver, focusCellNode), true);
|
2172
2357
|
}
|
2173
2358
|
}
|
2174
2359
|
} else if (selection && $isTableSelection(selection) && selection.is(prevSelection) && selection.tableKey === tableNode.getKey()) {
|
2175
2360
|
// if selection goes outside of the table we need to change it to Range selection
|
2176
|
-
const domSelection = getDOMSelection(
|
2361
|
+
const domSelection = getDOMSelection(editorWindow);
|
2177
2362
|
if (domSelection && domSelection.anchorNode && domSelection.focusNode) {
|
2178
2363
|
const focusNode = $getNearestNodeFromDOMNode(domSelection.focusNode);
|
2179
2364
|
const isFocusOutside = focusNode && !tableNode.is($findTableNode(focusNode));
|
@@ -2191,9 +2376,9 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
|
|
2191
2376
|
}
|
2192
2377
|
if (selection && !selection.is(prevSelection) && ($isTableSelection(selection) || $isTableSelection(prevSelection)) && tableObserver.tableSelection && !tableObserver.tableSelection.is(prevSelection)) {
|
2193
2378
|
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
|
2194
|
-
tableObserver
|
2379
|
+
tableObserver.$updateTableTableSelection(selection);
|
2195
2380
|
} else if (!$isTableSelection(selection) && $isTableSelection(prevSelection) && prevSelection.tableKey === tableObserver.tableNodeKey) {
|
2196
|
-
tableObserver
|
2381
|
+
tableObserver.$updateTableTableSelection(null);
|
2197
2382
|
}
|
2198
2383
|
return false;
|
2199
2384
|
}
|
@@ -2348,14 +2533,14 @@ function $forEachTableCell(grid, cb) {
|
|
2348
2533
|
}
|
2349
2534
|
}
|
2350
2535
|
function $addHighlightStyleToTable(editor, tableSelection) {
|
2351
|
-
tableSelection
|
2536
|
+
tableSelection.$disableHighlightStyle();
|
2352
2537
|
$forEachTableCell(tableSelection.table, cell => {
|
2353
2538
|
cell.highlighted = true;
|
2354
2539
|
$addHighlightToDOM(editor, cell);
|
2355
2540
|
});
|
2356
2541
|
}
|
2357
2542
|
function $removeHighlightStyleToTable(editor, tableObserver) {
|
2358
|
-
tableObserver
|
2543
|
+
tableObserver.$enableHighlightStyle();
|
2359
2544
|
$forEachTableCell(tableObserver.table, cell => {
|
2360
2545
|
const elem = cell.elem;
|
2361
2546
|
cell.highlighted = false;
|
@@ -2400,33 +2585,105 @@ const selectTableNodeInDirection = (tableObserver, tableNode, x, y, direction) =
|
|
2400
2585
|
return false;
|
2401
2586
|
}
|
2402
2587
|
};
|
2403
|
-
|
2404
|
-
|
2405
|
-
|
2406
|
-
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
2411
|
-
|
2412
|
-
case 'up':
|
2413
|
-
if (y !== 0) {
|
2414
|
-
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y - 1, tableObserver.table));
|
2415
|
-
return true;
|
2416
|
-
} else {
|
2417
|
-
return false;
|
2418
|
-
}
|
2419
|
-
case 'down':
|
2420
|
-
if (y !== tableObserver.table.rows - 1) {
|
2421
|
-
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y + 1, tableObserver.table));
|
2422
|
-
return true;
|
2423
|
-
} else {
|
2424
|
-
return false;
|
2425
|
-
}
|
2426
|
-
default:
|
2427
|
-
return false;
|
2588
|
+
function getCorner(rect, cellValue) {
|
2589
|
+
let colName;
|
2590
|
+
let rowName;
|
2591
|
+
if (cellValue.startColumn === rect.minColumn) {
|
2592
|
+
colName = 'minColumn';
|
2593
|
+
} else if (cellValue.startColumn + cellValue.cell.__colSpan - 1 === rect.maxColumn) {
|
2594
|
+
colName = 'maxColumn';
|
2595
|
+
} else {
|
2596
|
+
return null;
|
2428
2597
|
}
|
2429
|
-
|
2598
|
+
if (cellValue.startRow === rect.minRow) {
|
2599
|
+
rowName = 'minRow';
|
2600
|
+
} else if (cellValue.startRow + cellValue.cell.__rowSpan - 1 === rect.maxRow) {
|
2601
|
+
rowName = 'maxRow';
|
2602
|
+
} else {
|
2603
|
+
return null;
|
2604
|
+
}
|
2605
|
+
return [colName, rowName];
|
2606
|
+
}
|
2607
|
+
function getCornerOrThrow(rect, cellValue) {
|
2608
|
+
const corner = getCorner(rect, cellValue);
|
2609
|
+
if (!(corner !== null)) {
|
2610
|
+
throw Error(`getCornerOrThrow: cell ${cellValue.cell.getKey()} is not at a corner of rect`);
|
2611
|
+
}
|
2612
|
+
return corner;
|
2613
|
+
}
|
2614
|
+
function oppositeCorner([colName, rowName]) {
|
2615
|
+
return [colName === 'minColumn' ? 'maxColumn' : 'minColumn', rowName === 'minRow' ? 'maxRow' : 'minRow'];
|
2616
|
+
}
|
2617
|
+
function cellAtCornerOrThrow(tableMap, rect, [colName, rowName]) {
|
2618
|
+
const rowNum = rect[rowName];
|
2619
|
+
const rowMap = tableMap[rowNum];
|
2620
|
+
if (!(rowMap !== undefined)) {
|
2621
|
+
throw Error(`cellAtCornerOrThrow: ${rowName} = ${String(rowNum)} missing in tableMap`);
|
2622
|
+
}
|
2623
|
+
const colNum = rect[colName];
|
2624
|
+
const cell = rowMap[colNum];
|
2625
|
+
if (!(cell !== undefined)) {
|
2626
|
+
throw Error(`cellAtCornerOrThrow: ${colName} = ${String(colNum)} missing in tableMap`);
|
2627
|
+
}
|
2628
|
+
return cell;
|
2629
|
+
}
|
2630
|
+
function $extractRectCorners(tableMap, anchorCellValue, newFocusCellValue) {
|
2631
|
+
// We are sure that the focus now either contracts or expands the rect
|
2632
|
+
// but both the anchor and focus might be moved to ensure a rectangle
|
2633
|
+
// given a potentially ragged merge shape
|
2634
|
+
const rect = $computeTableCellRectBoundary(tableMap, anchorCellValue, newFocusCellValue);
|
2635
|
+
const anchorCorner = getCorner(rect, anchorCellValue);
|
2636
|
+
if (anchorCorner) {
|
2637
|
+
return [cellAtCornerOrThrow(tableMap, rect, anchorCorner), cellAtCornerOrThrow(tableMap, rect, oppositeCorner(anchorCorner))];
|
2638
|
+
}
|
2639
|
+
const newFocusCorner = getCorner(rect, newFocusCellValue);
|
2640
|
+
if (newFocusCorner) {
|
2641
|
+
return [cellAtCornerOrThrow(tableMap, rect, oppositeCorner(newFocusCorner)), cellAtCornerOrThrow(tableMap, rect, newFocusCorner)];
|
2642
|
+
}
|
2643
|
+
// TODO this doesn't have to be arbitrary, use the closest corner instead
|
2644
|
+
const newAnchorCorner = ['minColumn', 'minRow'];
|
2645
|
+
return [cellAtCornerOrThrow(tableMap, rect, newAnchorCorner), cellAtCornerOrThrow(tableMap, rect, oppositeCorner(newAnchorCorner))];
|
2646
|
+
}
|
2647
|
+
function $adjustFocusInDirection(tableObserver, tableMap, anchorCellValue, focusCellValue, direction) {
|
2648
|
+
const rect = $computeTableCellRectBoundary(tableMap, anchorCellValue, focusCellValue);
|
2649
|
+
const spans = $computeTableCellRectSpans(tableMap, rect);
|
2650
|
+
const {
|
2651
|
+
topSpan,
|
2652
|
+
leftSpan,
|
2653
|
+
bottomSpan,
|
2654
|
+
rightSpan
|
2655
|
+
} = spans;
|
2656
|
+
const anchorCorner = getCornerOrThrow(rect, anchorCellValue);
|
2657
|
+
const [focusColumn, focusRow] = oppositeCorner(anchorCorner);
|
2658
|
+
let fCol = rect[focusColumn];
|
2659
|
+
let fRow = rect[focusRow];
|
2660
|
+
if (direction === 'forward') {
|
2661
|
+
fCol += focusColumn === 'maxColumn' ? 1 : leftSpan;
|
2662
|
+
} else if (direction === 'backward') {
|
2663
|
+
fCol -= focusColumn === 'minColumn' ? 1 : rightSpan;
|
2664
|
+
} else if (direction === 'down') {
|
2665
|
+
fRow += focusRow === 'maxRow' ? 1 : topSpan;
|
2666
|
+
} else if (direction === 'up') {
|
2667
|
+
fRow -= focusRow === 'minRow' ? 1 : bottomSpan;
|
2668
|
+
}
|
2669
|
+
const targetRowMap = tableMap[fRow];
|
2670
|
+
if (targetRowMap === undefined) {
|
2671
|
+
return false;
|
2672
|
+
}
|
2673
|
+
const newFocusCellValue = targetRowMap[fCol];
|
2674
|
+
if (newFocusCellValue === undefined) {
|
2675
|
+
return false;
|
2676
|
+
}
|
2677
|
+
// We can be certain that anchorCellValue and newFocusCellValue are
|
2678
|
+
// contained within the desired selection, but we are not certain if
|
2679
|
+
// they need to be expanded or not to maintain a rectangular shape
|
2680
|
+
const [finalAnchorCell, finalFocusCell] = $extractRectCorners(tableMap, anchorCellValue, newFocusCellValue);
|
2681
|
+
const anchorDOM = $getObserverCellFromCellNodeOrThrow(tableObserver, finalAnchorCell.cell);
|
2682
|
+
const focusDOM = $getObserverCellFromCellNodeOrThrow(tableObserver, finalFocusCell.cell);
|
2683
|
+
tableObserver.$setAnchorCellForSelection(anchorDOM);
|
2684
|
+
tableObserver.$setFocusCellForSelection(focusDOM, true);
|
2685
|
+
return true;
|
2686
|
+
}
|
2430
2687
|
function $isSelectionInTable(selection, tableNode) {
|
2431
2688
|
if ($isRangeSelection(selection) || $isTableSelection(selection)) {
|
2432
2689
|
const isAnchorInside = tableNode.isParentOf(selection.anchor.getNode());
|
@@ -2442,20 +2699,14 @@ function selectTableCellNode(tableCell, fromStart) {
|
|
2442
2699
|
tableCell.selectEnd();
|
2443
2700
|
}
|
2444
2701
|
}
|
2445
|
-
const BROWSER_BLUE_RGB = '172,206,247';
|
2446
2702
|
function $addHighlightToDOM(editor, cell) {
|
2447
2703
|
const element = cell.elem;
|
2704
|
+
const editorThemeClasses = editor._config.theme;
|
2448
2705
|
const node = $getNearestNodeFromDOMNode(element);
|
2449
2706
|
if (!$isTableCellNode(node)) {
|
2450
2707
|
throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
|
2451
2708
|
}
|
2452
|
-
|
2453
|
-
if (backgroundColor === null) {
|
2454
|
-
element.style.setProperty('background-color', `rgb(${BROWSER_BLUE_RGB})`);
|
2455
|
-
} else {
|
2456
|
-
element.style.setProperty('background-image', `linear-gradient(to right, rgba(${BROWSER_BLUE_RGB},0.85), rgba(${BROWSER_BLUE_RGB},0.85))`);
|
2457
|
-
}
|
2458
|
-
element.style.setProperty('caret-color', 'transparent');
|
2709
|
+
addClassNamesToElement(element, editorThemeClasses.tableCellSelected);
|
2459
2710
|
}
|
2460
2711
|
function $removeHighlightFromDOM(editor, cell) {
|
2461
2712
|
const element = cell.elem;
|
@@ -2463,12 +2714,8 @@ function $removeHighlightFromDOM(editor, cell) {
|
|
2463
2714
|
if (!$isTableCellNode(node)) {
|
2464
2715
|
throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
|
2465
2716
|
}
|
2466
|
-
const
|
2467
|
-
|
2468
|
-
element.style.removeProperty('background-color');
|
2469
|
-
}
|
2470
|
-
element.style.removeProperty('background-image');
|
2471
|
-
element.style.removeProperty('caret-color');
|
2717
|
+
const editorThemeClasses = editor._config.theme;
|
2718
|
+
removeClassNamesFromElement(element, editorThemeClasses.tableCellSelected);
|
2472
2719
|
}
|
2473
2720
|
function $findCellNode(node) {
|
2474
2721
|
const cellNode = $findMatchingParent(node, $isTableCellNode);
|
@@ -2559,8 +2806,8 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2559
2806
|
const lastCellCoords = tableNode.getCordsFromCellNode(lastCellNode, tableObserver.table);
|
2560
2807
|
const firstCellDOM = tableNode.getDOMCellFromCordsOrThrow(firstCellCoords.x, firstCellCoords.y, tableObserver.table);
|
2561
2808
|
const lastCellDOM = tableNode.getDOMCellFromCordsOrThrow(lastCellCoords.x, lastCellCoords.y, tableObserver.table);
|
2562
|
-
tableObserver
|
2563
|
-
tableObserver
|
2809
|
+
tableObserver.$setAnchorCellForSelection(firstCellDOM);
|
2810
|
+
tableObserver.$setFocusCellForSelection(lastCellDOM, true);
|
2564
2811
|
return true;
|
2565
2812
|
}
|
2566
2813
|
}
|
@@ -2627,7 +2874,7 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2627
2874
|
return false;
|
2628
2875
|
}
|
2629
2876
|
if (isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction)) {
|
2630
|
-
return $handleTableExit(event, anchorNode, tableNode, direction);
|
2877
|
+
return $handleTableExit(event, anchorNode, anchorCellNode, tableNode, direction);
|
2631
2878
|
}
|
2632
2879
|
return false;
|
2633
2880
|
}
|
@@ -2640,7 +2887,7 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2640
2887
|
if (anchor.type === 'element') {
|
2641
2888
|
edgeSelectionRect = anchorDOM.getBoundingClientRect();
|
2642
2889
|
} else {
|
2643
|
-
const domSelection =
|
2890
|
+
const domSelection = getDOMSelection(getEditorWindow(editor));
|
2644
2891
|
if (domSelection === null || domSelection.rangeCount === 0) {
|
2645
2892
|
return false;
|
2646
2893
|
}
|
@@ -2662,8 +2909,8 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2662
2909
|
const cords = tableNode.getCordsFromCellNode(anchorCellNode, tableObserver.table);
|
2663
2910
|
if (event.shiftKey) {
|
2664
2911
|
const cell = tableNode.getDOMCellFromCordsOrThrow(cords.x, cords.y, tableObserver.table);
|
2665
|
-
tableObserver
|
2666
|
-
tableObserver
|
2912
|
+
tableObserver.$setAnchorCellForSelection(cell);
|
2913
|
+
tableObserver.$setFocusCellForSelection(cell, true);
|
2667
2914
|
} else {
|
2668
2915
|
return selectTableNodeInDirection(tableObserver, tableNode, cords.x, cords.y, direction);
|
2669
2916
|
}
|
@@ -2684,15 +2931,15 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2684
2931
|
if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableNode(tableNodeFromSelection) || tableElement == null) {
|
2685
2932
|
return false;
|
2686
2933
|
}
|
2687
|
-
tableObserver
|
2934
|
+
tableObserver.$updateTableTableSelection(selection);
|
2688
2935
|
const grid = getTable(tableNodeFromSelection, tableElement);
|
2689
2936
|
const cordsAnchor = tableNode.getCordsFromCellNode(anchorCellNode, grid);
|
2690
2937
|
const anchorCell = tableNode.getDOMCellFromCordsOrThrow(cordsAnchor.x, cordsAnchor.y, grid);
|
2691
|
-
tableObserver
|
2938
|
+
tableObserver.$setAnchorCellForSelection(anchorCell);
|
2692
2939
|
stopEvent(event);
|
2693
2940
|
if (event.shiftKey) {
|
2694
|
-
const
|
2695
|
-
return
|
2941
|
+
const [tableMap, anchorValue, focusValue] = $computeTableMap(tableNode, anchorCellNode, focusCellNode);
|
2942
|
+
return $adjustFocusInDirection(tableObserver, tableMap, anchorValue, focusValue, direction);
|
2696
2943
|
} else {
|
2697
2944
|
focusCellNode.selectEnd();
|
2698
2945
|
}
|
@@ -2728,11 +2975,7 @@ function $isExitingTableTextAnchor(type, offset, anchorNode, direction) {
|
|
2728
2975
|
const hasValidOffset = direction === 'backward' ? offset === 0 : offset === anchorNode.getTextContentSize();
|
2729
2976
|
return type === 'text' && hasValidOffset && (direction === 'backward' ? parentNode.getPreviousSibling() === null : parentNode.getNextSibling() === null);
|
2730
2977
|
}
|
2731
|
-
function $handleTableExit(event, anchorNode, tableNode, direction) {
|
2732
|
-
const anchorCellNode = $findMatchingParent(anchorNode, $isTableCellNode);
|
2733
|
-
if (!$isTableCellNode(anchorCellNode)) {
|
2734
|
-
return false;
|
2735
|
-
}
|
2978
|
+
function $handleTableExit(event, anchorNode, anchorCellNode, tableNode, direction) {
|
2736
2979
|
const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
|
2737
2980
|
if (!isExitingCell(tableMap, cellValue, direction)) {
|
2738
2981
|
return false;
|
@@ -2783,7 +3026,7 @@ function $getTableEdgeCursorPosition(editor, selection, tableNode) {
|
|
2783
3026
|
}
|
2784
3027
|
|
2785
3028
|
// TODO: Add support for nested tables
|
2786
|
-
const domSelection =
|
3029
|
+
const domSelection = getDOMSelection(getEditorWindow(editor));
|
2787
3030
|
if (!domSelection) {
|
2788
3031
|
return undefined;
|
2789
3032
|
}
|
@@ -2823,6 +3066,13 @@ function $getTableEdgeCursorPosition(editor, selection, tableNode) {
|
|
2823
3066
|
return undefined;
|
2824
3067
|
}
|
2825
3068
|
}
|
3069
|
+
function $getObserverCellFromCellNodeOrThrow(tableObserver, tableCellNode) {
|
3070
|
+
const {
|
3071
|
+
tableNode
|
3072
|
+
} = tableObserver.$lookup();
|
3073
|
+
const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
|
3074
|
+
return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
|
3075
|
+
}
|
2826
3076
|
|
2827
3077
|
/**
|
2828
3078
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|