UNPKG

@itwin/core-react

Version:

A react component library of iTwin.js UI general purpose components

160 lines 8.02 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Inputs */ import "./NumberInput.scss"; import classnames from "classnames"; import * as React from "react"; import { Key } from "ts-key-enum"; import { Input } from "@itwin/itwinui-react"; import { Icon } from "../../icons/IconComponent.js"; import { SvgCaretDown, SvgCaretDownSmall, SvgCaretUp, SvgCaretUpSmall, } from "@itwin/itwinui-icons-react"; const ForwardRefNumberInput = React.forwardRef(function ForwardRefNumberInput(props, ref) { const { containerClassName, className, value, min, max, precision, format, parse, onChange, onBlur, onKeyDown, step, snap, showTouchButtons, containerStyle, isControlled, ...otherProps } = props; const currentValueRef = React.useRef(value); /** * Used internally to parse the argument x to it's numeric representation. * If the argument cannot be converted to finite number returns 0; If a * "precision" prop is specified uses it round the number with that * precision (no fixed precision here because the return value is float, not * string). */ const parseInternal = React.useCallback((x) => { let n; if (parse) n = parse(x); if (undefined === n || null === n) { n = parseFloat(x); if (isNaN(n) || !isFinite(n)) { n = 0; } } const localPrecision = undefined === precision ? 10 : precision; const q = Math.pow(10, localPrecision); const localMin = undefined === min ? Number.MIN_SAFE_INTEGER : min; const localMax = undefined === max ? Number.MAX_SAFE_INTEGER : max; n = Math.min(Math.max(n, localMin), localMax); n = Math.round(n * q) / q; return n; }, [parse, precision, min, max]); /** * This is used internally to format a number to its display representation. * It will invoke the format function if one is provided. */ const formatInternal = React.useCallback((num) => { const localPrecision = undefined === precision || null === precision ? 0 : precision; const str = undefined === num || null === num ? "" : num.toFixed(localPrecision); if (format) return format(num, str); return str; }, [format, precision]); const [formattedValue, setFormattedValue] = React.useState(() => formatInternal(value)); // See if new initialValue props have changed since component mounted React.useEffect(() => { currentValueRef.current = value; const currentFormattedValue = formatInternal(currentValueRef.current); setFormattedValue(currentFormattedValue); }, [formatInternal, value]); const handleChange = React.useCallback((event) => { const newVal = event.currentTarget.value; setFormattedValue(newVal); isControlled && onChange && onChange(parseInternal(newVal), newVal); }, [isControlled, onChange, parseInternal]); const updateValue = React.useCallback((newVal) => { const newFormattedVal = formatInternal(newVal); onChange && onChange(newVal, newFormattedVal); setFormattedValue(newFormattedVal); }, [onChange, formatInternal]); const updateValueFromString = React.useCallback((strValue) => { const newVal = parseInternal(strValue); updateValue(newVal); }, [parseInternal, updateValue]); const handleBlur = React.useCallback((event) => { const newVal = parseInternal(event.target.value); onBlur && onBlur(event); updateValue(newVal); }, [parseInternal, updateValue, onBlur]); const getIncrementValue = React.useCallback((increment) => { if (typeof step === "function") { const stepVal = step(increment ? "up" : "down"); return stepVal ? stepVal : 1; } return !step ? 1 : step; }, [step]); /** * The internal method that actually sets the new value on the input */ const applyStep = React.useCallback((increment) => { const incrementValue = getIncrementValue(increment); let num = parseInternal(formattedValue) + (increment ? incrementValue : -incrementValue); if (snap) { num = Math.round(num / incrementValue) * incrementValue; } const localMin = undefined === min ? Number.MIN_SAFE_INTEGER : min; const localMax = undefined === max ? Number.MAX_SAFE_INTEGER : max; num = Math.min(Math.max(num, localMin), localMax); updateValue(num); }, [ formattedValue, getIncrementValue, max, min, parseInternal, snap, updateValue, ]); const handleKeyDown = React.useCallback((event) => { if (event.key === Key.Enter.valueOf()) { updateValueFromString(event.currentTarget.value); event.preventDefault(); event.stopPropagation(); } else if (event.key === Key.Escape.valueOf()) { setFormattedValue(formatInternal(currentValueRef.current)); event.preventDefault(); } else if (event.key === Key.ArrowDown.valueOf()) { applyStep(false); event.preventDefault(); event.stopPropagation(); } else if (event.key === Key.ArrowUp.valueOf()) { applyStep(true); event.preventDefault(); event.stopPropagation(); } onKeyDown && onKeyDown(event); }, [applyStep, formatInternal, updateValueFromString, onKeyDown]); const handleDownClick = React.useCallback((event) => { applyStep(false); event.preventDefault(); }, [applyStep]); const handleUpClick = React.useCallback((event) => { applyStep(true); event.preventDefault(); }, [applyStep]); const handleFocus = React.useCallback((event) => { event.currentTarget.select(); }, []); const isDisabled = !!otherProps.disabled; const containerClasses = classnames("core-number-input-container", containerClassName, showTouchButtons && "core-number-buttons-for-touch", isDisabled && "core-number-input-disabled"); const caretUp = showTouchButtons ? React.createElement(SvgCaretUp, null) : React.createElement(SvgCaretUpSmall, null); const caretDown = showTouchButtons ? React.createElement(SvgCaretDown, null) : React.createElement(SvgCaretDownSmall, null); return (React.createElement("div", { className: containerClasses, style: containerStyle }, React.createElement(Input, { ref: ref, value: formattedValue, onChange: handleChange, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, size: "small", className: classnames("core-input", className), ...otherProps }), React.createElement("div", { className: classnames("core-number-input-buttons-container", showTouchButtons && "core-number-buttons-for-touch") }, React.createElement("div", { className: "core-number-input-button core-number-input-button-up", tabIndex: -1, onClick: handleUpClick, role: "presentation" }, React.createElement(Icon, { iconSpec: caretUp })), React.createElement("div", { className: "core-number-input-button core-number-input-button-down", tabIndex: -1, onClick: handleDownClick, role: "presentation" }, React.createElement(Icon, { iconSpec: caretDown }))))); }); /** Input component for numbers with up and down buttons to increment and decrement the value. * @public * @deprecated in 4.12.0. Use {@link https://itwinui.bentley.com/docs/input iTwinUI input} instead. */ export const NumberInput = ForwardRefNumberInput; //# sourceMappingURL=NumberInput.js.map