@gravity-ui/uikit
Version:
Gravity UI base styling and components
146 lines (145 loc) • 6.83 kB
JavaScript
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NumberInput = void 0;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const React = tslib_1.__importStar(require("react"));
const constants_1 = require("../../constants.js");
const hooks_1 = require("../../hooks/index.js");
const private_1 = require("../../hooks/private/index.js");
const TextInput_1 = require("../controls/TextInput/index.js");
const utils_1 = require("../controls/utils.js");
const cn_1 = require("../utils/cn.js");
const NumericArrows_1 = require("./NumericArrows/NumericArrows.js");
const utils_2 = require("./utils.js");
require("./NumberInput.css");
const b = (0, cn_1.block)('number-input');
function getStringValue(value) {
return value === null ? '' : String(value);
}
exports.NumberInput = React.forwardRef(function NumberInput({ endContent, defaultValue: externalDefaultValue, ...props }, ref) {
const { value: externalValue, onChange: handleChange, onUpdate: externalOnUpdate, min: externalMin, max: externalMax, shiftMultiplier: externalShiftMultiplier = 10, step: externalStep, size = 'm', view = 'normal', disabled, hiddenControls, validationState, onBlur, onKeyDown, allowDecimal = false, className, } = props;
const { min, max, step: baseStep, value: internalValue, defaultValue, shiftMultiplier, } = (0, utils_2.getInternalState)({
min: externalMin,
max: externalMax,
step: externalStep,
shiftMultiplier: externalShiftMultiplier,
allowDecimal,
value: externalValue,
defaultValue: externalDefaultValue,
});
const [value, setValue] = (0, hooks_1.useControlledState)(internalValue, defaultValue ?? null, externalOnUpdate);
const [inputValue, setInputValue] = React.useState(getStringValue(value));
React.useEffect(() => {
const stringPropsValue = getStringValue(value);
setInputValue((currentInputValue) => {
if (!(0, utils_2.areStringRepresentationOfNumbersEqual)(currentInputValue, stringPropsValue)) {
return stringPropsValue;
}
return currentInputValue;
});
}, [value]);
const clamp = !(allowDecimal && !externalStep);
const safeValue = value ?? 0;
const state = (0, utils_1.getInputControlState)(validationState);
const canIncrementNumber = safeValue < (max ?? Number.MAX_SAFE_INTEGER);
const canDecrementNumber = safeValue > (min ?? Number.MIN_SAFE_INTEGER);
const innerControlRef = React.useRef(null);
const fieldRef = (0, private_1.useFormResetHandler)({
initialValue: value,
onReset: setValue,
});
const handleRef = (0, hooks_1.useForkRef)(props.controlRef, innerControlRef, fieldRef);
const handleValueDelta = (e, direction) => {
const step = e.shiftKey ? shiftMultiplier * baseStep : baseStep;
const deltaWithSign = direction === 'up' ? step : -step;
if (direction === 'up' ? canIncrementNumber : canDecrementNumber) {
const newValue = (0, utils_2.clampToNearestStepValue)({
value: safeValue + deltaWithSign,
step: baseStep,
min,
max,
direction,
});
setValue?.(newValue);
setInputValue(newValue.toString());
}
};
const handleKeyDown = (e) => {
if (e.key === constants_1.KeyCode.ARROW_DOWN) {
e.preventDefault();
handleValueDelta(e, 'down');
}
else if (e.key === constants_1.KeyCode.ARROW_UP) {
e.preventDefault();
handleValueDelta(e, 'up');
}
else if (e.key === constants_1.KeyCode.HOME) {
e.preventDefault();
if (min !== undefined) {
setValue?.(min);
setInputValue(min.toString());
}
}
else if (e.key === constants_1.KeyCode.END) {
e.preventDefault();
if (max !== undefined) {
const newValue = (0, utils_2.clampToNearestStepValue)({
value: max,
step: baseStep,
min,
max,
});
setValue?.(newValue);
setInputValue(newValue.toString());
}
}
onKeyDown?.(e);
};
const handleBlur = (e) => {
if (clamp && value !== null) {
const clampedValue = (0, utils_2.clampToNearestStepValue)({
value,
step: baseStep,
min,
max,
});
if (value !== clampedValue) {
setValue?.(clampedValue);
}
setInputValue(clampedValue.toString());
}
onBlur?.(e);
};
const handleUpdate = (v) => {
setInputValue(v);
const preparedStringValue = (0, utils_2.getPossibleNumberSubstring)(v, allowDecimal);
(0, utils_2.updateCursorPosition)(innerControlRef, v, preparedStringValue);
const { valid, value: parsedNumberValue } = (0, utils_2.getParsedValue)(preparedStringValue);
if (valid && parsedNumberValue !== value) {
setValue?.(parsedNumberValue);
}
};
const handleInput = (e) => {
const preparedStringValue = (0, utils_2.getPossibleNumberSubstring)(e.currentTarget.value, allowDecimal);
(0, utils_2.updateCursorPosition)(innerControlRef, e.currentTarget.value, preparedStringValue);
};
return ((0, jsx_runtime_1.jsx)(TextInput_1.TextInput, { ...props, className: b({ size, view, state }, className), controlProps: {
onInput: handleInput,
...props.controlProps,
role: 'spinbutton',
inputMode: allowDecimal ? 'decimal' : 'numeric',
pattern: props.controlProps?.pattern ?? (0, utils_2.getInputPattern)(allowDecimal, false),
'aria-valuemin': props.min,
'aria-valuemax': props.max,
'aria-valuenow': value === null ? undefined : value,
}, controlRef: handleRef, value: inputValue, onChange: handleChange, onUpdate: handleUpdate, onKeyDown: handleKeyDown, onBlur: handleBlur, ref: ref, endContent: (0, jsx_runtime_1.jsxs)(React.Fragment, { children: [endContent, hiddenControls ? null : ((0, jsx_runtime_1.jsx)(NumericArrows_1.NumericArrows, { className: b('arrows'), size: size, disabled: disabled, onUpClick: (e) => {
innerControlRef.current?.focus();
handleValueDelta(e, 'up');
}, onDownClick: (e) => {
innerControlRef.current?.focus();
handleValueDelta(e, 'down');
} }))] }) }));
});
//# sourceMappingURL=NumberInput.js.map