@activecollab/components
Version:
ActiveCollab Components
250 lines (248 loc) • 10.2 kB
JavaScript
import { useState, useRef, useCallback, useEffect } from "react";
import { validateNumberInput } from "../utils";
import { currencyMultiplier, formatNumber, getNumberFromString } from "../utils/currencyUtils";
export const useInputNumber = (_ref, inputRef) => {
let {
decimalSeparator = ".",
thousandSeparator = ",",
disableAbbreviation,
disableMacros,
decimalLength,
value = undefined,
onChange,
onSave,
onEnterKeyPress,
onClick,
onCancel,
allowEmptyValue,
step = 1,
trimDecimals = true,
limit,
validation = validateNumberInput,
min,
max,
onBlur,
update,
shortenThreshold = 1000
} = _ref;
const isMaxValid = max === undefined || min === undefined || Number(max) >= Number(min);
if (!isMaxValid) {
console.warn("Warning: The maximum value is set to be lower than the minimum value. The maximum value will be ignored.");
}
const [numericValue, setNumericValue] = useState(() => value);
const [prevNumericValue, setPrevNumericValue] = useState(() => value);
const [currentValue, setCurrentValue] = useState(() => formatNumber(value, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation ? "long" : "short", shortenThreshold));
const [prevValue, setPrevValue] = useState(() => formatNumber(value, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation ? "long" : "short", shortenThreshold));
const [unformattedValue, setUnformattedValue] = useState(() => formatNumber(value, "", decimalSeparator, false, decimalLength, "long", shortenThreshold));
const [unformattedPrevValue, setUnformattedPrevValue] = useState(() => formatNumber(value, "", decimalSeparator, false, decimalLength, "long", shortenThreshold));
const [focused, setFocused] = useState(false);
useEffect(() => {
if (value !== prevNumericValue && (!focused || update)) {
setCurrentValue(formatNumber(value, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation ? "long" : "short", shortenThreshold));
setPrevValue(formatNumber(value, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation ? "long" : "short", shortenThreshold));
setUnformattedValue(formatNumber(value, "", decimalSeparator, false, decimalLength, "long", shortenThreshold));
setUnformattedPrevValue(formatNumber(value, "", decimalSeparator, false, decimalLength, "long", shortenThreshold));
setNumericValue(value);
setPrevNumericValue(value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disableAbbreviation, thousandSeparator, decimalSeparator, decimalLength, trimDecimals, value, focused]);
const escapeRef = useRef(false);
const handleBlur = useCallback(e => {
if (escapeRef.current) {
setCurrentValue(prevValue);
setUnformattedValue(unformattedPrevValue);
setNumericValue(prevNumericValue);
if (onChange) onChange(prevNumericValue);
} else {
if (e.target.value.trim().length > 0 && prevValue !== e.target.value) {
const _value = formatNumber(currentValue, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation ? "long" : "short", shortenThreshold);
const _unformatedValue = formatNumber(currentValue, "", decimalSeparator, false, decimalLength, "long", shortenThreshold);
const _numericValue = getNumberFromString(_unformatedValue, thousandSeparator, decimalSeparator);
setPrevValue(_value);
setUnformattedPrevValue(_unformatedValue);
setUnformattedValue(_unformatedValue);
setNumericValue(_numericValue);
setPrevNumericValue(_numericValue);
if (_numericValue !== prevNumericValue) onChange == null || onChange(_numericValue);
setCurrentValue(_value);
typeof onSave === "function" && onSave(e);
} else {
if (!allowEmptyValue) {
setCurrentValue(prevValue);
setUnformattedValue(unformattedPrevValue);
setNumericValue(prevNumericValue);
onChange == null || onChange(prevNumericValue);
typeof onCancel === "function" && onCancel(e);
} else {
if (prevValue !== e.target.value) {
onSave == null || onSave(e);
} else {
typeof onCancel === "function" && onCancel(e);
}
}
}
}
setFocused(false);
typeof onBlur === "function" && onBlur(e);
}, [currentValue, onBlur, prevValue, unformattedPrevValue, prevNumericValue, onChange, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation, shortenThreshold, onSave, allowEmptyValue, onCancel]);
const updateValue = useCallback(type => {
if (max !== undefined && Number(numericValue) > max || min !== undefined && Number(numericValue) < min) {
return;
}
let newValue = numericValue;
if (type === "increment") {
if (max === undefined || Number(numericValue) + step <= max) {
newValue = Number(numericValue) + step;
} else if (Number(numericValue) < max) {
newValue = max;
} else {
return;
}
} else if (type === "decrement") {
if (min === undefined || Number(numericValue) - step >= min) {
newValue = Number(numericValue) - step;
} else if (Number(numericValue) > min) {
newValue = min;
} else {
return;
}
}
let formattedValue;
if (decimalLength !== undefined) {
formattedValue = Number(newValue).toFixed(decimalLength);
} else {
formattedValue = Number(newValue).toFixed(2);
}
if (decimalLength !== undefined && decimalLength === 0) {
formattedValue = Math.round(Number(newValue)).toString();
}
if (formattedValue.includes(".") || formattedValue.includes(",")) {
formattedValue = formattedValue.replace(".", decimalSeparator);
}
setNumericValue(newValue);
setPrevNumericValue(newValue);
setUnformattedValue(formattedValue);
setCurrentValue(formattedValue);
if (onChange) onChange(newValue);
}, [max, numericValue, min, decimalLength, onChange, step, decimalSeparator]);
const handleKeyDown = useCallback(e => {
if (e.key === "Enter") {
e.target.blur();
if (typeof onEnterKeyPress === "function") onEnterKeyPress(numericValue);
}
if (e.key === "ArrowLeft") {
return;
}
if (e.key === "ArrowRight") {
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
updateValue("increment");
}
if (e.key === "ArrowDown") {
e.preventDefault();
updateValue("decrement");
}
if (e.key === "Escape") {
escapeRef.current = true;
e.target.blur();
typeof onCancel === "function" && onCancel(e);
escapeRef.current = false;
}
if (e.key === "Backspace") {
return;
}
if (e.key === "Delete") {
return;
}
if ((e.metaKey || e.ctrlKey) && e.key === "a") {
var _inputRef$current;
(_inputRef$current = inputRef.current) == null || _inputRef$current.select();
return;
}
if (e.key === "Tab") {
return;
}
if ((e.metaKey || e.ctrlKey) && e.key === "v") {
e.preventDefault();
return;
}
// Disallow "-" if min is 0 or greater
if (e.key === "-" && min !== undefined && Number(min) >= 0) {
e.preventDefault();
return;
}
// Disallow decimal separator if decimalLength is 0
if (e.key === decimalSeparator && decimalLength === 0) {
e.preventDefault();
return;
}
const input = e.target;
const currentValue = input.value;
const start = input.selectionStart;
const end = input.selectionEnd;
const newValue = currentValue.substring(0, start) + e.key + currentValue.substring(end);
if (!validation(newValue, Boolean(disableMacros), decimalSeparator, decimalLength != null ? decimalLength : 0, limit)) {
e.preventDefault();
return;
}
}, [decimalLength, decimalSeparator, disableMacros, inputRef, limit, min, numericValue, onCancel, onEnterKeyPress, updateValue, validation]);
const handleChange = useCallback(e => {
const inputValue = e.target.value;
let numericNewValue = undefined;
const numericInput = disableMacros ? inputValue : inputValue.replace(/(\d+(?:[.,]\d+)?)([kmbtKMBT])/, (_, num, unit) => {
const normalizedNum = num.replace(",", ".");
const parts = normalizedNum.split(".");
const integerPart = parts[0];
const fractionalPart = parts[1];
const newInteger = parseInt(integerPart, 10) * currencyMultiplier[unit.toLowerCase()];
numericNewValue = parseFloat((newInteger ? newInteger : 0) + "." + (fractionalPart ? fractionalPart : 0));
let result = newInteger.toString();
if (fractionalPart !== undefined) {
result += decimalSeparator + fractionalPart;
}
return result;
});
if (numericNewValue === undefined) {
if (inputValue) {
numericNewValue = parseFloat(inputValue.replace(",", "."));
} else {
numericNewValue = undefined;
}
}
setCurrentValue(numericInput);
setUnformattedValue(numericInput);
setNumericValue(numericNewValue);
if (onChange && numericNewValue !== numericValue) onChange(numericNewValue);
}, [disableMacros, onChange, numericValue, decimalSeparator]);
const handleClick = useCallback(e => {
if (typeof onClick === "function") {
onClick(e);
}
}, [onClick]);
const handleFocus = useCallback(() => {
setCurrentValue(unformattedValue);
setFocused(true);
}, [unformattedValue]);
const handleDoubleClick = useCallback(() => {
if (inputRef.current) {
var _inputRef$current2;
(_inputRef$current2 = inputRef.current) == null || _inputRef$current2.select();
}
}, [inputRef]);
return {
value: currentValue,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
onChange: handleChange,
onClick: handleClick,
onDoubleClick: handleDoubleClick,
onFocus: handleFocus,
focused,
unformattedValue,
numericValue
};
};
//# sourceMappingURL=useInputNumber.js.map