Skip to content

Instantly share code, notes, and snippets.

@ondras
Last active November 25, 2022 17:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ondras/4fc21274761b5d6c5f4759a24ebbf279 to your computer and use it in GitHub Desktop.
Save ondras/4fc21274761b5d6c5f4759a24ebbf279 to your computer and use it in GitHub Desktop.
function updateButtons(node, shift) {
[...node.querySelectorAll("button")].forEach(button => {
let value = button.dataset.value;
switch (value) {
case "backspace": button.textContent = "←"; break;
case "shift": button.textContent = "↑"; break;
case " ": button.textContent = "(space)"; break;
default: button.dataset.value = button.textContent = (shift ? value.toUpperCase() : value.toLowerCase()); break;
}
});
}
function buildButton(value) {
let button = document.createElement("button");
button.style = `
all: revert;
flex: 1 1 0;
margin: 0 2px;
font-size: 26px;
`;
button.dataset.value = value;
return button;
}
function buildRow(values, width) {
let row = document.createElement("div");
row.style = `
all: revert;
display: flex;
width: ${width*100}%;
margin: 3px auto;
`;
values.map(buildButton).forEach(b => row.appendChild(b));
return row;
}
function onClick(value) {
const input = state.input;
if (!input) { return; }
let start = input.selectionStart;
let end = input.selectionEnd;
let content = input.value.split("");
switch (value) {
case "shift":
state.shift = !state.shift;
updateButtons(state.node, state.shift);
return;
break;
case "backspace":
if (start == end) {
if (start == 0) { return; }
start -= 1;
content.splice(start, 1)
} else {
content.splice(start, end-start);
}
input.value = content.join("");
input.selectionStart = state.input.selectionEnd = start;
break;
default:
if (start != end) { content.splice(start, end-start); }
content.splice(start, 0, value);
input.value = content.join("");
input.selectionStart = state.input.selectionEnd = start+1;
break;
}
input.dispatchEvent(new CustomEvent("input"));
}
function build() {
let node = document.createElement("div");
node.style = `
all: revert;
z-index: 100;
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.85);
`;
node.appendChild(buildRow([..."1234567890"], 1));
node.appendChild(buildRow([..."qwertyuiop"], 1));
node.appendChild(buildRow([..."asdfghjkl"], 9/10));
node.appendChild(buildRow(["shift", ..."zxcvbnm", "backspace"], 9/10));
node.appendChild(buildRow([" "], 0.5));
node.addEventListener("mousedown", e => e.preventDefault()); // blur prevention
node.addEventListener("click", e => {
let value = e.target.dataset.value;
value && onClick(value);
});
return node;
}
let state = {
node: build(),
input: null,
shift: false
}
function isAllowed(input) {
if (input.nodeName.toLowerCase() == "textarea") { return true; }
if (input.nodeName.toLowerCase() == "input") { return ["text", "password"].includes(input.type); }
return false;
}
function activate(input) {
if (!isAllowed(input)) { return; }
document.body.appendChild(state.node);
state.input = input;
}
function deactivate() {
const input = state.input;
if (input) {
state.input.dispatchEvent(new CustomEvent("change"));
state.input = null;
}
state.node.remove();
}
function init() {
updateButtons(state.node, state.shift);
document.addEventListener("focus", e => activate(e.target), true);
document.addEventListener("blur", e => deactivate(), true);
}
init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment