[lexical] Bug Fix: $getDocument() should fall back to the global document when there is no active editor#8813
Merged
etrepum merged 1 commit intoJul 8, 2026
Conversation
…tive editor PR facebook#8788 migrated node createDOM() bodies from the bare `document` global to $getDocument(), defined as getRootOwnerDocument($getEditor()._rootElement). $getEditor() throws when there is no active editor, so this silently added a new precondition to every migrated node: createDOM()/exportDOM() now require an ambient active editor. Consumers that serialize nodes headlessly — calling node.exportDOM(editor) or createDOM(config, editor) outside editor.read()/update() — now throw "Unable to find an active editor". This is a backwards-incompatible tightening of public methods with no shim/deprecation, affecting every node migrated by facebook#8788 (List, Link, Table, DecoratorBlock, ...). Make $getDocument() tolerate the no-active-editor case using the existing non-throwing internalGetActiveEditor() lookup, falling back to the global document (which getRootOwnerDocument(null) already returns). This keeps the Shadow DOM / cross-origin iframe benefit when an editor is active and restores pre-facebook#8788 behavior otherwise.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Thanks for catching this — the |
etrepum
approved these changes
Jul 8, 2026
etrepum
left a comment
Collaborator
There was a problem hiding this comment.
The jsdoc here isn’t quite correct anymore, but we can fix that later.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
#8788 added the
$getDocument()API and migrated a number of nodecreateDOM()bodies from the baredocumentglobal to it:$getEditor()throws ("Unable to find an active editor ... can only be used synchronously during the callback of editor.update(), editor.read(), ...") when there is no active editor. As a result, #8788 silently added a new precondition to every node it migrated:createDOM()/exportDOM()now require an ambient active editor.Any consumer that serializes nodes headlessly — calling
node.exportDOM(editor)/node.createDOM(config, editor)outsideeditor.read()/editor.update()— now throws. Note theeditoris already passed to those methods as an argument, but$getDocument()ignores it and relies on the ambient$getEditor().This is a backwards-incompatible tightening of public methods with no shim or deprecation. The blast radius is every node migrated by #8788 (
ListNode,LinkNode,TableNode,DecoratorBlockNode,TextNode,ParagraphNode, …), and any external consumer doing headlessexportDOMis one upgrade away from the same throw.Fix
Make
$getDocument()tolerate the no-active-editor case. It uses the active editor's root-owner document when an editor is present, and otherwise falls back to the globaldocument:internalGetActiveEditor()is the existing non-throwing active-editor lookup (returnsLexicalEditor | null).getRootOwnerDocument(null)already returns the globaldocument, so the fallback path reuses existing behavior.<iframe>benefit from [lexical][lexical-eslint-plugin] Feature: Add $getDocument() API and Shadow DOM lint enforcement #8788 is preserved.document) instead of throwing.The doc comment for
$getDocument()is updated to state that — unlike most$-prefixed helpers — it does not require an ambient active editor, precisely because it must remain callable fromcreateDOM/exportDOMduring headless serialization.Test plan
Added a regression unit test in
packages/lexical/src/__tests__/unit/LexicalUtils.test.tscovering the no-active-editor case, alongside the two existing$getDocumenttests.Before
Calling a migrated node's
exportDOMoutsideeditor.read()/update()throws:After
The headless
ListNode.exportDOM(editor)call above returns the expected<ul>element instead of throwing.pnpm tsc,prettier --check, andeslintall pass on the changed files.