Override selection on tap

I’m trying to select entire words when a user taps on one but my changes end up getting overriden by the native selection logic. Here’s the code I’m using to set it up:

const tapThreshold = 200; // milliseconds
const moveThreshold = 25; // pixels

export const onTap = (callback: OnTapCallback): Extension => {
  let touchStartX = 0;
  let touchStartY = 0;
  let startTime = 0;

  return EditorView.domEventHandlers({
    touchstart: (event) => {
      touchStartX = event.touches[0].clientX;
      touchStartY = event.touches[0].clientY;
      startTime = Date.now();
    },
    touchend: (event, view) => {
      const touchEndX = event.changedTouches[0].clientX;
      const touchEndY = event.changedTouches[0].clientY;
      const endTime = Date.now();

      const duration = endTime - startTime;

      if (duration > tapThreshold) { return; }

      const deltaX = touchEndX - touchStartX;
      const deltaY = touchEndY - touchStartY;
      const moveDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

      if (moveDistance > moveThreshold) { return; }

      callback(view.posAtCoords({ x: touchEndX, y: touchEndY }));
    },
  });
};

// extension on editor state
      onTap((position) => {
        if (!position) { return; }

        const word = editor.state.wordAt(position);

        if (!word) { return; }

        editor.dispatch({
          selection: { anchor: word.from, head: word.to },
        });
      }),

Is there another way I should be doing this or a way I can prevent the two selection methods from fighting? I tried using a transactionFilter but I only want this behavior on tap so it turned out being pretty complex to try and do it that way.

Does returning true at the end of the handler help?

Still doesn’t help. I can tell it’s being overriden because sometimes I see my custom selection happen for 1 frame and then it goes away.

By calling event.preventDefault() in the touchend handler it works only when the editor is not focused, but still being overriden when the editor has focus.

This issue also only seems to happen on WebKit. On Chrome using devtools to simulate touches it is working as expected when calling event.preventDefault()

Also seems to be working on Safari on mac, but not on iOS.

Text editing in mobile Safari is unfortunately quite a buggy mess. I don’t have an iOS device handy to test right now, but it sounds like it unconditionally makes the default tap behavior go through regardless of preventDefault. It may be possible to work around this by delaying your dispatch until after the native selection change with setTimeout, maybe.