vue3-otp-input
Version:
A fully customizable, OTP (one-time password) input component built with Vue 3.x and Vue Composition API.
2 lines (1 loc) • 5.24 kB
JavaScript
import{defineComponent as T,ref as v,computed as _,watch as j,onMounted as E,createElementBlock as m,openBlock as d,createElementVNode as F,createCommentVNode as O,normalizeClass as K,Fragment as H,renderList as N,createBlock as V}from"vue";const $={style:{display:"flex","align-items":"center"}},q=["type","inputmode","placeholder","disabled","maxlength","value"],z={key:0},S=["innerHTML"],W=T({__name:"single-otp-input",props:{inputType:{default:"tel"},inputmode:{default:"numeric"},value:{default:""},separator:{default:""},focus:{type:Boolean,default:!1},inputClasses:{default:""},conditionalClass:{default:""},shouldAutoFocus:{type:Boolean,default:!1},isLastChild:{type:Boolean,default:!1},placeholder:{default:""},isDisabled:{type:Boolean,default:!1}},emits:["on-change","on-keydown","on-paste","on-focus","on-blur"],setup(C,{emit:k}){const s=C,l=k,i=v(s.value||""),n=v(null),t=e=>{const u=e.target.value;return u&&u.trim().length>1?(e.clipboardData={getData:()=>u.trim()},l("on-paste",e)):l("on-change",u)},f=e=>e>=65&&e<=90,D=e=>e>=48&&e<=57||e>=96&&e<=105,I=e=>{s.isDisabled&&e.preventDefault();const u=e||window.event,c=u.which?u.which:u.keyCode;if(/iPhone|iPad|iPod/.test(navigator.userAgent)){l("on-keydown",e);return}D(c)||s.inputType==="letter-numeric"&&f(c)||[8,9,13,37,39,46,86].includes(c)?l("on-keydown",e):u.preventDefault()},h=e=>l("on-paste",e),r=()=>(n.value.select(),l("on-focus")),y=()=>l("on-blur"),g=_(()=>["letter-numeric","number"].includes(s.inputType)?"text":s.inputType);return j(()=>s.value,(e,u)=>{e!==u&&(i.value=e)}),j(()=>s.focus,(e,u)=>{u!==e&&n.value&&s.focus&&(n.value.focus(),n.value.select())}),E(()=>{n.value&&s.focus&&s.shouldAutoFocus&&(n.value.focus(),n.value.select())}),(e,u)=>(d(),m("div",$,[F("input",{"data-test":"single-input",type:g.value,inputmode:e.inputmode,placeholder:e.placeholder,disabled:e.isDisabled,ref_key:"input",ref:n,min:"0",max:"9",maxlength:e.isLastChild?1:void 0,pattern:"[0-9]",value:i.value,class:K([e.inputClasses,e.conditionalClass,{"is-complete":i.value}]),onInput:t,onKeydown:I,onPaste:h,onFocus:r,onBlur:y},null,42,q),!e.isLastChild&&e.separator?(d(),m("span",z,[F("span",{innerHTML:e.separator},null,8,S)])):O("",!0)]))}}),G={style:{display:"flex"},class:"otp-input-container"},J={key:0,autocomplete:"off",name:"hidden",type:"text",style:{display:"none"}},Q=8,R=37,U=39,X=46,Y=T({__name:"vue3-otp-input",props:{value:{default:""},numInputs:{default:4},separator:{default:""},inputClasses:{default:""},conditionalClass:{default:()=>[]},inputType:{},inputmode:{default:"text"},shouldAutoFocus:{type:Boolean,default:!1},placeholder:{default:()=>[]},isDisabled:{type:Boolean,default:!1},shouldFocusOrder:{type:Boolean}},emits:["update:value","on-change","on-complete"],setup(C,{expose:k,emit:s}){const l=C,i=s,n=v(0),t=v([]),f=v([]);j(()=>l.value,a=>{if(a.length===l.numInputs||t.value.length===0){const o=a.split("");t.value=o}},{immediate:!0});const D=a=>{n.value=a},I=()=>{n.value!==l.numInputs-1&&(n.value=-1)},h=()=>t.value.join("").length===l.numInputs?(i("update:value",t.value.join("")),i("on-complete",t.value.join(""))):"Wait until the user enters the required number of characters",r=a=>{n.value=Math.max(Math.min(l.numInputs-1,a),0)},y=()=>{r(n.value+1)},g=()=>{r(n.value-1)},e=a=>{f.value=Object.assign([],t.value),t.value[n.value]=a.toString(),f.value.join("")!==t.value.join("")&&(i("update:value",t.value.join("")),i("on-change",t.value.join("")),h())},u=a=>{a.preventDefault();const o=a.clipboardData.getData("text/plain").slice(0,l.numInputs-n.value).split("");if(l.inputType==="number"&&!o.join("").match(/^\d+$/)||l.inputType==="letter-numeric"&&!o.join("").match(/^\w+$/))return"Invalid pasted data";const w=t.value.slice(0,n.value).concat(o);return w.slice(0,l.numInputs).forEach(function(p,b){t.value[b]=p}),r(w.slice(0,l.numInputs).length),h()},c=a=>{e(a),n.value<l.numInputs-1&&y()},L=()=>{t.value.length>0&&(i("update:value",""),i("on-change","")),t.value=[],n.value=0},A=a=>{const o=a.split("");o.length===l.numInputs&&(t.value=o,i("update:value",t.value.join("")),i("on-complete",t.value.join("")))},M=(a,o)=>{switch(a.keyCode){case Q:a.preventDefault(),e(""),g();break;case X:a.preventDefault(),e("");break;case R:a.preventDefault(),g();break;case U:a.preventDefault(),y();break;default:P(o);break}},P=a=>{l.shouldFocusOrder&&setTimeout(()=>{const o=t.value.join("").length;a-o>=0&&(n.value=o,t.value[a]="")},100)};return k({clearInput:L,fillInput:A}),(a,o)=>(d(),m("div",G,[a.inputType==="password"?(d(),m("input",J)):O("",!0),(d(!0),m(H,null,N(a.numInputs,(w,p)=>{var b,x;return d(),V(W,{key:p,focus:n.value===p,value:t.value[p],separator:a.separator,"input-type":a.inputType,inputmode:a.inputmode,"input-classes":a.inputClasses,conditionalClass:(b=a.conditionalClass)==null?void 0:b[p],"is-last-child":p===a.numInputs-1,"should-auto-focus":a.shouldAutoFocus,placeholder:(x=a.placeholder)==null?void 0:x[p],"is-disabled":a.isDisabled,onOnChange:c,onOnKeydown:B=>M(B,p),onOnPaste:u,onOnFocus:B=>D(p),onOnBlur:I},null,8,["focus","value","separator","input-type","inputmode","input-classes","conditionalClass","is-last-child","should-auto-focus","placeholder","is-disabled","onOnKeydown","onOnFocus"])}),128))]))}});export{Y as default};