wix-style-react
Version:
wix-style-react
131 lines • 5.48 kB
JavaScript
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