Skip to content

Commit 8c67ed8

Browse files
committed
fix(VOtpInput): support paste and autofill on mobile
fixes #14801
1 parent e60e5a9 commit 8c67ed8

File tree

2 files changed

+26
-70
lines changed

2 files changed

+26
-70
lines changed

packages/vuetify/src/components/VOtpInput/VOtpInput.ts

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export default baseMixins.extend<options>().extend({
4949
},
5050

5151
data: () => ({
52-
badInput: false,
5352
initialValue: null,
5453
isBooted: false,
5554
otp: [] as string[],
@@ -66,9 +65,6 @@ export default baseMixins.extend<options>().extend({
6665
'v-otp-input--plain': this.plain,
6766
}
6867
},
69-
isDirty (): boolean {
70-
return VInput.options.computed.isDirty.call(this) || this.badInput
71-
},
7268
},
7369

7470
watch: {
@@ -159,18 +155,17 @@ export default baseMixins.extend<options>().extend({
159155
},
160156
attrs: {
161157
...this.attrs$,
158+
autocomplete: 'one-time-code',
162159
disabled: this.isDisabled,
163160
readonly: this.isReadonly,
164161
type: this.type,
165162
id: `${this.computedId}--${otpIdx}`,
166163
class: `otp-field-box--${otpIdx}`,
167-
maxlength: 1,
168164
},
169165
on: Object.assign(listeners, {
170166
blur: this.onBlur,
171167
input: (e: Event) => this.onInput(e, otpIdx),
172168
focus: (e: Event) => this.onFocus(e, otpIdx),
173-
paste: (e: ClipboardEvent) => this.onPaste(e, otpIdx),
174169
keydown: this.onKeyDown,
175170
keyup: (e: KeyboardEvent) => this.onKeyUp(e, otpIdx),
176171
}),
@@ -212,22 +207,31 @@ export default baseMixins.extend<options>().extend({
212207
e && this.$emit('focus', e)
213208
}
214209
},
215-
onInput (e: Event, otpIdx: number) {
210+
onInput (e: Event, index: number) {
211+
const maxCursor = +this.length - 1
212+
216213
const target = e.target as HTMLInputElement
217214
const value = target.value
218-
this.applyValue(otpIdx, target.value, () => {
219-
this.internalValue = this.otp.join('')
220-
})
221-
this.badInput = target.validity && target.validity.badInput
215+
const inputDataArray = value?.split('') || []
216+
217+
const newOtp: string[] = [...this.otp]
218+
for (let i = 0; i < inputDataArray.length; i++) {
219+
const appIdx = index + i
220+
if (appIdx > maxCursor) break
221+
newOtp[appIdx] = inputDataArray[i].toString()
222+
}
223+
if (!inputDataArray.length) {
224+
newOtp.splice(index, 1)
225+
}
226+
227+
this.otp = newOtp
228+
this.internalValue = this.otp.join('')
222229

223-
const nextIndex = otpIdx + 1
224-
if (value) {
225-
if (nextIndex < +this.length) {
226-
this.changeFocus(nextIndex)
227-
} else {
228-
this.clearFocus(otpIdx)
229-
this.onCompleted()
230-
}
230+
if (index + inputDataArray.length >= +this.length) {
231+
this.onCompleted()
232+
this.clearFocus(index)
233+
} else if (inputDataArray.length) {
234+
this.changeFocus(index + inputDataArray.length)
231235
}
232236
},
233237
clearFocus (index: number) {
@@ -255,30 +259,6 @@ export default baseMixins.extend<options>().extend({
255259

256260
VInput.options.methods.onMouseUp.call(this, e)
257261
},
258-
onPaste (event: ClipboardEvent, index: number) {
259-
const maxCursor = +this.length - 1
260-
const inputVal = event?.clipboardData?.getData('Text')
261-
const inputDataArray = inputVal?.split('') || []
262-
event.preventDefault()
263-
const newOtp: string[] = [...this.otp]
264-
for (let i = 0; i < inputDataArray.length; i++) {
265-
const appIdx = index + i
266-
if (appIdx > maxCursor) break
267-
newOtp[appIdx] = inputDataArray[i].toString()
268-
}
269-
this.otp = newOtp
270-
this.internalValue = this.otp.join('')
271-
const targetFocus = Math.min(index + inputDataArray.length, maxCursor)
272-
this.changeFocus(targetFocus)
273-
274-
if (newOtp.length === +this.length) { this.onCompleted(); this.clearFocus(targetFocus) }
275-
},
276-
applyValue (index: number, inputVal: string, next: Function) {
277-
const newOtp: string[] = [...this.otp]
278-
newOtp[index] = inputVal
279-
this.otp = newOtp
280-
next()
281-
},
282262
changeFocus (index: number) {
283263
this.onFocus(undefined, index || 0)
284264
},

packages/vuetify/src/components/VOtpInput/__tests__/VOtpInput.spec.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,7 @@ describe('VOtpInput.ts', () => {
173173
expect(change).toHaveBeenCalledTimes(2)
174174
})
175175

176-
it('should process input when paste event', async () => {
177-
const getData = jest.fn(() => '1337078')
178-
const event = {
179-
clipboardData: {
180-
getData,
181-
},
182-
}
183-
176+
it('should process input on paste', async () => {
184177
const wrapper = mountFunction({})
185178

186179
const input = wrapper.findAll('input').at(0)
@@ -190,30 +183,13 @@ describe('VOtpInput.ts', () => {
190183
await wrapper.vm.$nextTick()
191184
expect(document.activeElement === element).toBe(true)
192185

193-
input.trigger('paste', event)
186+
element.value = '1337078'
187+
input.trigger('input')
194188
await wrapper.vm.$nextTick()
195189

196190
expect(wrapper.vm.otp).toStrictEqual('133707'.split(''))
197191
})
198192

199-
it('should process input when paste event with empty event', async () => {
200-
const event = {}
201-
202-
const wrapper = mountFunction({})
203-
204-
const input = wrapper.findAll('input').at(0)
205-
206-
const element = input.element as HTMLInputElement
207-
input.trigger('focus')
208-
await wrapper.vm.$nextTick()
209-
expect(document.activeElement === element).toBe(true)
210-
211-
input.trigger('paste', event)
212-
await wrapper.vm.$nextTick()
213-
214-
expect(wrapper.vm.otp).toStrictEqual(''.split(''))
215-
})
216-
217193
it('should clear cursor when input typing is done', async () => {
218194
const onFinish = jest.fn()
219195
const clearFocus = jest.fn()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy