v0.28.0
facebook/lexicalv0.28.0Mar 18, 2025by etrepum
AI Summary
This ad-hoc minor release (v0.28.0) addresses critical bugs and introduces enabling features, most notably fixing a 3+ year old bug (#7341) where nodes could fail to update in the DOM under certain conditions. The release includes breaking changes to NodeSelection delete handlers, ListItemNode CSS styling (requiring theme updates), and TableCellNode importDOM behavior.
Key Highlights
- Critical bug fix: nodes not updating in DOM under certain conditions (#7341) - existed for 3+ years
- New default delete handler for NodeSelection that fixes subtle bugs in existing implementations (#7323)
- RootNode transforms now applied last with ordering guarantee (#7344)
- ListItemNode text style inheritance moved to CSS custom properties requiring theme updates (#7325)
- TableCellNode importDOM fixed to properly handle line breaks like other Lexical roots (#7318)
Breaking Changes
- NodeSelection default delete handler (#7323) - existing $onDelete handlers for KEY_DELETE_COMMAND/KEY_BACKSPACE_COMMAND need to be removed
- RootNode and ListItemNode splice (#7341) - exception behavior changed, automatic ListItemNode wrapping now applies in all insertion situations
- $insertNodeToNearestRoot (#7342) - now respects canBeEmpty() and won't split nodes like ListNode when they can be empty
- ListItemNode CSS (#7325) - requires adding CSS to theme for marker pseudo-element styling with custom properties
- TableCellNode importDOM (#7318) - corrected behavior may require removing workarounds for previous behavior
- getDOMSlot API (#7336) - type parameter added for ElementDOMSlot
New Features
- mutatedNodes property added to UpdateListener payload for comprehensive mutation tracking (#7321)
- NodeSelection.deleteNodes() method added to support default delete handler (#7323)
- RootNode node transform ordering guarantee - transforms called last with special dirty behavior
- onElementChanged callback added to DraggableBlockPlugin in @lexical/react (#7338)
- Type predicate added to objectKlassEquals utility (#7340)
- TableNode scrollable updateDOM fixed (#7336)
Full Release Notes
Ad-hoc minor release with important bug fixes and some enabling features. The most important fix is in #7341 - where under certain conditions nodes may not be updated in the DOM at all (this bug is rarely triggered and has been around for 3+ years).
## Breaking Changes
### NodeSelection default delete handler #7323
If you have any `$onDelete` handlers copied from the playground for `KEY_DELETE_COMMAND` and `KEY_BACKSPACE_COMMAND`, you can delete them all now. If you leave them in, it will have similar bugs as prior to this PR.
The default `KEY_DELETE_COMMAND` and `KEY_BACKSPACE_COMMAND` handlers now `event.preventDefault()` and call `DELETE_CHARACTER_COMMAND` for `NodeSelection` (in addition to the existing `RangeSelection` behavior).
The `DELETE_CHARACTER_COMMAND` handler now handles NodeSelection by calling the new `NodeSelection.deleteNodes()` method which places a new `RangeSelection` where the first selected node was (if any, and if it was the current selection) and then removes all of the selected nodes.
### RootNode and ListItemNode splice #7341
This PR moves the incorrectly overridden `append` methods for `RootNode` and `ListNode` and moves it to the `ElementNode`'s primitive `splice` method. For RootNode this means that the exception you'd get for appending a leaf node to the root will be thrown in all situations when that node is inserted, rather than just append. For `ListNode` this means that the automatic `ListItemNode` wrapping applies in all situations when children are inserted into the node, not just append.
### $insertNodeToNearestRoot changes #7342
Previously `$insertNodeToNearestRoot` could create empty `ElementNode` when splitting the current node, now it respects `canBeEmpty()` and will not split in those cases (e.g. `ListNode`).
### ListItemNode CSS #7325
The approach used in #7024 (since v0.26.0) to have a `ListItemNode` bullet inherit the style of the first text node was too broad, the inline style on the `ListItemNode` cascades to *all* of its children. The only way around this is to change the approach.
To retain this feature, you need to add CSS to your theme to specifically style the marker pseudo-element based on these new custom properties. Here's an example from the playground css:
```css
.PlaygroundEditorTheme__listItem::marker {
color: var(--listitem-marker-color);
background-color: var(--listitem-marker-background-color);
font-family: var(--listitem-marker-font-family);
font-size: var(--listitem-marker-font-size);
}
```
### TableCellNode importDOM #7318
The `importDOM` implementation for `TableCellNode` has been corrected to behave as other Lexical roots do with regard to converting `<br>` tags to `LineBreakNode` and wrapping runs of top-level inline nodes (including inline decorators) with `ParagraphNode`. If you have any specific workarounds accounting for the previous behavior, you can remove them. The one difference from other import paths is that a solitary `<br>` (e.g. `<td><br></td>`) is converted to an empty `ParagraphNode` rather than a `ParagraphNode` containing a `LineBreakNode`.
### getDOMSlot #7336
If you're using the undocumented internal getDOMSlot API, you may need to change your types. There is now a type parameter for ElementDOMSlot so that the element property can be specialized.
## Highlights
Core:
* 🆕 #7321 Add `mutatedNodes` to `UpdateListener` payload
* 🆕 #7323 Add a default delete handler for `NodeSelection` - this allowed a lot of boilerplate code to be deleted, and all of the existing implementations were subtly broken
* ✅ #7341 Fix bug in transformer loop that would cause nodes not to get reconciled
* ✅ #7342 Handle `canBeEmpty()` in `$splitNodes`
* 🆕 #7344 Apply `RootNode` transforms last
List:
* ✅ #7325 Move `ListItemNode` text style inheritance to CSS custom properties
Tables:
* ✅ #7336 Fix updateDOM for scrollable TableNode
* ✅ #7318 Fix table cell line breaks
React:
* ✅ #7315 Remove unused direct dependencies
* 🆕 #7338 Add `onElementChanged` callback to DraggableBlockPlugin
Playground:
* ✅ #7337 Table actions should clear selection instead of moving it to the beginning
* 🆕 #7338 Add "+" button to DraggableBlockPlugin
Utils:
* 🆕 #7340 Add a type predicate to `objectKlassEquals`
## New APIs
### UpdateListenerPayload mutatedNodes #7321
A `mutatedNodes` property is now present in the `UpdateListener` payload. This was done to accommodate the use case when you want to have a MutationListener that listens to all nodes (this is useful in combination with NodeState). Note that this property is only calculated when at least one `MutationListener` is registered (e.g. `editor.registerMutationListener(RootNode, () => {})` is sufficient to compute `mutatedNodes` for all nodes).
### NodeSelection deleteNodes #7323
`NodeSelection.deleteNodes()` was added to support the default delete handler
### RootNode node transform #7344
There is now an ordering guarantee that RootNode node transforms are called last, and documentation about the special case that RootNode is always considered intentionally dirty when any other node is dirty
### @lexical/react/LexicalDraggableBlockPlugin onElementChanged #7338
An `onElementChanged` prop was added to make it possible to implement the "+" button in the playground
## What's Changed
* v0.27.2 by @etrepum in https://github.com/facebook/lexical/pull/7314
* [lexical-react]: Chore: remove unused dependencies from @lexical/react by @AlessioGr in https://github.com/facebook/lexical/pull/7315
* [lexical-editor][Bug fix] Add LexicalEditor.hasNode to flow typing to match typescript by @Zhangerr in https://github.com/facebook/lexical/pull/7320
* [Breaking Change][lexical][lexical-table] Bug Fix: Scrollable TableNode updateDOM fixes and getDOMSlot type refactoring by @etrepum in https://github.com/facebook/lexical/pull/7336
* [lexical] Feature: Add mutatedNodes to UpdateListener payload by @etrepum in https://github.com/facebook/lexical/pull/7321
* [Breaking Change][lexical-list] Bug Fix: Move ListItemNode text style inheritance to custom properties and CSS by @etrepum in https://github.com/facebook/lexical/pull/7325
* [Breaking Change][lexical][lexical-playground] Feature: Add a default delete handler for NodeSelection by @etrepum in https://github.com/facebook/lexical/pull/7323
* [lexical-playground] Table actions should clear selection instead of moving it to the beginning by @etrepum in https://github.com/facebook/lexical/pull/7337
* [lexical-utils] Feature: Add type predicate to objectKlassEquals by @2wheeh in https://github.com/facebook/lexical/pull/7340
* [Breaking Change][lexical] Bug Fix: Fix bug in transformer loop that would cause nodes not to get reconciled by @etrepum in https://github.com/facebook/lexical/pull/7341
* [Breaking Change][lexical-table] Bug Fix: Table cell line breaks behave differently from the intended HTML behavior. by @dineug in https://github.com/facebook/lexical/pull/7318
* [Breaking Change][lexical][lexical-utils]: Bug Fix: Handle canBeEmpty in $splitNodes by @etrepum in https://github.com/facebook/lexical/pull/7342
* [lexical-playground][lexical-react] Feature: Push Draggable Element to Parent by @sescandell in https://github.com/facebook/lexical/pull/7338
* [lexical-website] Docs: Fix broken links to React Rich Collab Example by @etrepum in https://github.com/facebook/lexical/pull/7347
* [lexical] Feature: Apply RootNode transforms last by @etrepum in https://github.com/facebook/lexical/pull/7344
## New Contributors
* @Zhangerr made their first contribution in https://github.com/facebook/lexical/pull/7320
* @dineug made their first contribution in https://github.com/facebook/lexical/pull/7318
* @sescandell made their first contribution in https://github.com/facebook/lexical/pull/7338
**Full Changelog**: https://github.com/facebook/lexical/compare/v0.27.2...v0.28.0