All files / atom/pinInput/src index.js

79.16% Statements 19/24
63.63% Branches 7/11
50% Functions 4/8
79.16% Lines 19/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153                            1x   1x                                         5x 5x 5x           5x   5x         5x 3x     5x 3x     5x             5x             5x   30x         5x       5x                                                     1x 1x                                                                              
import {forwardRef, useCallback, useEffect, useRef} from 'react'
 
import PropTypes from 'prop-types'
 
import useMergeRefs from '@s-ui/react-hooks/lib/useMergeRefs'
 
import useKeyPress from './hooks/useKeyPress.js'
import useUpdateEffect from './hooks/useUpdateEffect.js'
import {actions as pinInputActions, usePinInputReducer} from './reducer/index.js'
import {BASE_CLASSNAME, getValueType, MASK, SIZES, STATUS} from './config.js'
import PinInputChildren from './PinInputChildren.js'
import {PinInputContextProvider} from './PinInputContext.js'
import PinInputField from './PinInputField.js'
 
const CLASSNAME = BASE_CLASSNAME
 
const PinInput = forwardRef(
  (
    {
      autoFocus = false,
      children,
      defaultValue = '',
      disabled,
      inputMode,
      isOneTimeCode = true,
      isPassword = false,
      length = 6,
      mask,
      onChange,
      placeholder = '',
      size = SIZES.MEDIUM,
      status,
      value,
      ...props
    },
    forwardedRef
  ) => {
    const innerRef = useRef()
    const targetRef = useRef()
    const [reducerStore, dispatch] = usePinInputReducer({
      mask,
      defaultValue,
      value,
      disabled
    })
    const {innerValue, focusPosition, elements} = reducerStore
 
    useUpdateEffect(() => {
      const innerValue = typeof value === 'string' ? value.split('') : value || []
      dispatch(pinInputActions.setValue({innerValue}))
    }, [`${value}`, dispatch])
 
    useEffect(() => {
      dispatch(pinInputActions.setDisabled({disabled}))
    }, [disabled, dispatch])
 
    useEffect(() => {
      dispatch(pinInputActions.setMask({mask}))
    }, [mask, dispatch])
 
    const setFocus = useCallback(
      position => {
        dispatch(pinInputActions.setFocus({focusPosition: position}))
      },
      [dispatch]
    )
 
    useKeyPress(
      event => {
        dispatch(pinInputActions.setKey({event, onChange}))
      },
      {target: targetRef, onChange}
    )
 
    const getIndex = useCallback(
      node => {
        return elements.indexOf(node)
      },
      [elements]
    )
 
    useUpdateEffect(() => {
      setFocus(focusPosition)
    }, [focusPosition])
 
    return (
      <div className={CLASSNAME} ref={targetRef}>
        <PinInputContextProvider
          disabled={reducerStore.disabled}
          dispatch={dispatch}
          getIndex={getIndex}
          inputMode={inputMode}
          isOneTimeCode={isOneTimeCode}
          isPassword={isPassword}
          mask={reducerStore.mask}
          placeholder={placeholder}
          setFocus={setFocus}
          size={size}
          status={status}
          targetRef={innerRef}
          value={innerValue}
        >
          <PinInputChildren length={length} autoFocus={autoFocus}>
            {children}
          </PinInputChildren>
        </PinInputContextProvider>
        <input type="hidden" value={innerValue.filter(Boolean).join('')} ref={useMergeRefs(innerRef, forwardedRef)} />
      </div>
    )
  }
)
 
PinInput.displayName = 'PinInput'
PinInput.propTypes = {
  /** boolean to autoFocus the first input */
  autoFocus: PropTypes.bool,
  /** children the components is gonna have  */
  children: PropTypes.node,
  /** default value for the input */
  defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
  /** true for disabled false for default */
  disabled: PropTypes.bool,
  /** inputmode **/
  inputMode: PropTypes.string,
  /** true for autocomplete from keyboard false for default  */
  isOneTimeCode: PropTypes.bool,
  /** true to make the input type password false for text */
  isPassword: PropTypes.bool,
  /** defines the number of cells */
  length: PropTypes.number,
  /** name of the custom mask (NUMBER, ALPHABETIC, ALPHANUMERIC) */
  mask: PropTypes.oneOfType([PropTypes.oneOf(Object.values(MASK)), PropTypes.string]),
  /** function executed on value change */
  onChange: PropTypes.func,
  /** placeholder for the input */
  placeholder: PropTypes.string,
  /** set the size of the input (XXSMALL, XSMALL, SMALL, MEDIUM, LARGE, XLARGE, XXLARGE) */
  size: PropTypes.oneOf(Object.values(SIZES)),
  /** set the input status (ERROR, SUCCESS, WARNING) */
  status: PropTypes.oneOf(Object.values(BASE_CLASSNAME)),
  /** input value */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])
}
export default PinInput
 
export {
  PinInputField,
  MASK as pinInputMask,
  SIZES as pinInputSizes,
  STATUS as pinInputStatus,
  getValueType as getPinInputValueType
}