Skip to content

Instantly share code, notes, and snippets.

@delaneyj
Created January 24, 2026 21:26
Show Gist options
  • Select an option

  • Save delaneyj/be7c37fa7c3f4f14dbc8a53e4b381b28 to your computer and use it in GitHub Desktop.

Select an option

Save delaneyj/be7c37fa7c3f4f14dbc8a53e4b381b28 to your computer and use it in GitHub Desktop.
<div
id="demo"
data-signals="{ values: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 } }"
data-computed:bar="Object.values($values).join('')"
data-on-signal-patch="@post('/examples/one_time_passcode')"
>
<h3>OTP Input:</h3>
<div
data-on:keydown__prevent="
const input = event.target;
if (!input || input.tagName !== 'INPUT' || event.ctrlKey || event.metaKey || event.altKey) return;
const isDigit = /^[0-9]$/.test(event.key);
if (!isDigit && event.key !== 'Backspace' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && event.key !== 'Tab') return;
const inputs = Array.from(input.parentNode.children).filter((child) => child.tagName === 'INPUT');
const index = inputs.indexOf(input);
if (event.key === 'ArrowLeft') {
const prevInput = input.previousElementSibling;
if (prevInput && prevInput.tagName === 'INPUT') prevInput.focus();
} else if (event.key === 'ArrowRight') {
const nextInput = input.nextElementSibling;
if (nextInput && nextInput.tagName === 'INPUT') nextInput.focus();
} else if (event.key === 'Tab') {
const focusables = Array.from(document.querySelectorAll(
'a[href], button, input, select, textarea, details, [tabindex]:not([tabindex=\'-1\'])'
)).filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
const currentIndex = focusables.indexOf(input);
const next = focusables[currentIndex + (event.shiftKey ? -1 : 1)];
if (next) next.focus();
} else if (event.key === 'Backspace') {
input.value = '';
$values[index] = 0;
const prevInput = input.previousElementSibling;
if (prevInput && prevInput.tagName === 'INPUT') prevInput.focus();
} else if (isDigit) {
input.value = event.key;
$values[index] = Number(event.key);
const nextInput = input.nextElementSibling;
if (nextInput && nextInput.tagName === 'INPUT') {
nextInput.value = '';
nextInput.focus();
}
}
"
data-on:paste__prevent="
const input = event.target;
if (!input || input.tagName !== 'INPUT') return;
const pasteData = (event.clipboardData || window.clipboardData).getData('text');
if (!/^\d+$/.test(pasteData)) return;
const inputs = Array.from(input.parentNode.children).filter((child) => child.tagName === 'INPUT');
let pasteIndex = inputs.indexOf(input);
for (let i = 0; i < pasteData.length && pasteIndex + i < inputs.length; i++) {
inputs[pasteIndex + i].value = pasteData[i];
$values[pasteIndex + i] = Number(pasteData[i]);
}
const nextInput = inputs[pasteIndex + pasteData.length - 1]?.nextElementSibling;
if (nextInput && nextInput.tagName === 'INPUT') nextInput.focus();
"
>
<input type="number" inputmode="numeric" data-bind:values.0 />
<input type="number" inputmode="numeric" data-bind:values.1 />
<input type="number" inputmode="numeric" data-bind:values.2 />
<input type="number" inputmode="numeric" data-bind:values.3 />
<input type="number" inputmode="numeric" data-bind:values.4 />
<input type="number" inputmode="numeric" data-bind:values.5 />
</div>
<p>
Entered Code:
<span data-text="$bar"></span>
</p>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment