@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
146 lines (143 loc) • 5.56 kB
JavaScript
import React, { useEffect, useState } from 'react';
import { NUMBER_CLEAR_REGEX } from '../../constants/numberInput';
import { formateNumber, isValidString, parseFloatWithDecimals } from '../../utils/numberInput';
import Input from '../input/Input';
const NumberInput = _ref => {
let {
isDecimalInput,
isMoneyInput,
isTimeInput,
isInvalid,
maxNumber = Infinity,
value,
shouldTriggerChangeOnFormat = true,
placeholder,
onBlur,
isDisabled,
onChange,
shouldShowOnlyBottomBorder,
minNumber = -Infinity
} = _ref;
// the plainText will be shown in the input, when it is in focus
const [plainText, setPlainText] = useState('');
// the formattedValue will be shown in the input, when it is not in focus
const [formattedValue, setFormattedValue] = useState('');
const [hasFocus, setHasFocus] = useState(false);
const [shouldRemainPlaceholder, setShouldRemainPlaceholder] = useState(false);
const [isValueInvalid, setIsValueInvalid] = useState(false);
const localPlaceholder = placeholder ?? (isMoneyInput ? '€' : undefined);
const onLocalChange = event => {
const newValue = event.target.value;
const sanitizedValueString = newValue
// Removes everything except numbers, commas and points
.replace(NUMBER_CLEAR_REGEX, '');
if (isTimeInput && (sanitizedValueString.includes(':') && sanitizedValueString.length > 5 || !sanitizedValueString.includes(':') && sanitizedValueString.length > 4)) {
return;
}
const valueToCheck = sanitizedValueString.replaceAll(',', '.');
if (!isValidString({
string: valueToCheck,
isMoneyInput,
isDecimalInput,
isTimeInput
})) {
return;
}
if (maxNumber && Number(valueToCheck) > maxNumber || minNumber && Number(valueToCheck) < minNumber) {
return;
}
if (newValue.length === 1 && newValue.match(/^[0-9]+$/) === null) {
setShouldRemainPlaceholder(true);
} else {
setShouldRemainPlaceholder(false);
}
setPlainText(sanitizedValueString.replaceAll('.', ','));
if (typeof onChange === 'function') {
onChange(sanitizedValueString.replaceAll('.', ','));
}
};
const onLocalBlur = () => {
const sanitizedValue = plainText.length === 0 ? '0' : plainText;
let newIsInvalid = false;
let parsedNumber = null;
if (!isTimeInput) {
parsedNumber = parseFloatWithDecimals({
stringValue: sanitizedValue.replace(',', '.').replaceAll(':', '').replace('€', ''),
decimals: isMoneyInput ? 2 : undefined
});
if (parsedNumber !== 0 && (parsedNumber > maxNumber || parsedNumber < minNumber)) {
newIsInvalid = true;
}
setIsValueInvalid(newIsInvalid);
}
const newStringValue = plainText.length === 0 ? '' : formateNumber({
number: isTimeInput ? sanitizedValue : parsedNumber,
isMoneyInput,
isTimeInput
});
setFormattedValue(`${newStringValue} ${isMoneyInput ? '€' : ''}`);
setPlainText(newStringValue.replaceAll('.', ''));
setHasFocus(false);
if (typeof onChange === 'function' && shouldTriggerChangeOnFormat) {
onChange(newStringValue.replaceAll('.', ''));
}
if (typeof onBlur === 'function') {
if (isTimeInput) {
onBlur(newStringValue, newIsInvalid);
} else {
onBlur(parsedNumber === 0 ? null : parsedNumber, newIsInvalid);
}
}
};
const onLocalFocus = () => {
// formattedValue will be a number string with german number format (e.g. 1.000,00)
// It will remove all dots, so that the user can type in the number
setPlainText(formattedValue.replaceAll('.', '').replace('€', '').replaceAll(' ', ''));
// This will update the external state
if (typeof onChange === 'function' && shouldTriggerChangeOnFormat) {
onChange(formattedValue.replaceAll('.', '').replace('€', '').replaceAll(' ', ''));
}
setIsValueInvalid(false);
setHasFocus(true);
};
// updates the formattedValue, when the value changes
useEffect(() => {
let parsedNumber = null;
if (!isTimeInput) {
parsedNumber = parseFloatWithDecimals({
stringValue: plainText.replace(',', '.').replaceAll(':', '').replace('€', '').replaceAll(' ', ''),
decimals: isMoneyInput ? 2 : undefined
});
// checks, if a given number is invalid, if the input is not in focus
if (!hasFocus) {
setIsValueInvalid(parsedNumber > maxNumber || parsedNumber < minNumber);
}
}
setFormattedValue(plainText.length === 0 ? '' : `${formateNumber({
number: isTimeInput ? plainText : parsedNumber,
isMoneyInput,
isTimeInput
})}${isMoneyInput ? ' €' : ''}`);
}, [hasFocus, isMoneyInput, isTimeInput, maxNumber, minNumber, plainText]);
useEffect(() => {
if (typeof value === 'string') {
setPlainText(value.replace('€', '').replaceAll(' ', ''));
}
}, [value]);
return /*#__PURE__*/React.createElement(Input, {
shouldRemainPlaceholder: shouldRemainPlaceholder,
shouldShowOnlyBottomBorder: shouldShowOnlyBottomBorder,
inputMode: "decimal",
onChange: onLocalChange,
value: hasFocus ? plainText : formattedValue,
placeholder: localPlaceholder,
onBlur: onLocalBlur,
onFocus: onLocalFocus,
isDisabled: isDisabled,
isInvalid: typeof isInvalid === 'boolean' ? isInvalid : isValueInvalid,
shouldShowCenteredContent: shouldShowOnlyBottomBorder
});
};
NumberInput.displayName = 'NumberInput';
export default NumberInput;
//# sourceMappingURL=NumberInput.js.map