@itwin/core-react
Version:
A react component library of iTwin.js UI general purpose components
160 lines • 8.02 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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