|
<html> |
|
<head> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" /> |
|
</head> |
|
|
|
<body> |
|
|
|
<div class="wrap"> |
|
<input inputmode="decimal" type="number" maxlength="1" /> |
|
<input inputmode="decimal" type="number" maxlength="1" /> |
|
<input inputmode="decimal" type="number" maxlength="1" /> |
|
<input inputmode="decimal" type="number" maxlength="1" /> |
|
<input inputmode="decimal" type="number" maxlength="1" /> |
|
<input inputmode="decimal" type="number" maxlength="1" /> |
|
</div> |
|
|
|
<script> |
|
const inputs = [...document.querySelectorAll('input')]; |
|
|
|
let previousKey = ''; |
|
|
|
function isNumber(value) { |
|
return typeof value === 'number' && !isNaN(value); |
|
} |
|
|
|
function insertNumber(i, pastedData) { |
|
if (!isNumber(parseInt(pastedData))) { |
|
return; |
|
} |
|
|
|
let count = i; |
|
|
|
while (count < inputs.length && count - i < pastedData.length) { |
|
inputs[count].value = pastedData[count - i]; |
|
count++; |
|
} |
|
} |
|
|
|
inputs.forEach((input, i) => { |
|
input.addEventListener('keydown', function(e) { |
|
// if coming from predictive texting, let the input event handle it. |
|
if (!event.isTrusted) { |
|
return; |
|
} |
|
|
|
const tryingToPaste = previousKey === 'Meta' && e.key === 'v'; |
|
|
|
if (e.key !== 'Tab' && !tryingToPaste) { |
|
e.preventDefault(); |
|
} |
|
|
|
previousKey = e.key; |
|
|
|
if (e.key === 'Backspace') { |
|
if (input.value) { |
|
input.value = ''; |
|
} else if (i > 0) { |
|
inputs[i - 1].focus(); |
|
inputs[i - 1].value = ''; |
|
} |
|
} else if (e.repeat) { |
|
return; |
|
} |
|
|
|
const value = parseInt(e.key); |
|
|
|
if (isNumber(value)) { |
|
input.value = e.key; |
|
|
|
if (i + 1 < inputs.length) { |
|
inputs[i + 1].focus(); |
|
} else { |
|
input.select(); |
|
} |
|
} |
|
}); |
|
|
|
input.addEventListener('focus', function(e) { |
|
input.select(); |
|
index = i; |
|
}); |
|
|
|
input.addEventListener('paste', function(e) { |
|
e.preventDefault(); |
|
const clipboardData = e.clipboardData || window.clipboardData; |
|
|
|
insertNumber(i, clipboardData.getData('text')); |
|
}); |
|
|
|
// Handle predictive text on mobile. Because we preventDefault() onkeydown, mobile predictive text should |
|
// be the only thing causing the input event to occur |
|
input.addEventListener('input', function(event) { |
|
// On a physical iPhone, the input event occurs once for every digit in the predictive text, so this will |
|
// fire a total of 6 times before this conditional is true. On an iOS simulator in contrast, only a single |
|
// input event fires when tapping a predictive text, and in that scenario this conditional is not necessary. |
|
if (event.target.value.length === inputs.length) { |
|
insertNumber(i, event.target.value); |
|
} |
|
}); |
|
}); |
|
</script> |
|
|
|
<style> |
|
.wrap { |
|
align-items: center; |
|
display: flex; |
|
gap: 5px; |
|
justify-content: center; |
|
margin: 200px auto 0; |
|
} |
|
|
|
input { |
|
border-radius: 10px; |
|
border: 1px solid #ddd; |
|
font-size: 20px; |
|
height: 50px; |
|
text-align: center; |
|
width: 50px; |
|
-moz-appearance: textfield; |
|
} |
|
|
|
input::-webkit-outer-spin-button, |
|
input::-webkit-inner-spin-button { |
|
margin: 0; |
|
-webkit-appearance: none; |
|
} |
|
</style> |
|
|
|
</body> |
|
</html> |