rittenhop-ghost/versions/5.94.2/node_modules/@lexical/table/LexicalTable.dev.js

2427 lines
85 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var utils = require('@lexical/utils');
var lexical = require('lexical');
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const TableCellHeaderStates = {
BOTH: 3,
COLUMN: 2,
NO_STATUS: 0,
ROW: 1
};
/** @noInheritDoc */
class TableCellNode extends lexical.ElementNode {
/** @internal */
/** @internal */
/** @internal */
/** @internal */
/** @internal */
static getType() {
return 'tablecell';
}
static clone(node) {
const cellNode = new TableCellNode(node.__headerState, node.__colSpan, node.__width, node.__key);
cellNode.__rowSpan = node.__rowSpan;
cellNode.__backgroundColor = node.__backgroundColor;
return cellNode;
}
static importDOM() {
return {
td: node => ({
conversion: convertTableCellNodeElement,
priority: 0
}),
th: node => ({
conversion: convertTableCellNodeElement,
priority: 0
})
};
}
static importJSON(serializedNode) {
const colSpan = serializedNode.colSpan || 1;
const rowSpan = serializedNode.rowSpan || 1;
const cellNode = $createTableCellNode(serializedNode.headerState, colSpan, serializedNode.width || undefined);
cellNode.__rowSpan = rowSpan;
cellNode.__backgroundColor = serializedNode.backgroundColor || null;
return cellNode;
}
constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
super(key);
this.__colSpan = colSpan;
this.__rowSpan = 1;
this.__headerState = headerState;
this.__width = width;
this.__backgroundColor = null;
}
createDOM(config) {
const element = document.createElement(this.getTag());
if (this.__width) {
element.style.width = `${this.__width}px`;
}
if (this.__colSpan > 1) {
element.colSpan = this.__colSpan;
}
if (this.__rowSpan > 1) {
element.rowSpan = this.__rowSpan;
}
if (this.__backgroundColor !== null) {
element.style.backgroundColor = this.__backgroundColor;
}
utils.addClassNamesToElement(element, config.theme.tableCell, this.hasHeader() && config.theme.tableCellHeader);
return element;
}
exportDOM(editor) {
const {
element
} = super.exportDOM(editor);
if (element) {
const element_ = element;
const maxWidth = 700;
const colCount = this.getParentOrThrow().getChildrenSize();
element_.style.border = '1px solid black';
if (this.__colSpan > 1) {
element_.colSpan = this.__colSpan;
}
if (this.__rowSpan > 1) {
element_.rowSpan = this.__rowSpan;
}
element_.style.width = `${this.getWidth() || Math.max(90, maxWidth / colCount)}px`;
element_.style.verticalAlign = 'top';
element_.style.textAlign = 'start';
const backgroundColor = this.getBackgroundColor();
if (backgroundColor !== null) {
element_.style.backgroundColor = backgroundColor;
} else if (this.hasHeader()) {
element_.style.backgroundColor = '#f2f3f5';
}
}
return {
element
};
}
exportJSON() {
return {
...super.exportJSON(),
backgroundColor: this.getBackgroundColor(),
colSpan: this.__colSpan,
headerState: this.__headerState,
rowSpan: this.__rowSpan,
type: 'tablecell',
width: this.getWidth()
};
}
getColSpan() {
return this.__colSpan;
}
setColSpan(colSpan) {
this.getWritable().__colSpan = colSpan;
return this;
}
getRowSpan() {
return this.__rowSpan;
}
setRowSpan(rowSpan) {
this.getWritable().__rowSpan = rowSpan;
return this;
}
getTag() {
return this.hasHeader() ? 'th' : 'td';
}
setHeaderStyles(headerState) {
const self = this.getWritable();
self.__headerState = headerState;
return this.__headerState;
}
getHeaderStyles() {
return this.getLatest().__headerState;
}
setWidth(width) {
const self = this.getWritable();
self.__width = width;
return this.__width;
}
getWidth() {
return this.getLatest().__width;
}
getBackgroundColor() {
return this.getLatest().__backgroundColor;
}
setBackgroundColor(newBackgroundColor) {
this.getWritable().__backgroundColor = newBackgroundColor;
}
toggleHeaderStyle(headerStateToToggle) {
const self = this.getWritable();
if ((self.__headerState & headerStateToToggle) === headerStateToToggle) {
self.__headerState -= headerStateToToggle;
} else {
self.__headerState += headerStateToToggle;
}
return self;
}
hasHeaderState(headerState) {
return (this.getHeaderStyles() & headerState) === headerState;
}
hasHeader() {
return this.getLatest().__headerState !== TableCellHeaderStates.NO_STATUS;
}
updateDOM(prevNode) {
return prevNode.__headerState !== this.__headerState || prevNode.__width !== this.__width || prevNode.__colSpan !== this.__colSpan || prevNode.__rowSpan !== this.__rowSpan || prevNode.__backgroundColor !== this.__backgroundColor;
}
isShadowRoot() {
return true;
}
collapseAtStart() {
return true;
}
canBeEmpty() {
return false;
}
canIndent() {
return false;
}
}
function convertTableCellNodeElement(domNode) {
const domNode_ = domNode;
const nodeName = domNode.nodeName.toLowerCase();
let width = undefined;
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
width = parseFloat(domNode_.style.width);
}
const tableCellNode = $createTableCellNode(nodeName === 'th' ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS, domNode_.colSpan, width);
tableCellNode.__rowSpan = domNode_.rowSpan;
const backgroundColor = domNode_.style.backgroundColor;
if (backgroundColor !== '') {
tableCellNode.__backgroundColor = backgroundColor;
}
return {
forChild: (lexicalNode, parentLexicalNode) => {
if ($isTableCellNode(parentLexicalNode) && !lexical.$isElementNode(lexicalNode)) {
const paragraphNode = lexical.$createParagraphNode();
if (lexical.$isLineBreakNode(lexicalNode) && lexicalNode.getTextContent() === '\n') {
return null;
}
paragraphNode.append(lexicalNode);
return paragraphNode;
}
return lexicalNode;
},
node: tableCellNode
};
}
function $createTableCellNode(headerState, colSpan = 1, width) {
return lexical.$applyNodeReplacement(new TableCellNode(headerState, colSpan, width));
}
function $isTableCellNode(node) {
return node instanceof TableCellNode;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
/** @noInheritDoc */
class TableRowNode extends lexical.ElementNode {
/** @internal */
static getType() {
return 'tablerow';
}
static clone(node) {
return new TableRowNode(node.__height, node.__key);
}
static importDOM() {
return {
tr: node => ({
conversion: convertTableRowElement,
priority: 0
})
};
}
static importJSON(serializedNode) {
return $createTableRowNode(serializedNode.height);
}
constructor(height, key) {
super(key);
this.__height = height;
}
exportJSON() {
return {
...super.exportJSON(),
type: 'tablerow',
version: 1
};
}
createDOM(config) {
const element = document.createElement('tr');
if (this.__height) {
element.style.height = `${this.__height}px`;
}
utils.addClassNamesToElement(element, config.theme.tableRow);
return element;
}
isShadowRoot() {
return true;
}
setHeight(height) {
const self = this.getWritable();
self.__height = height;
return this.__height;
}
getHeight() {
return this.getLatest().__height;
}
updateDOM(prevNode) {
return prevNode.__height !== this.__height;
}
canBeEmpty() {
return false;
}
canIndent() {
return false;
}
}
function convertTableRowElement(domNode) {
const domNode_ = domNode;
let height = undefined;
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) {
height = parseFloat(domNode_.style.height);
}
return {
node: $createTableRowNode(height)
};
}
function $createTableRowNode(height) {
return lexical.$applyNodeReplacement(new TableRowNode(height));
}
function $isTableRowNode(node) {
return node instanceof TableRowNode;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders = true) {
const tableNode = $createTableNode();
for (let iRow = 0; iRow < rowCount; iRow++) {
const tableRowNode = $createTableRowNode();
for (let iColumn = 0; iColumn < columnCount; iColumn++) {
let headerState = TableCellHeaderStates.NO_STATUS;
if (typeof includeHeaders === 'object') {
if (iRow === 0 && includeHeaders.rows) headerState |= TableCellHeaderStates.ROW;
if (iColumn === 0 && includeHeaders.columns) headerState |= TableCellHeaderStates.COLUMN;
} else if (includeHeaders) {
if (iRow === 0) headerState |= TableCellHeaderStates.ROW;
if (iColumn === 0) headerState |= TableCellHeaderStates.COLUMN;
}
const tableCellNode = $createTableCellNode(headerState);
const paragraphNode = lexical.$createParagraphNode();
paragraphNode.append(lexical.$createTextNode());
tableCellNode.append(paragraphNode);
tableRowNode.append(tableCellNode);
}
tableNode.append(tableRowNode);
}
return tableNode;
}
function $getTableCellNodeFromLexicalNode(startingNode) {
const node = utils.$findMatchingParent(startingNode, n => $isTableCellNode(n));
if ($isTableCellNode(node)) {
return node;
}
return null;
}
function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
const node = utils.$findMatchingParent(startingNode, n => $isTableRowNode(n));
if ($isTableRowNode(node)) {
return node;
}
throw new Error('Expected table cell to be inside of table row.');
}
function $getTableNodeFromLexicalNodeOrThrow(startingNode) {
const node = utils.$findMatchingParent(startingNode, n => $isTableNode(n));
if ($isTableNode(node)) {
return node;
}
throw new Error('Expected table cell to be inside of table.');
}
function $getTableRowIndexFromTableCellNode(tableCellNode) {
const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableRowNode);
return tableNode.getChildren().findIndex(n => n.is(tableRowNode));
}
function $getTableColumnIndexFromTableCellNode(tableCellNode) {
const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
return tableRowNode.getChildren().findIndex(n => n.is(tableCellNode));
}
function $getTableCellSiblingsFromTableCellNode(tableCellNode, table) {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
const {
x,
y
} = tableNode.getCordsFromCellNode(tableCellNode, table);
return {
above: tableNode.getCellNodeFromCords(x, y - 1, table),
below: tableNode.getCellNodeFromCords(x, y + 1, table),
left: tableNode.getCellNodeFromCords(x - 1, y, table),
right: tableNode.getCellNodeFromCords(x + 1, y, table)
};
}
function $removeTableRowAtIndex(tableNode, indexToDelete) {
const tableRows = tableNode.getChildren();
if (indexToDelete >= tableRows.length || indexToDelete < 0) {
throw new Error('Expected table cell to be inside of table row.');
}
const targetRowNode = tableRows[indexToDelete];
targetRowNode.remove();
return tableNode;
}
function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, table) {
const tableRows = tableNode.getChildren();
if (targetIndex >= tableRows.length || targetIndex < 0) {
throw new Error('Table row target index out of range');
}
const targetRowNode = tableRows[targetIndex];
if ($isTableRowNode(targetRowNode)) {
for (let r = 0; r < rowCount; r++) {
const tableRowCells = targetRowNode.getChildren();
const tableColumnCount = tableRowCells.length;
const newTableRowNode = $createTableRowNode();
for (let c = 0; c < tableColumnCount; c++) {
const tableCellFromTargetRow = tableRowCells[c];
if (!$isTableCellNode(tableCellFromTargetRow)) {
throw Error(`Expected table cell`);
}
const {
above,
below
} = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow, table);
let headerState = TableCellHeaderStates.NO_STATUS;
const width = above && above.getWidth() || below && below.getWidth() || undefined;
if (above && above.hasHeaderState(TableCellHeaderStates.COLUMN) || below && below.hasHeaderState(TableCellHeaderStates.COLUMN)) {
headerState |= TableCellHeaderStates.COLUMN;
}
const tableCellNode = $createTableCellNode(headerState, 1, width);
tableCellNode.append(lexical.$createParagraphNode());
newTableRowNode.append(tableCellNode);
}
if (shouldInsertAfter) {
targetRowNode.insertAfter(newTableRowNode);
} else {
targetRowNode.insertBefore(newTableRowNode);
}
}
} else {
throw new Error('Row before insertion index does not exist.');
}
return tableNode;
}
const getHeaderState = (currentState, possibleState) => {
if (currentState === TableCellHeaderStates.BOTH || currentState === possibleState) {
return possibleState;
}
return TableCellHeaderStates.NO_STATUS;
};
function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
const selection = lexical.$getSelection();
if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
throw Error(`Expected a RangeSelection or GridSelection`);
}
const focus = selection.focus.getNode();
const [focusCell,, grid] = $getNodeTriplet(focus);
const [gridMap, focusCellMap] = $computeTableMap(grid, focusCell, focusCell);
const columnCount = gridMap[0].length;
const {
startRow: focusStartRow
} = focusCellMap;
if (insertAfter) {
const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
const focusEndRowMap = gridMap[focusEndRow];
const newRow = $createTableRowNode();
for (let i = 0; i < columnCount; i++) {
const {
cell,
startRow
} = focusEndRowMap[i];
if (startRow + cell.__rowSpan - 1 <= focusEndRow) {
const currentCell = focusEndRowMap[i].cell;
const currentCellHeaderState = currentCell.__headerState;
const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.COLUMN);
newRow.append($createTableCellNode(headerState).append(lexical.$createParagraphNode()));
} else {
cell.setRowSpan(cell.__rowSpan + 1);
}
}
const focusEndRowNode = grid.getChildAtIndex(focusEndRow);
if (!$isTableRowNode(focusEndRowNode)) {
throw Error(`focusEndRow is not a TableRowNode`);
}
focusEndRowNode.insertAfter(newRow);
} else {
const focusStartRowMap = gridMap[focusStartRow];
const newRow = $createTableRowNode();
for (let i = 0; i < columnCount; i++) {
const {
cell,
startRow
} = focusStartRowMap[i];
if (startRow === focusStartRow) {
const currentCell = focusStartRowMap[i].cell;
const currentCellHeaderState = currentCell.__headerState;
const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.COLUMN);
newRow.append($createTableCellNode(headerState).append(lexical.$createParagraphNode()));
} else {
cell.setRowSpan(cell.__rowSpan + 1);
}
}
const focusStartRowNode = grid.getChildAtIndex(focusStartRow);
if (!$isTableRowNode(focusStartRowNode)) {
throw Error(`focusEndRow is not a TableRowNode`);
}
focusStartRowNode.insertBefore(newRow);
}
}
function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, columnCount, table) {
const tableRows = tableNode.getChildren();
const tableCellsToBeInserted = [];
for (let r = 0; r < tableRows.length; r++) {
const currentTableRowNode = tableRows[r];
if ($isTableRowNode(currentTableRowNode)) {
for (let c = 0; c < columnCount; c++) {
const tableRowChildren = currentTableRowNode.getChildren();
if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
throw new Error('Table column target index out of range');
}
const targetCell = tableRowChildren[targetIndex];
if (!$isTableCellNode(targetCell)) {
throw Error(`Expected table cell`);
}
const {
left,
right
} = $getTableCellSiblingsFromTableCellNode(targetCell, table);
let headerState = TableCellHeaderStates.NO_STATUS;
if (left && left.hasHeaderState(TableCellHeaderStates.ROW) || right && right.hasHeaderState(TableCellHeaderStates.ROW)) {
headerState |= TableCellHeaderStates.ROW;
}
const newTableCell = $createTableCellNode(headerState);
newTableCell.append(lexical.$createParagraphNode());
tableCellsToBeInserted.push({
newTableCell,
targetCell
});
}
}
}
tableCellsToBeInserted.forEach(({
newTableCell,
targetCell
}) => {
if (shouldInsertAfter) {
targetCell.insertAfter(newTableCell);
} else {
targetCell.insertBefore(newTableCell);
}
});
return tableNode;
}
function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
const selection = lexical.$getSelection();
if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
throw Error(`Expected a RangeSelection or GridSelection`);
}
const anchor = selection.anchor.getNode();
const focus = selection.focus.getNode();
const [anchorCell] = $getNodeTriplet(anchor);
const [focusCell,, grid] = $getNodeTriplet(focus);
const [gridMap, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
const rowCount = gridMap.length;
const startColumn = insertAfter ? Math.max(focusCellMap.startColumn, anchorCellMap.startColumn) : Math.min(focusCellMap.startColumn, anchorCellMap.startColumn);
const insertAfterColumn = insertAfter ? startColumn + focusCell.__colSpan - 1 : startColumn - 1;
const gridFirstChild = grid.getFirstChild();
if (!$isTableRowNode(gridFirstChild)) {
throw Error(`Expected firstTable child to be a row`);
}
let firstInsertedCell = null;
function $createTableCellNodeForInsertTableColumn(headerState = TableCellHeaderStates.NO_STATUS) {
const cell = $createTableCellNode(headerState).append(lexical.$createParagraphNode());
if (firstInsertedCell === null) {
firstInsertedCell = cell;
}
return cell;
}
let loopRow = gridFirstChild;
rowLoop: for (let i = 0; i < rowCount; i++) {
if (i !== 0) {
const currentRow = loopRow.getNextSibling();
if (!$isTableRowNode(currentRow)) {
throw Error(`Expected row nextSibling to be a row`);
}
loopRow = currentRow;
}
const rowMap = gridMap[i];
const currentCellHeaderState = rowMap[insertAfterColumn < 0 ? 0 : insertAfterColumn].cell.__headerState;
const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.ROW);
if (insertAfterColumn < 0) {
$insertFirst(loopRow, $createTableCellNodeForInsertTableColumn(headerState));
continue;
}
const {
cell: currentCell,
startColumn: currentStartColumn,
startRow: currentStartRow
} = rowMap[insertAfterColumn];
if (currentStartColumn + currentCell.__colSpan - 1 <= insertAfterColumn) {
let insertAfterCell = currentCell;
let insertAfterCellRowStart = currentStartRow;
let prevCellIndex = insertAfterColumn;
while (insertAfterCellRowStart !== i && insertAfterCell.__rowSpan > 1) {
prevCellIndex -= currentCell.__colSpan;
if (prevCellIndex >= 0) {
const {
cell: cell_,
startRow: startRow_
} = rowMap[prevCellIndex];
insertAfterCell = cell_;
insertAfterCellRowStart = startRow_;
} else {
loopRow.append($createTableCellNodeForInsertTableColumn(headerState));
continue rowLoop;
}
}
insertAfterCell.insertAfter($createTableCellNodeForInsertTableColumn(headerState));
} else {
currentCell.setColSpan(currentCell.__colSpan + 1);
}
}
if (firstInsertedCell !== null) {
$moveSelectionToCell(firstInsertedCell);
}
}
function $deleteTableColumn(tableNode, targetIndex) {
const tableRows = tableNode.getChildren();
for (let i = 0; i < tableRows.length; i++) {
const currentTableRowNode = tableRows[i];
if ($isTableRowNode(currentTableRowNode)) {
const tableRowChildren = currentTableRowNode.getChildren();
if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
throw new Error('Table column target index out of range');
}
tableRowChildren[targetIndex].remove();
}
}
return tableNode;
}
function $deleteTableRow__EXPERIMENTAL() {
const selection = lexical.$getSelection();
if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
throw Error(`Expected a RangeSelection or GridSelection`);
}
const anchor = selection.anchor.getNode();
const focus = selection.focus.getNode();
const [anchorCell,, grid] = $getNodeTriplet(anchor);
const [focusCell] = $getNodeTriplet(focus);
const [gridMap, anchorCellMap, focusCellMap] = $computeTableMap(grid, anchorCell, focusCell);
const {
startRow: anchorStartRow
} = anchorCellMap;
const {
startRow: focusStartRow
} = focusCellMap;
const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
if (gridMap.length === focusEndRow - anchorStartRow + 1) {
// Empty grid
grid.remove();
return;
}
const columnCount = gridMap[0].length;
const nextRow = gridMap[focusEndRow + 1];
const nextRowNode = grid.getChildAtIndex(focusEndRow + 1);
for (let row = focusEndRow; row >= anchorStartRow; row--) {
for (let column = columnCount - 1; column >= 0; column--) {
const {
cell,
startRow: cellStartRow,
startColumn: cellStartColumn
} = gridMap[row][column];
if (cellStartColumn !== column) {
// Don't repeat work for the same Cell
continue;
}
// Rows overflowing top have to be trimmed
if (row === anchorStartRow && cellStartRow < anchorStartRow) {
cell.setRowSpan(cell.__rowSpan - (cellStartRow - anchorStartRow));
}
// Rows overflowing bottom have to be trimmed and moved to the next row
if (cellStartRow >= anchorStartRow && cellStartRow + cell.__rowSpan - 1 > focusEndRow) {
cell.setRowSpan(cell.__rowSpan - (focusEndRow - cellStartRow + 1));
if (!(nextRowNode !== null)) {
throw Error(`Expected nextRowNode not to be null`);
}
if (column === 0) {
$insertFirst(nextRowNode, cell);
} else {
const {
cell: previousCell
} = nextRow[column - 1];
previousCell.insertAfter(cell);
}
}
}
const rowNode = grid.getChildAtIndex(row);
if (!$isTableRowNode(rowNode)) {
throw Error(`Expected GridNode childAtIndex(${String(row)}) to be RowNode`);
}
rowNode.remove();
}
if (nextRow !== undefined) {
const {
cell
} = nextRow[0];
$moveSelectionToCell(cell);
} else {
const previousRow = gridMap[anchorStartRow - 1];
const {
cell
} = previousRow[0];
$moveSelectionToCell(cell);
}
}
function $deleteTableColumn__EXPERIMENTAL() {
const selection = lexical.$getSelection();
if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
throw Error(`Expected a RangeSelection or GridSelection`);
}
const anchor = selection.anchor.getNode();
const focus = selection.focus.getNode();
const [anchorCell,, grid] = $getNodeTriplet(anchor);
const [focusCell] = $getNodeTriplet(focus);
const [gridMap, anchorCellMap, focusCellMap] = $computeTableMap(grid, anchorCell, focusCell);
const {
startColumn: anchorStartColumn
} = anchorCellMap;
const {
startRow: focusStartRow,
startColumn: focusStartColumn
} = focusCellMap;
const startColumn = Math.min(anchorStartColumn, focusStartColumn);
const endColumn = Math.max(anchorStartColumn + anchorCell.__colSpan - 1, focusStartColumn + focusCell.__colSpan - 1);
const selectedColumnCount = endColumn - startColumn + 1;
const columnCount = gridMap[0].length;
if (columnCount === endColumn - startColumn + 1) {
// Empty grid
grid.selectPrevious();
grid.remove();
return;
}
const rowCount = gridMap.length;
for (let row = 0; row < rowCount; row++) {
for (let column = startColumn; column <= endColumn; column++) {
const {
cell,
startColumn: cellStartColumn
} = gridMap[row][column];
if (cellStartColumn < startColumn) {
if (column === startColumn) {
const overflowLeft = startColumn - cellStartColumn;
// Overflowing left
cell.setColSpan(cell.__colSpan -
// Possible overflow right too
Math.min(selectedColumnCount, cell.__colSpan - overflowLeft));
}
} else if (cellStartColumn + cell.__colSpan - 1 > endColumn) {
if (column === endColumn) {
// Overflowing right
const inSelectedArea = endColumn - cellStartColumn + 1;
cell.setColSpan(cell.__colSpan - inSelectedArea);
}
} else {
cell.remove();
}
}
}
const focusRowMap = gridMap[focusStartRow];
const nextColumn = focusRowMap[focusStartColumn + focusCell.__colSpan];
if (nextColumn !== undefined) {
const {
cell
} = nextColumn;
$moveSelectionToCell(cell);
} else {
const previousRow = focusRowMap[focusStartColumn - 1];
const {
cell
} = previousRow;
$moveSelectionToCell(cell);
}
}
function $moveSelectionToCell(cell) {
const firstDescendant = cell.getFirstDescendant();
if (firstDescendant == null) {
cell.selectStart();
} else {
firstDescendant.getParentOrThrow().selectStart();
}
}
function $insertFirst(parent, node) {
const firstChild = parent.getFirstChild();
if (firstChild !== null) {
firstChild.insertBefore(node);
} else {
parent.append(node);
}
}
function $unmergeCell() {
const selection = lexical.$getSelection();
if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
throw Error(`Expected a RangeSelection or GridSelection`);
}
const anchor = selection.anchor.getNode();
const [cell, row, grid] = $getNodeTriplet(anchor);
const colSpan = cell.__colSpan;
const rowSpan = cell.__rowSpan;
if (colSpan > 1) {
for (let i = 1; i < colSpan; i++) {
cell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
}
cell.setColSpan(1);
}
if (rowSpan > 1) {
const [map, cellMap] = $computeTableMap(grid, cell, cell);
const {
startColumn,
startRow
} = cellMap;
let currentRowNode;
for (let i = 1; i < rowSpan; i++) {
const currentRow = startRow + i;
const currentRowMap = map[currentRow];
currentRowNode = (currentRowNode || row).getNextSibling();
if (!$isTableRowNode(currentRowNode)) {
throw Error(`Expected row next sibling to be a row`);
}
let insertAfterCell = null;
for (let column = 0; column < startColumn; column++) {
const currentCellMap = currentRowMap[column];
const currentCell = currentCellMap.cell;
if (currentCellMap.startRow === currentRow) {
insertAfterCell = currentCell;
}
if (currentCell.__colSpan > 1) {
column += currentCell.__colSpan - 1;
}
}
if (insertAfterCell === null) {
for (let j = 0; j < colSpan; j++) {
$insertFirst(currentRowNode, $createTableCellNode(TableCellHeaderStates.NO_STATUS));
}
} else {
for (let j = 0; j < colSpan; j++) {
insertAfterCell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
}
}
}
cell.setRowSpan(1);
}
}
function $computeTableMap(grid, cellA, cellB) {
const tableMap = [];
let cellAValue = null;
let cellBValue = null;
function write(startRow, startColumn, cell) {
const value = {
cell,
startColumn,
startRow
};
const rowSpan = cell.__rowSpan;
const colSpan = cell.__colSpan;
for (let i = 0; i < rowSpan; i++) {
if (tableMap[startRow + i] === undefined) {
tableMap[startRow + i] = [];
}
for (let j = 0; j < colSpan; j++) {
tableMap[startRow + i][startColumn + j] = value;
}
}
if (cellA.is(cell)) {
cellAValue = value;
}
if (cellB.is(cell)) {
cellBValue = value;
}
}
function isEmpty(row, column) {
return tableMap[row] === undefined || tableMap[row][column] === undefined;
}
const gridChildren = grid.getChildren();
for (let i = 0; i < gridChildren.length; i++) {
const row = gridChildren[i];
if (!$isTableRowNode(row)) {
throw Error(`Expected GridNode children to be TableRowNode`);
}
const rowChildren = row.getChildren();
let j = 0;
for (const cell of rowChildren) {
if (!$isTableCellNode(cell)) {
throw Error(`Expected TableRowNode children to be TableCellNode`);
}
while (!isEmpty(i, j)) {
j++;
}
write(i, j, cell);
j += cell.__colSpan;
}
}
if (!(cellAValue !== null)) {
throw Error(`Anchor not found in Grid`);
}
if (!(cellBValue !== null)) {
throw Error(`Focus not found in Grid`);
}
return [tableMap, cellAValue, cellBValue];
}
function $getNodeTriplet(source) {
let cell;
if (source instanceof TableCellNode) {
cell = source;
} else if ('__type' in source) {
const cell_ = utils.$findMatchingParent(source, $isTableCellNode);
if (!$isTableCellNode(cell_)) {
throw Error(`Expected to find a parent TableCellNode`);
}
cell = cell_;
} else {
const cell_ = utils.$findMatchingParent(source.getNode(), $isTableCellNode);
if (!$isTableCellNode(cell_)) {
throw Error(`Expected to find a parent TableCellNode`);
}
cell = cell_;
}
const row = cell.getParent();
if (!$isTableRowNode(row)) {
throw Error(`Expected TableCellNode to have a parent TableRowNode`);
}
const grid = row.getParent();
if (!$isTableNode(grid)) {
throw Error(`Expected TableRowNode to have a parent GridNode`);
}
return [cell, row, grid];
}
function $getTableCellNodeRect(tableCellNode) {
const [cellNode,, gridNode] = $getNodeTriplet(tableCellNode);
const rows = gridNode.getChildren();
const rowCount = rows.length;
const columnCount = rows[0].getChildren().length;
// Create a matrix of the same size as the table to track the position of each cell
const cellMatrix = new Array(rowCount);
for (let i = 0; i < rowCount; i++) {
cellMatrix[i] = new Array(columnCount);
}
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = rows[rowIndex];
const cells = row.getChildren();
let columnIndex = 0;
for (let cellIndex = 0; cellIndex < cells.length; cellIndex++) {
// Find the next available position in the matrix, skip the position of merged cells
while (cellMatrix[rowIndex][columnIndex]) {
columnIndex++;
}
const cell = cells[cellIndex];
const rowSpan = cell.__rowSpan || 1;
const colSpan = cell.__colSpan || 1;
// Put the cell into the corresponding position in the matrix
for (let i = 0; i < rowSpan; i++) {
for (let j = 0; j < colSpan; j++) {
cellMatrix[rowIndex + i][columnIndex + j] = cell;
}
}
// Return to the original index, row span and column span of the cell.
if (cellNode === cell) {
return {
colSpan,
columnIndex,
rowIndex,
rowSpan
};
}
columnIndex += colSpan;
}
}
return null;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
class TableSelection {
constructor(tableKey, anchor, focus) {
this.anchor = anchor;
this.focus = focus;
anchor._selection = this;
focus._selection = this;
this._cachedNodes = null;
this.dirty = false;
this.tableKey = tableKey;
}
getStartEndPoints() {
return [this.anchor, this.focus];
}
/**
* Returns whether the Selection is "backwards", meaning the focus
* logically precedes the anchor in the EditorState.
* @returns true if the Selection is backwards, false otherwise.
*/
isBackward() {
return this.focus.isBefore(this.anchor);
}
getCachedNodes() {
return this._cachedNodes;
}
setCachedNodes(nodes) {
this._cachedNodes = nodes;
}
is(selection) {
if (!$isTableSelection(selection)) {
return false;
}
return this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
}
set(tableKey, anchorCellKey, focusCellKey) {
this.dirty = true;
this.tableKey = tableKey;
this.anchor.key = anchorCellKey;
this.focus.key = focusCellKey;
this._cachedNodes = null;
}
clone() {
return new TableSelection(this.tableKey, this.anchor, this.focus);
}
isCollapsed() {
return false;
}
extract() {
return this.getNodes();
}
insertRawText(text) {
// Do nothing?
}
insertText() {
// Do nothing?
}
insertNodes(nodes) {
const focusNode = this.focus.getNode();
if (!lexical.$isElementNode(focusNode)) {
throw Error(`Expected TableSelection focus to be an ElementNode`);
}
const selection = lexical.$normalizeSelection__EXPERIMENTAL(focusNode.select(0, focusNode.getChildrenSize()));
selection.insertNodes(nodes);
}
// TODO Deprecate this method. It's confusing when used with colspan|rowspan
getShape() {
const anchorCellNode = lexical.$getNodeByKey(this.anchor.key);
if (!$isTableCellNode(anchorCellNode)) {
throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
}
const anchorCellNodeRect = $getTableCellNodeRect(anchorCellNode);
if (!(anchorCellNodeRect !== null)) {
throw Error(`getCellRect: expected to find AnchorNode`);
}
const focusCellNode = lexical.$getNodeByKey(this.focus.key);
if (!$isTableCellNode(focusCellNode)) {
throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
}
const focusCellNodeRect = $getTableCellNodeRect(focusCellNode);
if (!(focusCellNodeRect !== null)) {
throw Error(`getCellRect: expected to find focusCellNode`);
}
const startX = Math.min(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
const stopX = Math.max(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
const startY = Math.min(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
const stopY = Math.max(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
return {
fromX: Math.min(startX, stopX),
fromY: Math.min(startY, stopY),
toX: Math.max(startX, stopX),
toY: Math.max(startY, stopY)
};
}
getNodes() {
const cachedNodes = this._cachedNodes;
if (cachedNodes !== null) {
return cachedNodes;
}
const anchorNode = this.anchor.getNode();
const focusNode = this.focus.getNode();
const anchorCell = utils.$findMatchingParent(anchorNode, $isTableCellNode);
// todo replace with triplet
const focusCell = utils.$findMatchingParent(focusNode, $isTableCellNode);
if (!$isTableCellNode(anchorCell)) {
throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
}
if (!$isTableCellNode(focusCell)) {
throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
}
const anchorRow = anchorCell.getParent();
if (!$isTableRowNode(anchorRow)) {
throw Error(`Expected anchorCell to have a parent TableRowNode`);
}
const tableNode = anchorRow.getParent();
if (!$isTableNode(tableNode)) {
throw Error(`Expected tableNode to have a parent TableNode`);
}
const focusCellGrid = focusCell.getParents()[1];
if (focusCellGrid !== tableNode) {
if (!tableNode.isParentOf(focusCell)) {
// focus is on higher Grid level than anchor
const gridParent = tableNode.getParent();
if (!(gridParent != null)) {
throw Error(`Expected gridParent to have a parent`);
}
this.set(this.tableKey, gridParent.getKey(), focusCell.getKey());
} else {
// anchor is on higher Grid level than focus
const focusCellParent = focusCellGrid.getParent();
if (!(focusCellParent != null)) {
throw Error(`Expected focusCellParent to have a parent`);
}
this.set(this.tableKey, focusCell.getKey(), focusCellParent.getKey());
}
return this.getNodes();
}
// TODO Mapping the whole Grid every time not efficient. We need to compute the entire state only
// once (on load) and iterate on it as updates occur. However, to do this we need to have the
// ability to store a state. Killing TableSelection and moving the logic to the plugin would make
// this possible.
const [map, cellAMap, cellBMap] = $computeTableMap(tableNode, anchorCell, focusCell);
let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
let exploredMinColumn = minColumn;
let exploredMinRow = minRow;
let exploredMaxColumn = minColumn;
let exploredMaxRow = minRow;
function expandBoundary(mapValue) {
const {
cell,
startColumn: cellStartColumn,
startRow: cellStartRow
} = mapValue;
minColumn = Math.min(minColumn, cellStartColumn);
minRow = Math.min(minRow, cellStartRow);
maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
}
while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
if (minColumn < exploredMinColumn) {
// Expand on the left
const rowDiff = exploredMaxRow - exploredMinRow;
const previousColumn = exploredMinColumn - 1;
for (let i = 0; i <= rowDiff; i++) {
expandBoundary(map[exploredMinRow + i][previousColumn]);
}
exploredMinColumn = previousColumn;
}
if (minRow < exploredMinRow) {
// Expand on top
const columnDiff = exploredMaxColumn - exploredMinColumn;
const previousRow = exploredMinRow - 1;
for (let i = 0; i <= columnDiff; i++) {
expandBoundary(map[previousRow][exploredMinColumn + i]);
}
exploredMinRow = previousRow;
}
if (maxColumn > exploredMaxColumn) {
// Expand on the right
const rowDiff = exploredMaxRow - exploredMinRow;
const nextColumn = exploredMaxColumn + 1;
for (let i = 0; i <= rowDiff; i++) {
expandBoundary(map[exploredMinRow + i][nextColumn]);
}
exploredMaxColumn = nextColumn;
}
if (maxRow > exploredMaxRow) {
// Expand on the bottom
const columnDiff = exploredMaxColumn - exploredMinColumn;
const nextRow = exploredMaxRow + 1;
for (let i = 0; i <= columnDiff; i++) {
expandBoundary(map[nextRow][exploredMinColumn + i]);
}
exploredMaxRow = nextRow;
}
}
const nodes = [tableNode];
let lastRow = null;
for (let i = minRow; i <= maxRow; i++) {
for (let j = minColumn; j <= maxColumn; j++) {
const {
cell
} = map[i][j];
const currentRow = cell.getParent();
if (!$isTableRowNode(currentRow)) {
throw Error(`Expected TableCellNode parent to be a TableRowNode`);
}
if (currentRow !== lastRow) {
nodes.push(currentRow);
}
nodes.push(cell, ...$getChildrenRecursively(cell));
lastRow = currentRow;
}
}
if (!lexical.isCurrentlyReadOnlyMode()) {
this._cachedNodes = nodes;
}
return nodes;
}
getTextContent() {
const nodes = this.getNodes();
let textContent = '';
for (let i = 0; i < nodes.length; i++) {
textContent += nodes[i].getTextContent();
}
return textContent;
}
}
function $isTableSelection(x) {
return x instanceof TableSelection;
}
function $createTableSelection() {
const anchor = lexical.$createPoint('root', 0, 'element');
const focus = lexical.$createPoint('root', 0, 'element');
return new TableSelection('root', anchor, focus);
}
function $getChildrenRecursively(node) {
const nodes = [];
const stack = [node];
while (stack.length > 0) {
const currentNode = stack.pop();
if (!(currentNode !== undefined)) {
throw Error(`Stack.length > 0; can't be undefined`);
}
if (lexical.$isElementNode(currentNode)) {
stack.unshift(...currentNode.getChildren());
}
if (currentNode !== node) {
nodes.push(currentNode);
}
}
return nodes;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
class TableObserver {
constructor(editor, tableNodeKey) {
this.isHighlightingCells = false;
this.anchorX = -1;
this.anchorY = -1;
this.focusX = -1;
this.focusY = -1;
this.listenersToRemove = new Set();
this.tableNodeKey = tableNodeKey;
this.editor = editor;
this.table = {
columns: 0,
domRows: [],
rows: 0
};
this.tableSelection = null;
this.anchorCellNodeKey = null;
this.focusCellNodeKey = null;
this.anchorCell = null;
this.focusCell = null;
this.hasHijackedSelectionStyles = false;
this.trackTable();
}
getTable() {
return this.table;
}
removeListeners() {
Array.from(this.listenersToRemove).forEach(removeListener => removeListener());
}
trackTable() {
const observer = new MutationObserver(records => {
this.editor.update(() => {
let gridNeedsRedraw = false;
for (let i = 0; i < records.length; i++) {
const record = records[i];
const target = record.target;
const nodeName = target.nodeName;
if (nodeName === 'TABLE' || nodeName === 'TR') {
gridNeedsRedraw = true;
break;
}
}
if (!gridNeedsRedraw) {
return;
}
const tableElement = this.editor.getElementByKey(this.tableNodeKey);
if (!tableElement) {
throw new Error('Expected to find TableElement in DOM');
}
this.table = getTable(tableElement);
});
});
this.editor.update(() => {
const tableElement = this.editor.getElementByKey(this.tableNodeKey);
if (!tableElement) {
throw new Error('Expected to find TableElement in DOM');
}
this.table = getTable(tableElement);
observer.observe(tableElement, {
childList: true,
subtree: true
});
});
}
clearHighlight() {
const editor = this.editor;
this.isHighlightingCells = false;
this.anchorX = -1;
this.anchorY = -1;
this.focusX = -1;
this.focusY = -1;
this.tableSelection = null;
this.anchorCellNodeKey = null;
this.focusCellNodeKey = null;
this.anchorCell = null;
this.focusCell = null;
this.hasHijackedSelectionStyles = false;
this.enableHighlightStyle();
editor.update(() => {
const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
if (!$isTableNode(tableNode)) {
throw new Error('Expected TableNode.');
}
const tableElement = editor.getElementByKey(this.tableNodeKey);
if (!tableElement) {
throw new Error('Expected to find TableElement in DOM');
}
const grid = getTable(tableElement);
$updateDOMForSelection(editor, grid, null);
lexical.$setSelection(null);
editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
});
}
enableHighlightStyle() {
const editor = this.editor;
editor.update(() => {
const tableElement = editor.getElementByKey(this.tableNodeKey);
if (!tableElement) {
throw new Error('Expected to find TableElement in DOM');
}
utils.removeClassNamesFromElement(tableElement, editor._config.theme.tableSelection);
tableElement.classList.remove('disable-selection');
this.hasHijackedSelectionStyles = false;
});
}
disableHighlightStyle() {
const editor = this.editor;
editor.update(() => {
const tableElement = editor.getElementByKey(this.tableNodeKey);
if (!tableElement) {
throw new Error('Expected to find TableElement in DOM');
}
utils.addClassNamesToElement(tableElement, editor._config.theme.tableSelection);
this.hasHijackedSelectionStyles = true;
});
}
updateTableTableSelection(selection) {
if (selection !== null && selection.tableKey === this.tableNodeKey) {
const editor = this.editor;
this.tableSelection = selection;
this.isHighlightingCells = true;
this.disableHighlightStyle();
$updateDOMForSelection(editor, this.table, this.tableSelection);
} else if (selection == null) {
this.clearHighlight();
} else {
this.tableNodeKey = selection.tableKey;
this.updateTableTableSelection(selection);
}
}
setFocusCellForSelection(cell, ignoreStart = false) {
const editor = this.editor;
editor.update(() => {
const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
if (!$isTableNode(tableNode)) {
throw new Error('Expected TableNode.');
}
const tableElement = editor.getElementByKey(this.tableNodeKey);
if (!tableElement) {
throw new Error('Expected to find TableElement in DOM');
}
const cellX = cell.x;
const cellY = cell.y;
this.focusCell = cell;
if (this.anchorCell !== null) {
const domSelection = getDOMSelection(editor._window);
// Collapse the selection
if (domSelection) {
domSelection.setBaseAndExtent(this.anchorCell.elem, 0, this.focusCell.elem, 0);
}
}
if (!this.isHighlightingCells && (this.anchorX !== cellX || this.anchorY !== cellY || ignoreStart)) {
this.isHighlightingCells = true;
this.disableHighlightStyle();
} else if (cellX === this.focusX && cellY === this.focusY) {
return;
}
this.focusX = cellX;
this.focusY = cellY;
if (this.isHighlightingCells) {
const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
const focusNodeKey = focusTableCellNode.getKey();
this.tableSelection = this.tableSelection.clone() || $createTableSelection();
this.focusCellNodeKey = focusNodeKey;
this.tableSelection.set(this.tableNodeKey, this.anchorCellNodeKey, this.focusCellNodeKey);
lexical.$setSelection(this.tableSelection);
editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
$updateDOMForSelection(editor, this.table, this.tableSelection);
}
}
});
}
setAnchorCellForSelection(cell) {
this.isHighlightingCells = false;
this.anchorCell = cell;
this.anchorX = cell.x;
this.anchorY = cell.y;
this.editor.update(() => {
const anchorTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
if ($isTableCellNode(anchorTableCellNode)) {
const anchorNodeKey = anchorTableCellNode.getKey();
this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
this.anchorCellNodeKey = anchorNodeKey;
}
});
}
formatCells(type) {
this.editor.update(() => {
const selection = lexical.$getSelection();
if (!$isTableSelection(selection)) {
{
throw Error(`Expected grid selection`);
}
}
const formatSelection = lexical.$createRangeSelection();
const anchor = formatSelection.anchor;
const focus = formatSelection.focus;
selection.getNodes().forEach(cellNode => {
if ($isTableCellNode(cellNode) && cellNode.getTextContentSize() !== 0) {
anchor.set(cellNode.getKey(), 0, 'element');
focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
formatSelection.formatText(type);
}
});
lexical.$setSelection(selection);
this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
});
}
clearText() {
const editor = this.editor;
editor.update(() => {
const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
if (!$isTableNode(tableNode)) {
throw new Error('Expected TableNode.');
}
const selection = lexical.$getSelection();
if (!$isTableSelection(selection)) {
{
throw Error(`Expected grid selection`);
}
}
const selectedNodes = selection.getNodes().filter($isTableCellNode);
if (selectedNodes.length === this.table.columns * this.table.rows) {
tableNode.selectPrevious();
// Delete entire table
tableNode.remove();
const rootNode = lexical.$getRoot();
rootNode.selectStart();
return;
}
selectedNodes.forEach(cellNode => {
if (lexical.$isElementNode(cellNode)) {
const paragraphNode = lexical.$createParagraphNode();
const textNode = lexical.$createTextNode();
paragraphNode.append(textNode);
cellNode.append(paragraphNode);
cellNode.getChildren().forEach(child => {
if (child !== paragraphNode) {
child.remove();
}
});
}
});
$updateDOMForSelection(editor, this.table, null);
lexical.$setSelection(null);
editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
});
}
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
const rootElement = editor.getRootElement();
if (rootElement === null) {
throw new Error('No root element.');
}
const tableObserver = new TableObserver(editor, tableNode.getKey());
const editorWindow = editor._window || window;
attachTableObserverToTableElement(tableElement, tableObserver);
tableElement.addEventListener('mousedown', event => {
setTimeout(() => {
if (event.button !== 0) {
return;
}
if (!editorWindow) {
return;
}
const anchorCell = getDOMCellFromTarget(event.target);
if (anchorCell !== null) {
stopEvent(event);
tableObserver.setAnchorCellForSelection(anchorCell);
}
const onMouseUp = () => {
editorWindow.removeEventListener('mouseup', onMouseUp);
editorWindow.removeEventListener('mousemove', onMouseMove);
};
const onMouseMove = moveEvent => {
const focusCell = getDOMCellFromTarget(moveEvent.target);
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
moveEvent.preventDefault();
tableObserver.setFocusCellForSelection(focusCell);
}
};
editorWindow.addEventListener('mouseup', onMouseUp);
editorWindow.addEventListener('mousemove', onMouseMove);
}, 0);
});
// Clear selection when clicking outside of dom.
const mouseDownCallback = event => {
if (event.button !== 0) {
return;
}
editor.update(() => {
const selection = lexical.$getSelection();
const target = event.target;
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey && rootElement.contains(target)) {
tableObserver.clearHighlight();
}
});
};
editorWindow.addEventListener('mousedown', mouseDownCallback);
tableObserver.listenersToRemove.add(() => editorWindow.removeEventListener('mousedown', mouseDownCallback));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => $handleArrowKey(editor, event, 'down', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => $handleArrowKey(editor, event, 'up', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, event => $handleArrowKey(editor, event, 'backward', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_RIGHT_COMMAND, event => $handleArrowKey(editor, event, 'forward', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, event => {
const selection = lexical.$getSelection();
if ($isTableSelection(selection)) {
const focusCellNode = utils.$findMatchingParent(selection.focus.getNode(), $isTableCellNode);
if ($isTableCellNode(focusCellNode)) {
stopEvent(event);
focusCellNode.selectEnd();
return true;
}
}
return false;
}, lexical.COMMAND_PRIORITY_HIGH));
const deleteTextHandler = command => () => {
const selection = lexical.$getSelection();
if (!$isSelectionInTable(selection, tableNode)) {
return false;
}
if ($isTableSelection(selection)) {
tableObserver.clearText();
return true;
} else if (lexical.$isRangeSelection(selection)) {
const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
if (!$isTableCellNode(tableCellNode)) {
return false;
}
const anchorNode = selection.anchor.getNode();
const focusNode = selection.focus.getNode();
const isAnchorInside = tableNode.isParentOf(anchorNode);
const isFocusInside = tableNode.isParentOf(focusNode);
const selectionContainsPartialTable = isAnchorInside && !isFocusInside || isFocusInside && !isAnchorInside;
if (selectionContainsPartialTable) {
tableObserver.clearText();
return true;
}
const nearestElementNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
const topLevelCellElementNode = nearestElementNode && utils.$findMatchingParent(nearestElementNode, n => lexical.$isElementNode(n) && $isTableCellNode(n.getParent()));
if (!lexical.$isElementNode(topLevelCellElementNode) || !lexical.$isElementNode(nearestElementNode)) {
return false;
}
if (command === lexical.DELETE_LINE_COMMAND && topLevelCellElementNode.getPreviousSibling() === null) {
// TODO: Fix Delete Line in Table Cells.
return true;
}
if (command === lexical.DELETE_CHARACTER_COMMAND || command === lexical.DELETE_WORD_COMMAND) {
if (selection.isCollapsed() && selection.anchor.offset === 0) {
if (nearestElementNode !== topLevelCellElementNode) {
const children = nearestElementNode.getChildren();
const newParagraphNode = lexical.$createParagraphNode();
children.forEach(child => newParagraphNode.append(child));
nearestElementNode.replace(newParagraphNode);
nearestElementNode.getWritable().__parent = tableCellNode.getKey();
return true;
}
}
}
}
return false;
};
[lexical.DELETE_WORD_COMMAND, lexical.DELETE_LINE_COMMAND, lexical.DELETE_CHARACTER_COMMAND].forEach(command => {
tableObserver.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), lexical.COMMAND_PRIORITY_CRITICAL));
});
const deleteCellHandler = event => {
const selection = lexical.$getSelection();
if (!$isSelectionInTable(selection, tableNode)) {
return false;
}
if ($isTableSelection(selection)) {
event.preventDefault();
event.stopPropagation();
tableObserver.clearText();
return true;
} else if (lexical.$isRangeSelection(selection)) {
const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
if (!$isTableCellNode(tableCellNode)) {
return false;
}
}
return false;
};
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_DELETE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.FORMAT_TEXT_COMMAND, payload => {
const selection = lexical.$getSelection();
if (!$isSelectionInTable(selection, tableNode)) {
return false;
}
if ($isTableSelection(selection)) {
tableObserver.formatCells(payload);
return true;
} else if (lexical.$isRangeSelection(selection)) {
const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
if (!$isTableCellNode(tableCellNode)) {
return false;
}
}
return false;
}, lexical.COMMAND_PRIORITY_CRITICAL));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.CONTROLLED_TEXT_INSERTION_COMMAND, payload => {
const selection = lexical.$getSelection();
if (!$isSelectionInTable(selection, tableNode)) {
return false;
}
if ($isTableSelection(selection)) {
tableObserver.clearHighlight();
return false;
} else if (lexical.$isRangeSelection(selection)) {
const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
if (!$isTableCellNode(tableCellNode)) {
return false;
}
}
return false;
}, lexical.COMMAND_PRIORITY_CRITICAL));
if (hasTabHandler) {
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_TAB_COMMAND, event => {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
return false;
}
const tableCellNode = $findCellNode(selection.anchor.getNode());
if (tableCellNode === null) {
return false;
}
stopEvent(event);
const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
selectTableNodeInDirection(tableObserver, tableNode, currentCords.x, currentCords.y, !event.shiftKey ? 'forward' : 'backward');
return true;
}, lexical.COMMAND_PRIORITY_CRITICAL));
}
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.FOCUS_COMMAND, payload => {
return tableNode.isSelected();
}, lexical.COMMAND_PRIORITY_HIGH));
function getObserverCellFromCellNode(tableCellNode) {
const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
}
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, selectionPayload => {
const {
nodes,
selection
} = selectionPayload;
const anchorAndFocus = selection.getStartEndPoints();
const isTableSelection = $isTableSelection(selection);
const isRangeSelection = lexical.$isRangeSelection(selection);
const isSelectionInsideOfGrid = isRangeSelection && utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n)) !== null && utils.$findMatchingParent(selection.focus.getNode(), n => $isTableCellNode(n)) !== null || isTableSelection;
if (nodes.length !== 1 || !$isTableNode(nodes[0]) || !isSelectionInsideOfGrid || anchorAndFocus === null) {
return false;
}
const [anchor] = anchorAndFocus;
const newGrid = nodes[0];
const newGridRows = newGrid.getChildren();
const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
const newRowCount = newGrid.getChildrenSize();
const gridCellNode = utils.$findMatchingParent(anchor.getNode(), n => $isTableCellNode(n));
const gridRowNode = gridCellNode && utils.$findMatchingParent(gridCellNode, n => $isTableRowNode(n));
const gridNode = gridRowNode && utils.$findMatchingParent(gridRowNode, n => $isTableNode(n));
if (!$isTableCellNode(gridCellNode) || !$isTableRowNode(gridRowNode) || !$isTableNode(gridNode)) {
return false;
}
const startY = gridRowNode.getIndexWithinParent();
const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
const startX = gridCellNode.getIndexWithinParent();
const stopX = Math.min(gridRowNode.getChildrenSize() - 1, startX + newColumnCount - 1);
const fromX = Math.min(startX, stopX);
const fromY = Math.min(startY, stopY);
const toX = Math.max(startX, stopX);
const toY = Math.max(startY, stopY);
const gridRowNodes = gridNode.getChildren();
let newRowIdx = 0;
let newAnchorCellKey;
let newFocusCellKey;
for (let r = fromY; r <= toY; r++) {
const currentGridRowNode = gridRowNodes[r];
if (!$isTableRowNode(currentGridRowNode)) {
return false;
}
const newGridRowNode = newGridRows[newRowIdx];
if (!$isTableRowNode(newGridRowNode)) {
return false;
}
const gridCellNodes = currentGridRowNode.getChildren();
const newGridCellNodes = newGridRowNode.getChildren();
let newColumnIdx = 0;
for (let c = fromX; c <= toX; c++) {
const currentGridCellNode = gridCellNodes[c];
if (!$isTableCellNode(currentGridCellNode)) {
return false;
}
const newGridCellNode = newGridCellNodes[newColumnIdx];
if (!$isTableCellNode(newGridCellNode)) {
return false;
}
if (r === fromY && c === fromX) {
newAnchorCellKey = currentGridCellNode.getKey();
} else if (r === toY && c === toX) {
newFocusCellKey = currentGridCellNode.getKey();
}
const originalChildren = currentGridCellNode.getChildren();
newGridCellNode.getChildren().forEach(child => {
if (lexical.$isTextNode(child)) {
const paragraphNode = lexical.$createParagraphNode();
paragraphNode.append(child);
currentGridCellNode.append(child);
} else {
currentGridCellNode.append(child);
}
});
originalChildren.forEach(n => n.remove());
newColumnIdx++;
}
newRowIdx++;
}
if (newAnchorCellKey && newFocusCellKey) {
const newTableSelection = $createTableSelection();
newTableSelection.set(nodes[0].getKey(), newAnchorCellKey, newFocusCellKey);
lexical.$setSelection(newTableSelection);
}
return true;
}, lexical.COMMAND_PRIORITY_CRITICAL));
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_CHANGE_COMMAND, () => {
const selection = lexical.$getSelection();
const prevSelection = lexical.$getPreviousSelection();
if (lexical.$isRangeSelection(selection)) {
const {
anchor,
focus
} = selection;
const anchorNode = anchor.getNode();
const focusNode = focus.getNode();
// Using explicit comparison with table node to ensure it's not a nested table
// as in that case we'll leave selection resolving to that table
const anchorCellNode = $findCellNode(anchorNode);
const focusCellNode = $findCellNode(focusNode);
const isAnchorInside = anchorCellNode && tableNode.is($findTableNode(anchorCellNode));
const isFocusInside = focusCellNode && tableNode.is($findTableNode(focusCellNode));
const isPartialyWithinTable = isAnchorInside !== isFocusInside;
const isWithinTable = isAnchorInside && isFocusInside;
const isBackward = selection.isBackward();
if (isPartialyWithinTable) {
const newSelection = selection.clone();
newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
lexical.$setSelection(newSelection);
$addHighlightStyleToTable(editor, tableObserver);
} else if (isWithinTable) {
// Handle case when selection spans across multiple cells but still
// has range selection, then we convert it into grid selection
if (!anchorCellNode.is(focusCellNode)) {
tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
}
}
}
if (selection && !selection.is(prevSelection) && ($isTableSelection(selection) || $isTableSelection(prevSelection)) && tableObserver.tableSelection && !tableObserver.tableSelection.is(prevSelection)) {
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
tableObserver.updateTableTableSelection(selection);
} else if (!$isTableSelection(selection) && $isTableSelection(prevSelection) && prevSelection.tableKey === tableObserver.tableNodeKey) {
tableObserver.updateTableTableSelection(null);
}
return false;
}
if (tableObserver.hasHijackedSelectionStyles && !tableNode.isSelected()) {
$removeHighlightStyleToTable(editor, tableObserver);
} else if (!tableObserver.hasHijackedSelectionStyles && tableNode.isSelected()) {
$addHighlightStyleToTable(editor, tableObserver);
}
return false;
}, lexical.COMMAND_PRIORITY_CRITICAL));
return tableObserver;
}
function attachTableObserverToTableElement(tableElement, tableObserver) {
tableElement[LEXICAL_ELEMENT_KEY] = tableObserver;
}
function getTableObserverFromTableElement(tableElement) {
return tableElement[LEXICAL_ELEMENT_KEY];
}
function getDOMCellFromTarget(node) {
let currentNode = node;
while (currentNode != null) {
const nodeName = currentNode.nodeName;
if (nodeName === 'TD' || nodeName === 'TH') {
// @ts-expect-error: internal field
const cell = currentNode._cell;
if (cell === undefined) {
return null;
}
return cell;
}
currentNode = currentNode.parentNode;
}
return null;
}
function getTable(tableElement) {
const domRows = [];
const grid = {
columns: 0,
domRows,
rows: 0
};
let currentNode = tableElement.firstChild;
let x = 0;
let y = 0;
domRows.length = 0;
while (currentNode != null) {
const nodeMame = currentNode.nodeName;
if (nodeMame === 'TD' || nodeMame === 'TH') {
const elem = currentNode;
const cell = {
elem,
hasBackgroundColor: elem.style.backgroundColor !== '',
highlighted: false,
x,
y
};
// @ts-expect-error: internal field
currentNode._cell = cell;
let row = domRows[y];
if (row === undefined) {
row = domRows[y] = [];
}
row[x] = cell;
} else {
const child = currentNode.firstChild;
if (child != null) {
currentNode = child;
continue;
}
}
const sibling = currentNode.nextSibling;
if (sibling != null) {
x++;
currentNode = sibling;
continue;
}
const parent = currentNode.parentNode;
if (parent != null) {
const parentSibling = parent.nextSibling;
if (parentSibling == null) {
break;
}
y++;
x = 0;
currentNode = parentSibling;
}
}
grid.columns = x + 1;
grid.rows = y + 1;
return grid;
}
function $updateDOMForSelection(editor, table, selection) {
const selectedCellNodes = new Set(selection ? selection.getNodes() : []);
$forEachTableCell(table, (cell, lexicalNode) => {
const elem = cell.elem;
if (selectedCellNodes.has(lexicalNode)) {
cell.highlighted = true;
$addHighlightToDOM(editor, cell);
} else {
cell.highlighted = false;
$removeHighlightFromDOM(editor, cell);
if (!elem.getAttribute('style')) {
elem.removeAttribute('style');
}
}
});
}
function $forEachTableCell(grid, cb) {
const {
domRows
} = grid;
for (let y = 0; y < domRows.length; y++) {
const row = domRows[y];
if (!row) {
continue;
}
for (let x = 0; x < row.length; x++) {
const cell = row[x];
if (!cell) {
continue;
}
const lexicalNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
if (lexicalNode !== null) {
cb(cell, lexicalNode, {
x,
y
});
}
}
}
}
function $addHighlightStyleToTable(editor, tableSelection) {
tableSelection.disableHighlightStyle();
$forEachTableCell(tableSelection.table, cell => {
cell.highlighted = true;
$addHighlightToDOM(editor, cell);
});
}
function $removeHighlightStyleToTable(editor, tableObserver) {
tableObserver.enableHighlightStyle();
$forEachTableCell(tableObserver.table, cell => {
const elem = cell.elem;
cell.highlighted = false;
$removeHighlightFromDOM(editor, cell);
if (!elem.getAttribute('style')) {
elem.removeAttribute('style');
}
});
}
const selectTableNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
const isForward = direction === 'forward';
switch (direction) {
case 'backward':
case 'forward':
if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table), isForward);
} else {
if (y !== (isForward ? tableObserver.table.rows - 1 : 0)) {
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : tableObserver.table.columns - 1, y + (isForward ? 1 : -1), tableObserver.table), isForward);
} else if (!isForward) {
tableNode.selectPrevious();
} else {
tableNode.selectNext();
}
}
return true;
case 'up':
if (y !== 0) {
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableObserver.table), false);
} else {
tableNode.selectPrevious();
}
return true;
case 'down':
if (y !== tableObserver.table.rows - 1) {
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableObserver.table), true);
} else {
tableNode.selectNext();
}
return true;
default:
return false;
}
};
const adjustFocusNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
const isForward = direction === 'forward';
switch (direction) {
case 'backward':
case 'forward':
if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table));
}
return true;
case 'up':
if (y !== 0) {
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y - 1, tableObserver.table));
return true;
} else {
return false;
}
case 'down':
if (y !== tableObserver.table.rows - 1) {
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y + 1, tableObserver.table));
return true;
} else {
return false;
}
default:
return false;
}
};
function $isSelectionInTable(selection, tableNode) {
if (lexical.$isRangeSelection(selection) || $isTableSelection(selection)) {
const isAnchorInside = tableNode.isParentOf(selection.anchor.getNode());
const isFocusInside = tableNode.isParentOf(selection.focus.getNode());
return isAnchorInside && isFocusInside;
}
return false;
}
function selectTableCellNode(tableCell, fromStart) {
if (fromStart) {
tableCell.selectStart();
} else {
tableCell.selectEnd();
}
}
const BROWSER_BLUE_RGB = '172,206,247';
function $addHighlightToDOM(editor, cell) {
const element = cell.elem;
const node = lexical.$getNearestNodeFromDOMNode(element);
if (!$isTableCellNode(node)) {
throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
}
const backgroundColor = node.getBackgroundColor();
if (backgroundColor === null) {
element.style.setProperty('background-color', `rgb(${BROWSER_BLUE_RGB})`);
} else {
element.style.setProperty('background-image', `linear-gradient(to right, rgba(${BROWSER_BLUE_RGB},0.85), rgba(${BROWSER_BLUE_RGB},0.85))`);
}
element.style.setProperty('caret-color', 'transparent');
}
function $removeHighlightFromDOM(editor, cell) {
const element = cell.elem;
const node = lexical.$getNearestNodeFromDOMNode(element);
if (!$isTableCellNode(node)) {
throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
}
const backgroundColor = node.getBackgroundColor();
if (backgroundColor === null) {
element.style.removeProperty('background-color');
}
element.style.removeProperty('background-image');
element.style.removeProperty('caret-color');
}
function $findCellNode(node) {
const cellNode = utils.$findMatchingParent(node, $isTableCellNode);
return $isTableCellNode(cellNode) ? cellNode : null;
}
function $findTableNode(node) {
const tableNode = utils.$findMatchingParent(node, $isTableNode);
return $isTableNode(tableNode) ? tableNode : null;
}
function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
const selection = lexical.$getSelection();
if (!$isSelectionInTable(selection, tableNode)) {
return false;
}
if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
// Horizontal move between cels seem to work well without interruption
// so just exit early, and handle vertical moves
if (direction === 'backward' || direction === 'forward') {
return false;
}
const {
anchor,
focus
} = selection;
const anchorCellNode = utils.$findMatchingParent(anchor.getNode(), $isTableCellNode);
const focusCellNode = utils.$findMatchingParent(focus.getNode(), $isTableCellNode);
if (!$isTableCellNode(anchorCellNode) || !anchorCellNode.is(focusCellNode)) {
return false;
}
const anchorCellTable = $findTableNode(anchorCellNode);
if (anchorCellTable !== tableNode && anchorCellTable != null) {
const anchorCellTableElement = editor.getElementByKey(anchorCellTable.getKey());
if (anchorCellTableElement != null) {
tableObserver.table = getTable(anchorCellTableElement);
return $handleArrowKey(editor, event, direction, anchorCellTable, tableObserver);
}
}
const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
const anchorDOM = editor.getElementByKey(anchor.key);
if (anchorDOM == null || anchorCellDom == null) {
return false;
}
let edgeSelectionRect;
if (anchor.type === 'element') {
edgeSelectionRect = anchorDOM.getBoundingClientRect();
} else {
const domSelection = window.getSelection();
if (domSelection === null || domSelection.rangeCount === 0) {
return false;
}
const range = domSelection.getRangeAt(0);
edgeSelectionRect = range.getBoundingClientRect();
}
const edgeChild = direction === 'up' ? anchorCellNode.getFirstChild() : anchorCellNode.getLastChild();
if (edgeChild == null) {
return false;
}
const edgeChildDOM = editor.getElementByKey(edgeChild.__key);
if (edgeChildDOM == null) {
return false;
}
const edgeRect = edgeChildDOM.getBoundingClientRect();
const isExiting = direction === 'up' ? edgeRect.top > edgeSelectionRect.top - edgeSelectionRect.height : edgeSelectionRect.bottom + edgeSelectionRect.height > edgeRect.bottom;
if (isExiting) {
stopEvent(event);
const cords = tableNode.getCordsFromCellNode(anchorCellNode, tableObserver.table);
if (event.shiftKey) {
const cell = tableNode.getDOMCellFromCordsOrThrow(cords.x, cords.y, tableObserver.table);
tableObserver.setAnchorCellForSelection(cell);
tableObserver.setFocusCellForSelection(cell, true);
} else {
return selectTableNodeInDirection(tableObserver, tableNode, cords.x, cords.y, direction);
}
return true;
}
} else if ($isTableSelection(selection)) {
const {
anchor,
focus
} = selection;
const anchorCellNode = utils.$findMatchingParent(anchor.getNode(), $isTableCellNode);
const focusCellNode = utils.$findMatchingParent(focus.getNode(), $isTableCellNode);
const [tableNodeFromSelection] = selection.getNodes();
const tableElement = editor.getElementByKey(tableNodeFromSelection.getKey());
if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableNode(tableNodeFromSelection) || tableElement == null) {
return false;
}
tableObserver.updateTableTableSelection(selection);
const grid = getTable(tableElement);
const cordsAnchor = tableNode.getCordsFromCellNode(anchorCellNode, grid);
const anchorCell = tableNode.getDOMCellFromCordsOrThrow(cordsAnchor.x, cordsAnchor.y, grid);
tableObserver.setAnchorCellForSelection(anchorCell);
stopEvent(event);
if (event.shiftKey) {
const cords = tableNode.getCordsFromCellNode(focusCellNode, grid);
return adjustFocusNodeInDirection(tableObserver, tableNodeFromSelection, cords.x, cords.y, direction);
} else {
focusCellNode.selectEnd();
}
return true;
}
return false;
}
function stopEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
/** @noInheritDoc */
class TableNode extends lexical.ElementNode {
static getType() {
return 'table';
}
static clone(node) {
return new TableNode(node.__key);
}
static importDOM() {
return {
table: _node => ({
conversion: convertTableElement,
priority: 1
})
};
}
static importJSON(_serializedNode) {
return $createTableNode();
}
constructor(key) {
super(key);
}
exportJSON() {
return {
...super.exportJSON(),
type: 'table',
version: 1
};
}
createDOM(config, editor) {
const tableElement = document.createElement('table');
utils.addClassNamesToElement(tableElement, config.theme.table);
return tableElement;
}
updateDOM() {
return false;
}
exportDOM(editor) {
return {
...super.exportDOM(editor),
after: tableElement => {
if (tableElement) {
const newElement = tableElement.cloneNode();
const colGroup = document.createElement('colgroup');
const tBody = document.createElement('tbody');
if (utils.isHTMLElement(tableElement)) {
tBody.append(...tableElement.children);
}
const firstRow = this.getFirstChildOrThrow();
if (!$isTableRowNode(firstRow)) {
throw new Error('Expected to find row node.');
}
const colCount = firstRow.getChildrenSize();
for (let i = 0; i < colCount; i++) {
const col = document.createElement('col');
colGroup.append(col);
}
newElement.replaceChildren(colGroup, tBody);
return newElement;
}
}
};
}
// TODO 0.10 deprecate
canExtractContents() {
return false;
}
canBeEmpty() {
return false;
}
isShadowRoot() {
return true;
}
getCordsFromCellNode(tableCellNode, table) {
const {
rows,
domRows
} = table;
for (let y = 0; y < rows; y++) {
const row = domRows[y];
if (row == null) {
continue;
}
const x = row.findIndex(cell => {
if (!cell) return;
const {
elem
} = cell;
const cellNode = lexical.$getNearestNodeFromDOMNode(elem);
return cellNode === tableCellNode;
});
if (x !== -1) {
return {
x,
y
};
}
}
throw new Error('Cell not found in table.');
}
getDOMCellFromCords(x, y, table) {
const {
domRows
} = table;
const row = domRows[y];
if (row == null) {
return null;
}
const cell = row[x];
if (cell == null) {
return null;
}
return cell;
}
getDOMCellFromCordsOrThrow(x, y, table) {
const cell = this.getDOMCellFromCords(x, y, table);
if (!cell) {
throw new Error('Cell not found at cords.');
}
return cell;
}
getCellNodeFromCords(x, y, table) {
const cell = this.getDOMCellFromCords(x, y, table);
if (cell == null) {
return null;
}
const node = lexical.$getNearestNodeFromDOMNode(cell.elem);
if ($isTableCellNode(node)) {
return node;
}
return null;
}
getCellNodeFromCordsOrThrow(x, y, table) {
const node = this.getCellNodeFromCords(x, y, table);
if (!node) {
throw new Error('Node at cords not TableCellNode.');
}
return node;
}
canSelectBefore() {
return true;
}
canIndent() {
return false;
}
}
function $getElementForTableNode(editor, tableNode) {
const tableElement = editor.getElementByKey(tableNode.getKey());
if (tableElement == null) {
throw new Error('Table Element Not Found');
}
return getTable(tableElement);
}
function convertTableElement(_domNode) {
return {
node: $createTableNode()
};
}
function $createTableNode() {
return lexical.$applyNodeReplacement(new TableNode());
}
function $isTableNode(node) {
return node instanceof TableNode;
}
exports.$computeTableMap = $computeTableMap;
exports.$createTableCellNode = $createTableCellNode;
exports.$createTableNode = $createTableNode;
exports.$createTableNodeWithDimensions = $createTableNodeWithDimensions;
exports.$createTableRowNode = $createTableRowNode;
exports.$createTableSelection = $createTableSelection;
exports.$deleteTableColumn = $deleteTableColumn;
exports.$deleteTableColumn__EXPERIMENTAL = $deleteTableColumn__EXPERIMENTAL;
exports.$deleteTableRow__EXPERIMENTAL = $deleteTableRow__EXPERIMENTAL;
exports.$getElementForTableNode = $getElementForTableNode;
exports.$getNodeTriplet = $getNodeTriplet;
exports.$getTableCellNodeFromLexicalNode = $getTableCellNodeFromLexicalNode;
exports.$getTableCellNodeRect = $getTableCellNodeRect;
exports.$getTableColumnIndexFromTableCellNode = $getTableColumnIndexFromTableCellNode;
exports.$getTableNodeFromLexicalNodeOrThrow = $getTableNodeFromLexicalNodeOrThrow;
exports.$getTableRowIndexFromTableCellNode = $getTableRowIndexFromTableCellNode;
exports.$getTableRowNodeFromTableCellNodeOrThrow = $getTableRowNodeFromTableCellNodeOrThrow;
exports.$insertTableColumn = $insertTableColumn;
exports.$insertTableColumn__EXPERIMENTAL = $insertTableColumn__EXPERIMENTAL;
exports.$insertTableRow = $insertTableRow;
exports.$insertTableRow__EXPERIMENTAL = $insertTableRow__EXPERIMENTAL;
exports.$isTableCellNode = $isTableCellNode;
exports.$isTableNode = $isTableNode;
exports.$isTableRowNode = $isTableRowNode;
exports.$isTableSelection = $isTableSelection;
exports.$removeTableRowAtIndex = $removeTableRowAtIndex;
exports.$unmergeCell = $unmergeCell;
exports.INSERT_TABLE_COMMAND = INSERT_TABLE_COMMAND;
exports.TableCellHeaderStates = TableCellHeaderStates;
exports.TableCellNode = TableCellNode;
exports.TableNode = TableNode;
exports.TableObserver = TableObserver;
exports.TableRowNode = TableRowNode;
exports.applyTableHandlers = applyTableHandlers;
exports.getDOMCellFromTarget = getDOMCellFromTarget;
exports.getTableObserverFromTableElement = getTableObserverFromTableElement;