Dispatch changes without redrawing the widget

This is a follow up question to nested CM editors, where a new instance of CM6 is embedded into replacing decoration.

How to mutate the data without recreating the widget?
For example I have

class Widget extends WidgetType {
  constructor(visibleValue) {
    super();
    this.visibleValue = visibleValue;
    this.subEditor = subEditor;
  }
  eq(other) {
    return this.visibleValue.str === other.visibleValue.str;
  }
  toDOM(view) {
    let span = document.createElement("span");

    const visibleValue = this.visibleValue;
    // create a sub-editor, which will mutate the data underneath
    this.subEditor({
      doc: this.visibleValue.str,
      parent: span,
      update: (upd) => {
        const change = {from: visibleValue.pos, to: visibleValue.pos + visibleValue.length, insert: upd}
        console.log('insert change');
        console.log(change);
        view.dispatch({changes: change});
      }
    });

    return span;
  }

where subEditor is just a

const subEditor = (p) => new EditorView({
    doc: p.doc,
    extensions: [
      minimalSetup,
      EditorView.updateListener.of((v) => {
        if (v.docChanged) {
          p.update(v.state.doc.toString());
        }
      }),
    ],
    
    parent: p.parent
  });

Here one child editor mutate the data, which is covered by the replacement decoration (atomic). However, it causes each time dispatch, that destroys everything and creates again.

I was wondering, if there is a way to mutate the text underneath without redrawing, but just reassigning the widget range to a new one (if a new text is larger or shorter).

Have you tried defining a WidgetType.updateDOM method?

Yes. I added it to return true to the widget class, but looking back to console log output, it has no effect.

PS: I guess the reason why is that eq method return false, since the inner content was changed, right? Yeah, since there is no unique identifier for this widget anymore, CM cannot decide if it is the same widget or not.

updateDOM should be called if a widget of the same type (but non-eq) appears in the same position in the document. You can see it in action here.

Thanks! Did not know you can easily code via Try CM page.
I think I got the idea, so update method works great if we keep the same length. Here is an example

  1. If one replaces just one character inside the nested editor : updateDOM
  2. if one writes or deletes characters - the length is different : toDOM

I do not know, if it is actually possible in CM6 system to handle the second case as if it was the 1st one. Looks like I need to perform some low-level dispatch and manually perform all transitions to prevent constructor of a widget to be called.

Ok. I have a feeling I am very close to the solution.
If I turn eq(other) method to return always true, then the editor does not update the DOM element.

Now I am trying to update the field, which eq method compares to the others. However the line 19 seems not able to mutate the widgets properties like

this.str = value //not working ;(

where

eq: (other) => {other.str === this.str} //remains the same

That isn’t how eq is supposed to be used, and will certainly cause issues.

I don’t think there was a good reason for the restriction that length must stay the same to allow in-place updating. This patch removes it.

1 Like

I did not expect that such a complicated thing can be done so easily. Many thanks to you, @marijn !

Screenshot 2023-04-21 at 11.08.22

3 levels of CM6 is working! ;D