@lexical/table 0.17.2-nightly.20240906.0 → 0.17.2-nightly.20240910.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 +117 -72
- package/LexicalTable.dev.mjs +117 -72
- package/LexicalTable.js.flow +3 -3
- package/LexicalTable.prod.js +85 -82
- package/LexicalTable.prod.mjs +1 -1
- package/LexicalTableCellNode.d.ts +5 -4
- package/package.json +4 -4
package/LexicalTable.dev.js
CHANGED
@@ -56,10 +56,12 @@ class TableCellNode extends lexical.ElementNode {
|
|
56
56
|
return 'tablecell';
|
57
57
|
}
|
58
58
|
static clone(node) {
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
return new TableCellNode(node.__headerState, node.__colSpan, node.__width, node.__key);
|
60
|
+
}
|
61
|
+
afterCloneFrom(node) {
|
62
|
+
super.afterCloneFrom(node);
|
63
|
+
this.__rowSpan = node.__rowSpan;
|
64
|
+
this.__backgroundColor = node.__backgroundColor;
|
63
65
|
}
|
64
66
|
static importDOM() {
|
65
67
|
return {
|
@@ -76,10 +78,7 @@ class TableCellNode extends lexical.ElementNode {
|
|
76
78
|
static importJSON(serializedNode) {
|
77
79
|
const colSpan = serializedNode.colSpan || 1;
|
78
80
|
const rowSpan = serializedNode.rowSpan || 1;
|
79
|
-
|
80
|
-
cellNode.__rowSpan = rowSpan;
|
81
|
-
cellNode.__backgroundColor = serializedNode.backgroundColor || null;
|
82
|
-
return cellNode;
|
81
|
+
return $createTableCellNode(serializedNode.headerState, colSpan, serializedNode.width || undefined).setRowSpan(rowSpan).setBackgroundColor(serializedNode.backgroundColor || null);
|
83
82
|
}
|
84
83
|
constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
|
85
84
|
super(key);
|
@@ -145,26 +144,28 @@ class TableCellNode extends lexical.ElementNode {
|
|
145
144
|
};
|
146
145
|
}
|
147
146
|
getColSpan() {
|
148
|
-
return this.__colSpan;
|
147
|
+
return this.getLatest().__colSpan;
|
149
148
|
}
|
150
149
|
setColSpan(colSpan) {
|
151
|
-
this.getWritable()
|
152
|
-
|
150
|
+
const self = this.getWritable();
|
151
|
+
self.__colSpan = colSpan;
|
152
|
+
return self;
|
153
153
|
}
|
154
154
|
getRowSpan() {
|
155
|
-
return this.__rowSpan;
|
155
|
+
return this.getLatest().__rowSpan;
|
156
156
|
}
|
157
157
|
setRowSpan(rowSpan) {
|
158
|
-
this.getWritable()
|
159
|
-
|
158
|
+
const self = this.getWritable();
|
159
|
+
self.__rowSpan = rowSpan;
|
160
|
+
return self;
|
160
161
|
}
|
161
162
|
getTag() {
|
162
163
|
return this.hasHeader() ? 'th' : 'td';
|
163
164
|
}
|
164
|
-
setHeaderStyles(headerState) {
|
165
|
+
setHeaderStyles(headerState, mask = TableCellHeaderStates.BOTH) {
|
165
166
|
const self = this.getWritable();
|
166
|
-
self.__headerState = headerState;
|
167
|
-
return
|
167
|
+
self.__headerState = headerState & mask | self.__headerState & ~mask;
|
168
|
+
return self;
|
168
169
|
}
|
169
170
|
getHeaderStyles() {
|
170
171
|
return this.getLatest().__headerState;
|
@@ -172,7 +173,7 @@ class TableCellNode extends lexical.ElementNode {
|
|
172
173
|
setWidth(width) {
|
173
174
|
const self = this.getWritable();
|
174
175
|
self.__width = width;
|
175
|
-
return
|
176
|
+
return self;
|
176
177
|
}
|
177
178
|
getWidth() {
|
178
179
|
return this.getLatest().__width;
|
@@ -181,7 +182,9 @@ class TableCellNode extends lexical.ElementNode {
|
|
181
182
|
return this.getLatest().__backgroundColor;
|
182
183
|
}
|
183
184
|
setBackgroundColor(newBackgroundColor) {
|
184
|
-
this.getWritable()
|
185
|
+
const self = this.getWritable();
|
186
|
+
self.__backgroundColor = newBackgroundColor;
|
187
|
+
return self;
|
185
188
|
}
|
186
189
|
toggleHeaderStyle(headerStateToToggle) {
|
187
190
|
const self = this.getWritable();
|
@@ -769,7 +772,7 @@ function $deleteTableRow__EXPERIMENTAL() {
|
|
769
772
|
}
|
770
773
|
const rowNode = grid.getChildAtIndex(row);
|
771
774
|
if (!$isTableRowNode(rowNode)) {
|
772
|
-
throw Error(`Expected
|
775
|
+
throw Error(`Expected TableNode childAtIndex(${String(row)}) to be RowNode`);
|
773
776
|
}
|
774
777
|
rowNode.remove();
|
775
778
|
}
|
@@ -879,18 +882,44 @@ function $unmergeCell() {
|
|
879
882
|
const [cell, row, grid] = $getNodeTriplet(anchor);
|
880
883
|
const colSpan = cell.__colSpan;
|
881
884
|
const rowSpan = cell.__rowSpan;
|
885
|
+
if (colSpan === 1 && rowSpan === 1) {
|
886
|
+
return;
|
887
|
+
}
|
888
|
+
const [map, cellMap] = $computeTableMap(grid, cell, cell);
|
889
|
+
const {
|
890
|
+
startColumn,
|
891
|
+
startRow
|
892
|
+
} = cellMap;
|
893
|
+
// Create a heuristic for what the style of the unmerged cells should be
|
894
|
+
// based on whether every row or column already had that state before the
|
895
|
+
// unmerge.
|
896
|
+
const baseColStyle = cell.__headerState & TableCellHeaderStates.COLUMN;
|
897
|
+
const colStyles = Array.from({
|
898
|
+
length: colSpan
|
899
|
+
}, (_v, i) => {
|
900
|
+
let colStyle = baseColStyle;
|
901
|
+
for (let rowIdx = 0; colStyle !== 0 && rowIdx < map.length; rowIdx++) {
|
902
|
+
colStyle &= map[rowIdx][i + startColumn].cell.__headerState;
|
903
|
+
}
|
904
|
+
return colStyle;
|
905
|
+
});
|
906
|
+
const baseRowStyle = cell.__headerState & TableCellHeaderStates.ROW;
|
907
|
+
const rowStyles = Array.from({
|
908
|
+
length: rowSpan
|
909
|
+
}, (_v, i) => {
|
910
|
+
let rowStyle = baseRowStyle;
|
911
|
+
for (let colIdx = 0; rowStyle !== 0 && colIdx < map[0].length; colIdx++) {
|
912
|
+
rowStyle &= map[i + startRow][colIdx].cell.__headerState;
|
913
|
+
}
|
914
|
+
return rowStyle;
|
915
|
+
});
|
882
916
|
if (colSpan > 1) {
|
883
917
|
for (let i = 1; i < colSpan; i++) {
|
884
|
-
cell.insertAfter($createTableCellNode(
|
918
|
+
cell.insertAfter($createTableCellNode(colStyles[i] | rowStyles[0]).append(lexical.$createParagraphNode()));
|
885
919
|
}
|
886
920
|
cell.setColSpan(1);
|
887
921
|
}
|
888
922
|
if (rowSpan > 1) {
|
889
|
-
const [map, cellMap] = $computeTableMap(grid, cell, cell);
|
890
|
-
const {
|
891
|
-
startColumn,
|
892
|
-
startRow
|
893
|
-
} = cellMap;
|
894
923
|
let currentRowNode;
|
895
924
|
for (let i = 1; i < rowSpan; i++) {
|
896
925
|
const currentRow = startRow + i;
|
@@ -911,12 +940,12 @@ function $unmergeCell() {
|
|
911
940
|
}
|
912
941
|
}
|
913
942
|
if (insertAfterCell === null) {
|
914
|
-
for (let j =
|
915
|
-
$insertFirst(currentRowNode, $createTableCellNode(
|
943
|
+
for (let j = colSpan - 1; j >= 0; j--) {
|
944
|
+
$insertFirst(currentRowNode, $createTableCellNode(colStyles[j] | rowStyles[i]).append(lexical.$createParagraphNode()));
|
916
945
|
}
|
917
946
|
} else {
|
918
|
-
for (let j =
|
919
|
-
insertAfterCell.insertAfter($createTableCellNode(
|
947
|
+
for (let j = colSpan - 1; j >= 0; j--) {
|
948
|
+
insertAfterCell.insertAfter($createTableCellNode(colStyles[j] | rowStyles[i]).append(lexical.$createParagraphNode()));
|
920
949
|
}
|
921
950
|
}
|
922
951
|
}
|
@@ -926,10 +955,10 @@ function $unmergeCell() {
|
|
926
955
|
function $computeTableMap(grid, cellA, cellB) {
|
927
956
|
const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(grid, cellA, cellB);
|
928
957
|
if (!(cellAValue !== null)) {
|
929
|
-
throw Error(`Anchor not found in
|
958
|
+
throw Error(`Anchor not found in Table`);
|
930
959
|
}
|
931
960
|
if (!(cellBValue !== null)) {
|
932
|
-
throw Error(`Focus not found in
|
961
|
+
throw Error(`Focus not found in Table`);
|
933
962
|
}
|
934
963
|
return [tableMap, cellAValue, cellBValue];
|
935
964
|
}
|
@@ -937,49 +966,58 @@ function $computeTableMapSkipCellCheck(grid, cellA, cellB) {
|
|
937
966
|
const tableMap = [];
|
938
967
|
let cellAValue = null;
|
939
968
|
let cellBValue = null;
|
940
|
-
function
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
startRow
|
945
|
-
};
|
946
|
-
const rowSpan = cell.__rowSpan;
|
947
|
-
const colSpan = cell.__colSpan;
|
948
|
-
for (let i = 0; i < rowSpan; i++) {
|
949
|
-
if (tableMap[startRow + i] === undefined) {
|
950
|
-
tableMap[startRow + i] = [];
|
951
|
-
}
|
952
|
-
for (let j = 0; j < colSpan; j++) {
|
953
|
-
tableMap[startRow + i][startColumn + j] = value;
|
954
|
-
}
|
955
|
-
}
|
956
|
-
if (cellA !== null && cellA.is(cell)) {
|
957
|
-
cellAValue = value;
|
958
|
-
}
|
959
|
-
if (cellB !== null && cellB.is(cell)) {
|
960
|
-
cellBValue = value;
|
969
|
+
function getMapRow(i) {
|
970
|
+
let row = tableMap[i];
|
971
|
+
if (row === undefined) {
|
972
|
+
tableMap[i] = row = [];
|
961
973
|
}
|
962
|
-
|
963
|
-
function isEmpty(row, column) {
|
964
|
-
return tableMap[row] === undefined || tableMap[row][column] === undefined;
|
974
|
+
return row;
|
965
975
|
}
|
966
976
|
const gridChildren = grid.getChildren();
|
967
|
-
for (let
|
968
|
-
const row = gridChildren[
|
977
|
+
for (let rowIdx = 0; rowIdx < gridChildren.length; rowIdx++) {
|
978
|
+
const row = gridChildren[rowIdx];
|
969
979
|
if (!$isTableRowNode(row)) {
|
970
|
-
throw Error(`Expected
|
980
|
+
throw Error(`Expected TableNode children to be TableRowNode`);
|
971
981
|
}
|
972
|
-
|
973
|
-
let j = 0;
|
974
|
-
for (const cell of rowChildren) {
|
982
|
+
for (let cell = row.getFirstChild(), colIdx = 0; cell != null; cell = cell.getNextSibling()) {
|
975
983
|
if (!$isTableCellNode(cell)) {
|
976
984
|
throw Error(`Expected TableRowNode children to be TableCellNode`);
|
985
|
+
} // Skip past any columns that were merged from a higher row
|
986
|
+
const startMapRow = getMapRow(rowIdx);
|
987
|
+
while (startMapRow[colIdx] !== undefined) {
|
988
|
+
colIdx++;
|
977
989
|
}
|
978
|
-
|
979
|
-
|
990
|
+
const value = {
|
991
|
+
cell,
|
992
|
+
startColumn: colIdx,
|
993
|
+
startRow: rowIdx
|
994
|
+
};
|
995
|
+
const {
|
996
|
+
__rowSpan: rowSpan,
|
997
|
+
__colSpan: colSpan
|
998
|
+
} = cell;
|
999
|
+
for (let j = 0; j < rowSpan; j++) {
|
1000
|
+
if (rowIdx + j >= gridChildren.length) {
|
1001
|
+
// The table is non-rectangular with a rowSpan
|
1002
|
+
// below the last <tr> in the table.
|
1003
|
+
// We should probably handle this with a node transform
|
1004
|
+
// to ensure that tables are always rectangular but this
|
1005
|
+
// will avoid crashes such as #6584
|
1006
|
+
// Note that there are probably still latent bugs
|
1007
|
+
// regarding colSpan or general cell count mismatches.
|
1008
|
+
break;
|
1009
|
+
}
|
1010
|
+
const mapRow = getMapRow(rowIdx + j);
|
1011
|
+
for (let i = 0; i < colSpan; i++) {
|
1012
|
+
mapRow[colIdx + i] = value;
|
1013
|
+
}
|
1014
|
+
}
|
1015
|
+
if (cellA !== null && cellAValue === null && cellA.is(cell)) {
|
1016
|
+
cellAValue = value;
|
1017
|
+
}
|
1018
|
+
if (cellB !== null && cellBValue === null && cellB.is(cell)) {
|
1019
|
+
cellBValue = value;
|
980
1020
|
}
|
981
|
-
write(i, j, cell);
|
982
|
-
j += cell.__colSpan;
|
983
1021
|
}
|
984
1022
|
}
|
985
1023
|
return [tableMap, cellAValue, cellBValue];
|
@@ -1007,7 +1045,7 @@ function $getNodeTriplet(source) {
|
|
1007
1045
|
}
|
1008
1046
|
const grid = row.getParent();
|
1009
1047
|
if (!$isTableNode(grid)) {
|
1010
|
-
throw Error(`Expected TableRowNode to have a parent
|
1048
|
+
throw Error(`Expected TableRowNode to have a parent TableNode`);
|
1011
1049
|
}
|
1012
1050
|
return [cell, row, grid];
|
1013
1051
|
}
|
@@ -1149,9 +1187,9 @@ class TableSelection {
|
|
1149
1187
|
throw Error(`getCellRect: expected to find focusCellNode`);
|
1150
1188
|
}
|
1151
1189
|
const startX = Math.min(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
|
1152
|
-
const stopX = Math.max(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
|
1190
|
+
const stopX = Math.max(anchorCellNodeRect.columnIndex + anchorCellNodeRect.colSpan - 1, focusCellNodeRect.columnIndex + focusCellNodeRect.colSpan - 1);
|
1153
1191
|
const startY = Math.min(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
|
1154
|
-
const stopY = Math.max(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
|
1192
|
+
const stopY = Math.max(anchorCellNodeRect.rowIndex + anchorCellNodeRect.rowSpan - 1, focusCellNodeRect.rowIndex + focusCellNodeRect.rowSpan - 1);
|
1155
1193
|
return {
|
1156
1194
|
fromX: Math.min(startX, stopX),
|
1157
1195
|
fromY: Math.min(startY, stopY),
|
@@ -1265,7 +1303,10 @@ class TableSelection {
|
|
1265
1303
|
exploredMaxRow = nextRow;
|
1266
1304
|
}
|
1267
1305
|
}
|
1268
|
-
|
1306
|
+
|
1307
|
+
// We use a Map here because merged cells in the grid would otherwise
|
1308
|
+
// show up multiple times in the nodes array
|
1309
|
+
const nodeMap = new Map([[tableNode.getKey(), tableNode]]);
|
1269
1310
|
let lastRow = null;
|
1270
1311
|
for (let i = minRow; i <= maxRow; i++) {
|
1271
1312
|
for (let j = minColumn; j <= maxColumn; j++) {
|
@@ -1277,12 +1318,16 @@ class TableSelection {
|
|
1277
1318
|
throw Error(`Expected TableCellNode parent to be a TableRowNode`);
|
1278
1319
|
}
|
1279
1320
|
if (currentRow !== lastRow) {
|
1280
|
-
|
1321
|
+
nodeMap.set(currentRow.getKey(), currentRow);
|
1322
|
+
}
|
1323
|
+
nodeMap.set(cell.getKey(), cell);
|
1324
|
+
for (const child of $getChildrenRecursively(cell)) {
|
1325
|
+
nodeMap.set(child.getKey(), child);
|
1281
1326
|
}
|
1282
|
-
nodes.push(cell, ...$getChildrenRecursively(cell));
|
1283
1327
|
lastRow = currentRow;
|
1284
1328
|
}
|
1285
1329
|
}
|
1330
|
+
const nodes = Array.from(nodeMap.values());
|
1286
1331
|
if (!lexical.isCurrentlyReadOnlyMode()) {
|
1287
1332
|
this._cachedNodes = nodes;
|
1288
1333
|
}
|
package/LexicalTable.dev.mjs
CHANGED
@@ -54,10 +54,12 @@ class TableCellNode extends ElementNode {
|
|
54
54
|
return 'tablecell';
|
55
55
|
}
|
56
56
|
static clone(node) {
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
return new TableCellNode(node.__headerState, node.__colSpan, node.__width, node.__key);
|
58
|
+
}
|
59
|
+
afterCloneFrom(node) {
|
60
|
+
super.afterCloneFrom(node);
|
61
|
+
this.__rowSpan = node.__rowSpan;
|
62
|
+
this.__backgroundColor = node.__backgroundColor;
|
61
63
|
}
|
62
64
|
static importDOM() {
|
63
65
|
return {
|
@@ -74,10 +76,7 @@ class TableCellNode extends ElementNode {
|
|
74
76
|
static importJSON(serializedNode) {
|
75
77
|
const colSpan = serializedNode.colSpan || 1;
|
76
78
|
const rowSpan = serializedNode.rowSpan || 1;
|
77
|
-
|
78
|
-
cellNode.__rowSpan = rowSpan;
|
79
|
-
cellNode.__backgroundColor = serializedNode.backgroundColor || null;
|
80
|
-
return cellNode;
|
79
|
+
return $createTableCellNode(serializedNode.headerState, colSpan, serializedNode.width || undefined).setRowSpan(rowSpan).setBackgroundColor(serializedNode.backgroundColor || null);
|
81
80
|
}
|
82
81
|
constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
|
83
82
|
super(key);
|
@@ -143,26 +142,28 @@ class TableCellNode extends ElementNode {
|
|
143
142
|
};
|
144
143
|
}
|
145
144
|
getColSpan() {
|
146
|
-
return this.__colSpan;
|
145
|
+
return this.getLatest().__colSpan;
|
147
146
|
}
|
148
147
|
setColSpan(colSpan) {
|
149
|
-
this.getWritable()
|
150
|
-
|
148
|
+
const self = this.getWritable();
|
149
|
+
self.__colSpan = colSpan;
|
150
|
+
return self;
|
151
151
|
}
|
152
152
|
getRowSpan() {
|
153
|
-
return this.__rowSpan;
|
153
|
+
return this.getLatest().__rowSpan;
|
154
154
|
}
|
155
155
|
setRowSpan(rowSpan) {
|
156
|
-
this.getWritable()
|
157
|
-
|
156
|
+
const self = this.getWritable();
|
157
|
+
self.__rowSpan = rowSpan;
|
158
|
+
return self;
|
158
159
|
}
|
159
160
|
getTag() {
|
160
161
|
return this.hasHeader() ? 'th' : 'td';
|
161
162
|
}
|
162
|
-
setHeaderStyles(headerState) {
|
163
|
+
setHeaderStyles(headerState, mask = TableCellHeaderStates.BOTH) {
|
163
164
|
const self = this.getWritable();
|
164
|
-
self.__headerState = headerState;
|
165
|
-
return
|
165
|
+
self.__headerState = headerState & mask | self.__headerState & ~mask;
|
166
|
+
return self;
|
166
167
|
}
|
167
168
|
getHeaderStyles() {
|
168
169
|
return this.getLatest().__headerState;
|
@@ -170,7 +171,7 @@ class TableCellNode extends ElementNode {
|
|
170
171
|
setWidth(width) {
|
171
172
|
const self = this.getWritable();
|
172
173
|
self.__width = width;
|
173
|
-
return
|
174
|
+
return self;
|
174
175
|
}
|
175
176
|
getWidth() {
|
176
177
|
return this.getLatest().__width;
|
@@ -179,7 +180,9 @@ class TableCellNode extends ElementNode {
|
|
179
180
|
return this.getLatest().__backgroundColor;
|
180
181
|
}
|
181
182
|
setBackgroundColor(newBackgroundColor) {
|
182
|
-
this.getWritable()
|
183
|
+
const self = this.getWritable();
|
184
|
+
self.__backgroundColor = newBackgroundColor;
|
185
|
+
return self;
|
183
186
|
}
|
184
187
|
toggleHeaderStyle(headerStateToToggle) {
|
185
188
|
const self = this.getWritable();
|
@@ -767,7 +770,7 @@ function $deleteTableRow__EXPERIMENTAL() {
|
|
767
770
|
}
|
768
771
|
const rowNode = grid.getChildAtIndex(row);
|
769
772
|
if (!$isTableRowNode(rowNode)) {
|
770
|
-
throw Error(`Expected
|
773
|
+
throw Error(`Expected TableNode childAtIndex(${String(row)}) to be RowNode`);
|
771
774
|
}
|
772
775
|
rowNode.remove();
|
773
776
|
}
|
@@ -877,18 +880,44 @@ function $unmergeCell() {
|
|
877
880
|
const [cell, row, grid] = $getNodeTriplet(anchor);
|
878
881
|
const colSpan = cell.__colSpan;
|
879
882
|
const rowSpan = cell.__rowSpan;
|
883
|
+
if (colSpan === 1 && rowSpan === 1) {
|
884
|
+
return;
|
885
|
+
}
|
886
|
+
const [map, cellMap] = $computeTableMap(grid, cell, cell);
|
887
|
+
const {
|
888
|
+
startColumn,
|
889
|
+
startRow
|
890
|
+
} = cellMap;
|
891
|
+
// Create a heuristic for what the style of the unmerged cells should be
|
892
|
+
// based on whether every row or column already had that state before the
|
893
|
+
// unmerge.
|
894
|
+
const baseColStyle = cell.__headerState & TableCellHeaderStates.COLUMN;
|
895
|
+
const colStyles = Array.from({
|
896
|
+
length: colSpan
|
897
|
+
}, (_v, i) => {
|
898
|
+
let colStyle = baseColStyle;
|
899
|
+
for (let rowIdx = 0; colStyle !== 0 && rowIdx < map.length; rowIdx++) {
|
900
|
+
colStyle &= map[rowIdx][i + startColumn].cell.__headerState;
|
901
|
+
}
|
902
|
+
return colStyle;
|
903
|
+
});
|
904
|
+
const baseRowStyle = cell.__headerState & TableCellHeaderStates.ROW;
|
905
|
+
const rowStyles = Array.from({
|
906
|
+
length: rowSpan
|
907
|
+
}, (_v, i) => {
|
908
|
+
let rowStyle = baseRowStyle;
|
909
|
+
for (let colIdx = 0; rowStyle !== 0 && colIdx < map[0].length; colIdx++) {
|
910
|
+
rowStyle &= map[i + startRow][colIdx].cell.__headerState;
|
911
|
+
}
|
912
|
+
return rowStyle;
|
913
|
+
});
|
880
914
|
if (colSpan > 1) {
|
881
915
|
for (let i = 1; i < colSpan; i++) {
|
882
|
-
cell.insertAfter($createTableCellNode(
|
916
|
+
cell.insertAfter($createTableCellNode(colStyles[i] | rowStyles[0]).append($createParagraphNode()));
|
883
917
|
}
|
884
918
|
cell.setColSpan(1);
|
885
919
|
}
|
886
920
|
if (rowSpan > 1) {
|
887
|
-
const [map, cellMap] = $computeTableMap(grid, cell, cell);
|
888
|
-
const {
|
889
|
-
startColumn,
|
890
|
-
startRow
|
891
|
-
} = cellMap;
|
892
921
|
let currentRowNode;
|
893
922
|
for (let i = 1; i < rowSpan; i++) {
|
894
923
|
const currentRow = startRow + i;
|
@@ -909,12 +938,12 @@ function $unmergeCell() {
|
|
909
938
|
}
|
910
939
|
}
|
911
940
|
if (insertAfterCell === null) {
|
912
|
-
for (let j =
|
913
|
-
$insertFirst(currentRowNode, $createTableCellNode(
|
941
|
+
for (let j = colSpan - 1; j >= 0; j--) {
|
942
|
+
$insertFirst(currentRowNode, $createTableCellNode(colStyles[j] | rowStyles[i]).append($createParagraphNode()));
|
914
943
|
}
|
915
944
|
} else {
|
916
|
-
for (let j =
|
917
|
-
insertAfterCell.insertAfter($createTableCellNode(
|
945
|
+
for (let j = colSpan - 1; j >= 0; j--) {
|
946
|
+
insertAfterCell.insertAfter($createTableCellNode(colStyles[j] | rowStyles[i]).append($createParagraphNode()));
|
918
947
|
}
|
919
948
|
}
|
920
949
|
}
|
@@ -924,10 +953,10 @@ function $unmergeCell() {
|
|
924
953
|
function $computeTableMap(grid, cellA, cellB) {
|
925
954
|
const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(grid, cellA, cellB);
|
926
955
|
if (!(cellAValue !== null)) {
|
927
|
-
throw Error(`Anchor not found in
|
956
|
+
throw Error(`Anchor not found in Table`);
|
928
957
|
}
|
929
958
|
if (!(cellBValue !== null)) {
|
930
|
-
throw Error(`Focus not found in
|
959
|
+
throw Error(`Focus not found in Table`);
|
931
960
|
}
|
932
961
|
return [tableMap, cellAValue, cellBValue];
|
933
962
|
}
|
@@ -935,49 +964,58 @@ function $computeTableMapSkipCellCheck(grid, cellA, cellB) {
|
|
935
964
|
const tableMap = [];
|
936
965
|
let cellAValue = null;
|
937
966
|
let cellBValue = null;
|
938
|
-
function
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
startRow
|
943
|
-
};
|
944
|
-
const rowSpan = cell.__rowSpan;
|
945
|
-
const colSpan = cell.__colSpan;
|
946
|
-
for (let i = 0; i < rowSpan; i++) {
|
947
|
-
if (tableMap[startRow + i] === undefined) {
|
948
|
-
tableMap[startRow + i] = [];
|
949
|
-
}
|
950
|
-
for (let j = 0; j < colSpan; j++) {
|
951
|
-
tableMap[startRow + i][startColumn + j] = value;
|
952
|
-
}
|
953
|
-
}
|
954
|
-
if (cellA !== null && cellA.is(cell)) {
|
955
|
-
cellAValue = value;
|
956
|
-
}
|
957
|
-
if (cellB !== null && cellB.is(cell)) {
|
958
|
-
cellBValue = value;
|
967
|
+
function getMapRow(i) {
|
968
|
+
let row = tableMap[i];
|
969
|
+
if (row === undefined) {
|
970
|
+
tableMap[i] = row = [];
|
959
971
|
}
|
960
|
-
|
961
|
-
function isEmpty(row, column) {
|
962
|
-
return tableMap[row] === undefined || tableMap[row][column] === undefined;
|
972
|
+
return row;
|
963
973
|
}
|
964
974
|
const gridChildren = grid.getChildren();
|
965
|
-
for (let
|
966
|
-
const row = gridChildren[
|
975
|
+
for (let rowIdx = 0; rowIdx < gridChildren.length; rowIdx++) {
|
976
|
+
const row = gridChildren[rowIdx];
|
967
977
|
if (!$isTableRowNode(row)) {
|
968
|
-
throw Error(`Expected
|
978
|
+
throw Error(`Expected TableNode children to be TableRowNode`);
|
969
979
|
}
|
970
|
-
|
971
|
-
let j = 0;
|
972
|
-
for (const cell of rowChildren) {
|
980
|
+
for (let cell = row.getFirstChild(), colIdx = 0; cell != null; cell = cell.getNextSibling()) {
|
973
981
|
if (!$isTableCellNode(cell)) {
|
974
982
|
throw Error(`Expected TableRowNode children to be TableCellNode`);
|
983
|
+
} // Skip past any columns that were merged from a higher row
|
984
|
+
const startMapRow = getMapRow(rowIdx);
|
985
|
+
while (startMapRow[colIdx] !== undefined) {
|
986
|
+
colIdx++;
|
975
987
|
}
|
976
|
-
|
977
|
-
|
988
|
+
const value = {
|
989
|
+
cell,
|
990
|
+
startColumn: colIdx,
|
991
|
+
startRow: rowIdx
|
992
|
+
};
|
993
|
+
const {
|
994
|
+
__rowSpan: rowSpan,
|
995
|
+
__colSpan: colSpan
|
996
|
+
} = cell;
|
997
|
+
for (let j = 0; j < rowSpan; j++) {
|
998
|
+
if (rowIdx + j >= gridChildren.length) {
|
999
|
+
// The table is non-rectangular with a rowSpan
|
1000
|
+
// below the last <tr> in the table.
|
1001
|
+
// We should probably handle this with a node transform
|
1002
|
+
// to ensure that tables are always rectangular but this
|
1003
|
+
// will avoid crashes such as #6584
|
1004
|
+
// Note that there are probably still latent bugs
|
1005
|
+
// regarding colSpan or general cell count mismatches.
|
1006
|
+
break;
|
1007
|
+
}
|
1008
|
+
const mapRow = getMapRow(rowIdx + j);
|
1009
|
+
for (let i = 0; i < colSpan; i++) {
|
1010
|
+
mapRow[colIdx + i] = value;
|
1011
|
+
}
|
1012
|
+
}
|
1013
|
+
if (cellA !== null && cellAValue === null && cellA.is(cell)) {
|
1014
|
+
cellAValue = value;
|
1015
|
+
}
|
1016
|
+
if (cellB !== null && cellBValue === null && cellB.is(cell)) {
|
1017
|
+
cellBValue = value;
|
978
1018
|
}
|
979
|
-
write(i, j, cell);
|
980
|
-
j += cell.__colSpan;
|
981
1019
|
}
|
982
1020
|
}
|
983
1021
|
return [tableMap, cellAValue, cellBValue];
|
@@ -1005,7 +1043,7 @@ function $getNodeTriplet(source) {
|
|
1005
1043
|
}
|
1006
1044
|
const grid = row.getParent();
|
1007
1045
|
if (!$isTableNode(grid)) {
|
1008
|
-
throw Error(`Expected TableRowNode to have a parent
|
1046
|
+
throw Error(`Expected TableRowNode to have a parent TableNode`);
|
1009
1047
|
}
|
1010
1048
|
return [cell, row, grid];
|
1011
1049
|
}
|
@@ -1147,9 +1185,9 @@ class TableSelection {
|
|
1147
1185
|
throw Error(`getCellRect: expected to find focusCellNode`);
|
1148
1186
|
}
|
1149
1187
|
const startX = Math.min(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
|
1150
|
-
const stopX = Math.max(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
|
1188
|
+
const stopX = Math.max(anchorCellNodeRect.columnIndex + anchorCellNodeRect.colSpan - 1, focusCellNodeRect.columnIndex + focusCellNodeRect.colSpan - 1);
|
1151
1189
|
const startY = Math.min(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
|
1152
|
-
const stopY = Math.max(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
|
1190
|
+
const stopY = Math.max(anchorCellNodeRect.rowIndex + anchorCellNodeRect.rowSpan - 1, focusCellNodeRect.rowIndex + focusCellNodeRect.rowSpan - 1);
|
1153
1191
|
return {
|
1154
1192
|
fromX: Math.min(startX, stopX),
|
1155
1193
|
fromY: Math.min(startY, stopY),
|
@@ -1263,7 +1301,10 @@ class TableSelection {
|
|
1263
1301
|
exploredMaxRow = nextRow;
|
1264
1302
|
}
|
1265
1303
|
}
|
1266
|
-
|
1304
|
+
|
1305
|
+
// We use a Map here because merged cells in the grid would otherwise
|
1306
|
+
// show up multiple times in the nodes array
|
1307
|
+
const nodeMap = new Map([[tableNode.getKey(), tableNode]]);
|
1267
1308
|
let lastRow = null;
|
1268
1309
|
for (let i = minRow; i <= maxRow; i++) {
|
1269
1310
|
for (let j = minColumn; j <= maxColumn; j++) {
|
@@ -1275,12 +1316,16 @@ class TableSelection {
|
|
1275
1316
|
throw Error(`Expected TableCellNode parent to be a TableRowNode`);
|
1276
1317
|
}
|
1277
1318
|
if (currentRow !== lastRow) {
|
1278
|
-
|
1319
|
+
nodeMap.set(currentRow.getKey(), currentRow);
|
1320
|
+
}
|
1321
|
+
nodeMap.set(cell.getKey(), cell);
|
1322
|
+
for (const child of $getChildrenRecursively(cell)) {
|
1323
|
+
nodeMap.set(child.getKey(), child);
|
1279
1324
|
}
|
1280
|
-
nodes.push(cell, ...$getChildrenRecursively(cell));
|
1281
1325
|
lastRow = currentRow;
|
1282
1326
|
}
|
1283
1327
|
}
|
1328
|
+
const nodes = Array.from(nodeMap.values());
|
1284
1329
|
if (!isCurrentlyReadOnlyMode()) {
|
1285
1330
|
this._cachedNodes = nodes;
|
1286
1331
|
}
|
package/LexicalTable.js.flow
CHANGED
@@ -62,12 +62,12 @@ declare export class TableCellNode extends ElementNode {
|
|
62
62
|
getRowSpan(): number;
|
63
63
|
setRowSpan(rowSpan: number): this;
|
64
64
|
getTag(): string;
|
65
|
-
setHeaderStyles(headerState: TableCellHeaderState):
|
65
|
+
setHeaderStyles(headerState: TableCellHeaderState, mask?: TableCellHeaderState): TableCellNode;
|
66
66
|
getHeaderStyles(): TableCellHeaderState;
|
67
|
-
setWidth(width: number):
|
67
|
+
setWidth(width: number): TableCellNode;
|
68
68
|
getWidth(): ?number;
|
69
69
|
getBackgroundColor(): null | string;
|
70
|
-
setBackgroundColor(newBackgroundColor: null | string):
|
70
|
+
setBackgroundColor(newBackgroundColor: null | string): TableCellNode;
|
71
71
|
toggleHeaderStyle(headerState: TableCellHeaderState): TableCellNode;
|
72
72
|
hasHeader(): boolean;
|
73
73
|
updateDOM(prevNode: TableCellNode): boolean;
|