diff --git a/packages/vuetify/src/components/VOtpInput/VOtpInput.ts b/packages/vuetify/src/components/VOtpInput/VOtpInput.ts index 0b94be46d2d..fdaa0efedfa 100644 --- a/packages/vuetify/src/components/VOtpInput/VOtpInput.ts +++ b/packages/vuetify/src/components/VOtpInput/VOtpInput.ts @@ -49,7 +49,6 @@ export default baseMixins.extend().extend({ }, data: () => ({ - badInput: false, initialValue: null, isBooted: false, otp: [] as string[], @@ -66,9 +65,6 @@ export default baseMixins.extend().extend({ 'v-otp-input--plain': this.plain, } }, - isDirty (): boolean { - return VInput.options.computed.isDirty.call(this) || this.badInput - }, }, watch: { @@ -159,18 +155,17 @@ export default baseMixins.extend().extend({ }, attrs: { ...this.attrs$, + autocomplete: 'one-time-code', disabled: this.isDisabled, readonly: this.isReadonly, type: this.type, id: `${this.computedId}--${otpIdx}`, class: `otp-field-box--${otpIdx}`, - maxlength: 1, }, on: Object.assign(listeners, { blur: this.onBlur, input: (e: Event) => this.onInput(e, otpIdx), focus: (e: Event) => this.onFocus(e, otpIdx), - paste: (e: ClipboardEvent) => this.onPaste(e, otpIdx), keydown: this.onKeyDown, keyup: (e: KeyboardEvent) => this.onKeyUp(e, otpIdx), }), @@ -212,22 +207,31 @@ export default baseMixins.extend().extend({ e && this.$emit('focus', e) } }, - onInput (e: Event, otpIdx: number) { + onInput (e: Event, index: number) { + const maxCursor = +this.length - 1 + const target = e.target as HTMLInputElement const value = target.value - this.applyValue(otpIdx, target.value, () => { - this.internalValue = this.otp.join('') - }) - this.badInput = target.validity && target.validity.badInput + const inputDataArray = value?.split('') || [] + + const newOtp: string[] = [...this.otp] + for (let i = 0; i < inputDataArray.length; i++) { + const appIdx = index + i + if (appIdx > maxCursor) break + newOtp[appIdx] = inputDataArray[i].toString() + } + if (!inputDataArray.length) { + newOtp.splice(index, 1) + } + + this.otp = newOtp + this.internalValue = this.otp.join('') - const nextIndex = otpIdx + 1 - if (value) { - if (nextIndex < +this.length) { - this.changeFocus(nextIndex) - } else { - this.clearFocus(otpIdx) - this.onCompleted() - } + if (index + inputDataArray.length >= +this.length) { + this.onCompleted() + this.clearFocus(index) + } else if (inputDataArray.length) { + this.changeFocus(index + inputDataArray.length) } }, clearFocus (index: number) { @@ -255,30 +259,6 @@ export default baseMixins.extend().extend({ VInput.options.methods.onMouseUp.call(this, e) }, - onPaste (event: ClipboardEvent, index: number) { - const maxCursor = +this.length - 1 - const inputVal = event?.clipboardData?.getData('Text') - const inputDataArray = inputVal?.split('') || [] - event.preventDefault() - const newOtp: string[] = [...this.otp] - for (let i = 0; i < inputDataArray.length; i++) { - const appIdx = index + i - if (appIdx > maxCursor) break - newOtp[appIdx] = inputDataArray[i].toString() - } - this.otp = newOtp - this.internalValue = this.otp.join('') - const targetFocus = Math.min(index + inputDataArray.length, maxCursor) - this.changeFocus(targetFocus) - - if (newOtp.length === +this.length) { this.onCompleted(); this.clearFocus(targetFocus) } - }, - applyValue (index: number, inputVal: string, next: Function) { - const newOtp: string[] = [...this.otp] - newOtp[index] = inputVal - this.otp = newOtp - next() - }, changeFocus (index: number) { this.onFocus(undefined, index || 0) }, diff --git a/packages/vuetify/src/components/VOtpInput/__tests__/VOtpInput.spec.ts b/packages/vuetify/src/components/VOtpInput/__tests__/VOtpInput.spec.ts index 2b7c59cb168..01de323d750 100644 --- a/packages/vuetify/src/components/VOtpInput/__tests__/VOtpInput.spec.ts +++ b/packages/vuetify/src/components/VOtpInput/__tests__/VOtpInput.spec.ts @@ -173,14 +173,7 @@ describe('VOtpInput.ts', () => { expect(change).toHaveBeenCalledTimes(2) }) - it('should process input when paste event', async () => { - const getData = jest.fn(() => '1337078') - const event = { - clipboardData: { - getData, - }, - } - + it('should process input on paste', async () => { const wrapper = mountFunction({}) const input = wrapper.findAll('input').at(0) @@ -190,30 +183,13 @@ describe('VOtpInput.ts', () => { await wrapper.vm.$nextTick() expect(document.activeElement === element).toBe(true) - input.trigger('paste', event) + element.value = '1337078' + input.trigger('input') await wrapper.vm.$nextTick() expect(wrapper.vm.otp).toStrictEqual('133707'.split('')) }) - it('should process input when paste event with empty event', async () => { - const event = {} - - const wrapper = mountFunction({}) - - const input = wrapper.findAll('input').at(0) - - const element = input.element as HTMLInputElement - input.trigger('focus') - await wrapper.vm.$nextTick() - expect(document.activeElement === element).toBe(true) - - input.trigger('paste', event) - await wrapper.vm.$nextTick() - - expect(wrapper.vm.otp).toStrictEqual(''.split('')) - }) - it('should clear cursor when input typing is done', async () => { const onFinish = jest.fn() const clearFocus = jest.fn()