v0.46.0#8748
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
zurfyx
approved these changes
Jun 25, 2026
potatowagon
approved these changes
Jun 25, 2026
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.
v0.46.0 is a monthly release headlined by two major new experimental capabilities:
editorStateThis release also includes many other fixes and new features across IME/composition, collaboration, markdown round-tripping, lists, code blocks, links, and mobile keyboards. It is also our first release (other than nightlies) to use NPM Trusted Publishing.
Special recognition for this release goes to @mayrang for doing the majority of the work on both of these new features as well as fixing some tricky IME/composition issues 👏 @levensta has also been doing a fantastic job going through the issue backlog, providing valuable feedback on these bleeding edge features, and identifying long-standing edge cases.
Breaking Changes
lexical— Node traversal methods no longer accept unsafe type parameters (#8661)The zero-argument generics on
getParent(OrThrow),getPreviousSibling(s),getNextSibling(s),getChildren,getFirstChild(OrThrow),getLastChild(OrThrow),getChildAtIndex,getFirstDescendant,getLastDescendant, andgetDescendantByIndexwere implicit unchecked casts — there is no inference site, so any non-base type argument was equivalent to anascast. Each method is now an overload pair: a documented non-generic signature returning the base type (LexicalNode/ElementNode | null), plus the old generic signature marked@deprecated. Code that leaned on contextual-type inference no longer type-checks:Existing
getFirstChild<ParagraphNode>()-style calls still compile but are now deprecated and will be removed in a future release. This is a types-only change with no runtime impact.lexical—$getNearestNodeFromDOMNode(rootElement)now returnsRootNode(#8588)The "selection captured outside of Lexical" mechanism is generalized beyond
DecoratorNodesubtrees:setDOMUnmanaged(dom, {captureSelection: true})now marks any subtree (e.g. aDOMRenderExtensionoverride or agetDOMSlotwidget) as selection-captured, andisDOMCapturingSelection(dom)walks ancestors so a descendant<input>reports as captured. As part of this, the root element now carries a__lexicalKey_*stash, so$getNodeFromDOM/$getNearestNodeFromDOMNoderesolve the root element to theRootNodeinstead ofnull. Two call sites become more correct (drop-on-root in clipboard, table-selection→range conversion), but external callers that relied on$getNearestNodeFromDOMNode(rootElement)returningnullmust update. The internal-only$isSelectionCapturedInDecoratorwas removed.lexical—insertNodespreserves a leading linebreak; managed<br>s are now tagged (#8615)RangeSelection.insertNodesnow preserves the firstLineBreakNodewhen inserting inline content ahead of a block element, instead of silently dropping it. Separately, the reconciler-inserted "managed" line breaks (the otherwise-invisible<br>s Lexical adds so the caret can land in empty lines) are now identifiable in the DOM as<br data-lexical-managed-linebreak="true">— and, on iOS Safari, an analogous<img data-lexical-managed-linebreak="true" …>in some cases. DOM/HTML snapshot test expectations may need to ignore or expect this attribute. Closes #3980.@lexical/htmland node extensions — DOMImportExtension rules now register implicitly (#8662) (experimental)The experimental DOM-import pipeline no longer requires you to list a per-package import extension. Each node-providing extension (
RichTextExtension,ListExtension,LinkExtension,TableExtension,CodeExtension) now registers its own import rules, and the standaloneRichTextImportExtension/ListImportExtension/LinkImportExtension/TableImportExtension/CodeImportExtension/HorizontalRuleImportExtensionbecome deprecated aliases that may be removed as early as v0.47.0. Rules stay inert unless HTML is routed throughClipboardDOMImportExtensionor$generateNodesFromDOMViaExtension; the legacyimportDOMpaste path is unchanged. If you had not adopted these v0.45.0 extensions yet, there is no breaking change.lexical/@lexical/yjs/@lexical/clipboard/@lexical/html— Named slots (#8603) (experimental)All slot machinery is opt-in and gated (an editor latches
_slotsUsedon the first$setSlot), so editors that never use slots take identical code paths to before. Two changes are observable regardless:syncLexicalUpdateToYjsV2__EXPERIMENTALtakes a newdirtyLeavesparameter, inserted betweendirtyElementsandnormalizedNodes(a slot host's values surface as dirty leaves).NodeSelectioncontaining anElementNodenow includes that element's children on copy/export ($getHtmlContent/$generateJSONFromSelectedNodes). The previous behavior serialized a childless shell, making cut of an elementNodeSelectionsilently lossy; partialRangeSelections keep per-child slicing andexcludeFromCopychildren remain excluded.@lexical/link/@lexical/list/@lexical/react— Removed exports deprecated since v0.32.1 (#8704)Exports marked
@deprecatedin v0.32.1 (2025-06-04, >12 months ago) with no Meta-internal consumers are removed:@lexical/link:toggleLink→ use$toggleLink@lexical/list:insertList/removeList→ use$insertList/$removeList(from an update or command listener)@lexical/react: theContentEditablePropstype alias → useContentEditablePropsNote:
lexical's$nodesOfTypeis intentionally un-deprecated (it still has many consumers). The remaining v0.32.1 deprecations that still have internal consumers (KEY_MODIFIER_COMMAND,$wrapNodes, the@lexical/tablerow/column helpers, etc.) are left for a follow-up.New APIs
lexical—editor.read(mode, fn)andEditorReadMode(#8702)readgains an optional first argument:'force-commit'(the default; flushes pending updates first — the previous one-argument behavior),'pending'(reads the pending state without flushing — the oldeditor.readPending, now removed), and'latest'(reads the committed state without flushing, equivalent toeditor.getEditorState().read(fn, {editor})).EditorReadModeis exported.readPendingonly shipped after v0.45.0, so its removal is not considered a breaking change.lexical— DOM shadow root support (#8694)An editor can now mount inside an open shadow tree without losing selection, focus, drag-and-drop, IME composition, or floating-UI behavior. Reads go through platform APIs —
Selection.getComposedRanges,Selection.direction,ShadowRoot.activeElement,Document.caretPositionFromPoint(x, y, {shadowRoots})— and nine new helpers ship fromlexical; the light-DOM code paths are unchanged. Follow-ups #8708 (insert nodes at the block cursor inside a shadow root) and #8740 (ignorebeforeinput/inputin captured decorators, fixing Firefox 152) complete the surface.lexical—onWarneditor hook (#8644)CreateEditorArgsgains an optionalonWarn?: ErrorHandler(stored as_onWarn, defaulting toconsole.warn), mirroringonError. The infinite-update-loop guard's recoverable warning now routes through it (wired end-to-end in #8658), so embedders that bridge reporting to their own telemetry no longer lose this signal to each user's browser console.lexical—$config()accessor-based nominal typing (#8645)An additive extension of the
$config()protocol: abstract base classes can declare configuration shared with their subclasses under aSymbol.for(<ClassName>)key (resolved bygetStaticNodeConfig), and accessor-based nominal typing lets subclasses opt into stricter$config()checks. No existing node class is changed.@lexical/history— Cut/paste get their own undo entry (#8649)Any update tagged
PASTE_TAGorCUT_TAGis now classified as anOTHERchange inside@lexical/history, which keeps it from merging into the preceding keystrokes and keeps following keystrokes from merging into it. Undoing a short paste (or an iOS autocorrect/prediction) or a cut no longer also undoes the text you typed immediately before it.@lexical/extension—SelectBlockExtension(#8532)Overrides
SELECT_ALL_COMMANDso the first invocation selects the nearest block element and the second selects the entire document; a selection already spanning multiple blocks expands straight to the document, and select-all is a no-op when everything is already selected. Includes an opt-incascadeSelectionoption for nested editors (image captions, etc.) and enablesPreventSelectAllExtensionby default to keepkeydownfrom leaking out ofinput/textareaelements. (Also introducededitor.read('pending', …)via the now-generalized #8702.)@lexical/yjs/@lexical/react— Collaborative cursors via the CSS Custom Highlight API (#8550)Remote collaborators' selections are now painted with the widely-supported CSS Custom Highlight API; the error-prone absolutely-positioned overlay remains only as a fallback. The remote caret is still rendered as a positioned element. Closes #4457, #5837.
@lexical/utils—dedupeSelectionRects(#8709)A new exported helper that drops zero-area rects and any rect that contains another (with 1px tolerance) from
Range.getClientRects(), fixing the duplicate/over-bright and spurious extra-wide fake-selection rects WebKit produces on some blocks. It is wired intopositionNodeOnRange, so every fake-selection consumer (markSelection,selectionAlwaysOnDisplay, the extension) gets clean rects. Addresses #7106, #7492.@lexical/code-core—escapeWithArrows(#8393)CodeIndentExtensiongains anescapeWithArrowsoption (defaultfalse): when a code block is the terminal node and the caret is at the end of its text, pressing the arrow keys creates a new paragraph and moves the caret into it. New$onEscapeDown/$onEscapeUphelpers back it in@lexical/utils. Closes #7912, #4685, #5968.@lexical/react— Self-containedLexicalErrorBoundary(#8720)LexicalErrorBoundaryno longer wrapsreact-error-boundary(the dependency is dropped) and gains an optionalfallbackprop — omit it for the default message, or passfallback={null}to render nothing. Its public type is unchanged, so it remains usable as theErrorBoundaryprop ofRichTextPlugin/PlainTextPlugin.Highlights
Core (
lexical):$getNearestNodeFromDOMNode(rootElement)resolves toRootNode; generalized captured selection viasetDOMUnmanaged({captureSelection})insertNodes; tag managed<br>withdata-lexical-managed-linebreakeditor.read(mode, fn)overload +EditorReadMode(replacesreadPending)onWarneditor hook ([lexical][lexical-react] Bug Fix: actually route the update-recursion guard through editor._onWarn end-to-end (follow-up to #8644) #8658 routes the recursion guard through it end-to-end)$config()accessor-based nominal typingTabNode.setTextContentfor Safari IME compositionCOMPOSITION_END_TAGfrom the FirefoxonInputdefer branchbeforeinput/inputin captured decorators (Firefox 152)devInvariant$assumeEditorwarns viadevInvariantinstead of throwing in prodklass.prototypenull check ingetStaticNodeConfiginheritsLoose-safe helper for class-inheritance loopstokenizeRawText/$generateNodesFromRawTextgetStaticNodeConfigT[]overArray<T>, modernized Flow stubs, dropped deprecated traversal type params in testsNamed slots (experimental):
editorStateHTML / DOM import:
$generateHtmlFromNodesself-establishes active-editor scope (back-compat for [Breaking Change][lexical][lexical-html][lexical-selection][lexical-utils][lexical-playground] Feature: Generalize DOMSlot and add DOMRenderExtension override surface #8519)Extension / History:
SelectBlockExtensionCode:
escapeWithArrows: create a paragraph around a terminal code block with the arrow keysLink / List:
LinkNodewrap on copy in Firefox/SafariDecoratorNodeon Backspace at the start of a top-level listMarkdown:
Mark / Clipboard / Table:
MarkNodemethod return types toboolean(restores subclass overrides)$handleTableSelectionChangeCommandCollaboration (
@lexical/yjs) / Dragon:@lexical/dragon: handlemakeChangesmessages and the listener-registration raceUtils / React:
dedupeSelectionRects— clean up WebKit fake-selection rectsgetScrollParentto@lexical/utilsLexicalErrorBoundary(dropsreact-error-boundary) with an optionalfallbackprop.js.flowfiles; 📝 [lexical-react][lexical-link][lexical-history][lexical-extension] Docs: API doc coverage of react exports #8714 API doc coverage of@lexical/reactexportsRich Text / Plain Text:
preventDefault()indragoverfor HTML5 DnD compliancePlayground:
AutocompleteDictionary)¶,↵,→,·), toggled from SettingsPageBreakNodeas<hr>so Safari firesbeforeinputafter paste/esm/proof of concept@flakytags)ActionsPluginInfrastructure / Tooling:
EACCESin Windows CI browser testseditor.getEditorState().read(...)toeditor.read('latest', ...)AGENTS.md/CLAUDE.md)tmpdependency; [examples] Chore: Replace node-state-style with the dev-examples version #8647 replace thenode-state-styleexample with the dev-examples versionWhat's Changed
so Safari fires beforeinput after paste by @mayrang in [lexical-playground] Bug Fix: Render PageBreakNode as <hr> so Safari fires beforeinput after paste #8719
New Contributors
Full Changelog: v0.45.0...v0.46.0