UNPKG

wix-style-react

Version:
131 lines 5.48 kB
import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import Input from '../Input'; import { defaultValueToNullIfEmpty, normalizeValue, isInRange, validateValue, } from './utils'; import { dataHooks } from './constants'; import deprecationLog from '../utils/deprecationLog'; const NumberInput = ({ suffix, defaultValue, strict = false, max, min, hideStepper = false, onChange, value: givenValue, inputRef, step = 1, status, invalidMessage, statusMessage, onInvalid, onKeyDown, ...props }) => { const initialState = { hasError: false, numberInputValue: '', }; const [state, setState] = React.useReducer((currentState, newState) => ({ ...currentState, ...newState, }), initialState); const [inputDOM, setInputDOM] = React.useState(null); const shouldShowError = state.hasError && invalidMessage; const setValueAndValidate = ({ value, shouldCallOnChangeCallback = true, }) => { const normalizedValue = normalizeValue(String(value)); const { hasError, validationType } = validateValue({ value: normalizedValue, minValue: min, maxValue: max, }); setState({ hasError, numberInputValue: normalizedValue, }); if (shouldCallOnChangeCallback && !hasError) { onChange?.(normalizedValue === '' ? null : Number(normalizedValue), normalizedValue); } if (hasError) { onInvalid?.(normalizedValue, { validationType, value: normalizedValue, }); } }; useEffect(() => { if (strict) { deprecationLog('<NumberInput/> - prop strict is deprecated and not needed anymore. By using min and max it will enforce strict both for the ticker and input automatically.'); } }, [strict]); useEffect(() => { const newNumberInputValue = defaultValueToNullIfEmpty(givenValue, defaultValue); setValueAndValidate({ value: newNumberInputValue, shouldCallOnChangeCallback: false, }); // TODO: fix ESLint error // eslint-disable-next-line react-hooks/exhaustive-deps }, [givenValue, defaultValue]); const increment = () => { const numberValue = parseFloat(String(state.numberInputValue) || String(inputDOM?.value)) || 0; const updatedValue = Number((numberValue + step).toPrecision(12)); if (isInRange({ value: updatedValue, minValue: min, maxValue: max })) { setValueAndValidate({ value: updatedValue }); } inputDOM?.focus(); }; const decrement = () => { const numberValue = parseFloat(String(state.numberInputValue) || String(inputDOM?.value)) || 0; const updatedValue = Number((numberValue - step).toPrecision(12)); if (isInRange({ value: updatedValue, minValue: min, maxValue: max })) { setValueAndValidate({ value: updatedValue }); } inputDOM?.focus(); }; const getInputRef = (ref) => { setInputDOM(ref); if (inputRef) { inputRef(ref); } }; const getStatusMessage = () => { if (shouldShowError) { return invalidMessage; } return statusMessage; }; const onInputValueChange = (event) => { const { value } = event.target; if (/^-?\d*[.,]?\d*$/.test(value)) { setValueAndValidate({ value }); } else { setState({ numberInputValue: state.numberInputValue }); } }; const incrementOrDecrementValue = (e) => { if (e.key === 'ArrowUp') { increment(); e.preventDefault(); } if (e.key === 'ArrowDown') { decrement(); e.preventDefault(); } onKeyDown?.(e); }; return (React.createElement(Input, { ...props, max: max, min: min, type: "text", value: state.numberInputValue, onChange: onInputValueChange, inputRef: getInputRef, status: shouldShowError ? 'error' : status, statusMessage: getStatusMessage(), onKeyDown: incrementOrDecrementValue, ariaRoledescription: "spin button", inputmode: "numeric", suffix: React.createElement(Input.Group, null, suffix, !hideStepper && (React.createElement(Input.Ticker, { onUp: increment, onDown: decrement, dataHook: dataHooks.numberInputTicker, upDisabled: max === state.numberInputValue, downDisabled: min === state.numberInputValue, onMouseDown: e => e.preventDefault() }))) })); }; NumberInput.displayName = 'NumberInput'; NumberInput.propTypes = { dataHook: PropTypes.string, className: PropTypes.string, id: PropTypes.string, defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), disabled: PropTypes.bool, status: PropTypes.oneOf(['error', 'warning', 'loading']), statusMessage: PropTypes.node, name: PropTypes.string, onBlur: PropTypes.func, onChange: PropTypes.func, onFocus: PropTypes.func, placeholder: PropTypes.string, prefix: PropTypes.node, size: PropTypes.oneOf(['small', 'medium', 'large']), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), min: PropTypes.number, max: PropTypes.number, step: PropTypes.number, strict: PropTypes.bool, hideStepper: PropTypes.bool, }; export default NumberInput; //# sourceMappingURL=NumberInput.js.map