npm @lexical/react 0.53.0
v0.53.0

3 hours ago

v0.53.0 is the monthly release and collects a month of work since v0.52.0. It removes the extensions deprecated as of v0.52.0 and the unchecked node type arguments, starts a curated CHANGELOG.md with migration notes, and adds several opt-in, experimental building blocks for structured documents and collaboration: declarative node structure in $config, stable block ids, set-valued NodeState with a wrapper-free MarkStateExtension, and GFM tables in @lexical/mdast. Fixes cover IME composition, spellcheck, inline decorators, iOS taps and drops, tables, Markdown, and the ordering of commit listeners that collaboration depends on.

Breaking Changes

  • All packages: extensions deprecated as of v0.52.0 are removed (#9316). Migrate as follows:
    • CodeImportExtension (@lexical/code-core): use CodeExtension
    • CodeHighlighterShikiExtension, CodeHighlighterShikiConfig (@lexical/code-shiki): use CodeShikiExtension with {tokenizer}
    • HorizontalRuleImportExtension (@lexical/html): use HorizontalRuleExtension plus any extension that brings in CoreImportExtension
    • LinkImportExtension, ListImportExtension, RichTextImportExtension, TableImportExtension: use LinkExtension, ListExtension, RichTextExtension, TableExtension
    • MdastImportExtension, MdastExportExtension and their *Output types (@lexical/mdast): use MdastExtension and MdastExtensionOutput
    • The *ImportRules arrays are not extensions and remain.
  • @lexical/mdast (experimental): table column alignment is no longer stored in the mdastTableAlign NodeState. Alignment is now the element format of the paragraph in each cell, so editor states serialized with the old state lose their column alignment on load (#9478)
  • lexical: The deprecated unchecked type arguments on node traversal and key lookup are removed from TypeScript and Flow: getParent<T>(), getParentOrThrow<T>(), getChildren<T>(), getFirstChild<T>(), getNextSibling<T>(), $getNodeByKey<T>() and the rest of that family. Runtime behavior is unchanged. Remove the type argument and narrow the result with a node guard such as $isListItemNode (#9479)
  • lexical: ElementNode.reconcileObservedMutation (@internal) restores the DOM of the element's committed children from the latest reconciled state, not the children of the update it is called in. Code that called it inside an update to render that update's children must place those children's DOM itself (#9329)

New APIs & Features

  • lexical / @lexical/extension (experimental): Declarative node structure in $config. New optional keys inline, children, mergeAdjacent, and a derived flag on fields and states describe what an element may hold and when adjacent siblings are the same thing, so importers, serializers and storage layers can read rules that used to live only in transforms. getDeclaredNodeStructure(klass) reads them, and the opt-in NodeStructureExtension enforces them (#9452)
  • @lexical/extension / @lexical/html (experimental): Stable block ids. BlockIdExtension gives blocks an id that survives serialization, reloads, undo/redo, the clipboard and collaboration, stored as NodeState ("$": {"blockId": "…"}), so agents and server pipelines can address blocks directly. BlockIdHTMLExtension, imported from @lexical/html/BlockIdHTMLExtension, carries the ids through HTML (#9421, #9474)
  • lexical / @lexical/mark (experimental): Set-valued NodeState and MarkStateExtension. arraySetValue(item) declares a state whose value is a canonical (sorted, deduplicated, frozen) set of strings. MarkStateExtension uses it to store mark ids on the leaves they cover instead of in MarkNode wrappers, so overlapping comments need no tree restructuring and ids travel with the text (#9380)
  • @lexical/mdast (experimental): GFM tables. MdastTableExtension imports tables the way GFM renders them and exports any table as a GFM table, using <br> for multi-block cells and padding merged or ragged cells (#9478)
  • @lexical/clipboard: Clipboard import middleware can replace the data and selection for the remaining handlers with $next(data?, selection?), the equivalent of ProseMirror's transformPastedHTML, so off-the-shelf HTML cleanup can be plugged in (#9312)
  • @lexical/link: ClickableLinkExtension accepts getWindow(editor), so iframe editors can navigate in their own window or leave the click unhandled (#9360)
  • @lexical/react: LexicalAutoEmbedPlugin accepts $disableMenuFor to suppress the embed menu where a block embed does not belong, such as inline-only slots (#9365)
  • Changelog with migration notes: CHANGELOG.md is now built from changesets, with Breaking changes (each with Migration: and Detect:), Behavior changes, Deprecations and the full commit list for every release, and it ships in the lexical package at node_modules/lexical/CHANGELOG.md for people and tools upgrading across versions (#9357)
  • Continuous builds: every PR and main commit is published to pkg.pr.new, so a fix can be tried before the next release with npm i https://pkg.pr.new/lexical@main (npm only; see the PR for pnpm and yarn) (#9385, #9387, #9394)

Notable Fixes

Security

  • Links opened in a new tab by registerClickableLink / ClickableLinkPlugin no longer get window.opener, closing a reverse-tabnabbing path (#9303)
  • Markdown export escapes literal brackets and significant < and &, so literal text no longer turns into HTML, character references or links when the Markdown is parsed again. Import decodes escapes and character references in a single pass (#9481)

Input, composition & spellcheck

  • Composing into a TextNode subclass with an IME or a dead key no longer replaces it with a plain TextNode, so it keeps its type, key and NodeState (#9291); cancelled macOS Pinyin compositions in desktop WebKit finish, so the next Enter works (#9362)
  • Chromium keeps spellcheck underlines when Lexical rewrites a text node (#9354); Markdown shortcuts work in iframe editors (#9352); selectionchange events from unfocused inputs and textareas no longer clear pending formatting (#9477); copy, cut and paste inside a decorator's own input are left to that input (#9288)

Selection & editing

  • Mouse drags select across inline decorators such as images, equations and mentions (#9329); line deletion keeps the line break before a line starting with an inline decorator and preserves decorators across soft wraps (#9301, #9321)
  • Selected text and formatting are preserved at range boundaries, and $patchStyleText now styles the selection it is given (#9261); caret mutations, endpoint repair and cross-parent moves no longer corrupt sibling links, throw, or misorder the DOM (#9262, #9263, #9302)
  • Selections and deletions stay inside slot boundaries (#9373); root text content stays correct after repeated slot edits and for custom getTextContent() overrides (#9283, #9260)
  • Enter splits a heading correctly after a line break or inline decorator (#9295); removing a list keeps a caret that sits after an inline decorator (#9300); HISTORY_PUSH_TAG always pushes its own undo entry (#9218); setting a NodeState key still held in unknownState no longer leaves the stale JSON value behind (#9468)

iOS

  • Tapping a decorator no longer scrolls the page to the top of the editor (#9325); double-tapping an empty table cell stays in the cell (#9267); native text-file drops insert their text (#9320)

Collaboration

  • Commit listeners stay in order when editor.read() is called during listener dispatch, fixing lost content with CollabV2 and the Expected shared type to include type attribute corruption in CollabV1 (#9335, closes #7709)
  • @lexical/yjs re-creates only the children that actually moved, keeping sibling Yjs identities and turning a 1.3 s move in an 8,000-paragraph document into a fast one (#9349)
  • Table commands repair ragged tables, which concurrent row and column inserts can produce, instead of throwing (#9375)

Tables, links, code & Markdown

  • Clicks on a nested editor's table, and on nested links, are left to the nested editor (#9356, #9360); $toggleLink(null) no longer throws when the selection only touches a link edge (#9412)
  • Imported HTML code content is normalized to code lines (#9272); Tab works in a code block whose language the highlighter does not know (#9472)
  • Markdown import handles out-of-range character references and keeps backslashes in leading code spans (#9388, #9396); @lexical/mdast keeps consecutive and leading line breaks in a round trip (#9392)

React, SSR & misc

  • Composers no longer read Math.random() while rendering on the server, so Next.js 16 prerendering with cacheComponents works without Suspense (#9319)
  • A read-only ContentEditable no longer renders an unnamed role="textbox" (#9270); stale asynchronous auto-embed and typeahead results are ignored (#9331, #9332); positionNodeOnRange no longer loops forever once its root is detached (#9358)
  • The "node does not exist in active editor state" error names the node and explains how to guard a stale reference (#9366); garbage collection no longer revisits dirty subtrees (#9264)
  • DevTools: the Edge and Safari store listings are gone, and Chrome publishing moves to Web Store API v2 and runs automatically after each latest npm release (#9296, #9457, #9369)

What's Changed

  • v0.52.0 by @etrepum in #9252
  • [lexical-website] Documentation Update: fix five incorrect snippets and claims in the React plugins guide by @potatowagon in #9254
  • [lexical-website] Documentation Update: make the create-plugin snippets valid JSX by @potatowagon in #9255
  • [lexical-website] Documentation Update: webpack and Parcel need an adapter for HMRExtension's hot config by @potatowagon in #9256
  • [lexical] Bug Fix: Make the registerEventListener Flow options type libdef-independent by @potatowagon in #9258
  • [lexical] Refactor: avoid repeated traversal of dirty subtrees during GC by @etrepum in #9264
  • [lexical] Bug Fix: share selection operations and repair endpoints without direction comparisons by @etrepum in #9263
  • [lexical] Bug Fix: preserve tree links and selections during caret mutations by @etrepum in #9262
  • [lexical][lexical-selection] Bug Fix: preserve selected text and formatting at range boundaries by @etrepum in #9261
  • [lexical-devtools][lexical-website] Chore: Remove Edge extension (taken down from the Edge Add-ons store) by @zurfyx in #9296
  • lexical extension devtool bugfix by @potatowagon in #9299
  • [lexical-website] Chore: drop preset-classic to stop installing the unused Algolia search theme by @etrepum in #9298
  • build(deps): bump dompurify from 3.4.13 to 3.4.16 by @dependabot[bot] in #9297
  • [lexical-table] Bug Fix: keep iOS empty-cell double taps inside the cell by @etrepum in #9267
  • [lexical] Bug Fix: preserve insertion anchors when moving children across parents by @etrepum in #9302
  • [lexical-code-core] Bug Fix: normalize imported code content by @maximilliangrand in #9272
  • [lexical-list] Bug Fix: Keep a caret after an inline decorator when removing a list by @rinarakaki in #9300
  • [lexical] Bug Fix: Keep the line break when deleting a line that starts with an inline decorator by @rinarakaki in #9301
  • [lexical] Bug Fix: preserve custom element text in root cache by @maximilliangrand in #9260
  • [lexical] Chore: reduce unit test environment overhead by @etrepum in #9304
  • [lexical] Bug Fix: Keep a TextNode subclass's type, key and node state when composing into it by @rinarakaki in #9291
  • [lexical-rich-text] Bug Fix: Split a heading on Enter after a line break or an inline decorator by @rinarakaki in #9295
  • [lexical-link] Bug Fix: open new-tab links without window.opener by @mbret in #9303
  • build(deps): bump the docusaurus-and-typedoc group with 2 updates by @dependabot[bot] in #9307
  • build(deps-dev): bump the dev-dependencies group with 16 updates by @dependabot[bot] in #9311
  • build(deps-dev): bump flow-bin from 0.331.0 to 0.333.0 in the flow-and-hermes group by @dependabot[bot] in #9308
  • build(deps): bump the rollup group across 1 directory with 5 updates by @dependabot[bot] in #9309
  • [lexical][lexical-playground] Bug Fix: keep root text right after repeated slot edits, and read the DOM caret on keydown by @rinarakaki in #9283
  • build(deps-dev): bump the vitest group across 1 directory with 2 updates by @dependabot[bot] in #9310
  • build(deps): bump undici from 7.27.0 to 7.30.0 by @dependabot[bot] in #9314
  • build(deps): bump fast-uri from 3.1.2 to 3.1.8 by @dependabot[bot] in #9315
  • Documentation Update: Describe $config json schemas in the AGENTS.md custom node guide by @etrepum in #9313
  • [lexical-clipboard][lexical-website] Feature: Allow clipboard import middleware to replace data and selection by @etrepum in #9312
  • [lexical][lexical-react] Bug Fix: Don't read Math.random while rendering on the server by @etrepum in #9319
  • [lexical-rich-text][lexical-playground] Bug Fix: Allow native iOS text-file drops by @etrepum in #9320
  • [lexical] Bug Fix: preserve inline decorators across soft-wrap line deletion by @etrepum in #9321
  • [*] Breaking change: Remove extensions deprecated as of v0.52.0 by @etrepum in #9316
  • [lexical-extension][lexical-playground] Bug Fix: trim triple-click overselection however late the selection change arrives by @etrepum in #9317
  • [lexical-playground] Chore: Reduce E2E overhead and move focused coverage to faster suites by @etrepum in #9322
  • [lexical][lexical-rich-text] Bug Fix: Stop the page jumping when tapping a decorator on iOS by @etrepum in #9325
  • [lexical] Bug Fix: Restore the notified selection when a no-op update is dropped by @followdarko in #9328
  • [lexical-react] Bug Fix: Ignore stale asynchronous auto-embed results by @Tabisharaza in #9331
  • [lexical-react] Bug Fix: Ignore stale asynchronous typeahead close completions by @Tabisharaza in #9332
  • [lexical][lexical-react][lexical-website] Bug Fix: Keep commit listeners in order when editor.read() is called during listener dispatch by @Vedthakar in #9335
  • build(deps): bump proxy-addr from 2.0.7 to 2.0.8 by @dependabot[bot] in #9345
  • build(deps): bump compression from 1.8.1 to 1.8.2 by @dependabot[bot] in #9344
  • build(deps): bump joi from 17.13.4 to 17.13.8 by @dependabot[bot] in #9343
  • [lexical-eslint-plugin-internal] Chore: lint window messaging channel scoping by @potatowagon in #9339
  • [lexical-yjs][Chore] Change last Object to any in lexical by @SamChou19815 in #9353
  • [lexical][lexical-clipboard][lexical-playground][lexical-selection][lexical-table] Bug Fix: Let mouse drags select across inline decorators by @vijayojha89 in #9329
  • [lexical][lexical-playground] Bug Fix: Markdown shortcuts in iframe editors by @hossiendehghan989 in #9352
  • [lexical-yjs] Bug Fix: Re-create only moved children in syncChildrenFromLexical by @adstep in #9349
  • [lexical-table] Bug Fix: Leave clicks on another editor's table to that editor by @Thanatos9404 in #9356
  • [lexical-utils] Bug Fix: positionNodeOnRange no longer loops when its root element is detached by @pataar in #9358
  • [lexical-react][lexical-playground] Feature: Add optional $disableMenuFor prop to LexicalAutoEmbedPlugin by @abhinavs1920 in #9365
  • [lexical-playground] Fix context menu icons not rendering by @ivailop7 in #9368
  • [lexical] Bug Fix: Explain how to guard a stale node reference in the missing node error by @Thanatos9404 in #9366
  • [lexical-playground] Feature: Add "Copy as Markdown" to the context menu by @ivailop7 in #9367
  • [lexical-playground][lexical-website] Chore: Fix automated copyright checks by @xiezhenjia-meta in #9370
  • [lexical-react] Bug Fix: no widget role or widget-only ARIA on a non-editable editor by @potatowagon in #9270
  • [lexical-devtools] Chore: Migrate Chrome publishing and release extensions with npm by @etrepum in #9369
  • [lexical] Bug Fix: Write the managed line break selectors so jsdom 26's nwsapi can parse them by @zurfyx in #9372
  • [lexical-playground] Bug Fix: fix the blank ‘Vertical Align’ menu item in Table Actions menu when width is < 1100 by @umaranis in #9346
  • [lexical-eslint-plugin-internal] Bug Fix: resolve messaging namespaces through imports, assertions and declaration scope by @potatowagon in #9351
  • [lexical-link] Bug Fix: Configure link navigation and isolate nested editors by @maximilliangrand in #9360
  • [lexical] Bug Fix: Keep spellcheck underlines when a text node's text changes by @badcuban in #9354
  • [lexical-playground] Bug Fix: Declare the playground's missing terser and @lexical/a11y dependencies by @GodfredArhinful in #9384
  • [lexical-website][ci] Feature: Publish every PR and main commit to pkg.pr.new by @zurfyx in #9385
  • [lexical][lexical-rich-text][lexical-playground] Bug Fix: Keep selections and deletions inside slot boundaries by @etrepum in #9373
  • [lexical-markdown] Bug Fix: Read an out-of-range decimal character reference as U+FFFD by @kevin9327 in #9388
  • [lexical-website][ci] Bug Fix: Stop advertising pkg.pr.new pnpm commands that install two copies of lexical by @zurfyx in #9387
  • [lexical-mdast] Bug Fix: Keep consecutive and leading line breaks in a Markdown round trip by @unknownhackerworld in #9392
  • [lexical-website][ci] Bug Fix: Stop advertising pkg.pr.new yarn commands that install extra copies of lexical by @zurfyx in #9394
  • [lexical] Bug Fix: Finish cancelled desktop WebKit compositions by @justinchen13 in #9362
  • [lexical-markdown] Bug Fix: Keep backslashes in leading code spans and unescape text only once by @WeAreResilience in #9396
  • build(deps-dev): bump vite from 8.3.1 to 8.3.3 by @dependabot[bot] in #9399
  • build(deps): bump follow-redirects from 1.16.0 to 1.16.1 by @dependabot[bot] in #9397
  • build(deps): bump launch-editor from 2.13.2 to 2.14.2 by @dependabot[bot] in #9398
  • [lexical-history] Bug Fix: HISTORY_PUSH_TAG is ignored when the only dirty leaf is an unchanged TextNode by @ump45nose in #9218
  • [lexical-table][lexical-website] Bug Fix: Table commands throw or misbehave on a ragged table by @zurfyx in #9375
  • [lexical-link] Bug Fix: Fix $toggleLink(null) crash when the selection touches a link boundary by @zurfyx in #9412
  • [lexical][lexical-code-core][lexical-devtools][lexical-extension][lexical-link][lexical-list][lexical-mark][lexical-mdast][lexical-rich-text][lexical-table][lexical-website] Feature: Experimental node structure in $config (inline, children, mergeAdjacent, derived) and an opt-in NodeStructureExtension by @zurfyx in #9452
  • [lexical][lexical-compiler][lexical-fast-check][lexical-mark][lexical-website][lexical-yjs] Feature: Experimental MarkStateExtension and set-valued NodeState with arraySetValue by @zurfyx in #9380
  • [lexical-extension][lexical-html][lexical-yjs][lexical-website][lexical-devtools] Feature: Stable block ids (BlockIdExtension) by @zurfyx in #9421
  • [lexical-devtools] Documentation Update: refresh marketplace access docs and document publishing credentials by @potatowagon in #9330
  • [lexical-devtools][lexical-website] Chore: Remove Safari publishing (taken down from the Mac App Store) by @potatowagon in #9457
  • [lexical-devtools] Documentation Update: Document Chrome service account key rotation by @potatowagon in #9473
  • [lexical] Bug Fix: Ignore text-control selectionchange events by @etrepum in #9477
  • [lexical] Bug Fix: Setting a NodeState key that is still in unknownState no longer leaves the JSON copy behind by @zurfyx in #9468
  • [lexical-code-prism][lexical-code-shiki] Bug Fix: Tab in a code block whose language the highlighter does not know by @zurfyx in #9472
  • [lexical-mdast][lexical-website] Feature: GFM tables in @lexical/mdast by @etrepum in #9478
  • build(deps): bump happy-dom from 20.9.0 to 21.0.4 by @dependabot[bot] in #9456
  • [lexical-plain-text][lexical-rich-text] Bug Fix: Leave a copy, cut or paste in a decorator's input to the input by @rinarakaki in #9288
  • [scripts] Chore: Map the bare tests/utils alias to the index module only by @zurfyx in #9482
  • [lexical] Breaking Change: Remove deprecated unchecked node type arguments by @etrepum in #9479
  • [lexical-html][lexical-website] Breaking change: give BlockIdHTMLExtension its own entry point by @potatowagon in #9474
  • [lexical-markdown] Bug Fix: Preserve literal text and escaped link brackets in Markdown round trips by @etrepum in #9481
  • [lexical][lexical-website] Chore: Changesets-based CHANGELOG with migration notes, plus changesets for everything since v0.52.0 by @zurfyx in #9357

New Contributors

Full Changelog: v0.52.0...v0.53.0

Don't miss a new react release

NewReleases is sending notifications on new releases.