Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove rows w/ span support #4078

Merged
merged 1 commit into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
click,
clickSelectors,
copyToClipboard,
deleteTableRows,
focusEditor,
html,
initialize,
Expand Down Expand Up @@ -1218,4 +1219,60 @@ test.describe('Tables', () => {
`,
);
});

test('Delete rows (with conflicting merged cell)', async ({
page,
isPlainText,
}) => {
test.skip(isPlainText);

await focusEditor(page);

await insertTable(page, 4, 2);

await selectCellsFromTableCords(
page,
{x: 1, y: 1},
{x: 1, y: 3},
false,
false,
);
await mergeTableCells(page);

await page.pause();
await selectCellsFromTableCords(
page,
{x: 0, y: 0},
{x: 0, y: 1},
true,
true,
);

await deleteTableRows(page);

await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
<table class="PlaygroundEditorTheme__table">
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell" rowspan="2">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
</tr>
</table>
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ export async function moveRight(page, numCharacters = 1, delayMs) {
}
}

export async function moveUp(page, numCharacters = 1, delayMs) {
for (let i = 0; i < numCharacters; i++) {
if (delayMs !== undefined) {
await sleep(delayMs);
}
await page.keyboard.press('ArrowUp');
}
}

export async function moveDown(page, numCharacters = 1, delayMs) {
for (let i = 0; i < numCharacters; i++) {
if (delayMs !== undefined) {
Expand Down
15 changes: 15 additions & 0 deletions packages/lexical-playground/__tests__/utils/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,21 @@ export async function mergeTableCells(page) {
await click(page, '.item[data-test-id="table-merge-cells"]');
}

export async function deleteTableRows(page) {
await click(page, '.table-cell-action-button-container');
await click(page, '.item[data-test-id="table-delete-rows"]');
}

export async function deleteTableColumns(page) {
await click(page, '.table-cell-action-button-container');
await click(page, '.item[data-test-id="table-delete-columns"]');
}

export async function deleteTable(page) {
await click(page, '.table-cell-action-button-container');
await click(page, '.item[data-test-id="table-delete"]');
}

export async function enableCompositionKeyEvents(page) {
const targetPage = IS_COLLAB ? await page.frame('left') : page;
await targetPage.evaluate(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import useLexicalEditable from '@lexical/react/useLexicalEditable';
import {
$deleteTableColumn,
$deleteTableRow__EXPERIMENTAL,
$getTableCellNodeFromLexicalNode,
$getTableColumnIndexFromTableCellNode,
$getTableNodeFromLexicalNodeOrThrow,
Expand All @@ -18,7 +19,6 @@ import {
$insertTableRow__EXPERIMENTAL,
$isTableCellNode,
$isTableRowNode,
$removeTableRowAtIndex,
getTableSelectionFromTableElement,
HTMLTableElementWithWithTableSelectionState,
TableCellHeaderStates,
Expand Down Expand Up @@ -251,15 +251,10 @@ function TableActionMenu({

const deleteTableRowAtSelection = useCallback(() => {
editor.update(() => {
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);

$removeTableRowAtIndex(tableNode, tableRowIndex);

clearTableSelection();
$deleteTableRow__EXPERIMENTAL();
onClose();
});
}, [editor, tableCellNode, clearTableSelection, onClose]);
}, [editor, onClose]);

const deleteTableAtSelection = useCallback(() => {
editor.update(() => {
Expand Down Expand Up @@ -419,13 +414,22 @@ function TableActionMenu({
</span>
</button>
<hr />
<button className="item" onClick={() => deleteTableColumnAtSelection()}>
<button
className="item"
onClick={() => deleteTableColumnAtSelection()}
data-test-id="table-delete-columns">
<span className="text">Delete column</span>
</button>
<button className="item" onClick={() => deleteTableRowAtSelection()}>
<button
className="item"
onClick={() => deleteTableRowAtSelection()}
data-test-id="table-delete-rows">
<span className="text">Delete row</span>
</button>
<button className="item" onClick={() => deleteTableAtSelection()}>
<button
className="item"
onClick={() => deleteTableAtSelection()}
data-test-id="table-delete">
<span className="text">Delete table</span>
</button>
<hr />
Expand Down
95 changes: 95 additions & 0 deletions packages/lexical-table/src/LexicalTableUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import type {Grid} from './LexicalTableSelection';
import type {ElementNode} from 'lexical';

import {$findMatchingParent} from '@lexical/utils';
import {
Expand All @@ -18,6 +19,7 @@ import {
DEPRECATED_$getNodeTriplet,
DEPRECATED_$isGridRowNode,
DEPRECATED_$isGridSelection,
DEPRECATED_GridCellNode,
LexicalNode,
} from 'lexical';
import invariant from 'shared/invariant';
Expand Down Expand Up @@ -394,3 +396,96 @@ export function $deleteTableColumn(

return tableNode;
}

export function $deleteTableRow__EXPERIMENTAL(): void {
const selection = $getSelection();
invariant(
$isRangeSelection(selection) || DEPRECATED_$isGridSelection(selection),
'Expected a RangeSelection or GridSelection',
);
const anchor = selection.anchor.getNode();
const focus = selection.focus.getNode();
const [anchorCell, , grid] = DEPRECATED_$getNodeTriplet(anchor);
const [focusCell] = DEPRECATED_$getNodeTriplet(focus);
const [gridMap, anchorCellMap, focusCellMap] = DEPRECATED_$computeGridMap(
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);
invariant(
DEPRECATED_$isGridRowNode(nextRowNode),
'Expected GridNode childAtIndex(%s) to be RowNode',
String(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 (column === 0) {
$insertFirst(nextRowNode, cell);
} else {
const {cell: previousCell} = nextRow[column - 1];
previousCell.insertAfter(cell);
}
}
}
const rowNode = grid.getChildAtIndex(row);
invariant(
DEPRECATED_$isGridRowNode(rowNode),
'Expected GridNode childAtIndex(%s) to be RowNode',
String(row),
);
rowNode.remove();
}
if (nextRow !== undefined) {
const {cell} = nextRow[0];
$moveSelectionToCell(cell);
} else {
const previousRow = gridMap[anchorStartRow - 1];
const {cell} = previousRow[0];
$moveSelectionToCell(cell);
}
}

function $moveSelectionToCell(cell: DEPRECATED_GridCellNode): void {
const firstDescendant = cell.getFirstDescendant();
invariant(firstDescendant !== null, 'Unexpected empty cell');
firstDescendant.getParentOrThrow().selectStart();
}

function $insertFirst(parent: ElementNode, node: LexicalNode): void {
const firstChild = parent.getFirstChild();
if (firstChild !== null) {
parent.insertBefore(firstChild);
} else {
parent.append(node);
}
}
2 changes: 2 additions & 0 deletions packages/lexical-table/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
import {
$createTableNodeWithDimensions,
$deleteTableColumn,
$deleteTableRow__EXPERIMENTAL,
$getTableCellNodeFromLexicalNode,
$getTableColumnIndexFromTableCellNode,
$getTableNodeFromLexicalNodeOrThrow,
Expand All @@ -60,6 +61,7 @@ export {
$createTableNodeWithDimensions,
$createTableRowNode,
$deleteTableColumn,
$deleteTableRow__EXPERIMENTAL,
$getElementGridForTableNode,
$getTableCellNodeFromLexicalNode,
$getTableColumnIndexFromTableCellNode,
Expand Down