Dispatch SideEffect on focus change

Here’s what I’m trying to accomplish:

  1. Given a starting value (e.g {{ }}), when editor is focused I want to create a transaction of selection: { anchor: 4 }
  2. Having created the above transaction, I want to run the startCompletion command.

However I am having a hard time trying to trigger both a transaction and a subsequent command.


Here’s what I’ve tried:

Transaction Filter

const startFocusEffect = StateEffect.define<boolean>();
const focusExtension = EditorView.focusChangeEffect.of((state, focus) => {
  if (focus) {
    return startFocusEffect.of(focus);
  }
  return null;
});

const setSelectionExtension = EditorState.transactionFilter.of((transaction) => {
  const transactions = [transaction]
  for (const effect of transaction.effects) {
    if (effect.is(startFocusEffect)) {
      if (transaction.state.doc.toString().trim() === '{{  }}') {
        transactions.push({
          selection: {
            anchor: 3,
          }
        })
      }
    }
  }
  return transactions;
});

I can use the EditorView.focusChangeEffect to determine the focus state of the Editor, and then use the EditorState.transactionFilter to include the transaction based certain conditionals.

However this leaves me with the startCompletion command. The focusChangeEffect facet expects me to return a state effect. startCompletion internally dispatches a state effect,

view.dispatch({ effects: startCompletionEffect.of(true) });

but because it takes view as an argument (and it doesn’t return the state effect), I’m not able to use it within the focusChangeEffect facet as that facet only has access to the state, not the view.

ViewPlugin Approach

function openCompletionOnFocus() {
  return ViewPlugin.fromClass(
    class {
      update(update) {
        if (
          update.focusChanged &&
          update.view.hasFocus &&
          update.state.doc.toString().trim() === "{{ }}"
        ) {
          const transactions = [{selection: {anchor: 4}} ];
         // state update error
          update.view.dispatch(...transactions);
          startCompletion(update.view);
        }
      }
    },
  );
}

The other direction is trying to incorporate this within a ViewPlugin. Although this gives access ViewUpdate class and to the EditorView class, I get the same error as above.


Taking inspiration from this previous post: Trigger a StateEffect when focus changes I could try the setTimeout approach, but this feels like the wrong way of approaching this.

Are there any other approaches that I’m not seeing? I’ve looked through the docs in hopes that I can dispatch side-effects based on a focus change, but coming up short. Thank you in advance.

You definitely shouldn’t do imperative things like executing commands from transaction filters—those are allowed to adjust the transaction, and nothing else. I don’t think you need focusChangeEffects here, a view plugin (or even update listener) that checks for focus changes and schedules the command to be executed might work.