import "./style.pcss"; import type { Ref, ChangeEvent, FocusEvent, TextareaHTMLAttributes, FC, ReactElement, } from "react"; import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, createRef, } from "react"; import type { IInput, IInputStateActions, TInputType, IResetEventParams, IInputRef, IInputEventParams, } from "./config/types"; import type { IComponentBaseProps } from "../../types/components"; import { useCN } from "../../utils/react/useCN"; import { useFormValidation } from "../../utils/react/useFormValidation"; import { useListeners } from "../../utils/react/useListeners"; import { getFieldProps } from "../form/lib/getFieldProps"; /** * Поле ввода * @returns {ReactElement} */ const Input: FC = ({ baseClass = "formInput", name = "", id, type = "text", placeholder, title, isRequired = false, autoComplete = "off", maxLength, isDisabled = false, isChecked = false, isReadOnly = false, value = "", tabIndex, inputMode, form = null, listeners = {}, onChange, onValidate, onBlur, onFocus, onReset, getCN, onKeyDown, defaultValue, autoFocus, extraClasses = {}, extraAttrs = {}, ref, }): ReactElement => { const { isCanChecked, isTextArea, isHidden, isFile, } = getFieldProps({ type }); const inputRef = useRef(null); const originalValue = useRef(value); const originalChecked = useRef(isChecked); const { getCN: getClassName } = useCN(baseClass); const [ inputValueState, setInputValueState ] = useState(value); const [ inputCheckedState, setInputCheckedState ] = useState(isChecked); const [ inputDisabledState, setInputDisabledState ] = useState(isDisabled); const [ inputRequiredState, setInputRequiredState ] = useState(isRequired); const [ inputReadOnlyState, setInputReadOnlyState ] = useState(isReadOnly); const [ inputPlaceholderState, setInputPlaceholderState ] = useState(placeholder); const [ inputTypeState, setInputTypeState ] = useState(type); const setValue = useCallback( (value: boolean | string) => { isCanChecked ? setInputCheckedState(!!value) : setInputValueState(typeof value === "string" ? value : ""); }, [ isCanChecked ] ); const stateActions: IInputStateActions = useMemo( () => ({ setValue, setInputCheckedState, setInputRequiredState, setInputValueState, setInputDisabledState, setInputPlaceholderState, setInputTypeState, setInputReadOnlyState, }), [] ); const handleChange = useCallback( (event: ChangeEvent) => { if (typeof onChange === "function") { onChange({ event, stateActions }); } else { const target = event.target as HTMLInputElement; const { checked, value } = target; setValue(isCanChecked ? checked : value); } }, [ onChange, isCanChecked, setValue, stateActions ] ); const handleBlur = useCallback( (event: FocusEvent) => { onBlur?.({ event, stateActions }); }, [ onBlur, stateActions ] ); const handleFocus = useCallback( (event: FocusEvent) => { onFocus?.({ event, stateActions }); }, [ onFocus, stateActions ] ); const handleReset = useCallback( (event: Event) => { if (typeof onReset === "function") { onReset({ event, stateActions, data: { original: { isChecked: originalChecked.current, value: originalValue.current, }, }, }); } else { setValue(isCanChecked ? originalChecked.current : originalValue.current); } }, [ onReset, isCanChecked, setValue ] ); const commonProps = useMemo( () => { return { id: id === null ? undefined : (id || name || undefined), name: name || undefined, placeholder: isCanChecked || isHidden ? undefined : inputPlaceholderState, maxLength: isCanChecked || isFile || isHidden ? undefined : maxLength, tabIndex: isFile || isHidden ? -1 : tabIndex, disabled: inputDisabledState, required: isRequired ? inputRequiredState : undefined, readOnly: inputReadOnlyState, autoComplete: isHidden || isFile || isCanChecked ? undefined : autoComplete, "data-js-input-required": isRequired ? inputRequiredState : undefined, form: (typeof form === "string" && !!form.length) ? form : undefined, title: typeof title === "string" ? title : undefined, inputMode, autoFocus, ...extraAttrs, }; }, [ id, name, isCanChecked, isHidden, isFile, inputPlaceholderState, maxLength, tabIndex, inputDisabledState, isRequired, inputRequiredState, inputReadOnlyState, autoComplete, form, title, inputMode, extraAttrs, ] ); const inputSpecificProps = useMemo( () => { let className: string | undefined; if (isHidden || !baseClass) { className = undefined; } else if (typeof getCN === "function") { className = getCN("", extraClasses); } else { className = getClassName("", extraClasses); } return { onChange: isFile || isHidden ? undefined : handleChange, onBlur: isFile || isHidden ? undefined : handleBlur, onFocus: isFile || isHidden ? undefined : handleFocus, onKeyDown: isFile || isHidden ? undefined : onKeyDown, value: defaultValue === undefined ? inputValueState : undefined, defaultValue: defaultValue === undefined ? undefined : defaultValue, className, checked: isCanChecked ? inputCheckedState : undefined, }; }, [ isFile, isHidden, handleChange, handleBlur, handleFocus, onKeyDown, defaultValue, inputValueState, baseClass, getCN, extraClasses, getClassName, isCanChecked, inputCheckedState, ] ); useListeners({ listeners: { reset: handleReset, ...listeners, }, ref: inputRef, }); useFormValidation(inputRef, onValidate || undefined); useEffect(() => { setInputPlaceholderState(placeholder); }, [ placeholder ]); useEffect(() => { setInputCheckedState(isChecked); }, [ isChecked ]); useEffect(() => { setInputDisabledState(isDisabled); }, [ isDisabled ]); useEffect(() => { setInputRequiredState(isRequired); }, [ isRequired ]); useEffect(() => { setInputValueState(value); }, [ value ]); useEffect(() => { setInputTypeState(type); }, [ type ]); useEffect(() => { setInputReadOnlyState(isReadOnly); }, [ isReadOnly ]); useImperativeHandle( ref, (): IInputRef => ({ el: inputRef.current, getData: () => ({ value: inputValueState, isChecked: inputCheckedState, }), ref: createRef(), utils: null, stateActions, }), [ inputValueState, inputCheckedState, stateActions ] ); if (isTextArea) { interface ICustomTextareaProps extends TextareaHTMLAttributes { isRequired?: string | boolean; "data-js-input-required": string | boolean | undefined; } const textareaProps: ICustomTextareaProps = { ...commonProps, ...inputSpecificProps, required: typeof inputRequiredState === "string" ? inputRequiredState === "true" : inputRequiredState, "data-js-input-required": inputRequiredState ? String(inputRequiredState) : undefined, }; return (