@mantine/core
Version:
React components library focused on usability, accessibility and developer experience
435 lines (434 loc) • 20.3 kB
JavaScript
"use client";
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
const require_noop = require("../../core/utils/noop/noop.cjs");
const require_get_size = require("../../core/utils/get-size/get-size.cjs");
const require_create_vars_resolver = require("../../core/styles-api/create-vars-resolver/create-vars-resolver.cjs");
const require_use_props = require("../../core/MantineProvider/use-props/use-props.cjs");
const require_use_resolved_styles_api = require("../../core/styles-api/use-resolved-styles-api/use-resolved-styles-api.cjs");
const require_use_styles = require("../../core/styles-api/use-styles/use-styles.cjs");
const require_factory = require("../../core/factory/factory.cjs");
const require_UnstyledButton = require("../UnstyledButton/UnstyledButton.cjs");
const require_InputBase = require("../InputBase/InputBase.cjs");
const require_NumberInputChevron = require("./NumberInputChevron.cjs");
const require_NumberInput_module = require("./NumberInput.module.cjs");
let react = require("react");
react = require_runtime.__toESM(react);
let _mantine_hooks = require("@mantine/hooks");
let clsx = require("clsx");
clsx = require_runtime.__toESM(clsx);
let react_jsx_runtime = require("react/jsx-runtime");
let react_number_format = require("react-number-format");
//#region packages/@mantine/core/src/components/NumberInput/NumberInput.tsx
const leadingDecimalZeroPattern = /^(0\.0*|-0(\.0*)?)$/;
const leadingZerosPattern = /^-?0\d+(\.\d+)?\.?$/;
const trailingZerosPattern = /\.\d*0$/;
const trailingDecimalSeparatorPattern = /^-?\d+\.$/;
function isNumberString(value) {
return typeof value === "string" && value !== "" && !Number.isNaN(Number(value));
}
function isBigIntValue(value) {
return typeof value === "bigint";
}
function canStep(value) {
if (typeof value === "number") return value < Number.MAX_SAFE_INTEGER;
return value === "" || isNumberString(value) && Number(value) < Number.MAX_SAFE_INTEGER;
}
function isValidBigIntString(value, allowNegative) {
if (value === "") return false;
if (value === "-") return false;
if (!allowNegative && value.startsWith("-")) return false;
return /^-?\d+$/.test(value);
}
function canStepBigInt(value, allowNegative) {
if (typeof value === "bigint") return true;
return value === "" || isValidBigIntString(value, allowNegative);
}
function parseBigIntFromString(value) {
if (!/^-?\d+$/.test(value)) return null;
try {
return BigInt(value);
} catch {
return null;
}
}
function toBigIntOrUndefined(value) {
if (typeof value === "bigint") return value;
if (typeof value === "number" && Number.isFinite(value) && Number.isInteger(value)) return BigInt(value);
}
function clampBigInt(value, min, max) {
if (min !== void 0 && value < min) return min;
if (max !== void 0 && value > max) return max;
return value;
}
function getTotalDigits(inputValue) {
return inputValue.toString().replace(".", "").length;
}
function isValidNumber(floatValue, value) {
return (typeof floatValue === "number" ? floatValue < Number.MAX_SAFE_INTEGER : !Number.isNaN(Number(floatValue))) && !Number.isNaN(floatValue) && getTotalDigits(value) < 14 && value !== "";
}
function isInRange(value, min, max) {
if (value === void 0) return true;
return (min === void 0 || value >= min) && (max === void 0 || value <= max);
}
const defaultProps = {
step: 1,
clampBehavior: "blur",
allowDecimal: true,
allowNegative: true,
withKeyboardEvents: true,
allowLeadingZeros: true,
trimLeadingZeroesOnBlur: true,
startValue: 0,
allowedDecimalSeparators: [".", ","]
};
const varsResolver = require_create_vars_resolver.createVarsResolver((_, { size }) => ({ controls: { "--ni-chevron-size": require_get_size.getSize(size, "ni-chevron-size") } }));
function clampAndSanitizeInput(sanitizedValue, max, min) {
const stringValue = sanitizedValue.toString();
const hasTrailingDecimalSeparator = trailingDecimalSeparatorPattern.test(stringValue);
const replaced = stringValue.replace(/^0+(?=\d)/, "");
const parsedValue = parseFloat(replaced);
if (Number.isNaN(parsedValue)) return replaced;
if (parsedValue > Number.MAX_SAFE_INTEGER) return max !== void 0 ? max : replaced;
const clamped = (0, _mantine_hooks.clamp)(parsedValue, min, max);
if (hasTrailingDecimalSeparator) return `${clamped.toString().replace(/^0+(?=\d)/, "")}.`;
return clamped;
}
function clampAndSanitizeBigIntInput(sanitizedValue, options) {
if (sanitizedValue === "" || sanitizedValue === "-") return sanitizedValue;
const parsed = parseBigIntFromString(sanitizedValue);
if (parsed === null) return sanitizedValue;
return options.clampBehavior === "blur" ? clampBigInt(parsed, options.min, options.max) : parsed;
}
const NumberInput = require_factory.genericFactory((_props) => {
const props = require_use_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, allowedDecimalSeparators, selectAllOnFocus, onMinReached, onMaxReached, onFocus, attributes, ref, ...others } = props;
const allowNegativeResolved = allowNegative ?? true;
const allowLeadingZerosResolved = allowLeadingZeros ?? true;
const getStyles = require_use_styles.useStyles({
name: "NumberInput",
classes: require_NumberInput_module.default,
props,
classNames,
styles,
unstyled,
attributes,
vars,
varsResolver
});
const { resolvedClassNames, resolvedStyles } = require_use_resolved_styles_api.useResolvedStylesApi({
classNames,
styles,
props
});
const valueModeRef = (0, react.useRef)(isBigIntValue(value) || isBigIntValue(defaultValue) ? "bigint" : "number");
if (isBigIntValue(value)) valueModeRef.current = "bigint";
else if (typeof value === "number") valueModeRef.current = "number";
const isBigIntMode = valueModeRef.current === "bigint";
const [_value, setValue] = (0, _mantine_hooks.useUncontrolled)({
value,
defaultValue,
finalValue: "",
onChange
});
const shouldUseStepInterval = stepHoldDelay !== void 0 && stepHoldInterval !== void 0;
const inputRef = (0, react.useRef)(null);
const onStepTimeoutRef = (0, react.useRef)(null);
const stepCountRef = (0, react.useRef)(0);
const minNumber = typeof min === "number" ? min : void 0;
const maxNumber = typeof max === "number" ? max : void 0;
const stepNumber = typeof step === "number" ? step : defaultProps.step;
const startValueNumber = typeof startValue === "number" ? startValue : defaultProps.startValue;
const minBigInt = toBigIntOrUndefined(min);
const maxBigInt = toBigIntOrUndefined(max);
const stepBigInt = toBigIntOrUndefined(step) ?? BigInt(1);
const startValueBigInt = toBigIntOrUndefined(startValue) ?? BigInt(0);
const parseBigIntOrString = (inputValue) => {
if (!isValidBigIntString(inputValue, allowNegativeResolved) || allowLeadingZerosResolved && leadingZerosPattern.test(inputValue)) return inputValue;
return parseBigIntFromString(inputValue) ?? inputValue;
};
const getBigIntFloatValue = (inputValue) => {
const numericValue = Number(inputValue);
return Number.isSafeInteger(numericValue) ? numericValue : void 0;
};
const handleValueChange = (payload, event) => {
if (event.source === "event") if (isBigIntMode) setValue(parseBigIntOrString(payload.value));
else setValue(isValidNumber(payload.floatValue, payload.value) && !leadingDecimalZeroPattern.test(payload.value) && !(allowLeadingZerosResolved ? leadingZerosPattern.test(payload.value) : false) && !trailingZerosPattern.test(payload.value) && !trailingDecimalSeparatorPattern.test(payload.value) ? 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 = (0, react.useRef)(require_noop.noop);
incrementRef.current = () => {
if (isBigIntMode) {
if (!canStepBigInt(_value, allowNegativeResolved)) return;
let val;
const currentValue = _value;
if (typeof currentValue === "bigint") {
const incrementedValue = currentValue + stepBigInt;
if (maxBigInt !== void 0 && incrementedValue > maxBigInt) onMaxReached?.();
val = maxBigInt !== void 0 && incrementedValue > maxBigInt ? maxBigInt : incrementedValue;
} else if (typeof currentValue === "string" && currentValue !== "") {
const parsed = parseBigIntFromString(currentValue);
if (parsed === null) return;
const incrementedValue = parsed + stepBigInt;
if (maxBigInt !== void 0 && incrementedValue > maxBigInt) onMaxReached?.();
val = maxBigInt !== void 0 && incrementedValue > maxBigInt ? maxBigInt : incrementedValue;
} else val = clampBigInt(startValueBigInt, minBigInt, maxBigInt);
const formattedValue = val.toString();
setValue(val);
onValueChange?.({
floatValue: getBigIntFloatValue(val),
formattedValue,
value: formattedValue
}, { source: "increment" });
setTimeout(() => adjustCursor(inputRef.current?.value.length), 0);
return;
}
if (!canStep(_value)) return;
let val;
const currentValuePrecision = getDecimalPlaces(_value);
const stepPrecision = getDecimalPlaces(stepNumber);
const maxPrecision = Math.max(currentValuePrecision, stepPrecision);
const factor = 10 ** maxPrecision;
if (!isNumberString(_value) && (typeof _value !== "number" || Number.isNaN(_value))) val = (0, _mantine_hooks.clamp)(startValueNumber, minNumber, maxNumber);
else if (maxNumber !== void 0) {
const incrementedValue = (Math.round(Number(_value) * factor) + Math.round(stepNumber * factor)) / factor;
if (incrementedValue > maxNumber) onMaxReached?.();
val = incrementedValue <= maxNumber ? incrementedValue : maxNumber;
} else val = (Math.round(Number(_value) * factor) + Math.round(stepNumber * 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 = (0, react.useRef)(require_noop.noop);
decrementRef.current = () => {
if (isBigIntMode) {
if (!canStepBigInt(_value, allowNegativeResolved)) return;
let val;
const minValue = minBigInt !== void 0 ? minBigInt : !allowNegativeResolved ? BigInt(0) : void 0;
const currentValue = _value;
if (typeof currentValue === "bigint") {
const decrementedValue = currentValue - stepBigInt;
if (minValue !== void 0 && decrementedValue < minValue) onMinReached?.();
val = minValue !== void 0 && decrementedValue < minValue ? minValue : decrementedValue;
} else if (typeof currentValue === "string" && currentValue !== "") {
const parsed = parseBigIntFromString(currentValue);
if (parsed === null) return;
const decrementedValue = parsed - stepBigInt;
if (minValue !== void 0 && decrementedValue < minValue) onMinReached?.();
val = minValue !== void 0 && decrementedValue < minValue ? minValue : decrementedValue;
} else val = clampBigInt(startValueBigInt, minValue, maxBigInt);
const formattedValue = val.toString();
setValue(val);
onValueChange?.({
floatValue: getBigIntFloatValue(val),
formattedValue,
value: formattedValue
}, { source: "decrement" });
setTimeout(() => adjustCursor(inputRef.current?.value.length), 0);
return;
}
if (!canStep(_value)) return;
let val;
const minValue = minNumber !== void 0 ? minNumber : !allowNegativeResolved ? 0 : Number.MIN_SAFE_INTEGER;
const currentValuePrecision = getDecimalPlaces(_value);
const stepPrecision = getDecimalPlaces(stepNumber);
const maxPrecision = Math.max(currentValuePrecision, stepPrecision);
const factor = 10 ** maxPrecision;
if (!isNumberString(_value) && typeof _value !== "number" || Number.isNaN(_value)) val = (0, _mantine_hooks.clamp)(startValueNumber, minValue, maxNumber);
else {
const decrementedValue = (Math.round(Number(_value) * factor) - Math.round(stepNumber * factor)) / factor;
if (minValue !== void 0 && decrementedValue < minValue) onMinReached?.();
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 handlePaste = (event) => {
const pastedText = event.clipboardData.getData("text");
const _decimalSeparator = others.decimalSeparator || ".";
const separatorsToReplace = (allowedDecimalSeparators || [".", ","]).filter((s) => s !== _decimalSeparator);
if (separatorsToReplace.some((s) => pastedText.includes(s))) {
event.preventDefault();
let modifiedText = pastedText;
separatorsToReplace.forEach((s) => {
modifiedText = modifiedText.split(s).join(_decimalSeparator);
});
const input = inputRef.current;
if (input) {
const start = input.selectionStart ?? 0;
const end = input.selectionEnd ?? 0;
const currentValue = input.value;
const newValue = currentValue.substring(0, start) + modifiedText + currentValue.substring(end);
(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set)?.call(input, newValue);
input.dispatchEvent(new Event("change", { bubbles: true }));
const cursorPos = start + modifiedText.length;
setTimeout(() => adjustCursor(cursorPos), 0);
}
}
others.onPaste?.(event);
};
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 && input.selectionStart === 0 && input.selectionStart === input.selectionEnd) {
event.preventDefault();
window.setTimeout(() => adjustCursor(0), 0);
}
}
};
const handleFocus = (event) => {
if (selectAllOnFocus) setTimeout(() => event.currentTarget.select(), 0);
onFocus?.(event);
};
const handleBlur = (event) => {
let sanitizedValue = _value;
if (isBigIntMode) {
if (clampBehavior === "blur" && typeof sanitizedValue === "bigint") sanitizedValue = clampBigInt(sanitizedValue, minBigInt, maxBigInt);
if (trimLeadingZeroesOnBlur && typeof sanitizedValue === "string") sanitizedValue = clampAndSanitizeBigIntInput(sanitizedValue, {
min: minBigInt,
max: maxBigInt,
clampBehavior
});
} else {
if (clampBehavior === "blur" && typeof sanitizedValue === "number") sanitizedValue = (0, _mantine_hooks.clamp)(sanitizedValue, minNumber, maxNumber);
if (trimLeadingZeroesOnBlur && typeof sanitizedValue === "string" && getDecimalPlaces(sanitizedValue) < 15) sanitizedValue = clampAndSanitizeInput(sanitizedValue, maxNumber, minNumber);
}
if (_value !== sanitizedValue) setValue(sanitizedValue);
onBlur?.(event);
};
(0, _mantine_hooks.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__ */ (0, react_jsx_runtime.jsxs)("div", {
...getStyles("controls"),
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_UnstyledButton.UnstyledButton, {
...getStyles("control"),
tabIndex: -1,
"aria-hidden": true,
disabled: disabled || typeof _value === "number" && maxNumber !== void 0 && _value >= maxNumber || typeof _value === "bigint" && maxBigInt !== void 0 && _value >= maxBigInt,
mod: { direction: "up" },
onMouseDown: (event) => event.preventDefault(),
onPointerDown: (event) => {
onStep(event, true);
},
onPointerUp: onStepDone,
onPointerLeave: onStepDone,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_NumberInputChevron.NumberInputChevron, { direction: "up" })
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_UnstyledButton.UnstyledButton, {
...getStyles("control"),
tabIndex: -1,
"aria-hidden": true,
disabled: disabled || typeof _value === "number" && minNumber !== void 0 && _value <= minNumber || typeof _value === "bigint" && minBigInt !== void 0 && _value <= minBigInt,
mod: { direction: "down" },
onMouseDown: (event) => event.preventDefault(),
onPointerDown: (event) => {
onStep(event, false);
},
onPointerUp: onStepDone,
onPointerLeave: onStepDone,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_NumberInputChevron.NumberInputChevron, { direction: "down" })
})]
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InputBase.InputBase, {
component: react_number_format.NumericFormat,
allowNegative,
className: (0, clsx.default)(require_NumberInput_module.default.root, className),
size,
...others,
inputMode: isBigIntMode ? "numeric" : "decimal",
readOnly,
disabled,
value: typeof _value === "bigint" ? _value.toString() : _value,
getInputRef: (0, _mantine_hooks.useMergedRef)(ref, inputRef),
onValueChange: handleValueChange,
rightSection: hideControls || readOnly || !(isBigIntMode ? canStepBigInt(_value, allowNegativeResolved) : canStep(_value)) ? rightSection : rightSection || controls,
classNames: resolvedClassNames,
styles: resolvedStyles,
unstyled,
__staticSelector: "NumberInput",
decimalScale: isBigIntMode ? 0 : allowDecimal ? decimalScale : 0,
onPaste: handlePaste,
onFocus: handleFocus,
onKeyDown: handleKeyDown,
onKeyDownCapture: handleKeyDownCapture,
rightSectionPointerEvents: rightSectionPointerEvents ?? (disabled ? "none" : void 0),
rightSectionWidth: rightSectionWidth ?? `var(--ni-right-section-width-${size || "sm"})`,
allowLeadingZeros,
allowedDecimalSeparators,
onBlur: handleBlur,
attributes,
isAllowed: (val) => {
if (!(isAllowed ? isAllowed(val) : true)) return false;
if (clampBehavior !== "strict") return true;
if (!isBigIntMode) return isInRange(val.floatValue, minNumber, maxNumber);
if (val.value === "" || val.value === "-") return true;
const parsed = parseBigIntFromString(val.value);
if (parsed === null) return true;
return (minBigInt === void 0 || parsed >= minBigInt) && (maxBigInt === void 0 || parsed <= maxBigInt);
}
});
});
NumberInput.classes = {
...require_InputBase.InputBase.classes,
...require_NumberInput_module.default
};
NumberInput.varsResolver = varsResolver;
NumberInput.displayName = "@mantine/core/NumberInput";
//#endregion
exports.NumberInput = NumberInput;
//# sourceMappingURL=NumberInput.cjs.map