Skip to content

[lexical-table] Bug Fix: Leave a cut with no end in the table to the editor's own cut - #9708

Open
therealemmanueloppong wants to merge 1 commit into
meta:mainfrom
therealemmanueloppong:fix-table-cut-outside-range
Open

therealemmanueloppong wants to merge 1 commit into
meta:mainfrom
therealemmanueloppong:fix-table-cut-outside-range

Conversation

@therealemmanueloppong

Copy link
Copy Markdown

Description

Current behavior: once a document holds a table, the table's CUT_COMMAND listener (COMMAND_PRIORITY_HIGH) answers every range selection, wherever the range is. For a range outside the table it copies the range, $deleteCellHandler returns false, then it calls selection.removeText() and returns true. So rich text's own cut never runs (a whole-document cut is not widened to its blocks and leaves the first block's type behind, and neither update is tagged CUT_TAG), and no listener below COMMAND_PRIORITY_HIGH runs either.

This PR makes the listener return false for a range selection with neither end inside its table, so the editor's own cut and other listeners handle it as they would with no table in the document. A table selection, a range in a cell, and a range with an end in the table are unchanged.

One existing test changes: "Cut table in the middle of a range selection" relied on the table cutting a range whose ends are both outside the table. It now registers rich text and waits for the cut to finish, since rich text removes the range after its copy resolves. It gives the same <p><br></p>.

The diagnosis and the diff are from the issue; I reproduced and checked it with the tests below.

Closes #9281

Changeset

Added .changeset/leave-a-cut-whose-range-has-no-end.md (@lexical/table, patch, [fix]). pnpm run check-changesets passes.

Test plan

Before

The new CutOutsideTable.test.ts fails on main: a COMMAND_PRIORITY_LOW cut listener never runs for a range in a paragraph after a table.

After

  • CutOutsideTable.test.ts: the LOW listener runs for a range outside the table; it does not run for a range inside a cell (the table keeps those).
  • pnpm exec vitest run --project unit packages/lexical-table packages/lexical-rich-text packages/lexical-clipboard: 52 files, 452 tests pass.
  • pnpm run tsc, eslint and prettier on the changed files are clean.

I ran the unit tests only (jsdom, macOS); I did not run the e2e suite.

@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lexical Ready Ready Preview Nov 1, 2026 5:17pm UTC
lexical-playground Ready Ready Preview Nov 1, 2026 5:17pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 1, 2026
@meta-code-review

meta-code-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 1, 2026, 5:38 PM UTC

@meta-code-review meta-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix. This looks good to merge as is.

The early return in packages/lexical-table/src/LexicalTableSelectionHelpers.ts (lines 970-977) hands a range with neither end in the table back to rich text's cut and to lower-priority CUT_COMMAND listeners, which fixes #9281. Select All + Cut over a heading, a table and a paragraph now leaves an empty paragraph, and its updates carry CUT_TAG. Cuts the table owns (a table selection, a range in a cell, a range with one end in the table) behave as before. The new test fails without the change and passes with it.

A few optional, non-blocking suggestions:

  1. Add a Verify: paragraph to the changeset (.changeset/leave-a-cut-whose-range-has-no-end.md). CONTRIBUTING.md (Changesets) and AGENTS.md ask a fix that changes observable editing behavior to carry one. For example:

    Verify: in a document that holds a table, cut a range with neither end in the table (for example Select All over a heading, a table and a paragraph) and check that CUT_COMMAND listeners below COMMAND_PRIORITY_HIGH run, the removal arrives in an update tagged CUT_TAG once the clipboard write resolves, and an empty paragraph rather than an empty heading is left. In a document with two tables, cutting from a cell of the second table to the text after it removes that table whole.
  2. Assert the outcome the issue describes (packages/lexical-table/src/__tests__/unit/CutOutsideTable.test.ts, lines 74-85). The test checks that a LOW listener ran. Registering rich text and asserting that a whole-document cut over heading/table/paragraph leaves a single paragraph, with an update tagged CUT_TAG, would pin the behavior people actually hit.

  3. Mention and test the two-table case. Every table registers its own HIGH cut listener, and before this change the first table answered every range. That included a range with one end in a second table, which then never got that table's widening: cutting from a cell of a second 2×2 table to the text after it left a one-cell table behind. With this change the owning table answers and removes the table whole, as in a single-table document. The description says ranges with an end in the table are unchanged, so this improvement deserves a sentence there and a test.

  4. Paste the Test plan output. AGENTS.md (Commits and Pull Requests) asks the ### Before / ### After sections to include the real output of the commands that were run, not a description of it.

'@lexical/table': patch
---

[fix] Leave a cut whose range has no end in a table to the editor's own cut and to other CUT_COMMAND listeners, instead of the table answering every range selection once the document holds a table

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: CONTRIBUTING.md (Changesets) asks a fix that changes observable editing behavior to add a Verify: paragraph. For example:

Verify: in a document that holds a table, cut a range with neither end in the table (for example Select All over a heading, a table and a paragraph) and check that CUT_COMMAND listeners below COMMAND_PRIORITY_HIGH run, the removal arrives in an update tagged CUT_TAG once the clipboard write resolves, and an empty paragraph rather than an empty heading is left. In a document with two tables, cutting from a cell of the second table to the text after it removes that table whole.

selection.focus.set(text.getKey(), 1, 'text');
$setSelection(selection);
});
expect(getLowPriorityCuts()).toBe(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: this proves a LOW listener runs, but not the outcome from #9281. A case that registers rich text, cuts $selectAll() over heading/table/paragraph, awaits the cut, and asserts the root holds a single paragraph (plus an update tagged CUT_TAG) would fail on main, which leaves heading, and pin the user-visible fix.

@pkg-pr-new

pkg-pr-new Bot commented Nov 1, 2026

Copy link
Copy Markdown
lexical

npm i https://pkg.pr.new/lexical@9708

@lexical/a11y

npm i https://pkg.pr.new/@lexical/a11y@9708

@lexical/clipboard

npm i https://pkg.pr.new/@lexical/clipboard@9708

@lexical/code

npm i https://pkg.pr.new/@lexical/code@9708

@lexical/code-core

npm i https://pkg.pr.new/@lexical/code-core@9708

@lexical/code-prism

npm i https://pkg.pr.new/@lexical/code-prism@9708

@lexical/code-shiki

npm i https://pkg.pr.new/@lexical/code-shiki@9708

@lexical/compiler

npm i https://pkg.pr.new/@lexical/compiler@9708

@lexical/devtools-core

npm i https://pkg.pr.new/@lexical/devtools-core@9708

@lexical/dragon

npm i https://pkg.pr.new/@lexical/dragon@9708

@lexical/eslint-plugin

npm i https://pkg.pr.new/@lexical/eslint-plugin@9708

@lexical/extension

npm i https://pkg.pr.new/@lexical/extension@9708

@lexical/fast-check

npm i https://pkg.pr.new/@lexical/fast-check@9708

@lexical/file

npm i https://pkg.pr.new/@lexical/file@9708

@lexical/hashtag

npm i https://pkg.pr.new/@lexical/hashtag@9708

@lexical/headless

npm i https://pkg.pr.new/@lexical/headless@9708

@lexical/history

npm i https://pkg.pr.new/@lexical/history@9708

@lexical/html

npm i https://pkg.pr.new/@lexical/html@9708

@lexical/internal

npm i https://pkg.pr.new/@lexical/internal@9708

@lexical/link

npm i https://pkg.pr.new/@lexical/link@9708

@lexical/list

npm i https://pkg.pr.new/@lexical/list@9708

@lexical/mark

npm i https://pkg.pr.new/@lexical/mark@9708

@lexical/markdown

npm i https://pkg.pr.new/@lexical/markdown@9708

@lexical/mdast

npm i https://pkg.pr.new/@lexical/mdast@9708

@lexical/offset

npm i https://pkg.pr.new/@lexical/offset@9708

@lexical/overflow

npm i https://pkg.pr.new/@lexical/overflow@9708

@lexical/plain-text

npm i https://pkg.pr.new/@lexical/plain-text@9708

@lexical/react

npm i https://pkg.pr.new/@lexical/react@9708

@lexical/rich-text

npm i https://pkg.pr.new/@lexical/rich-text@9708

@lexical/selection

npm i https://pkg.pr.new/@lexical/selection@9708

@lexical/table

npm i https://pkg.pr.new/@lexical/table@9708

@lexical/tailwind

npm i https://pkg.pr.new/@lexical/tailwind@9708

@lexical/text

npm i https://pkg.pr.new/@lexical/text@9708

@lexical/utils

npm i https://pkg.pr.new/@lexical/utils@9708

@lexical/y

npm i https://pkg.pr.new/@lexical/y@9708

@lexical/yjs

npm i https://pkg.pr.new/@lexical/yjs@9708

commit: d1301be

This branch was successfully deployed

2 active deployments
Preview – lexical — d1301be2 Deployed Nov 1, 2026 by vercel[bot]
Preview – lexical-playground — d1301be2 Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: [lexical-table] Once a document has a table, every cut goes through the table's handler, even one nowhere near the table

1 participant