vuetify icon indicating copy to clipboard operation
vuetify copied to clipboard

[Bug Report][3.4.0] Autofill on v-otp-input

Open loihp opened this issue 2 years ago • 10 comments
trafficstars

Environment

Vuetify Version: 3.4.0 Vue Version: 3.0.0 Browsers: Chrome 119.0.0.0 OS: Android, iOS

Steps to reproduce

I use v-otp-input to get otp from user

Expected Behavior

On mobile device when receive sms otp keyboard on mobile device auto showing and when click this auto fill to otp input

Actual Behavior

Fill first input like picture i attached

IMG_5047 Screenshot 2023-11-14 at 12 34 54

Reproduction Link

https://play.vuetifyjs.com/#...

loihp avatar Nov 14 '23 05:11 loihp

https://play.vuetifyjs.com/#eNptUstOwzAQ/JXFl4CEEwQcEEorceMPOFAOrrMBI8e27E3Uquq/s054JBGnxLPj2fHOvp5Eirp6CqEcehSPoibsglWE250DqAepQhh/x4P2jpRxGL8hBhszgLYqpc1OaN+gNC70lKCRrcUDfPaJTHuUGh1hhG4vb3fi9/Yomj4QCbS3PrJG6mOrNC5II81TmLTnOIDqyWvPnpGQr3uHkkyHbLVhkSW39bpP2PwDSmXtEh5UNMpRdsTO1kp0DLmb67s9xnVxdNlx/4mB0eg1ZZC5bpnQpfdlcVtXs8eup0B4INkatM1aA6r5WFljnOtfUBUn9ZMkV5dRZmRKuq5mG8DHpKMJBAmpnxbBdMFHghNEbOEMbfQdFLw8RaYD5+gSATuCTWZcFs9orYcXH21zUVzlBpMki4nztbgr78oHkb/35Y14+wKmZcSf

You can see that via v-model and can pass value. or use model-value to pass value to component

Vuetify can't handle your logic. this is just component and you have to implement it correctly.

websitevirtuoso avatar Nov 14 '23 05:11 websitevirtuoso

https://play.vuetifyjs.com/#eNptUstOwzAQ/JXFl4CEEwQcEEorceMPOFAOrrMBI8e27E3Uquq/s054JBGnxLPj2fHOvp5Eirp6CqEcehSPoibsglWE250DqAepQhh/x4P2jpRxGL8hBhszgLYqpc1OaN+gNC70lKCRrcUDfPaJTHuUGh1hhG4vb3fi9/Yomj4QCbS3PrJG6mOrNC5II81TmLTnOIDqyWvPnpGQr3uHkkyHbLVhkSW39bpP2PwDSmXtEh5UNMpRdsTO1kp0DLmb67s9xnVxdNlx/4mB0eg1ZZC5bpnQpfdlcVtXs8eup0B4INkatM1aA6r5WFljnOtfUBUn9ZMkV5dRZmRKuq5mG8DHpKMJBAmpnxbBdMFHghNEbOEMbfQdFLw8RaYD5+gSATuCTWZcFs9orYcXH21zUVzlBpMki4nztbgr78oHkb/35Y14+wKmZcSf

You can see that via v-model and can pass value. or use model-value to pass value to component

Vuetify can't handle your logic. this is just component and you have to implement it correctly.

i really know that! what i want is somthing like autocomplete="one-time-code"

loihp avatar Nov 14 '23 09:11 loihp

I'm experiencing this as well on iOS.

Reproduction will require getting a mobile device to receive an OTP text message which the operating system then displays as an autofill shortcut on the keyboard.

Say I received a text with a 6-digit OTP that my phone presented to me on my device's keyboard.

Expected Behavior

Tapping that autofill shortcut should put a digit in each text field

Actual Behavior

Only the first text field is modified with only the first digit (or maybe all 6, it's hard to tell)

bjacobgordon avatar Nov 14 '23 22:11 bjacobgordon

I see. right now this makes sense and more clear. thanks

websitevirtuoso avatar Nov 14 '23 22:11 websitevirtuoso

@websitevirtuoso I'm experiencing this as well, is it resolved?

d2461379109 avatar Feb 26 '24 02:02 d2461379109

It seems that this is related to ios not calling on paste:

https://stackoverflow.com/questions/73704634/autoread-otp-in-web-and-copy-paste-otp-from-clipboard-in-web-not-working-for-and/73757650#73757650

magicseth avatar Mar 07 '24 12:03 magicseth

It looks like this was fixed at some point in the past, but the fix was lost. Between a repo rename and converting to v3 there are too many changes to track down when exactly this broke.

OG commit to support this feature: https://github.com/vuetifyjs/vuetify/commit/8c67ed8cf96334a86c6f087b7abfa845992098a2

ssorallen avatar Mar 07 '24 23:03 ssorallen

The issue is the maxlength="1" prop on each of the <input> elements. iOS fires a textInput event with data: '12345' unlike a human typing who types a single character a time. I am working on a complete fix with correct focus handling, but in the meantime I used patch-package with the following patch:

patches/vuetify+3.5.9.patch:

diff --git a/node_modules/vuetify/dist/vuetify.esm.js b/node_modules/vuetify/dist/vuetify.esm.js
index 4e635a0..1e20d68 100644
--- a/node_modules/vuetify/dist/vuetify.esm.js
+++ b/node_modules/vuetify/dist/vuetify.esm.js
@@ -23167,7 +23167,6 @@ const VOtpInput = genericComponent()({
             "disabled": props.disabled,
             "inputmode": props.type === 'number' ? 'numeric' : 'text',
             "min": props.type === 'number' ? 0 : undefined,
-            "maxlength": "1",
             "placeholder": props.placeholder,
             "type": props.type === 'number' ? 'text' : props.type,
             "value": model.value[i],
diff --git a/node_modules/vuetify/dist/vuetify.js b/node_modules/vuetify/dist/vuetify.js
index 0164850..67da61c 100644
--- a/node_modules/vuetify/dist/vuetify.js
+++ b/node_modules/vuetify/dist/vuetify.js
@@ -23171,7 +23171,6 @@
               "disabled": props.disabled,
               "inputmode": props.type === 'number' ? 'numeric' : 'text',
               "min": props.type === 'number' ? 0 : undefined,
-              "maxlength": "1",
               "placeholder": props.placeholder,
               "type": props.type === 'number' ? 'text' : props.type,
               "value": model.value[i],
diff --git a/node_modules/vuetify/dist/vuetify.min.js b/node_modules/vuetify/dist/vuetify.min.js
index 04f4bce..8ff2b04 100644
--- a/node_modules/vuetify/dist/vuetify.min.js
+++ b/node_modules/vuetify/dist/vuetify.min.js
@@ -1796,7 +1796,7 @@ t[f.value]=a
 let l=null
 f.value>c.value.length?l=c.value.length+1:f.value+1!==v.value&&(l="next"),c.value=t,l&&ee(m.value,l)}function b(e){const t=c.value.slice(),a=f.value
 let l=null;["ArrowLeft","ArrowRight","Backspace","Delete"].includes(e.key)&&(e.preventDefault(),"ArrowLeft"===e.key?l="prev":"ArrowRight"===e.key?l="next":["Backspace","Delete"].includes(e.key)&&(t[f.value]="",c.value=t,f.value>0&&"Backspace"===e.key?l="prev":requestAnimationFrame((()=>{g.value[a]?.select()}))),requestAnimationFrame((()=>{null!=l&&ee(m.value,l)})))}function V(){u(),f.value=-1}return vt({VField:{color:t.computed((()=>e.color)),bgColor:t.computed((()=>e.color)),baseColor:t.computed((()=>e.baseColor)),disabled:t.computed((()=>e.disabled)),error:t.computed((()=>e.error)),variant:t.computed((()=>e.variant))}},{scoped:!0}),t.watch(c,(e=>{e.length===v.value&&o("finish",e.join(""))}),{deep:!0}),t.watch(f,(e=>{e<0||t.nextTick((()=>{g.value[e]?.select()}))})),At((()=>{const[a,o]=R(l)
-return t.createVNode("div",t.mergeProps({class:["v-otp-input",{"v-otp-input--divided":!!e.divider},e.class],style:[e.style]},a),[t.createVNode("div",{ref:m,class:"v-otp-input__content",style:[r.value]},[p.value.map(((a,l)=>t.createVNode(t.Fragment,null,[e.divider&&0!==l&&t.createVNode("span",{class:"v-otp-input__divider"},[e.divider]),t.createVNode(ti,{focused:i.value&&e.focusAll||f.value===l,key:l},{...n,loader:void 0,default:()=>t.createVNode("input",{ref:e=>g.value[l]=e,"aria-label":d(e.label,l+1),autofocus:0===l&&e.autofocus,autocomplete:"one-time-code",class:["v-otp-input__field"],disabled:e.disabled,inputmode:"number"===e.type?"numeric":"text",min:"number"===e.type?0:void 0,maxlength:"1",placeholder:e.placeholder,type:"number"===e.type?"text":e.type,value:c.value[l],onInput:y,onFocus:e=>{return t=l,s(),void(f.value=t)
+return t.createVNode("div",t.mergeProps({class:["v-otp-input",{"v-otp-input--divided":!!e.divider},e.class],style:[e.style]},a),[t.createVNode("div",{ref:m,class:"v-otp-input__content",style:[r.value]},[p.value.map(((a,l)=>t.createVNode(t.Fragment,null,[e.divider&&0!==l&&t.createVNode("span",{class:"v-otp-input__divider"},[e.divider]),t.createVNode(ti,{focused:i.value&&e.focusAll||f.value===l,key:l},{...n,loader:void 0,default:()=>t.createVNode("input",{ref:e=>g.value[l]=e,"aria-label":d(e.label,l+1),autofocus:0===l&&e.autofocus,autocomplete:"one-time-code",class:["v-otp-input__field"],disabled:e.disabled,inputmode:"number"===e.type?"numeric":"text",min:"number"===e.type?0:void 0,placeholder:e.placeholder,type:"number"===e.type?"text":e.type,value:c.value[l],onInput:y,onFocus:e=>{return t=l,s(),void(f.value=t)
 var t},onBlur:V,onKeydown:b,onPaste:e=>{return t=l,(a=e).preventDefault(),a.stopPropagation(),c.value=(a?.clipboardData?.getData("Text")??"").split(""),void g.value?.[t].blur()
 var t,a}},null)})]))),t.createVNode("input",t.mergeProps({class:"v-otp-input-input",type:"hidden"},o,{value:c.value.join("")}),null),t.createVNode(Hr,{contained:!0,"content-class":"v-otp-input__loader","model-value":!!e.loading,persistent:!0},{default:()=>[n.loader?.()??t.createVNode(Hl,{color:"boolean"==typeof e.loading?void 0:e.loading,indeterminate:!0,size:"24",width:"2"},null)]}),n.default?.()])])})),{blur:()=>{g.value?.some((e=>e.blur()))},focus:()=>{g.value?.[0].focus()},reset:function(){c.value=[]},isFocused:i}}})
 const rv=a({scale:{type:[Number,String],default:.5},...l()},"VParallax"),iv=gt()({name:"VParallax",props:rv(),setup(e,a){let{slots:l}=a
diff --git a/node_modules/vuetify/lib/components/VOtpInput/VOtpInput.mjs b/node_modules/vuetify/lib/components/VOtpInput/VOtpInput.mjs
index ce0a7d0..f29f80f 100644
--- a/node_modules/vuetify/lib/components/VOtpInput/VOtpInput.mjs
+++ b/node_modules/vuetify/lib/components/VOtpInput/VOtpInput.mjs
@@ -188,7 +188,6 @@ export const VOtpInput = genericComponent()({
             "disabled": props.disabled,
             "inputmode": props.type === 'number' ? 'numeric' : 'text',
             "min": props.type === 'number' ? 0 : undefined,
-            "maxlength": "1",
             "placeholder": props.placeholder,
             "type": props.type === 'number' ? 'text' : props.type,
             "value": model.value[i],

ssorallen avatar Mar 14 '24 15:03 ssorallen

Has this problem been fixed in any updates ?

ygarg465 avatar Mar 25 '24 14:03 ygarg465

Any update? We're quite dependant on the OTP component, and this renders it quite useless. Confirmed still an issue with 3.6.7

SteffenOpheim avatar May 26 '24 09:05 SteffenOpheim

I can confirm removing maxlength="1" fixes this, but I have struggled to create a Cypress test to prove the fix. Cypress's type command fires 1 input event per character as if a human were typing, and creating an event with the full OTP as the .data attribute and using Cypress's trigger also does not accurately mimic how Mobile Safari works.

I am reluctant to put up a pull request without a test because the fix could inadvertently be removed again in the future without a test.

ssorallen avatar Jul 09 '24 20:07 ssorallen

This appears to be an issue for us on v3.7.2, and we do have users complain about it from time to time.

Is there a plan to address it? Or would you recommend we look elsewhere for a different OTP input component?

knolandev avatar Dec 04 '24 19:12 knolandev

Is this going to be fixed?

RobbeCl avatar Feb 14 '25 12:02 RobbeCl

This issue makes v-otp input completely unusable for me.

dan-hale avatar Feb 20 '25 20:02 dan-hale