Search code examples
javascriptfocuscursorvirtual-keyboard

Virtual keyboard - wrong cursorPosition when refocussing input


I'm planning a virtual keyboard. It works for simple input, but when I repeat the typing in a setInterval it only works without refocussing on the input-element, otherwise the cursor-position jumps. If anybody spots the issue (I assume wrong sequence at any point....)

const keyboard = document.querySelector(".keyboard");
const validInputEls = [HTMLInputElement, HTMLTextAreaElement];

// variables and flags
let lastFocusedInput, isTyping, typingInterval, typingTimeout;

// get key/value handle exceptions
//(currently with warnings and early returns)
function handleKeyboard(event) {
    const key = event.target.closest(".key");
    if (!key) return;

    if (!lastFocusedInput) {
        console.warn("Please first choose a target for your input!");
        return;
    }

    if (!isTyping) {
        // start typing in interval here
        isTyping = true;
        let char = key.dataset.value;
        char = char === "Space" ? "\xa0" : char; // doesn't print " "

        if (!char) {
            console.warn(`Currently no action associated with ${key.innerText}-key.`);
            return;
        }
        type(char);
        typingTimeout = setTimeout(() => {
            typingInterval = setInterval(() => type(char), 150);
        }, 500);
    }
}

function type(char) {
    const cursorPosition = lastFocusedInput.selectionStart;
    const text = lastFocusedInput.value;

    const newValue =
        text.substring(0, cursorPosition) + char + text.substring(cursorPosition);
    lastFocusedInput.value = newValue;
    const newCursorPosition = cursorPosition + 1;

    lastFocusedInput.setSelectionRange(newCursorPosition, newCursorPosition);
}

function stopTyping() {
    clearTimeout(typingTimeout); // Cancel the typing timeout
    clearInterval(typingInterval);
    isTyping = false;
}

// set focused if valid
document.addEventListener("focusin", (event) => {
    if (!validInputEls.includes(event.target.constructor)) return;
    lastFocusedInput = event.target;
});

keyboard.addEventListener("mousedown", handleKeyboard);
keyboard.addEventListener("mouseup", stopTyping);
keyboard.addEventListener("mouseleave", stopTyping);

a working example is here: https://codepen.io/BarbWire/pen/rNogpOW


Solution

  • I found the focus problem: Instead of resetting the focus I need to set event.preventDefault() in the handle