139 lines
4.2 KiB
PHP
139 lines
4.2 KiB
PHP
@props([
|
|
'digits' => 6,
|
|
'name' => 'code',
|
|
])
|
|
|
|
<div
|
|
@focus-2fa-auth-code.window="$refs.input1?.focus()"
|
|
@clear-2fa-auth-code.window="clearAll()"
|
|
class="relative"
|
|
x-data="{
|
|
totalDigits: @js($digits),
|
|
digitIndices: @js(range(1, $digits)),
|
|
init() {
|
|
$nextTick(() => {
|
|
this.$refs.input1?.focus();
|
|
});
|
|
},
|
|
getInput(index) {
|
|
return this.$refs['input' + index];
|
|
},
|
|
setValue(index, value) {
|
|
this.getInput(index).value = value;
|
|
},
|
|
getCode() {
|
|
return this.digitIndices
|
|
.map(i => this.getInput(i).value)
|
|
.join('');
|
|
},
|
|
updateHiddenField() {
|
|
this.$refs.code.value = this.getCode();
|
|
this.$refs.code.dispatchEvent(new Event('input', { bubbles: true }));
|
|
this.$refs.code.dispatchEvent(new Event('change', { bubbles: true }));
|
|
},
|
|
handleNumberKey(index, key) {
|
|
this.setValue(index, key);
|
|
|
|
if (index < this.totalDigits) {
|
|
this.getInput(index + 1).focus();
|
|
}
|
|
|
|
$nextTick(() => {
|
|
this.updateHiddenField();
|
|
});
|
|
},
|
|
handleBackspace(index) {
|
|
const currentInput = this.getInput(index);
|
|
|
|
if (currentInput.value !== '') {
|
|
currentInput.value = '';
|
|
this.updateHiddenField();
|
|
return;
|
|
}
|
|
|
|
if (index <= 1) {
|
|
return;
|
|
}
|
|
|
|
const previousInput = this.getInput(index - 1);
|
|
|
|
previousInput.value = '';
|
|
previousInput.focus();
|
|
|
|
this.updateHiddenField();
|
|
},
|
|
handleKeyDown(index, event) {
|
|
const key = event.key;
|
|
|
|
if (/^[0-9]$/.test(key)) {
|
|
event.preventDefault();
|
|
this.handleNumberKey(index, key);
|
|
return;
|
|
}
|
|
|
|
if (key === 'Backspace') {
|
|
event.preventDefault();
|
|
this.handleBackspace(index);
|
|
return;
|
|
}
|
|
},
|
|
handlePaste(event) {
|
|
event.preventDefault();
|
|
|
|
const pastedText = (event.clipboardData || window.clipboardData).getData('text');
|
|
const numericOnly = pastedText.replace(/[^0-9]/g, '');
|
|
const digitsToFill = Math.min(numericOnly.length, this.totalDigits);
|
|
|
|
this.digitIndices
|
|
.slice(0, digitsToFill)
|
|
.forEach(index => {
|
|
this.setValue(index, numericOnly[index - 1]);
|
|
});
|
|
|
|
if (numericOnly.length >= this.totalDigits) {
|
|
this.updateHiddenField();
|
|
}
|
|
},
|
|
clearAll() {
|
|
this.digitIndices.forEach(index => {
|
|
this.setValue(index, '');
|
|
});
|
|
|
|
this.$refs.code.value = '';
|
|
this.$refs.input1?.focus();
|
|
}
|
|
}"
|
|
>
|
|
<div class="flex items-center">
|
|
@for ($x = 1; $x <= $digits; $x++)
|
|
<input
|
|
x-ref="input{{ $x }}"
|
|
type="text"
|
|
inputmode="numeric"
|
|
pattern="[0-9]"
|
|
maxlength="1"
|
|
autocomplete="off"
|
|
@paste="handlePaste"
|
|
@keydown="handleKeyDown({{ $x }}, $event)"
|
|
@focus="$el.select()"
|
|
@input="$el.value = $el.value.replace(/[^0-9]/g, '').slice(0, 1)"
|
|
@class([
|
|
'flex size-10 items-center justify-center border border-zinc-300 bg-accent-foreground text-center text-sm font-medium text-accent-content transition-colors focus:border-accent focus:border-2 focus:outline-none focus:relative focus:z-10 dark:border-zinc-700 dark:focus:border-accent',
|
|
'rounded-l-md' => $x === 1,
|
|
'rounded-r-md' => $x === $digits,
|
|
'-ml-px' => $x > 1,
|
|
])
|
|
/>
|
|
@endfor
|
|
</div>
|
|
|
|
<input
|
|
{{ $attributes->except(['digits']) }}
|
|
type="hidden"
|
|
x-ref="code"
|
|
name="{{ $name }}"
|
|
minlength="{{ $digits }}"
|
|
maxlength="{{ $digits }}"
|
|
/>
|
|
</div>
|