Is there a way to listen to a node selection change?

I have a properties editor that allows displaying and changing the attributes of a given, selected node. This is a seperate ‘panel’ which I effectively only want to to (re)draw whenever:

  • a different node gets selected in the editor
  • the selected node’s attribute are changed

Thoughts?

The plugin view can satisfy your need.

Here are some rough code. The detail need you to sculpt it.

const pluginKey = new PluginKey('plugin_my-panel');

new Plugin<HTMLElement>({
  key: pluginKey,
  state: {
    init() { return null; },
    apply(tr, value) {
      if (!value) return tr.getMeta(pluginKey);
      return value;
    },
  },
  props: {
    handleDOMEvents: {
      // sometimes you may need determine whether re-render the panel through event
      mousedown() {return false;},
      keyup() {return false;},
    },
  },
  view(view) {
    const panel = document.createElement('div');
    setTimeout(() => view.dispatch(view.state.tr.setMeta(pluginKey, panel)));
    return {
      update(view, prevState) {
        // note: this method will be called every time when the selection or doc of the editor changed
        // so you need to write your own logic to determine when re-render the panel
        renderPanel(view);
      },
    };
  },
});

function renderPanel(view: EditorView) {
  const { state } = view;
  const panel = pluginKey.getState(state);
  // find your target node
  const node = findTargetNode();
  panel.innerHTML = JSON.stringify(node.attrs)
}

function findTargetNode(){ ... }

And, you can check the official tooltip example. ProseMirror tooltip example

Thank you for your input.

I was actually thinking of using a custom event to indicate when the property panel needs to be redrawn.

My UI looks a bit like this:

|---------------------------------------------------|
|0. Toolbar                                         |
|---------------------------------------------------|
|1. Editor Panel              |2. Properties.       |
|                             |                     |
|                             |                     |
|-----------------------------|---------------------|

0, 1 and 2 are all Vue3 components that are rooted in a container Vue3 application shell. Following your line of thought of using a plugin; would it be possible for that plugin to emit a custom DOM event when redraw is required? The application shell would then listen for that event and delegate it to observers.

O, 1 and 2 are sibling components - and I don’t want to add any additional state mgmt library for event distribution.

Thanks.

As your latest description, I think you may needn’t the plugin view.

// EditorComponent
// The event bus is Vue's event bus, like their document says.
import vueEventBus from './xxx/event-bus.ts';

const view = new EditorView(target, {
  // ...
  dispatchTransaction(tr: Transaction) {
    const newState = view.state.apply(tr);
    view.updateState(newState);

    // write your logic to decide whether emit the custom event
    if(nodeChange) vueEventBus.$emit('selectedNodeChange', payload)
    if(attributesChange) vueEventBus.$emit('attributesChange', payload)
  };
})

// PanelComponent
// I don't know vue3's grammar, below I use vue2's.
import vueEventBus from './xxx/event-bus.ts';

export default {
  data(){return {}},
  created(){
    vueEventBus.$on('selectedNodeChange', ()=>{
      // do something
    })

    vueEventBus.$on('attributesChange', ()=>{
      // do something
    })
  }
  // ...
}

I think re-read the document of ProseMirror’s data flow is helpful for you.

ProseMirror Guide (check the Data Flow section)

1 Like

As I’m using tiptap I did something like this:

editor.on('update', () => (nodesChanged.value = true));
editor.on('selectionUpdate', () => {
  const uid = findParentNode(() => true)(editor.view.state.selection)?.node.attrs.uid as string;

  if (lastStoredUID.value != uid) {
    nodesChanged.value = true;
    lastStoredUID.value = uid;
  } else {
    nodesChanged.value = false;
  }
});

With this and the UniqueID plugin of tiptap UniqueID – Tiptap Editor, I could effectively determine when the properties editor needed to be redrawn (i.e. set nodesChanged = true and a watch on that property, to recalculate stuff)