Skip to content

Commit

Permalink
fix(VOtpInput): input focus control
Browse files Browse the repository at this point in the history
  • Loading branch information
johnleider committed Aug 2, 2023
1 parent dc23d1f commit 0a55521
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 18 deletions.
4 changes: 4 additions & 0 deletions packages/docs/src/pages/en/components/otp-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ The OTP input is used for MFA procedure of authenticating users by a one-time pa

----

::: warning
This feature requires [v3.3.11](/getting-started/release-notes/?version=v3.3.11)
:::

## Usage

Here we display a list of settings that could be applied within an application.
Expand Down
47 changes: 29 additions & 18 deletions packages/vuetify/src/labs/VOtpInput/VOtpInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,39 +103,50 @@ export const VOtpInput = genericComponent<VOtpInputSlots>()({
array[focusIndex.value] = value

model.value = array

let target: any = null

if (focusIndex.value > model.value.length) {
target = model.value.length + 1
} else if (focusIndex.value + 1 !== Number(props.length)) {
target = 'next'
} else {
requestAnimationFrame(() => current.value?.blur())
}

if (target) focusChild(contentRef.value!, target)
}

function onKeydown (e: KeyboardEvent) {
const array = model.value.slice()
const index = focusIndex.value
let target: 'next' | 'prev' | 'first' | 'last' | number | null = null

if (![
'ArrowLeft',
'ArrowRight',
'Backspace',
'Delete',
].includes(e.key)) return

e.preventDefault()

if (e.key === 'ArrowLeft') {
target = 'prev'
} else if (e.key === 'ArrowRight') {
target = 'next'
} else if (e.key === 'Backspace') {
if (focusIndex.value > 0) {
target = 'prev'
}
} else if (e.key === 'Delete') {
} else if (['Backspace', 'Delete'].includes(e.key)) {
array[focusIndex.value] = ''

model.value = array

requestAnimationFrame(() => {
inputRef.value[index].select()
})
} else if (props.type === 'number' && isNaN(parseInt(e.key))) {
return
} else if (focusIndex.value > model.value.length) {
target = model.value.length + 1
} else if (focusIndex.value + 1 !== Number(props.length)) {
target = 'next'
} else {
requestAnimationFrame(() => current.value?.blur())

return
if (focusIndex.value > 0 && e.key === 'Backspace') {
target = 'prev'
} else {
requestAnimationFrame(() => {
inputRef.value[index].select()
})
}
}

requestAnimationFrame(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/// <reference types="../../../../types/cypress" />

// Components
import { VOtpInput } from '../VOtpInput'

// Utilities
import { keyValues } from '@/util'

describe('VOtpInput', () => {
it('enters value and moves to next input and blurs at end', () => {
cy.mount(() => (<VOtpInput />))
.get('.v-otp-input input').eq(0)
.type('1')
.get('.v-otp-input input').eq(1)
.should('be.focused')
.type('2')
.get('.v-otp-input input').eq(2)
.should('be.focused')
.type('3')
.get('.v-otp-input input').eq(3)
.should('be.focused')
.type('4')
.get('.v-otp-input input').eq(4)
.should('be.focused')
.type('5')
.get('.v-otp-input input').eq(5)
.should('be.focused')
.type('6')
.should('not.be.focused')
})

it('removes value and stays on current input when using delete', () => {
cy.mount(() => (<VOtpInput />))
.get('.v-otp-input input').eq(0)
.type('1234')
.get('.v-otp-input input').eq(4)
.should('be.focused')
.trigger('keydown', { key: keyValues.left })
.trigger('keydown', { key: keyValues.left })
.get('.v-otp-input input').eq(2)
.should('be.focused')
.should('have.value', '3')
.trigger('keydown', { key: keyValues.delete })
.should('have.value', '4')
})

it('removes value and goes back when using backspace', () => {
cy.mount(() => (<VOtpInput />))
.get('.v-otp-input input').eq(0)
.type('1234')
.get('.v-otp-input input').eq(4)
.should('be.focused')
.trigger('keydown', { key: keyValues.backspace })
.get('.v-otp-input input').eq(3)
.should('be.focused')
.should('have.value', 4)
.trigger('keydown', { key: keyValues.backspace })
.get('.v-otp-input input').eq(2)
.should('be.focused')
.should('have.value', 3)
})
})

0 comments on commit 0a55521

Please sign in to comment.