I’m finding a similar bug to the one discussed here Native browser cursor "jumps" when there is a widget at the same position and seemingly fixed here https://github.com/ProseMirror/prosemirror/issues/710
In other words the browser cursor jumps back to just after the last dynamically added inline decoration and after that it begins to do so only from time to time.
The odd part is that it only happens when I have 2 plugins dynamically adding decorations, if I exclude any of them or simply stop adding decorations from them it just works fine.
Relevant code from the plugins:
Visible spaces:
export default new Plugin({
state: {
init(_, { doc }) {
let ranges = [[0, doc.content.size]],
spacesDecorations = getNewSpaceDecorations(ranges, doc);
return DecorationSet.create(doc, spacesDecorations);
},
apply(tr, set, _, state) {
if (tr.docChanged) {
let insertedRanges = getInsertedRanges(tr),
decorationsToAdd = getNewSpaceDecorations(insertedRanges, state.doc);
return set.map(tr.mapping, tr.doc).add(state.doc, decorationsToAdd);
}
return set;
}
},
props: {
decorations(state) { return this.getState(state); }
}
});
Custom spellchecker:
export const spellcheckPluginGenerator = (editorSpellingMistakes) => new Plugin({
state: {
init: function (_, state) { return decorateStateWithMistakes(state, editorSpellingMistakes) },
apply: function (tr, old, oldState, newState) {
let editorSpellingMistakes;
if (tr.meta.dictionaryChangeSpellingMistakes) {
editorSpellingMistakes = tr.meta.dictionaryChangeSpellingMistakes;
} else if (tr.meta.editorSpellingMistakesToAppend) {
if (!!window.chrome || window.ENV_TEST) document.getSelection().empty();
editorSpellingMistakes = [...tr.meta.editorSpellingMistakesToAppend, ...this.props.editorSpellingMistakes(oldState)];
} else {
return {
decorations: old.decorations.map(tr.mapping, tr.doc),
editorSpellingMistakes: old.editorSpellingMistakes,
};
}
return decorateStateWithMistakes(newState, editorSpellingMistakes);
}
},
props: {
decorations: function (state) {
return this.getState(state).decorations;
},
editorSpellingMistakes: function (state) {
return this.getState(state).editorSpellingMistakes;
},
}
});
function decorateStateWithMistakes(state, editorSpellingMistakes) {
let decorations = [],
rx = mistakesRegexp(editorSpellingMistakes),
{ openTag, closeTag, selfclosedTag } = state.schema.nodes,
tagTypes = [openTag, closeTag, selfclosedTag];
if (!editorSpellingMistakes.length) return { decorations: DecorationSet.empty, editorSpellingMistakes };
state.doc.descendants((node, currentPosition, parent) => {
let match;
if (!node.isText || tagTypes.includes(node.type) || tagTypes.includes(parent.type)) return;
while (match = rx.exec(node.text)) {
let from = currentPosition + match.index,
to = currentPosition + match.index + match[0].length;
decorations.push(
Decoration.inline(
from, to,
{ class: "translation_ui__form__textarea__error", "data-from": from, "data-to": to },
)
);
}
});
return {
decorations: DecorationSet.create(state.doc, decorations).map(state.tr.mapping, state.doc),
editorSpellingMistakes
};
}
This spellchecker also includes the only way I had to fix the issue. Using https://github.com/ProseMirror/prosemirror/issues/710#issuecomment-338047650 proposal:
if (!!window.chrome || window.ENV_TEST) document.getSelection().empty();