Proposal: Add `AttributeSpec.splittable`

By default, pressing Enter at the end of a text block creates a new paragraph without inheriting any attributes from the previous block. This behavior can be problematic for attributes like text-align, which ideally should be carried over to the new paragraph.

Similarly, when Enter is pressed in the middle of a checked todo item, it splits into two checked items. A more intuitive behavior would be for the new item to be unchecked, meaning it should not inherit the checked attribute.

To address these issues, we could override the default Enter key function with splitBlockAs, using a custom callback to control attribute inheritance. However, this solution can be cumbersome if the goal is simply to make text-align inheritable and checked not inheritable.

A proposed solution is to introduce a splittable?: boolean property to AttributeSpec and Attribute. This property could be undefined (default), true, or false. If true, the attribute would be inherited by default when a new paragraph is created upon pressing Enter. If false, the attribute would reset to its default value in the new split node.

I can submit pull requests for this feature if it seems beneficial.

Relate discussions on attribute inheritance when splitting block.


What actually happens when you run splitBlock at the end of a textblock is that a new block of the default type is created with its default attributes. I.e. if you press enter at the end of a heading, you’ll get a paragraph. As such, I don’t think the system you are proposing here will support the behavior (preserving some attributes across enter) you are looking for—even if both headings and paragraphs have an align attribute, they aren’t the same attribute, so it won’t be inherited between them.

Likely, the solution here is to use splitBlockAs with your own custom logic to implement the attribute inheritance you’re looking for.

Sorry for not being clear from the start. I do want to inherit attributes like text-align between different node (e.g., heading and paragraph), if they share the same attribute name and both have splittable: true.

This does pose a risk since attributes with the same name might have different meanings and types (e.g., one boolean, one string). We could address this by stating in the splittable property documentation that attributes with splittable: true must use the same type across different nodes in the whole schema. In another word, they are “shareable” attribute (This can be an alternative name of “splittable”).

@marijn Hi. I’ve released a standalone package called prosemirror-splittable to implement this idea. It seems to work well.

However, during the development, I found that the splitNoteAs callback doesn’t have information about the new block type (i.e. the default type), which is needed to determine what attributes should be inherited. I had to fork splitNoteAs to add this parameter myself. Do you think it makes sense to add this parameter to the prosemirror-commands package?

Are you not able to use the same trick that splitBlockAs uses (scan the edges from a content match for a textblock type) in your code?

The splitBlockAs from prosemirror-commands doesn’t provide $from to the callback argument. It seems that I’m not able to reliably get the default text block type, as splitBlockAs does (using defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1))) ).

If I don’t directly use splitBlockAs from prosemirror-commands and instead copy its code into my own project, I can use same trick of course, which is what I’m doing right now. Copying code make senses to me if we want to keep a simple core API.

That’s a good point. But the default type may be null, for example when no textblock type with defaultable attributes is allowed at this position, in which case your logic might still want to look for a block type. This seems messy. Would passing $from as 3rd argument to the splitNode callback be a workable solution for you?

Passing $from as 3rd argument to the splitNode callback works for me.

Done in this patch.

It works well! Thank you for the help!