[lexical-list] Fix: create copies ListNode/ListItemNode in split-like operations#8213
[lexical-list] Fix: create copies ListNode/ListItemNode in split-like operations#8213etrepum merged 8 commits intofacebook:mainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Using $copyNode is probably better than accessing the constructor directly |
|
Should also have unit tests that demonstrate that the change allows for the kind of functionality you’re trying to add |
Initially, I tried using |
|
You'll have to be more specific about "saving of the list markers to break". Not quite sure what that means, it should just re-use the same style of list marker when this copy is made? If the markdown export doesn't check for mismatched list markers then that should be fixed. There are some potential pitfalls with a full |
|
That doesn’t seem broken to use the same marker for an indented list? Either way you’re either verbosely copying state from one node to the next or verbosely resetting it. With either approach, using a method would allow this to be isolated and overridden if necessary. I don’t think adding direct constructor calls all over the place is a good idea, a few strategic places that are easy to find and maintain is ok. |
I agree. If you need to avoid using a constructor, I'd suggest creating a helper function that hides the boilerplate code for resetting properties and state function $spawnListItemNode(node) {
const copy = $copyNode(node).setValue(1).setChecked(undefined);
copy.getWritable().__state = undefined;
return copy;
} |
|
I do not recommend resetting NodeState in this way. |
But are there other ways? $setState assumes an explicit prodvide of a known state I looked at the internal available state API and maybe this approach is acceptable? const state = $getWritableNodeState(node);
state.knownState.forEach((_, stateConfig) => {
state.updateFromKnown(stateConfig, null);
});I would also suggest using the |
|
You’re misunderstanding me here, using the constructor is ok, but don’t spread it all over the code base. Put it in a method so it is easy to find for maintenance and can be overridden. As for NodeState it shouldn’t be cleared at all, if specific properties should be omitted in this kind of copy-as-template then that’s functionality we’d want to add to the NodeState configuration and support it with some kind of copyNode-like functionality. |
0fc3f26 to
09213aa
Compare
create() factory method for ElementNode and use it for List split-like operations
850a632 to
3ad514c
Compare
3ad514c to
74ce967
Compare
43cbc5d to
46acb61
Compare
|
Let's update the PR description to be a little more explicit about what this change is and how it affects various list operations before merge, this will help with the release notes |
done |

Description
ListNodeandListItemNodeinternally create new list structures during operations such as splitting lists, indenting/outdenting. Previously, these operations relied on factory functions ($createListNode/$createListItemNode).This caused issues when users extended ListNode or ListItemNode. Even if an editor contained custom subclasses, split-like operations would always create base ListNode / ListItemNode instances, breaking type consistency.
This change replaces factory-based creation with
$copyNode, ensuring that new nodes are created from the same class as the source node.Screen.Recording.2026-03-12.at.03.50.33.mov
https://codesandbox.io/p/sandbox/lexical-extended-list-rgrcq9
Test plan
Before
Split-like operations (list split, indent, outdent) always created base instances ListNode and ListItemNode. Even if the original list used subclasses.
After
Instances of the same class as the source node are now created using
$copyNodein the following cases:splicemethodThis allows you to save custom
ListNodeandListItemNodesubclasses.