Validated Input Field

An "input" that highlights invalid parts

by TigerAsks

HTML

<div class="validated-input" contenteditable="true">HAKUNAMATATA</div>

CSS

.validated-input{
    color: blue;
    white-space: nowrap;
    overflow-x: auto;
    width: 20em;
    border: solid 1px black;
}

.validated-input .error{
    color: red;
}

JavaScript

const input = document.querySelector(".validated-input")
const specialKeys = ['Enter', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'Shift', 'Alt', 'Control', 'Esc']
const invalidKeys = /[^AC-IK-NP-TVWY]/

//disallow multi-line input
input.addEventListener('keydown', e => {
  if ('Enter' === e.key) {
    e.stopPropagation()
    e.preventDefault()
    input.blur()
  }
})

//highlight invalid characters on keyup
input.addEventListener('keyup', e => {
  if (!specialKeys.includes(e.key)) {
    e.stopPropagation()
    validateInput()
  }
})

function validateInput() {
  const cursorPosition = getCursorPositionWithin(input)
  console.log(cursorPosition)
  const value = input.innerText.toUpperCase()
  input.innerHTML = validate(value)
  setCursorPositionWithin(input, cursorPosition)
}

function validate(value) {
  console.log(value)
  if (!value) return ''

  value = [...value]
  let formatted = ''
  for (start = 0; start < value.length;) {
    if (!invalidKeys.test(value[start])) {
      formatted += value[start++]
      continue
    }
    let stop = start
    while (++stop < value.length && invalidKeys.test(value[stop])) {}
    let invalids = value.slice(start, stop).join('')
    formatted += `<span class="error">${invalids}</span>`
    start = stop
  }
  console.log(formatted)

  return formatted
}

function getCursorPositionWithin(element) {
  //credit: https://stackoverflow.com/a/4812022/3322533
  const doc = element.ownerDocument || element.document;
  const win = doc.defaultView || doc.parentWindow;
  let caretOffset = 0;
  let sel;
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      let range = win.getSelection().getRangeAt(0);
      let preCaretRange = range.cloneRange();
      console.log(range)
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  }...