UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

373 lines (370 loc) 11.5 kB
import React, { forwardRef, useState, useRef, useEffect } from 'react'; import { clamp, assignRef, useMergedRef, useOs } from '@mantine/hooks'; import { rem, useComponentDefaultProps, getSize } from '@mantine/styles'; import { getInputMode } from './get-input-mode/get-input-mode.js'; import { Chevron } from './Chevron.js'; import useStyles, { CONTROL_SIZES } from './NumberInput.styles.js'; import { TextInput } from '../TextInput/TextInput.js'; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; const defaultFormatter = (value) => value || ""; const defaultParser = (num) => { if (num === "-") { return num; } let tempNum = num; if (tempNum[0] === ".") { tempNum = `0${num}`; } const parsedNum = parseFloat(tempNum); if (Number.isNaN(parsedNum)) { return ""; } return num; }; const CHEVRON_SIZES = { xs: rem(10), sm: rem(14), md: rem(16), lg: rem(18), xl: rem(20) }; const defaultProps = { step: 1, hideControls: false, size: "sm", precision: 0, noClampOnBlur: false, removeTrailingZeros: false, formatter: defaultFormatter, parser: defaultParser, type: "text" }; const NumberInput = forwardRef((props, ref) => { const _a = useComponentDefaultProps("NumberInput", defaultProps, props), { readOnly, disabled, value, onChange, decimalSeparator, min, max, startValue, step, stepHoldInterval, stepHoldDelay, onBlur, onKeyDown, onKeyUp, hideControls, radius, variant, precision, removeTrailingZeros, defaultValue, noClampOnBlur, handlersRef, classNames, styles, size, rightSection, rightSectionWidth, formatter, parser, inputMode, unstyled, type } = _a, others = __objRest(_a, [ "readOnly", "disabled", "value", "onChange", "decimalSeparator", "min", "max", "startValue", "step", "stepHoldInterval", "stepHoldDelay", "onBlur", "onKeyDown", "onKeyUp", "hideControls", "radius", "variant", "precision", "removeTrailingZeros", "defaultValue", "noClampOnBlur", "handlersRef", "classNames", "styles", "size", "rightSection", "rightSectionWidth", "formatter", "parser", "inputMode", "unstyled", "type" ]); const { classes, cx } = useStyles({ radius }, { classNames, styles, unstyled, name: "NumberInput", variant, size }); const parsePrecision = (val) => { if (val === "") return ""; let result = val.toFixed(precision); if (removeTrailingZeros && precision > 0) { result = result.replace(new RegExp(`[0]{0,${precision}}$`), ""); if (result.endsWith(".") || result.endsWith(decimalSeparator)) { result = result.slice(0, -1); } } return result; }; const [_value, setValue] = useState(typeof value === "number" ? value : typeof defaultValue === "number" ? defaultValue : ""); const finalValue = typeof value === "number" ? value : _value; const [tempValue, setTempValue] = useState(typeof finalValue === "number" ? parsePrecision(finalValue) : ""); const inputRef = useRef(); const handleValueChange = (val) => { if (val !== _value && !Number.isNaN(val)) { typeof onChange === "function" && onChange(val); setValue(val); } }; const formatNum = (val = "") => { let parsedStr = typeof val === "number" ? String(val) : val; if (decimalSeparator) { parsedStr = parsedStr.replace(/\./g, decimalSeparator); } return formatter(parsedStr); }; const parseNum = (val) => { let num = val; if (decimalSeparator) { num = num.replace(new RegExp(`\\${decimalSeparator}`, "g"), "."); } return parser(num); }; const _min = typeof min === "number" ? min : -Infinity; const _max = typeof max === "number" ? max : Infinity; const incrementRef = useRef(); incrementRef.current = () => { var _a2, _b, _c; if (_value === "") { handleValueChange((_a2 = startValue != null ? startValue : min) != null ? _a2 : 0); setTempValue(startValue ? (_c = (_b = parsePrecision(startValue)) != null ? _b : parsePrecision(min)) != null ? _c : "0" : "0"); } else { const result = parsePrecision(clamp(_value + step, _min, _max)); handleValueChange(parseFloat(result)); setTempValue(result); } }; const decrementRef = useRef(); decrementRef.current = () => { var _a2, _b, _c; if (_value === "") { handleValueChange((_a2 = startValue != null ? startValue : min) != null ? _a2 : 0); setTempValue(startValue ? (_c = (_b = parsePrecision(startValue)) != null ? _b : parsePrecision(min)) != null ? _c : "0" : "0"); } else { const result = parsePrecision(clamp(_value - step, _min, _max)); handleValueChange(parseFloat(result)); setTempValue(result); } }; assignRef(handlersRef, { increment: incrementRef.current, decrement: decrementRef.current }); useEffect(() => { if (typeof value === "number") { setValue(value); setTempValue(parsePrecision(value)); } if ((defaultValue === "" || defaultValue === void 0) && value === "") { setValue(value); setTempValue(""); } }, [value, precision]); const shouldUseStepInterval = stepHoldDelay !== void 0 && stepHoldInterval !== void 0; const onStepTimeoutRef = useRef(null); const stepCountRef = useRef(0); const onStepDone = () => { if (onStepTimeoutRef.current) { window.clearTimeout(onStepTimeoutRef.current); } onStepTimeoutRef.current = null; stepCountRef.current = 0; }; const onStepHandleChange = (isIncrement) => { if (isIncrement) { incrementRef.current(); } else { decrementRef.current(); } stepCountRef.current += 1; }; const onStepLoop = (isIncrement) => { onStepHandleChange(isIncrement); if (shouldUseStepInterval) { const interval = typeof stepHoldInterval === "number" ? stepHoldInterval : stepHoldInterval(stepCountRef.current); onStepTimeoutRef.current = window.setTimeout(() => onStepLoop(isIncrement), interval); } }; const onStep = (event, isIncrement) => { event.preventDefault(); inputRef.current.focus(); onStepHandleChange(isIncrement); if (shouldUseStepInterval) { onStepTimeoutRef.current = window.setTimeout(() => onStepLoop(isIncrement), stepHoldDelay); } }; useEffect(() => { onStepDone(); return onStepDone; }, []); const controls = /* @__PURE__ */ React.createElement("div", { className: classes.rightSection }, /* @__PURE__ */ React.createElement("button", { type: "button", tabIndex: -1, "aria-hidden": true, disabled: finalValue >= max, className: cx(classes.control, classes.controlUp), onPointerDown: (event) => { onStep(event, true); }, onPointerUp: onStepDone, onPointerLeave: onStepDone }, /* @__PURE__ */ React.createElement(Chevron, { size: getSize({ size, sizes: CHEVRON_SIZES }), direction: "up" })), /* @__PURE__ */ React.createElement("button", { type: "button", tabIndex: -1, "aria-hidden": true, disabled: finalValue <= min, className: cx(classes.control, classes.controlDown), onPointerDown: (event) => { onStep(event, false); }, onPointerUp: onStepDone, onPointerLeave: onStepDone }, /* @__PURE__ */ React.createElement(Chevron, { size: getSize({ size, sizes: CHEVRON_SIZES }), direction: "down" }))); const handleChange = (event) => { const evt = event.nativeEvent; if (evt.isComposing) { return; } const val = event.target.value; const parsed = parseNum(val); setTempValue(parsed); if (val === "" || val === "-") { handleValueChange(""); } else { val.trim() !== "" && !Number.isNaN(parsed) && handleValueChange(parseFloat(parsed)); } }; const handleBlur = (event) => { var _a2; if (typeof value === "number" || value === "") { setTempValue(parsePrecision(value)); return; } if (event.target.value === "") { setTempValue(""); handleValueChange(""); } else { let newNumber = event.target.value; if (newNumber[0] === `${decimalSeparator}` || newNumber[0] === ".") { newNumber = `0${newNumber}`; } const parsedVal = parseNum(newNumber); const val = clamp(parseFloat(parsedVal), _min, _max); if (!Number.isNaN(val)) { if (!noClampOnBlur) { setTempValue(parsePrecision(val)); handleValueChange(parseFloat(parsePrecision(val))); } } else { setTempValue((_a2 = parsePrecision(finalValue)) != null ? _a2 : ""); } } typeof onBlur === "function" && onBlur(event); }; const handleKeyDown = (event) => { typeof onKeyDown === "function" && onKeyDown(event); if (event.repeat && shouldUseStepInterval && (event.key === "ArrowUp" || event.key === "ArrowDown")) { event.preventDefault(); return; } if (!readOnly) { if (event.key === "ArrowUp") { onStep(event, true); } else if (event.key === "ArrowDown") { onStep(event, false); } } }; const handleKeyUp = (event) => { typeof onKeyUp === "function" && onKeyUp(event); if (event.key === "ArrowUp" || event.key === "ArrowDown") { onStepDone(); } }; return /* @__PURE__ */ React.createElement(TextInput, __spreadProps(__spreadValues({}, others), { type, variant, value: formatNum(tempValue), disabled, readOnly, ref: useMergedRef(inputRef, ref), onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, rightSection: rightSection || (disabled || readOnly || hideControls || variant === "unstyled" ? null : controls), rightSectionWidth: rightSectionWidth != null ? rightSectionWidth : `calc(${getSize({ size, sizes: CONTROL_SIZES })} + ${rem(1)})`, radius, max, min, step, size, styles, classNames, inputMode: inputMode || getInputMode(step, precision, useOs()), __staticSelector: "NumberInput", unstyled })); }); NumberInput.displayName = "@mantine/core/NumberInput"; export { NumberInput }; //# sourceMappingURL=NumberInput.js.map