UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

435 lines (434 loc) 20.3 kB
"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