UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

329 lines (326 loc) 11.4 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { 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/MantineProvider/MantineCssVariables/MantineCssVariables.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 leadingDecimalZeroPattern = /^(0\.0*|-0(\.0*)?)$/; const leadingZerosPattern = /^-?0\d+(\.\d+)?\.?$/; function isValidNumber(value) { return (typeof value === "number" ? value < Number.MAX_SAFE_INTEGER : !Number.isNaN(Number(value))) && !Number.isNaN(value); } 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, withKeyboardEvents: true, allowLeadingZeros: true, trimLeadingZeroesOnBlur: 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, onKeyDownCapture, handlersRef, startValue, disabled, rightSectionPointerEvents, allowNegative, readOnly, size, rightSectionWidth, stepHoldInterval, stepHoldDelay, allowLeadingZeros, withKeyboardEvents, trimLeadingZeroesOnBlur, ...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) && !leadingDecimalZeroPattern.test(payload.value) && !(allowLeadingZeros ? leadingZerosPattern.test(payload.value) : false) ? payload.floatValue : payload.value ); } onValueChange?.(payload, event); }; const getDecimalPlaces = (inputValue) => { const match = String(inputValue).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); if (!match) { return 0; } return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)); }; const adjustCursor = (position) => { if (inputRef.current && typeof position !== "undefined") { inputRef.current.setSelectionRange(position, position); } }; const incrementRef = useRef(); incrementRef.current = () => { let val; const currentValuePrecision = getDecimalPlaces(_value); const stepPrecision = getDecimalPlaces(step); const maxPrecision = Math.max(currentValuePrecision, stepPrecision); const factor = 10 ** maxPrecision; if (typeof _value !== "number" || Number.isNaN(_value)) { val = clamp(startValue, min, max); } else if (max !== void 0) { const incrementedValue = (Math.round(_value * factor) + Math.round(step * factor)) / factor; val = incrementedValue <= max ? incrementedValue : max; } else { val = (Math.round(_value * factor) + Math.round(step * factor)) / factor; } const formattedValue = val.toFixed(maxPrecision); setValue(parseFloat(formattedValue)); onValueChange?.( { floatValue: parseFloat(formattedValue), formattedValue, value: formattedValue }, { source: "increment" } ); setTimeout(() => adjustCursor(inputRef.current?.value.length), 0); }; const decrementRef = useRef(); decrementRef.current = () => { let val; const minValue = min !== void 0 ? min : !allowNegative ? 0 : Number.MIN_SAFE_INTEGER; const currentValuePrecision = getDecimalPlaces(_value); const stepPrecision = getDecimalPlaces(step); const maxPrecision = Math.max(currentValuePrecision, stepPrecision); const factor = 10 ** maxPrecision; if (typeof _value !== "number" || Number.isNaN(_value)) { val = clamp(startValue, minValue, max); } else { const decrementedValue = (Math.round(_value * factor) - Math.round(step * factor)) / factor; val = minValue !== void 0 && decrementedValue < minValue ? minValue : decrementedValue; } const formattedValue = val.toFixed(maxPrecision); setValue(parseFloat(formattedValue)); onValueChange?.( { floatValue: parseFloat(formattedValue), formattedValue, value: formattedValue }, { source: "decrement" } ); setTimeout(() => adjustCursor(inputRef.current?.value.length), 0); }; const handleKeyDown = (event) => { onKeyDown?.(event); if (readOnly || !withKeyboardEvents) { return; } if (event.key === "ArrowUp") { event.preventDefault(); incrementRef.current(); } if (event.key === "ArrowDown") { event.preventDefault(); decrementRef.current(); } }; const handleKeyDownCapture = (event) => { onKeyDownCapture?.(event); if (event.key === "Backspace") { const input = inputRef.current; if (input.selectionStart === 0 && input.selectionStart === input.selectionEnd) { event.preventDefault(); window.setTimeout(() => adjustCursor(0), 0); } } }; 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__ */ jsxs("div", { ...getStyles("controls"), children: [ /* @__PURE__ */ jsx( 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, children: /* @__PURE__ */ jsx(NumberInputChevron, { direction: "up" }) } ), /* @__PURE__ */ jsx( 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, children: /* @__PURE__ */ jsx(NumberInputChevron, { direction: "down" }) } ) ] }); return /* @__PURE__ */ jsx( 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, onKeyDownCapture: handleKeyDownCapture, 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)); } } if (trimLeadingZeroesOnBlur && typeof _value === "string") { const replaced = _value.replace(/^0+/, ""); const parsedValue = parseFloat(replaced); setValue( Number.isNaN(parsedValue) || parsedValue > Number.MAX_SAFE_INTEGER ? replaced : parsedValue ); } }, 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