UNPKG

@activecollab/components

Version:

ActiveCollab Components

228 lines (226 loc) • 9.23 kB
import { useState, useRef, useCallback, useEffect } from "react"; import { validateNumberInput } from "../utils"; import { currencyMultiplier, formatNumber } from "../utils/currencyUtils"; export const useInputNumber = (_ref, inputRef) => { let { decimalSeparator = ".", thousandSeparator = ",", disableAbbreviation, disableMacros, decimalLength, value = "", 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 [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 !== prevValue && (!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)); } // 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); if (onChange) onChange(String(unformattedPrevValue)); } else { if (e.target.value.trim().length > 0 && prevValue !== e.target.value) { const _value = formatNumber(currentValue, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation ? "long" : "short", shortenThreshold); setPrevValue(_value); setUnformattedPrevValue(formatNumber(currentValue, "", decimalSeparator, false, decimalLength, "long", shortenThreshold)); setUnformattedValue(formatNumber(currentValue, "", decimalSeparator, false, decimalLength, "long", shortenThreshold)); setCurrentValue(_value); typeof onSave === "function" && onSave(e); } else { if (!allowEmptyValue) { setCurrentValue(prevValue); setUnformattedValue(unformattedPrevValue); typeof onCancel === "function" && onCancel(e); } else { if (typeof onSave === "function" && prevValue !== e.target.value) { onSave(e); } else { typeof onCancel === "function" && onCancel(e); } } } } setFocused(false); typeof onBlur === "function" && onBlur(e); }, [onBlur, prevValue, unformattedPrevValue, onChange, currentValue, thousandSeparator, decimalSeparator, trimDecimals, decimalLength, disableAbbreviation, shortenThreshold, onSave, allowEmptyValue, onCancel]); const updateValue = useCallback(type => { const preformattedValue = String(unformattedValue).replace(thousandSeparator, "").replace(decimalSeparator, "."); const numericValue = parseFloat(preformattedValue); if (isNaN(numericValue)) return; if (max !== undefined && numericValue > Number(max) || min !== undefined && numericValue < Number(min)) { return; } let newValue = numericValue; if (type === "increment") { if (max === undefined || numericValue + step <= Number(max)) { newValue = numericValue + step; } else if (numericValue < Number(max)) { newValue = Number(max); } else { return; } } else if (type === "decrement") { if (min === undefined || numericValue - step >= Number(min)) { newValue = numericValue - step; } else if (numericValue > Number(min)) { newValue = Number(min); } else { return; } } let formattedValue; if (decimalLength !== undefined) { formattedValue = newValue.toFixed(decimalLength); } else { formattedValue = newValue.toFixed(2); } if (decimalLength !== undefined && decimalLength === 0) { formattedValue = Math.round(newValue).toString(); } if (formattedValue.includes(".") || formattedValue.includes(",")) { formattedValue = formattedValue.replace(".", decimalSeparator); } setUnformattedValue(formattedValue); setCurrentValue(formattedValue); if (onChange) onChange(formattedValue); }, [unformattedValue, thousandSeparator, decimalSeparator, decimalLength, onChange, step, min, max]); const handleKeyDown = useCallback(e => { if (e.key === "Enter") { e.target.blur(); if (typeof onEnterKeyPress === "function") onEnterKeyPress(e.target.value); } 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, onCancel, onEnterKeyPress, updateValue, validation]); const handleChange = useCallback(e => { const inputValue = e.target.value; 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()]; let result = newInteger.toString(); if (fractionalPart !== undefined) { result += decimalSeparator + fractionalPart; } return result; }); setCurrentValue(numericInput); setUnformattedValue(numericInput); if (onChange) onChange(numericInput); }, [disableMacros, decimalSeparator, onChange]); 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 }; }; //# sourceMappingURL=useInputNumber.js.map