textNodeLookup = (commonContainer, node, offset, direction = "forward") => {
  const startNode = node === commonContainer ? node.childNodes[offset] : node;

  if (isTextNode(startNode) && !direction.endsWith("next")) return startNode;

  const walker = commonContainer.ownerDocument.createTreeWalker(commonContainer, NodeFilter.SHOW_ALL);
  let currentNode = walker.nextNode();
  // tree walker can't go backward, so we go forward to startNode and record every text node
  // to find the last one before startNode
  let lastTextNode;

  while (currentNode && currentNode !== startNode) {
    if (isTextNode(currentNode)) lastTextNode = currentNode;
    currentNode = walker.nextNode();
  }

  if (currentNode && direction.startsWith("backward")) return lastTextNode;

  if (direction === "forward-next") currentNode = walker.nextNode();

  while (currentNode) {
    if (isTextNode(currentNode)) return currentNode;
    currentNode = walker.nextNode();
  }
}
Lookup closest text node
@param {HTMLElement} commonContainer
@param {HTMLElement} node
@param {number} offset
@param {string} direction forward, backward, forward-next, backward-next
                          "-next" when we need to skip node if it's a text node