UNPKG

@activecollab/components

Version:

ActiveCollab Components

250 lines (248 loc) • 10.2 kB
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