UNPKG

@gravity-ui/uikit

Version:

Gravity UI base styling and components

142 lines (141 loc) 6.53 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import * as React from 'react'; import { KeyCode } from "../../constants.js"; import { useControlledState, useForkRef } from "../../hooks/index.js"; import { useFormResetHandler } from "../../hooks/private/index.js"; import { TextInput } from "../controls/TextInput/index.js"; import { getInputControlState } from "../controls/utils.js"; import { block } from "../utils/cn.js"; import { NumericArrows } from "./NumericArrows/NumericArrows.js"; import { areStringRepresentationOfNumbersEqual, clampToNearestStepValue, getInputPattern, getInternalState, getParsedValue, getPossibleNumberSubstring, updateCursorPosition, } from "./utils.js"; import "./NumberInput.css"; const b = block('number-input'); function getStringValue(value) { return value === null ? '' : String(value); } export const 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, } = getInternalState({ min: externalMin, max: externalMax, step: externalStep, shiftMultiplier: externalShiftMultiplier, allowDecimal, value: externalValue, defaultValue: externalDefaultValue, }); const [value, setValue] = useControlledState(internalValue, defaultValue ?? null, externalOnUpdate); const [inputValue, setInputValue] = React.useState(getStringValue(value)); React.useEffect(() => { const stringPropsValue = getStringValue(value); setInputValue((currentInputValue) => { if (!areStringRepresentationOfNumbersEqual(currentInputValue, stringPropsValue)) { return stringPropsValue; } return currentInputValue; }); }, [value]); const clamp = !(allowDecimal && !externalStep); const safeValue = value ?? 0; const state = 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 = useFormResetHandler({ initialValue: value, onReset: setValue, }); const handleRef = 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 = clampToNearestStepValue({ value: safeValue + deltaWithSign, step: baseStep, min, max, direction, }); setValue?.(newValue); setInputValue(newValue.toString()); } }; const handleKeyDown = (e) => { if (e.key === KeyCode.ARROW_DOWN) { e.preventDefault(); handleValueDelta(e, 'down'); } else if (e.key === KeyCode.ARROW_UP) { e.preventDefault(); handleValueDelta(e, 'up'); } else if (e.key === KeyCode.HOME) { e.preventDefault(); if (min !== undefined) { setValue?.(min); setInputValue(min.toString()); } } else if (e.key === KeyCode.END) { e.preventDefault(); if (max !== undefined) { const newValue = clampToNearestStepValue({ value: max, step: baseStep, min, max, }); setValue?.(newValue); setInputValue(newValue.toString()); } } onKeyDown?.(e); }; const handleBlur = (e) => { if (clamp && value !== null) { const clampedValue = clampToNearestStepValue({ value, step: baseStep, min, max, }); if (value !== clampedValue) { setValue?.(clampedValue); } setInputValue(clampedValue.toString()); } onBlur?.(e); }; const handleUpdate = (v) => { setInputValue(v); const preparedStringValue = getPossibleNumberSubstring(v, allowDecimal); updateCursorPosition(innerControlRef, v, preparedStringValue); const { valid, value: parsedNumberValue } = getParsedValue(preparedStringValue); if (valid && parsedNumberValue !== value) { setValue?.(parsedNumberValue); } }; const handleInput = (e) => { const preparedStringValue = getPossibleNumberSubstring(e.currentTarget.value, allowDecimal); updateCursorPosition(innerControlRef, e.currentTarget.value, preparedStringValue); }; return (_jsx(TextInput, { ...props, className: b({ size, view, state }, className), controlProps: { onInput: handleInput, ...props.controlProps, role: 'spinbutton', inputMode: allowDecimal ? 'decimal' : 'numeric', pattern: props.controlProps?.pattern ?? 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: _jsxs(React.Fragment, { children: [endContent, hiddenControls ? null : (_jsx(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