205 lines
6.1 KiB
Vue
205 lines
6.1 KiB
Vue
|
|
<template>
|
||
|
|
<div :class="cx('root')" v-bind="ptmi('root')">
|
||
|
|
<template v-for="i in length" :key="i">
|
||
|
|
<slot :events="getTemplateEvents(i - 1)" :attrs="getTemplateAttrs(i - 1)" :index="i">
|
||
|
|
<OtpInputText
|
||
|
|
:value="tokens[i - 1]"
|
||
|
|
:type="inputType"
|
||
|
|
:class="cx('pcInputText')"
|
||
|
|
:name="$formName"
|
||
|
|
:inputmode="inputMode"
|
||
|
|
:variant="variant"
|
||
|
|
:readonly="readonly"
|
||
|
|
:disabled="disabled"
|
||
|
|
:size="size"
|
||
|
|
:invalid="invalid"
|
||
|
|
:tabindex="tabindex"
|
||
|
|
:unstyled="unstyled"
|
||
|
|
@input="onInput($event, i - 1)"
|
||
|
|
@focus="onFocus($event)"
|
||
|
|
@blur="onBlur($event)"
|
||
|
|
@paste="onPaste($event)"
|
||
|
|
@keydown="onKeyDown($event)"
|
||
|
|
@click="onClick($event)"
|
||
|
|
:pt="ptm('pcInputText')"
|
||
|
|
/>
|
||
|
|
</slot>
|
||
|
|
</template>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { isTouchDevice } from '@primeuix/utils/dom';
|
||
|
|
import InputText from 'primevue/inputtext';
|
||
|
|
import BaseInputOtp from './BaseInputOtp.vue';
|
||
|
|
|
||
|
|
export default {
|
||
|
|
name: 'InputOtp',
|
||
|
|
extends: BaseInputOtp,
|
||
|
|
inheritAttrs: false,
|
||
|
|
emits: ['change', 'focus', 'blur'],
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
tokens: []
|
||
|
|
};
|
||
|
|
},
|
||
|
|
watch: {
|
||
|
|
modelValue: {
|
||
|
|
immediate: true,
|
||
|
|
handler(newValue) {
|
||
|
|
this.tokens = newValue ? newValue.split('') : new Array(this.length);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
getTemplateAttrs(index) {
|
||
|
|
return {
|
||
|
|
value: this.tokens[index]
|
||
|
|
};
|
||
|
|
},
|
||
|
|
getTemplateEvents(index) {
|
||
|
|
return {
|
||
|
|
input: (event) => this.onInput(event, index),
|
||
|
|
keydown: (event) => this.onKeyDown(event),
|
||
|
|
focus: (event) => this.onFocus(event),
|
||
|
|
blur: (event) => this.onBlur(event),
|
||
|
|
paste: (event) => this.onPaste(event)
|
||
|
|
};
|
||
|
|
},
|
||
|
|
onInput(event, index) {
|
||
|
|
this.tokens[index] = event.target.value;
|
||
|
|
this.updateModel(event);
|
||
|
|
|
||
|
|
if (event.inputType === 'deleteContentBackward') {
|
||
|
|
this.moveToPrev(event);
|
||
|
|
} else if (event.inputType === 'insertText' || event.inputType === 'deleteContentForward' || (isTouchDevice() && event instanceof CustomEvent)) {
|
||
|
|
this.moveToNext(event);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
updateModel(event) {
|
||
|
|
const newValue = this.tokens.join('');
|
||
|
|
|
||
|
|
this.writeValue(newValue, event);
|
||
|
|
this.$emit('change', {
|
||
|
|
originalEvent: event,
|
||
|
|
value: newValue
|
||
|
|
});
|
||
|
|
},
|
||
|
|
moveToPrev(event) {
|
||
|
|
let prevInput = this.findPrevInput(event.target);
|
||
|
|
|
||
|
|
if (prevInput) {
|
||
|
|
prevInput.focus();
|
||
|
|
prevInput.select();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
moveToNext(event) {
|
||
|
|
let nextInput = this.findNextInput(event.target);
|
||
|
|
|
||
|
|
if (nextInput) {
|
||
|
|
nextInput.focus();
|
||
|
|
nextInput.select();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
findNextInput(element) {
|
||
|
|
let nextElement = element.nextElementSibling;
|
||
|
|
|
||
|
|
if (!nextElement) return;
|
||
|
|
|
||
|
|
return nextElement.nodeName === 'INPUT' ? nextElement : this.findNextInput(nextElement);
|
||
|
|
},
|
||
|
|
findPrevInput(element) {
|
||
|
|
let prevElement = element.previousElementSibling;
|
||
|
|
|
||
|
|
if (!prevElement) return;
|
||
|
|
|
||
|
|
return prevElement.nodeName === 'INPUT' ? prevElement : this.findPrevInput(prevElement);
|
||
|
|
},
|
||
|
|
onFocus(event) {
|
||
|
|
event.target.select();
|
||
|
|
this.$emit('focus', event);
|
||
|
|
},
|
||
|
|
onBlur(event) {
|
||
|
|
this.$emit('blur', event);
|
||
|
|
},
|
||
|
|
onClick(event) {
|
||
|
|
setTimeout(() => event.target.select(), 1);
|
||
|
|
},
|
||
|
|
onKeyDown(event) {
|
||
|
|
if (event.ctrlKey || event.metaKey) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (event.key) {
|
||
|
|
case 'ArrowLeft':
|
||
|
|
this.moveToPrev(event);
|
||
|
|
event.preventDefault();
|
||
|
|
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'ArrowUp':
|
||
|
|
case 'ArrowDown':
|
||
|
|
event.preventDefault();
|
||
|
|
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'Backspace':
|
||
|
|
if (event.target.value.length === 0) {
|
||
|
|
this.moveToPrev(event);
|
||
|
|
event.preventDefault();
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'ArrowRight':
|
||
|
|
this.moveToNext(event);
|
||
|
|
event.preventDefault();
|
||
|
|
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'Enter':
|
||
|
|
case 'Tab':
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
const target = event.target;
|
||
|
|
const hasSelection = target.selectionStart !== target.selectionEnd;
|
||
|
|
const isAtMaxLength = this.tokens.join('').length >= this.length;
|
||
|
|
const isValidKey = this.integerOnly ? /^[0-9]$/.test(event.key) : true;
|
||
|
|
|
||
|
|
if (!isValidKey || (isAtMaxLength && event.key !== 'Delete' && !hasSelection)) {
|
||
|
|
event.preventDefault();
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onPaste(event) {
|
||
|
|
let paste = event.clipboardData.getData('text');
|
||
|
|
|
||
|
|
if (paste.length) {
|
||
|
|
let pastedCode = paste.substring(0, this.length);
|
||
|
|
|
||
|
|
if (!this.integerOnly || !isNaN(pastedCode)) {
|
||
|
|
this.tokens = pastedCode.split('');
|
||
|
|
this.updateModel(event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
event.preventDefault();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
computed: {
|
||
|
|
inputMode() {
|
||
|
|
return this.integerOnly ? 'numeric' : 'text';
|
||
|
|
},
|
||
|
|
inputType() {
|
||
|
|
return this.mask ? 'password' : 'text';
|
||
|
|
}
|
||
|
|
},
|
||
|
|
components: {
|
||
|
|
OtpInputText: InputText
|
||
|
|
}
|
||
|
|
};
|
||
|
|
</script>
|