Skip to content

v0.46.0#8748

Merged
etrepum merged 1 commit into
mainfrom
0.46.0__release
Jun 26, 2026
Merged

v0.46.0#8748
etrepum merged 1 commit into
mainfrom
0.46.0__release

Conversation

@etrepum

@etrepum etrepum commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

v0.46.0 is a monthly release headlined by two major new experimental capabilities:

  • Named slots - a model that lets a single host element or decorator node own several isolated editable regions inside the host's own editorState
  • Shadow DOM - the root element can now be hosted in an open shadow root (or iframe)

This 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, and getDescendantByIndex were implicit unchecked casts — there is no inference site, so any non-base type argument was equivalent to an as cast. 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:

// Before: compiled (unsound). After: type error.
const node: ParagraphNode | null = $getRoot().getFirstChild();

// Port directly with an explicit cast (no behavior change):
const node = $getRoot().getFirstChild() as ParagraphNode | null;

// Or, better, narrow with a guard:
const node = $getRoot().getFirstChild();
if ($isParagraphNode(node)) { /* node: ParagraphNode */ }

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 returns RootNode (#8588)

The "selection captured outside of Lexical" mechanism is generalized beyond DecoratorNode subtrees: setDOMUnmanaged(dom, {captureSelection: true}) now marks any subtree (e.g. a DOMRenderExtension override or a getDOMSlot widget) as selection-captured, and isDOMCapturingSelection(dom) walks ancestors so a descendant <input> reports as captured. As part of this, the root element now carries a __lexicalKey_* stash, so $getNodeFromDOM / $getNearestNodeFromDOMNode resolve the root element to the RootNode instead of null. Two call sites become more correct (drop-on-root in clipboard, table-selection→range conversion), but external callers that relied on $getNearestNodeFromDOMNode(rootElement) returning null must update. The internal-only $isSelectionCapturedInDecorator was removed.

lexicalinsertNodes preserves a leading linebreak; managed <br>s are now tagged (#8615)

RangeSelection.insertNodes now preserves the first LineBreakNode when 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/html and 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 standalone RichTextImportExtension / ListImportExtension / LinkImportExtension / TableImportExtension / CodeImportExtension / HorizontalRuleImportExtension become deprecated aliases that may be removed as early as v0.47.0. Rules stay inert unless HTML is routed through ClipboardDOMImportExtension or $generateNodesFromDOMViaExtension; the legacy importDOM paste 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 _slotsUsed on the first $setSlot), so editors that never use slots take identical code paths to before. Two changes are observable regardless:

  • syncLexicalUpdateToYjsV2__EXPERIMENTAL takes a new dirtyLeaves parameter, inserted between dirtyElements and normalizedNodes (a slot host's values surface as dirty leaves).
  • A NodeSelection containing an ElementNode now includes that element's children on copy/export ($getHtmlContent / $generateJSONFromSelectedNodes). The previous behavior serialized a childless shell, making cut of an element NodeSelection silently lossy; partial RangeSelections keep per-child slicing and excludeFromCopy children remain excluded.

@lexical/link / @lexical/list / @lexical/react — Removed exports deprecated since v0.32.1 (#8704)

Exports marked @deprecated in 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: the ContentEditable Props type alias → use ContentEditableProps

Note: lexical's $nodesOfType is 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/table row/column helpers, etc.) are left for a follow-up.

New APIs

lexicaleditor.read(mode, fn) and EditorReadMode (#8702)

read gains 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 old editor.readPending, now removed), and 'latest' (reads the committed state without flushing, equivalent to editor.getEditorState().read(fn, {editor})). EditorReadMode is exported. readPending only 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 from lexical; the light-DOM code paths are unchanged. Follow-ups #8708 (insert nodes at the block cursor inside a shadow root) and #8740 (ignore beforeinput/input in captured decorators, fixing Firefox 152) complete the surface.

lexicalonWarn editor hook (#8644)

CreateEditorArgs gains an optional onWarn?: ErrorHandler (stored as _onWarn, defaulting to console.warn), mirroring onError. 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 a Symbol.for(<ClassName>) key (resolved by getStaticNodeConfig), 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_TAG or CUT_TAG is now classified as an OTHER change 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/extensionSelectBlockExtension (#8532)

Overrides SELECT_ALL_COMMAND so 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-in cascadeSelection option for nested editors (image captions, etc.) and enables PreventSelectAllExtension by default to keep keydown from leaking out of input/textarea elements. (Also introduced editor.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/utilsdedupeSelectionRects (#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 into positionNodeOnRange, so every fake-selection consumer (markSelection, selectionAlwaysOnDisplay, the extension) gets clean rects. Addresses #7106, #7492.

@lexical/code-coreescapeWithArrows (#8393)

CodeIndentExtension gains an escapeWithArrows option (default false): 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 / $onEscapeUp helpers back it in @lexical/utils. Closes #7912, #4685, #5968.

@lexical/react — Self-contained LexicalErrorBoundary (#8720)

LexicalErrorBoundary no longer wraps react-error-boundary (the dependency is dropped) and gains an optional fallback prop — omit it for the default message, or pass fallback={null} to render nothing. Its public type is unchanged, so it remains usable as the ErrorBoundary prop of RichTextPlugin / PlainTextPlugin.

Highlights

Core (lexical):

Named slots (experimental):

HTML / DOM import:

Extension / History:

Code:

Link / List:

Markdown:

Mark / Clipboard / Table:

Collaboration (@lexical/yjs) / Dragon:

Utils / React:

Rich Text / Plain Text:

Playground:

Infrastructure / Tooling:

What's Changed

New Contributors

Full Changelog: v0.45.0...v0.46.0

@vercel

vercel Bot commented Jun 25, 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 25, 2026 7:38pm
lexical-playground Ready Ready Preview Jun 25, 2026 7:38pm

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 25, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Jun 25, 2026
@etrepum etrepum marked this pull request as ready for review June 25, 2026 20:57
@etrepum etrepum added this pull request to the merge queue Jun 26, 2026
Merged via the queue into main with commit 041c864 Jun 26, 2026
90 of 91 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

3 participants