Widget Decoration Cursor Skip

Implementing a diffs decoration plugin; for deletions, I’m using a widget decoration like so:

function deletionWidget(fragment: Fragment, pos: number, attrs: DecorationAttrs = {}) {
  return Decoration.widget(pos, 
    function toDOM(view) {
      const span = document.createElement('span')
      span.className = 'ProseMirror-diff-deletion' + ' ' + attrs.class;
      span.contentEditable = "" + false;
      span.style.display = 'inline-block';
  
      // Create zero-width space nodes
      const zwsBefore = document.createTextNode('\u200B');
      const zwsAfter = document.createTextNode('\u200B');

      const serializer = DOMSerializer.fromSchema(view.state.schema);
      const htmlFragment = serializer.serializeFragment(fragment);
  
      // Append the zero-width space and the fragment
      span.appendChild(zwsBefore);
      span.appendChild(htmlFragment);
      span.appendChild(zwsAfter);
      return span;
    },
    { side: -1, ignoreSelection: false }
  )
}

I’m noticing there’s a weird cursor problem, with it being impossible to put an empty text selection on the left side of the widget even if it’s not a white space.

CleanShot 2024-10-17 at 15.42.06

Is there any modifications to the spec or widget to get around this behavior?

Widgets do not produce additional cursor positions. Their side determines whether they are (always) before the cursor at their position or (always) after it. Because cursor positions are stored with document positions, and widgets produce no new document positions, ProseMirror can only represent one type of cursor at a given widget’s position.

1 Like

I bumped into this problem when I tried to implement automatic delimiters (quotes, guillemots, etc.) around a Mark representing inline quotations; think of the <q> tag in HTML (I was actually working on the Quoted Inline element in Pandoc).

I started with decorations, first with inline ones, using CSS ::before and ::after;

then with widget decorations, but eventually I wrote a plugin that adds quotes as leaf nodes around quotations, just to have both cursor positions around delimiters.

When saving the content, I just ignore those nodes, while I reconstruct them during loading.

The hard part has been writing the appendTransaction function in the plugin, that fixes the delimiters, when the user input affects quotations.

1 Like

Widget decorations for diff deletions work well if the editor is read-only.

For Google Docs style of suggesting where diffs are editable and shown inline while editor is live, solution seems to be a plugin that annotates changes with marks, and keeps deleted content in the doc with appendTransaction.

Didn’t realize the gap in difficulty between VSCode style diffs where deletions aren’t editable, and Google Docs style diffs where they are :sweat_smile: