Mapping node decorations creates RangeError when deleting text

I have a plugin which adds a node decoration to every node that contains text (in the example it’s just putting a border around each node). This works completely fine, except when the decoration is within a list element and you press delete at the begging of the list item. Doing that will throw the error: RangeError: Position -1 outside of fragment

Is there something I’m doing wrong with the decorations? Or could this be a bug? I’m using the latest version of ProseMirror. The code only decorates the paragraph tags, including the paragraph tag within the list item. It doesn’t add the decoration to the list item itself.

Example of the error being triggered:

example

Simplified plugin code:


function shouldDecorateNode(node) {
    return node.isTextblock
}

function decorateNodes(nodes) {
    return nodes.map(item => {
        return Decoration.node(item.pos, item.pos + item.node.nodeSize, { class: 'test-decoration' })
    })
}

function findBlockNodes(doc) {
    const results = [];
    doc.descendants((child, pos) => {
        results.push({ node: child, pos });
    })

    return results.filter(child => child.node.isBlock);
}

function createPlugin() {
    return new Plugin({
        state: {
            init: (config, state) => {
                // Decorate matching nodes on init
                const matching = findBlockNodes(state.doc).filter(item => shouldDecorateNode(item.node))
                return DecorationSet.create(state.doc, decorateNodes(matching))
            },
            apply: (tr, decorationSet) => {
                const { doc } = tr

                // If the document hasn't changed, there's nothing to do
                if (!tr.docChanged) {
                    return decorationSet
                }

                // This is where the error happens:
                let mappedSet = decorationSet.map(tr.mapping, doc)

                const blockNodes = findBlockNodes(doc)
                const matching = blockNodes.filter(item => shouldDecorateNode(item.node))
                const otherNodes = blockNodes.filter(item => !matching.includes(item))

                // Remove decorations that are not within the correct nodes anymore
                let decorationsOld = otherNodes.map(item => mappedSet.find(item.pos, item.pos + item.node.nodeSize)).flat()
                mappedSet = mappedSet.remove(decorationsOld)

                // Decorate the matching nodes
                mappedSet = mappedSet.add(doc, decorateNodes(matching))

                return mappedSet
            },
        },
        props: {
            decorations(state) { return this.getState(state) },
        }
    })
}

I added this code to an editor and pressed delete at the beginning of list items (including removing the entire item), but did not manage to trigger any exception. Any other information I might need?

Hmm. I created a glitch project based off of the ProseMirror basic example and the error does show up. I tested it in both Chrome and Safari: https://glitch.com/edit/#!/powerful-weak-cathedral

What actions do I need to take in that demo to get the crash?

Hold on, I could reproduce the error by starting a new list and immediately pressing enter. Will debug this tomorrow.

1 Like

Click on a paragraph, and click on the bullet list icon to make it a list. Then click between the bullet and the first character of the paragraph and press delete. That should trigger the error. In my case I make the sentence “This is editable text. You can focus it and start typing.” a list item, then I put the cursor before the first letter T and press delete. The error should appear in the console, and instead of removing the list item the cursor just jumps backwards.

This patch should help here. Can you give it a try?

It seems to be working fine now with the patch. Thank you!

Thanks for the confirmation. Released as 1.23.10