Skip to content

Commit

Permalink
feat(otp-input): handle entering otp
Browse files Browse the repository at this point in the history
  • Loading branch information
bohdanmoroziuk committed Jul 18, 2023
1 parent 73f2eaa commit b46f0ed
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 5 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
//
'vuejs-accessibility/form-control-has-label': 'off',
'vuejs-accessibility/no-autofocus': 'off',
'no-shadow': 'off',
},
overrides: [
{
Expand Down
108 changes: 103 additions & 5 deletions src/components/OtpInput.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,107 @@
<script setup lang="ts">
import {
computed,
ref,
watch,
onMounted,
} from 'vue';
interface Props {
length?: number;
modelValue: string;
}
interface Emits {
(event: 'update:modelValue', payload: string): void;
}
const props = withDefaults(defineProps<Props>(), {
length: 4,
});
const emit = defineEmits<Emits>();
const root = ref<HTMLDivElement>();
const focusInput = (index: number) => {
const input = root.value?.children[index];
if (input) {
(input as HTMLInputElement).focus();
}
};
onMounted(() => {
focusInput(0);
});
const values = ref<string[]>([]);
const setValue = (index: number, value: string) => {
values.value[index] = value;
};
const resetValue = (index: number) => {
setValue(index, '');
};
const handleKeyUp = (event: KeyboardEvent, index: number) => {
const { key } = event;
if (key === 'ArrowRight' && index < props.length - 1) {
focusInput(index + 1);
return;
}
if (key === 'ArrowLeft' && index > 0) {
focusInput(index - 1);
return;
}
if ((key === 'Backspace' || key === 'Delete') && index > 0) {
resetValue(index);
focusInput(index - 1);
return;
}
const matched = key.match(/^[0-9]$/);
if (!matched) {
resetValue(index);
return;
}
setValue(index, key.charAt(0));
focusInput(index + 1);
};
const otp = computed(() => {
const truthyValues = values.value.filter(((value) => value));
return truthyValues.length === props.length
? truthyValues.join('')
: '';
});
watch(otp, (otp: string) => {
emit('update:modelValue', otp);
});
</script>

<template>
<div class="otp-input">
<input class="otp-input__input" type="text" autofocus />
<input class="otp-input__input" type="text" disabled />
<input class="otp-input__input" type="text" disabled />
<input class="otp-input__input" type="text" disabled />
<div
class="otp-input"
ref="root"
>
<input
v-for="item of length"
v-model="values[item - 1]"
:key="item"
class="otp-input__input"
type="text"
maxlength="1"
@keyup="handleKeyUp($event, item - 1)"
/>
</div>
</template>

Expand Down

0 comments on commit b46f0ed

Please sign in to comment.