-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
92 lines (75 loc) · 3.22 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import React, { useEffect, useRef, useState } from 'react'
const KEYBOARD_KEY_BACKSPACE = 8
const KEYBOARD_KEY_DELETE = 46
const MASK_CHAR = "*"
export default ({ value, size, autoFocus, onChange, ...props }) => {
const previousInput = useRef(null)
const refs = useRef([])
const [valueArray, setValueArray] = useState([])
useEffect(() => { setValueArray(mapPropsToState()) }, [value])
const mapPropsToState = () => Array(size).fill("").map((_, index) => (value || "")[index] || MASK_CHAR)
const clamp = (val, min, max) => val > max ? max : val < min ? min : val
const mapTransientArray = (cursor, valueString) => ([
...valueArray.slice(0, cursor),
...valueArray.slice(cursor, valueArray.length).map((_, index) => valueString[index] || valueArray[index + cursor])
])
const handleChange = (position, event) => {
var targetValue = event.target.value
/*
When adding value it could come from paste and have
more than 1 char, so we need a mapper instead of
replace at index
*/
if (targetValue) {
var valueArrayNew = mapTransientArray(position, targetValue)
} else {
var valueArrayNew = [...valueArray]
valueArrayNew[position] = MASK_CHAR
}
if (targetValue.length > 0) setCursorForward(position)
setValueArray(valueArrayNew)
onChange(valuesArrayToString(valueArrayNew), { valid: valueArrayNew.findIndex((char) => char === MASK_CHAR) < 0 })
}
const valuesArrayToString = (value) => value.join("")
const setCursorForward = (cursor) => {
refs.current[clamp(cursor + 1, 0, size - 1)].focus()
if (cursor + 1 < size) refs.current[clamp(cursor + 1, 0, size - 1)].select()
}
const setCursorBackward = (cursor) => {
refs.current[clamp(cursor - 1, 0, size - 1)].focus()
refs.current[clamp(cursor - 1, 0, size - 1)].select()
}
const handleFocus = (event) => event.target.select()
const handleKeyDown = (index) => previousInput.current = valueArray[index]
/*
When `onKeyUp` event is handled, `onChange` handler may have changed the focus because it fires before.
So, we need to make a check in the previous cursor input with `inputWas` to avoid moving cursor backwards two times
*/
const handleKeyUp = (index, event) => {
var value = String.fromCharCode(event.which)
if (previousInput.current === value) setCursorForward(index)
if (previousInput.current === MASK_CHAR && isBackKey(event)) setCursorBackward(index)
}
const isBackKey = (event) =>
KEYBOARD_KEY_BACKSPACE == (event.keyCode || event.charCode) || KEYBOARD_KEY_DELETE == (event.keyCode || event.charCode)
const charValue = (value) => value === MASK_CHAR ? "" : value
const shouldAutoFocus = (index) => autoFocus && index === 0
return (
<div>
{valueArray.map((valueItem, index) => (
<input
{...props}
key={index}
value={charValue(valueItem)}
ref={(ref) => refs.current.push(ref)}
autoFocus={shouldAutoFocus(index)}
onFocus={handleFocus}
onChange={(event) => handleChange(index, event)}
onKeyUp={(event) => handleKeyUp(index, event)}
onKeyDown={(event) => handleKeyDown(index, event)}
maxLength={1}
/>
))}
</div>
)
}