UNPKG

@ozen-ui/kit

Version:

React component library

143 lines (142 loc) 9.91 kB
import { __assign, __read, __rest } from "tslib"; import './InputNumber.css'; import React, { forwardRef, useRef, useState } from 'react'; import { SortDownIcon, SortUpIcon } from '@ozen-ui/icons'; import { useBoolean } from '../../hooks/useBoolean'; import { useControlled } from '../../hooks/useControlled'; import { useEventListener } from '../../hooks/useEventListener'; import { useInterval } from '../../hooks/useInterval'; import { useMultiRef } from '../../hooks/useMultiRef'; import { useThemeProps } from '../../hooks/useThemeProps'; import { cn } from '../../utils/classname'; import { FieldControl } from '../FieldControl'; import { FieldHint } from '../FieldHint'; import { FieldIcon } from '../FieldIcon'; import { FieldInput } from '../FieldInput'; import { FieldLabel } from '../FieldLabel'; import { Fieldset } from '../Fieldset'; import { IconButton } from '../IconButtonNext'; import { INPUT_NUMBER_DEFAULT_AUTO_FOCUS, INPUT_NUMBER_DEFAULT_DISABLED, INPUT_NUMBER_DEFAULT_ERROR, INPUT_NUMBER_DEFAULT_FULL_WIDTH, INPUT_NUMBER_DEFAULT_MAX, INPUT_NUMBER_DEFAULT_MIN, INPUT_NUMBER_DEFAULT_REQUIRED, INPUT_NUMBER_DEFAULT_SIZE, INPUT_NUMBER_DEFAULT_STEP, INPUT_NUMBER_DEFAULT_VALUE, } from './constants'; import { getValue, isInputInvalid, isValidValue } from './utils'; export var cnInputNumber = cn('InputNumber'); export var InputNumber = forwardRef(function (inProps, ref) { var props = useThemeProps({ props: inProps, name: 'InputNumber', }); var _a = props.size, size = _a === void 0 ? INPUT_NUMBER_DEFAULT_SIZE : _a, _b = props.step, step = _b === void 0 ? INPUT_NUMBER_DEFAULT_STEP : _b, _c = props.autoFocus, autoFocus = _c === void 0 ? INPUT_NUMBER_DEFAULT_AUTO_FOCUS : _c, _d = props.error, error = _d === void 0 ? INPUT_NUMBER_DEFAULT_ERROR : _d, _e = props.required, required = _e === void 0 ? INPUT_NUMBER_DEFAULT_REQUIRED : _e, _f = props.disabled, disabled = _f === void 0 ? INPUT_NUMBER_DEFAULT_DISABLED : _f, _g = props.fullWidth, fullWidth = _g === void 0 ? INPUT_NUMBER_DEFAULT_FULL_WIDTH : _g, _h = props.defaultValue, defaultValue = _h === void 0 ? INPUT_NUMBER_DEFAULT_VALUE : _h, _j = props.min, min = _j === void 0 ? INPUT_NUMBER_DEFAULT_MIN : _j, _k = props.max, max = _k === void 0 ? INPUT_NUMBER_DEFAULT_MAX : _k, label = props.label, placeholder = props.placeholder, id = props.id, name = props.name, renderLeft = props.renderLeft, renderRight = props.renderRight, hint = props.hint, className = props.className, inputProps = props.inputProps, valueProp = props.value, onChange = props.onChange, labelRef = props.labelRef, labelProps = props.labelProps, bodyProps = props.bodyProps, hintProps = props.hintProps, incrementButtonText = props.incrementButtonText, decrementButtonText = props.decrementButtonText, other = __rest(props, ["size", "step", "autoFocus", "error", "required", "disabled", "fullWidth", "defaultValue", "min", "max", "label", "placeholder", "id", "name", "renderLeft", "renderRight", "hint", "className", "inputProps", "value", "onChange", "labelRef", "labelProps", "bodyProps", "hintProps", "incrementButtonText", "decrementButtonText"]); var _l = __read(useBoolean(false), 2), focused = _l[0], _m = _l[1], onFocus = _m.on, offFocus = _m.off; var _o = __read(useState(null), 2), timeoutId = _o[0], setTimeoutId = _o[1]; var _p = __read(useState(null), 2), countDirection = _p[0], setCountDirection = _p[1]; var _q = __read(useControlled({ value: valueProp, name: 'InputNumber', state: 'value', defaultValue: defaultValue, }), 2), valueState = _q[0], setValueState = _q[1]; var bodyInnerRef = useRef(null); var fieldInnerRef = useRef(null); var filled = isValidValue(valueState); var handleChange = function (event) { if (disabled) return; var valueAsNumber = event.target.valueAsNumber; var value = isValidValue(valueAsNumber) ? valueAsNumber : ''; setValueState(value); onChange === null || onChange === void 0 ? void 0 : onChange(event, { id: id, name: name, value: value }); }; var startCount = function (countDirection) { var timeoutId = setTimeout(function () { setCountDirection(countDirection); }, 300); setTimeoutId(timeoutId); }; var handleMouseDown = function (countDirection) { return function (e) { var nextValue = getValue({ min: min, max: max, step: step, countDirection: countDirection, value: valueState, }); setValueState(nextValue); onChange === null || onChange === void 0 ? void 0 : onChange(e, { id: id, name: name, value: nextValue }); startCount(countDirection); }; }; var handleMouseUp = function () { if (timeoutId) { clearTimeout(timeoutId); } setCountDirection(null); setTimeoutId(null); }; var handleFocus = function (event) { var _a; onFocus(); (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onFocus) === null || _a === void 0 ? void 0 : _a.call(inputProps, event); }; var handleBlur = function (event) { var _a; offFocus(); (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(inputProps, event); }; /* В некоторых браузерах input[type=number] позволяет вводить * буквенные символы, поэтому мы вручную отключаем подобное поведение. */ var handleKeyDown = function (event) { var _a; if (isInputInvalid(event)) { event.preventDefault(); } (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(inputProps, event); }; /* Хук useEventListener необходим для того, чтобы при нажатии на кнопку мыши * внутри HTML-элемента label — фокус не переходил на body или на цель нажатия кнопки мыши. * Если же нажатие кнопки происходит на самом HTML-элементе input, то мы ничего не делаем, * так как в противном случае мы сломаем возможность выделять текст для копирования. */ useEventListener({ element: bodyInnerRef, eventName: 'mousedown', handler: function (e) { var _a; if (e.target !== fieldInnerRef.current) { e.preventDefault(); (_a = fieldInnerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); } }, }); useEventListener({ active: focused, eventName: 'mouseup', handler: handleMouseUp, }); useInterval(function () { if (countDirection) { var value = getValue({ min: min, max: max, step: step, countDirection: countDirection, value: valueState, }); setValueState(value); onChange === null || onChange === void 0 ? void 0 : onChange(null, { id: id, name: name, value: value }); } }, countDirection ? 100 : null); return (React.createElement(FieldControl, __assign({ size: size, error: error, filled: filled, focused: focused, disabled: disabled, required: required, fullWidth: fullWidth }, other, { ref: ref, className: cnInputNumber({ size: size }, [className]) }), React.createElement("label", __assign({}, bodyProps, { className: cnInputNumber('Body'), ref: useMultiRef([bodyProps === null || bodyProps === void 0 ? void 0 : bodyProps.ref, bodyInnerRef]) }), React.createElement(FieldIcon, { icon: renderLeft }), React.createElement("div", { className: cnInputNumber('FieldContainer') }, React.createElement(FieldLabel, __assign({}, labelProps, { ref: useMultiRef([labelProps === null || labelProps === void 0 ? void 0 : labelProps.ref, labelRef]), className: cnInputNumber('Label', [labelProps === null || labelProps === void 0 ? void 0 : labelProps.className]) }), label), React.createElement(FieldInput, __assign({ id: id, min: min, max: max, step: step, name: name, type: "number", value: valueState, autoFocus: autoFocus, placeholder: placeholder }, inputProps, { onBlur: handleBlur, onFocus: handleFocus, onChange: handleChange, onKeyDown: handleKeyDown, ref: useMultiRef([inputProps === null || inputProps === void 0 ? void 0 : inputProps.ref, fieldInnerRef]), className: cnInputNumber('Field', [inputProps === null || inputProps === void 0 ? void 0 : inputProps.className]) }))), React.createElement(FieldIcon, { icon: renderRight }), React.createElement("span", { className: cnInputNumber('Controls') }, React.createElement(IconButton, { size: size, type: "button", tabIndex: -1, variant: "ghost", icon: SortUpIcon, disabled: disabled, "aria-label": incrementButtonText, className: cnInputNumber('Increment'), onMouseDown: handleMouseDown('increment') }), React.createElement(IconButton, { size: size, tabIndex: -1, type: "button", variant: "ghost", icon: SortDownIcon, disabled: disabled, "aria-label": decrementButtonText, className: cnInputNumber('Decrement'), onMouseDown: handleMouseDown('decrement') })), React.createElement(Fieldset, { className: cnInputNumber('Fieldset') })), React.createElement(FieldHint, __assign({}, hintProps), hint))); }); InputNumber.displayName = 'InputNumber';