const inputFields = document.getElementsByTagName('input') const replacements = { a: "b" } // Create a simple keyboard const buttonA = document.createElement('button') buttonA.textContent = 'Insert "a"' const buttonB = document.createElement('button') buttonB.textContent = 'Insert "b"' const keyboard = document.createElement('div') keyboard.appendChild(buttonA) keyboard.appendChild(buttonB) let selectedInput = null addChar = (i, char) => { const start = i.selectionStart i.value = i.value.slice(0, start) + char + i.value.slice(i.selectionEnd) // Updating the input value moves the caret to the end by default i.selectionStart = i.selectionEnd = start + 1 } for (const input of inputFields) { input.onkeydown = e => { const character = replacements[e.key] if (!character) return addChar(input, character) return false } // Handle the case where the input is already focused if (document.activeElement === input) { input.parentElement.appendChild(keyboard) selectedInput = input } input.onfocus = e => { input.parentElement.appendChild(keyboard) selectedInput = input } // https://css-tricks.com/a-css-approach-to-trap-focus-inside-of-an-element/ input.ontransitionend = e => { if (input.matches(':focus') || keyboard.contains(document.activeElement)) e.target.focus() else { input.parentElement.removeChild(keyboard) selectedInput = null } } } keyboard.onclick = e => { return false } buttonA.onmouseup = e => { if (selectedInput) addChar(selectedInput, "a") } buttonB.onmouseup = e => { if (selectedInput) addChar(selectedInput, "b") }