vuetifyjs / vuetify

🐉 Vue Component Framework
https://vuetifyjs.com
MIT License
39.33k stars 6.93k forks source link

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

Open loihp opened 7 months ago

loihp commented 7 months ago

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/#...

websitevirtuoso commented 7 months ago

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.

loihp commented 7 months ago

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"

bjacobgordon commented 7 months ago

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)

websitevirtuoso commented 7 months ago

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

d2461379109 commented 4 months ago

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

magicseth commented 4 months ago

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

ssorallen commented 4 months ago

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 commented 3 months ago

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],
ygarg465 commented 3 months ago

Has this problem been fixed in any updates ?

SteffenOpheim commented 1 month ago

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