@ozen-ui/kit
Version:
React component library
143 lines (142 loc) • 9.91 kB
JavaScript
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';