All files / NumberField NumberField.js

100% Statements 34/34
88.46% Branches 23/26
100% Functions 6/6
100% Lines 34/34

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                              27x             606x       606x   606x 606x   606x   606x 140x       606x 606x   606x   8x     606x 162x     606x 352x       606x   140x 140x       140x     140x 140x       606x 140x     140x 136x 136x   4x 4x       606x             28x 28x   28x                                     27x                                      
import PropTypes from 'prop-types';
import {
  useEffect,
  useRef,
  useState
} from 'react';
 
import noop from 'lodash/noop';
 
import {
  nativeChangeFieldValue as nativeChangeField,
  TextField
} from '@folio/stripes/components';
 
 
const NumberField = (props) => {
  const {
    onBlur: passedOnBlur = noop,
    onChange: passedOnChange = noop,
    input,
    value,
    ...rest
  } = props;
 
  // Treat TextField ALWAYS as a controlled component, whether or not NumberField itself is controlled
  // This enforces the behaviour whereby 1e2 -> 100 in the typed field automatically
  const [forceControl, setForceControl] = useState(value ?? input?.value);
 
  const inputRef = useRef();
  const userInputRef = useRef();
 
  const [numValue, setNumValue] = useState('');
 
  const changeField = (val) => {
    nativeChangeField(inputRef, false, val);
  };
 
  // Allow direct control of field
  useEffect(() => {
    const valueToUse = value ?? input.value;
 
    if (!valueToUse && numValue) {
      // Make sure to empty out if it's cleared. Treating '' as empty instead of undefined
      setNumValue('');
    }
 
    if (valueToUse && numValue !== valueToUse) {
      setNumValue(valueToUse);
    }
 
    if (forceControl !== numValue) {
      setForceControl(numValue);
    }
  }, [forceControl, numValue, value, input]);
 
  const handleChange = (e) => {
    // Actually set the value in the form
    Eif (input?.onChange) {
      input.onChange(e);
    }
 
    // Set the forced control (this may be wiped by passedOnChange)
    setForceControl(e.target.value);
 
    // If the user has set up an onChange, this will ensure that that fires
    Eif (passedOnChange) {
      passedOnChange(e, e.target.value);
    }
  };
 
  const handleUserChange = (e) => {
    const parsedValue = parseFloat(e.target.value);
 
    // ReturnValue needed for controlled components
    if (parsedValue || parsedValue === 0) {
      setNumValue(parsedValue);
      changeField(parsedValue);
    } else {
      setNumValue('');
      changeField('');
    }
  };
 
  return (
    <>
      <TextField
        ref={userInputRef}
        {...rest} // Keep an eye on this
        onBlur={(event) => {
          // Make sure blur propogates to input
          Eif (input.onBlur) {
            input.onBlur(event);
          }
          passedOnBlur(event);
        }}
        onChange={handleUserChange}
        type="number"
        value={forceControl}
      />
      <input
        ref={inputRef}
        {...input}
        hidden
        id={input?.name}
        onChange={handleChange}
        type="number"
        value={numValue}
      />
    </>
  );
};
 
NumberField.propTypes = {
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  input: PropTypes.shape({
    name: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired,
    onBlur: PropTypes.func,
    value: PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.string
    ])
  }),
  value: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string
  ])
};
 
export default NumberField;