Как получить позицию текстового курсора после события нажатия клавиши?

Я пишу синтаксический ярлык. Маркер должен обновлять подсветку сразу при вводе текста и навигации с помощью клавиш со стрелками.

Проблема, с которой я сталкиваюсь, заключается в том, что при запуске события "keypress" вы все равно получаете старую позицию текстового курсора через window.getSelection().

Пример:

function handleKeyEvent(evt) {
  console.log(evt.type, window.getSelection().getRangeAt(0).startOffset);
}

var div = document.querySelector("div");
div.addEventListener("keydown", handleKeyEvent);
div.addEventListener("keypress", handleKeyEvent);
div.addEventListener("input", handleKeyEvent);
div.addEventListener("keyup", handleKeyEvent);
<div contenteditable="true">f<span class="highlight">oo</span></div>

Ответ 1

Вы можете использовать setTimeout для обработки события keydown асинхронно:

function handleKeyEvent(evt) {
    setTimeout(function () {
        console.log(evt.type, window.getSelection().getRangeAt(0).startOffset);
    }, 0);
}

var div = document.querySelector("div");
div.addEventListener("keydown", handleKeyEvent);
<div contenteditable="true">This is some text</div>

Ответ 2

Здесь решение, исправляющее позицию с использованием события "keydown":

function handleKeyEvent(evt) {
  var caretPos = window.getSelection().getRangeAt(0).startOffset;

  if (evt.type === "keydown") {
    switch(evt.key) {
      case "ArrowRight":
        if (caretPos < evt.target.innerText.length - 1) {
          caretPos++;
        }
        break;

      case "ArrowLeft":
        if (caretPos > 0) {
          caretPos--;
        }
        break;

      case "ArrowUp":
      case "Home":
        caretPos = 0;
        break;

      case "ArrowDown":
      case "End":
        caretPos = evt.target.innerText.length;
        break;

      default:
        return;
    }
  }
  console.log(caretPos);
}

var div = document.querySelector("div");
div.addEventListener("keydown", handleKeyEvent);
div.addEventListener("input", handleKeyEvent);
<div contenteditable="true">f<span class="highlight">oo</span></div>