UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

274 lines (271 loc) 8.8 kB
'use client'; import React, { useRef } from 'react'; import cx from 'clsx'; import { NumericFormat } from 'react-number-format'; import { useUncontrolled, clamp, assignRef, useMergedRef } from '@mantine/hooks'; import { getSize } from '../../core/utils/get-size/get-size.mjs'; import { createVarsResolver } from '../../core/styles-api/create-vars-resolver/create-vars-resolver.mjs'; import { useResolvedStylesApi } from '../../core/styles-api/use-resolved-styles-api/use-resolved-styles-api.mjs'; import { useStyles } from '../../core/styles-api/use-styles/use-styles.mjs'; import '../../core/MantineProvider/Mantine.context.mjs'; import '../../core/MantineProvider/default-theme.mjs'; import '../../core/MantineProvider/MantineProvider.mjs'; import '../../core/MantineProvider/MantineThemeProvider/MantineThemeProvider.mjs'; import { useProps } from '../../core/MantineProvider/use-props/use-props.mjs'; import '../../core/Box/Box.mjs'; import { factory } from '../../core/factory/factory.mjs'; import '../../core/DirectionProvider/DirectionProvider.mjs'; import { InputBase } from '../InputBase/InputBase.mjs'; import { UnstyledButton } from '../UnstyledButton/UnstyledButton.mjs'; import { NumberInputChevron } from './NumberInputChevron.mjs'; import classes from './NumberInput.module.css.mjs'; const partialNegativeNumberPattern = /^-0(\.0*)?$/; const leadingZerosPattern = /^-?0\d+$/; function isValidNumber(value) { return (typeof value === "number" ? value < Number.MAX_SAFE_INTEGER : !Number.isNaN(Number(value))) && !Number.isNaN(value); } function getDecrementedValue({ value, min, step = 1, allowNegative }) { const nextValue = value - step; if (min !== void 0 && nextValue < min) { return min; } if (!allowNegative && nextValue < 0 && min === void 0) { return value; } if (min !== void 0 && min >= 0 && nextValue <= min) { return nextValue; } return nextValue; } function isInRange(value, min, max) { if (value === void 0) { return true; } const minValid = min === void 0 || value >= min; const maxValid = max === void 0 || value <= max; return minValid && maxValid; } const defaultProps = { step: 1, clampBehavior: "blur", allowDecimal: true, allowNegative: true, startValue: 0 }; const varsResolver = createVarsResolver((_, { size }) => ({ controls: { "--ni-chevron-size": getSize(size, "ni-chevron-size") } })); const NumberInput = factory((_props, ref) => { const props = useProps("NumberInput", defaultProps, _props); const { className, classNames, styles, unstyled, vars, onChange, onValueChange, value, defaultValue, max, min, step, hideControls, rightSection, isAllowed, clampBehavior, onBlur, allowDecimal, decimalScale, onKeyDown, handlersRef, startValue, disabled, rightSectionPointerEvents, allowNegative, readOnly, size, rightSectionWidth, stepHoldInterval, stepHoldDelay, allowLeadingZeros, ...others } = props; const getStyles = useStyles({ name: "NumberInput", classes, props, classNames, styles, unstyled, vars, varsResolver }); const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({ classNames, styles, props }); const [_value, setValue] = useUncontrolled({ value, defaultValue, onChange }); const shouldUseStepInterval = stepHoldDelay !== void 0 && stepHoldInterval !== void 0; const inputRef = useRef(null); const onStepTimeoutRef = useRef(null); const stepCountRef = useRef(0); const handleValueChange = (payload, event) => { if (event.source === "event") { setValue( isValidNumber(payload.floatValue) && !partialNegativeNumberPattern.test(payload.value) && !(allowLeadingZeros ? leadingZerosPattern.test(payload.value) : false) ? payload.floatValue : payload.value ); } onValueChange?.(payload, event); }; const incrementRef = useRef(); incrementRef.current = () => { if (typeof _value !== "number" || Number.isNaN(_value)) { setValue(clamp(startValue, min, max)); } else if (max !== void 0) { setValue(_value + step <= max ? _value + step : max); } else { setValue(_value + step); } }; const decrementRef = useRef(); decrementRef.current = () => { if (typeof _value !== "number" || Number.isNaN(_value)) { setValue(clamp(startValue, min, max)); } else { setValue(getDecrementedValue({ value: _value, min, step, allowNegative })); } }; const handleKeyDown = (event) => { onKeyDown?.(event); if (readOnly) { return; } if (event.key === "ArrowUp") { event.preventDefault(); incrementRef.current(); } if (event.key === "ArrowDown") { event.preventDefault(); decrementRef.current(); } }; assignRef(handlersRef, { increment: incrementRef.current, decrement: decrementRef.current }); 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); } }; const onStepDone = () => { if (onStepTimeoutRef.current) { window.clearTimeout(onStepTimeoutRef.current); } onStepTimeoutRef.current = null; stepCountRef.current = 0; }; const controls = /* @__PURE__ */ React.createElement("div", { ...getStyles("controls") }, /* @__PURE__ */ React.createElement( UnstyledButton, { ...getStyles("control"), tabIndex: -1, "aria-hidden": true, disabled: disabled || typeof _value === "number" && max !== void 0 && _value >= max, mod: { direction: "up" }, onMouseDown: (event) => event.preventDefault(), onPointerDown: (event) => { onStep(event, true); }, onPointerUp: onStepDone, onPointerLeave: onStepDone }, /* @__PURE__ */ React.createElement(NumberInputChevron, { direction: "up" }) ), /* @__PURE__ */ React.createElement( UnstyledButton, { ...getStyles("control"), tabIndex: -1, "aria-hidden": true, disabled: disabled || typeof _value === "number" && min !== void 0 && _value <= min, mod: { direction: "down" }, onMouseDown: (event) => event.preventDefault(), onPointerDown: (event) => { onStep(event, false); }, onPointerUp: onStepDone, onPointerLeave: onStepDone }, /* @__PURE__ */ React.createElement(NumberInputChevron, { direction: "down" }) )); return /* @__PURE__ */ React.createElement( InputBase, { component: NumericFormat, allowNegative, className: cx(classes.root, className), size, ...others, readOnly, disabled, value: _value, getInputRef: useMergedRef(ref, inputRef), onValueChange: handleValueChange, rightSection: hideControls || readOnly ? rightSection : rightSection || controls, classNames: resolvedClassNames, styles: resolvedStyles, unstyled, __staticSelector: "NumberInput", decimalScale: allowDecimal ? decimalScale : 0, onKeyDown: handleKeyDown, rightSectionPointerEvents: rightSectionPointerEvents ?? (disabled ? "none" : void 0), rightSectionWidth: rightSectionWidth ?? `var(--ni-right-section-width-${size || "sm"})`, allowLeadingZeros, onBlur: (event) => { onBlur?.(event); if (clampBehavior === "blur" && typeof _value === "number") { const clampedValue = clamp(_value, min, max); if (clampedValue !== _value) { setValue(clamp(_value, min, max)); } } }, isAllowed: (val) => { if (clampBehavior === "strict") { if (isAllowed) { return isAllowed(val) && isInRange(val.floatValue, min, max); } return isInRange(val.floatValue, min, max); } return isAllowed ? isAllowed(val) : true; } } ); }); NumberInput.classes = { ...InputBase.classes, ...classes }; NumberInput.displayName = "@mantine/core/NumberInput"; export { NumberInput }; //# sourceMappingURL=NumberInput.mjs.map