Wrong state in `handleTextInput` when using macOS text replacement

Hello there :wave:

We are having an issue with the built-in input rules in Tiptap when used with the macOS text replacement feature. Tiptap input rules use handleTextInput and I’ve recreated the most simple CodeSandbox I could without using Tiptap to demonstrate the issue:

As a baseline example, the CodeSandbox above does this:

firefox_EimyPfYgjO

When the Markdown syntax for bold is typed, the handleTextInput handler will take care of deleting the starting and ending **, and apply the strong mark to the remaining text. This is all working fine, but the problem exists when using macOS text replacements where the replacement includes Markdown bold syntax:

211121196-5f67115e-6c61-4f50-9e24-a998f5c0b9cb

In this case, this is what happens:

firefox_mWOeWgSH61

Because we are suppressing the handleTextInput default behaviour, the replacement text is not actually being inserted into the editor, and both the range and the textContent state reflect that (the current content is [crd] - see the logs), while the delete and addMark calls are being called with the expectation that the editor state contains **[CARD]**, and boom!

Preventing the error can be achieved with:

if (text === textBefore) {
    return;
}

And from my testing, this doesn’t cause additional issues. However, the **[CARD]** text will remain in the editor, but it will not be handled as expected (i.e. remove **, and add the strong mark to the rest of the text).

Not sure I’m seeing how this can be fixed, any suggestions?

The issue in that code seems to be passing bad numbers to addMark (you have to work with positions in the document that has the changes you made in the transaction already applied, not pre-transaction positions), and not something that’s wrong with ProseMirror, unless I misunderstand things.

I never meant to imply that there was something wrong with ProseMirror, apologies if I gave that impression.


After spending some more time on this, I guess what I was looking for was a handleTextAfterInput handler, so that I could do all the processing required after the user directly inputs text.

My knowledge is limited, but I kind of have the feeling that there’s no other way. I mean, when the user clicks the text replacement from the macOS popover, the editor state is [crd, and handleTextInput will be called with text: "**[CARD]**". At this point, what I want to do is:

  • Take the replacement text (i.e. **[CARD]**), process it so that the ** are removed, and the bold mark is added to the rest of the text
  • Replace the [crd in the editor state with the processed data from the previous point

The problem is that I don’t know (and I can’t think of a reliable way to know) that the text variable in handleTextAfterInput came from a macOS text replacement, or something else, so that I can act accordingly.

That said, and like I mentioned at the beginning, the best way I can think of to fix this is to do what I need after the user directly inputs text, more specifically after this line is executed.


Anyway, for a solution, I replace the handleTextInput with handleDOMEvents, and I’m using the keyup event to do all the processing that I need. I had to adjust the calculation of some from and to positions, but this seems to be working. I’ve updated the CodeSandbox with this new approach, if you want to have a look.

But I’m wondering, can you think of a different/better solution that could keep using handlTextInput? I’m asking because making my change on Tiptap will involve a bit of a refactor, that I’m not quite the maintainers will be optimistic about. Only because it’s possible that it might introduce new issues.