Skip to content

[lexical-utils] Bug Fix: positionNodeOnRange leaking orphan rect nodes when rects shrink#8762

Merged
etrepum merged 1 commit into
facebook:mainfrom
durvesh1992:fix/positionnoderange-remove-orphan-rects
Jun 30, 2026
Merged

[lexical-utils] Bug Fix: positionNodeOnRange leaking orphan rect nodes when rects shrink#8762
etrepum merged 1 commit into
facebook:mainfrom
durvesh1992:fix/positionnoderange-remove-orphan-rects

Conversation

@durvesh1992

Copy link
Copy Markdown
Contributor

Summary

positionNodeOnRange (used by @lexical/utils markSelection / selectionAlwaysOnDisplay) renders one absolutely-positioned <div> per selection client-rect and reuses them across repositions. When the number of rects decreases between two repositions — e.g. a multi-line highlight collapses to a single line after an edit — the cleanup loop popped the excess entries from the lastNodes tracking array but never detached the corresponding <div> from the DOM:

while (lastNodes.length > rects.length) {
  lastNodes.pop(); // array shrinks, but the <div> stays in wrapperNode
}

So stale highlight rectangles remain painted inside wrapperNode until the next stop()/restart(). Note stop() (a few lines below) already does the right thing — for (const node of lastNodes) node.remove() — this just applies the same cleanup on the reposition path.

Fix

Detach the popped node:

while (lastNodes.length > rects.length) {
  const node = lastNodes.pop();
  if (node != null) {
    node.remove();
  }
}

Test plan

Added packages/lexical-utils/src/__tests__/unit/positionNodeOnRange.test.tsx: renders two rects, then shrinks to one via a reposition and asserts the wrapper's child-node count drops to 1.

  • Before: fails (expected 2 to be 1 — orphan div remains).
  • After: passes.
  • Full lexical-utils unit suite: 225 passed, no regressions.
npx vitest run --project unit packages/lexical-utils/src/__tests__/unit

When the number of selection rects decreases between repositions (e.g. a
multi-line highlight collapses to one line), the cleanup loop popped the
excess entries from the lastNodes tracking array but never detached the
corresponding <div> from the DOM. The orphaned rectangles stayed painted
inside wrapperNode until the next stop()/restart().

Call node.remove() on the popped node, mirroring the cleanup already done
in stop(). Adds a regression test (fails before / passes after).
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview Jun 29, 2026 7:50pm
lexical-playground Ready Ready Preview Jun 29, 2026 7:50pm

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 Jun 29, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Jun 29, 2026
@etrepum etrepum changed the title Fix positionNodeOnRange leaking orphan rect nodes when rects shrink [lexical-utils] Bug Fix: positionNodeOnRange leaking orphan rect nodes when rects shrink Jun 30, 2026
@etrepum etrepum added this pull request to the merge queue Jun 30, 2026
Merged via the queue into facebook:main with commit cf25494 Jun 30, 2026
50 checks passed
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. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants