rsuite
Version:
A suite of react components
157 lines (156 loc) • 5.14 kB
JavaScript
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import React from 'react';
import ArrowUpLineIcon from '@rsuite/icons/ArrowUpLine';
import ArrowDownLineIcon from '@rsuite/icons/ArrowDownLine';
import InputGroup from "../InputGroup/InputGroup.js";
import InputGroupAddon from "../InputGroup/InputGroupAddon.js";
import Input from "../Input/index.js";
import Button from "../Button/index.js";
import { useStyles, useCustom, useControlled, useEventCallback } from "../internals/hooks/index.js";
import { forwardRef, partitionHTMLProps, createChainedFunction } from "../internals/utils/index.js";
import { useNumberInputValue } from "./hooks/useNumberInputValue.js";
import { useEvents } from "./hooks/useEvents.js";
import { valueReachesMax, valueReachesMin } from "./utils/number.js";
/**
* The `NumberInput` component is used to enter a numerical value.
* @see https://rsuitejs.com/components/number-input
*/
const NumberInput = forwardRef((props, ref) => {
const {
propsWithDefaults
} = useCustom('NumberInput', props);
const {
as,
className,
classPrefix = 'number-input',
controls = true,
disabled,
decimalSeparator,
formatter,
readOnly,
plaintext,
value: valueProp,
defaultValue,
size,
prefix: prefixElement,
postfix,
suffix = postfix,
step = 1,
buttonAppearance = 'subtle',
min: minProp,
max: maxProp,
scrollable = true,
onChange,
onWheel,
onBlur: onBlurProp,
onFocus: onFocusProp,
...rest
} = propsWithDefaults;
const min = minProp !== null && minProp !== void 0 ? minProp : -Infinity;
const max = maxProp !== null && maxProp !== void 0 ? maxProp : Infinity;
const [value, setValue] = useControlled(valueProp, defaultValue);
const {
withPrefix,
merge,
prefix
} = useStyles(classPrefix);
const classes = merge(className, withPrefix());
const [htmlInputProps, restProps] = partitionHTMLProps(rest);
const onChangeValue = (currentValue, event) => {
if (currentValue !== value) {
setValue(currentValue);
onChange === null || onChange === void 0 || onChange(currentValue, event);
}
};
const {
inputRef,
isFocused,
onStepUp,
onStepDown,
onKeyDown,
onFocus,
onBlur
} = useEvents({
min: minProp,
max: maxProp,
step,
value,
scrollable,
disabled,
readOnly,
decimalSeparator,
onWheel,
onChangeValue
});
const handleChange = useEventCallback((value, event) => {
const separator = decimalSeparator || '.';
const escapedSeparator = separator.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
// Support both custom decimalSeparator and standard decimal point '.'
let regex;
if (separator !== '.') {
// Allow both the custom separator and the standard decimal point
regex = new RegExp(`^-?(?:\\d+)?([.${escapedSeparator}])?\\d*$`);
} else {
regex = new RegExp(`^-?(?:\\d+)?(${escapedSeparator})?\\d*$`);
}
if (!regex.test(value) && value !== '') {
return;
}
onChangeValue(value, event);
});
const inputValue = useNumberInputValue({
value,
isFocused,
formatter,
decimalSeparator
});
const input = /*#__PURE__*/React.createElement(Input, _extends({}, htmlInputProps, {
ref: plaintext ? ref : undefined,
inputRef: inputRef,
autoComplete: "off",
inputMode: "numeric",
step: step,
value: inputValue,
disabled: disabled,
readOnly: readOnly,
plaintext: plaintext,
onKeyDown: onKeyDown,
onChange: handleChange,
onBlur: createChainedFunction(onBlur, onBlurProp),
onFocus: createChainedFunction(onFocus, onFocusProp)
}));
if (plaintext) {
return input;
}
const stepUpDisabled = disabled || readOnly || valueReachesMax(value, max);
const stepDownDisabled = disabled || readOnly || valueReachesMin(value, min);
return /*#__PURE__*/React.createElement(InputGroup, _extends({
as: as,
ref: ref,
className: classes,
disabled: disabled,
size: size,
inside: true
}, restProps), prefixElement && /*#__PURE__*/React.createElement(InputGroupAddon, null, prefixElement), input, suffix && /*#__PURE__*/React.createElement(InputGroupAddon, null, suffix), controls && /*#__PURE__*/React.createElement("span", {
className: prefix('btn-group-vertical')
}, /*#__PURE__*/React.createElement(Button, {
tabIndex: -1,
appearance: buttonAppearance,
className: prefix('touchspin-up'),
onClick: onStepUp,
disabled: stepUpDisabled,
"aria-label": "Increment",
size: size
}, typeof controls === 'function' ? controls('up') : /*#__PURE__*/React.createElement(ArrowUpLineIcon, null)), /*#__PURE__*/React.createElement(Button, {
tabIndex: -1,
appearance: buttonAppearance,
className: prefix('touchspin-down'),
onClick: onStepDown,
disabled: stepDownDisabled,
"aria-label": "Decrement",
size: size
}, typeof controls === 'function' ? controls('down') : /*#__PURE__*/React.createElement(ArrowDownLineIcon, null))));
});
NumberInput.displayName = 'NumberInput';
export default NumberInput;