Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 71 additions & 50 deletions resources/views/components/otp-input.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,60 +32,83 @@
:state-path="$getStatePath()"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be replaced with a variable if you're already extracting.

>
<div x-data="{
state: $wire.$entangle('{{ $getStatePath() }}'),
length: {{$numberInput}},
autoFocus: '{{$isAutofocused}}',
type: '{{$inputType}}',
init: function(){
if (this.autoFocus){
this.$refs[1].focus();
}
},
handleInput(e, i) {
const input = e.target;
if(input.value.length > 1){
input.value = input.value.substring(0, 1);
state: $wire.$entangle('{{ $statePath }}'),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would separate style fixes and functional changes into separate PRs for easier reviews

length: {{ $numberInput }},
autoFocus: '{{ $isAutofocused }}',
type: '{{ $inputType }}',
init: function() {
this.$nextTick(() => {
if (this.autoFocus) {
this.$refs['otp_1'].focus();
}
});
},
handleInput(e, i) {
const input = e.target;

this.state = Array.from(Array(this.length), (element, i) => {
const el = this.$refs[(i + 1)];
return el.value ? el.value : '';
}).join('');
// If type is number only allow numeric characters and limit to one character
input.value = (this.type === 'number')
? input.value.replace(/\D/g, '').substring(0, 1)
: input.value.substring(0, 1);

this.state = Array.from({ length: this.length }).map((element, idx) => {
return this.$refs[`otp_${idx + 1}`].value || '';
}).join('');

if (i < this.length) {
this.$refs[i+1].focus();
this.$refs[i+1].select();
}
if(i == this.length){
@this.set('{{ $getStatePath() }}', this.state)
if (input.value && i < this.length) {
this.$nextTick(() => {
this.$refs[`otp_${i + 1}`].focus();
this.$refs[`otp_${i + 1}`].select();
});
}

if (i === this.length) {
@this.set('{{ $statePath }}', this.state);
}
},
handlePaste(e) {
// Get the pasted data, if type is number filter only numeric characters, and limit it to the maximum length of inputs
const paste = (this.type === 'number')
? e.clipboardData.getData('text').replace(/\D/g, '').substring(0, this.length)
: e.clipboardData.getData('text');

const inputs = Array.from(Array(this.length));

inputs.forEach((element, idx) => {
if (paste[idx]) {
this.$refs[`otp_${idx + 1}`].value = paste[idx];
}
},
});

const focusInputNumber = (paste.length < this.length) ? paste.length+1 : this.length;

handlePaste(e) {
const paste = e.clipboardData.getData('text');
this.value = paste;
const inputs = Array.from(Array(this.length));
this.$nextTick(() => {
this.$refs[`otp_${focusInputNumber}`].focus();
});

inputs.forEach((element, i) => {
this.$refs[(i+1)].focus();
this.$refs[(i+1)].value = paste[i] || '';
if (paste.length === this.length) {
@this.set('{{ $statePath }}', paste);
}

e.preventDefault();
},
handleBackspace(e) {
const ref = e.target.getAttribute('x-ref').split('_')[1];
e.target.value = '';
const previous = ref - 1;

if (previous >= 1) {
this.$nextTick(() => {
this.$refs[`otp_${previous}`].focus();
this.$refs[`otp_${previous}`].select();
});
},
}

handleBackspace(e) {
const ref = e.target.getAttribute('x-ref');
e.target.value = '';
const previous = ref - 1;
this.$refs[previous] && this.$refs[previous].focus();
this.$refs[previous] && this.$refs[previous].select();
e.preventDefault();
},
}">
e.preventDefault();
},
}">
<div class="flex justify-between gap-6 fi-otp-input-container" dir="{{ $isRtl ? 'rtl' : 'ltr' }}">

@foreach(range(1, $numberInput) as $column)

<x-filament::input.wrapper
:disabled="$isDisabled"
:inline-prefix="$isPrefixInline"
Expand All @@ -105,21 +128,19 @@
"
>
<input
{{$isDisabled ? 'disabled' : ''}}
type="{{$inputType}}"
{{ $isDisabled ? 'disabled' : '' }}
type="{{ $inputType }}"
maxlength="1"
x-ref="{{$column}}"
x-ref="otp_{{ $column }}"
required
autocomplete="{{$autocomplete}}"
autocomplete="{{ $autocomplete }}"
class="fi-input fi-otp-input block w-full border-none py-1.5 text-base text-gray-950 transition duration-75 placeholder:text-gray-400 focus:ring-0 disabled:text-gray-500 disabled:[-webkit-text-fill-color:theme(colors.gray.500)] disabled:placeholder:[-webkit-text-fill-color:theme(colors.gray.400)] dark:text-white dark:placeholder:text-gray-500 dark:disabled:text-gray-400 dark:disabled:[-webkit-text-fill-color:theme(colors.gray.400)] dark:disabled:placeholder:[-webkit-text-fill-color:theme(colors.gray.500)] sm:text-sm sm:leading-6 bg-white/0 ps-3 pe-3 text-center"
x-on:input="handleInput($event, {{$column}})"
x-on:input="handleInput($event, {{ $column }})"
x-on:paste="handlePaste($event)"
x-on:keydown.backspace="handleBackspace($event)"
/>

</x-filament::input.wrapper>
@endforeach

</div>
</div>
</x-dynamic-component>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/OtpInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function getType(): string
return $this->evaluate($this->type);
}

public function rtl(bool|\Closure $condition = false): static
public function rtl(bool|\Closure $condition = true): static
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved to a separate PR

{
$this->isRtl = $condition;

Expand Down