/** * The new input element * * @author Platon Fedorov * @date 2021-01-13 */ import * as React from 'react'; import TextAreaAntd from 'antd/es/input/TextArea'; import * as styles from './input.m.scss'; import {Icon, joinClassNames, safeInvoke, SIZE} from '../../index'; import {createHtmlPropsFilter} from '../../utils/createHtmlPropsFilter'; import {Props} from './Input.types'; import {InputPassword} from './InputPassword'; import {InputMask} from './InputMask'; import {InputTime} from './InputTime'; import {InputNumber} from './InputNumber'; import {Wrapper, WrapperProps} from '../wrapper/Wrapper'; import {getSizeThemeKey} from '../../utils/getSizeThemeKey'; import {SizeClassNames} from '../wrapper/Wrapper.types'; import {InputColorPicker} from './InputColorPicker'; const filterProps = createHtmlPropsFilter, 'size' | 'disabled' | 'prefix' | 'defaultValue' | 'value' | 'onFocus' | 'onBlur'>>([ 'size', 'title', 'forwardRef', 'onPressEnter', 'onPressEscape', 'errorMessage', 'prefix', 'suffix', 'allowClear', 'hasError', 'isDisabled', 'onPrefixClick', 'onSuffixClick' ]); type InputState = { isFocused: boolean; } export type InputProps = Props; export { TextAreaAntd }; export interface ITextAreaProps extends Omit, 'size'> { prefixCls?: string; autosize?: boolean; autoSize?: boolean; onPressEnter?: React.KeyboardEventHandler; 'data-qaid'?: string; } export class Input extends React.Component { static TextArea = TextAreaAntd; static Password = InputPassword; static Mask = InputMask; static Time = InputTime; static Number = InputNumber; static Color = InputColorPicker; override state = { isFocused: false }; element = React.createRef(); wrapperRef = React.createRef(); handleChange = (e: React.ChangeEvent) => { safeInvoke(this.props.onChange, e); }; handleFocus = (e: React.FocusEvent) => { if (!this.state.isFocused) { this.setState({ isFocused: true }); safeInvoke(this.props.onFocus, e); } }; handleBlur = (e: React.FocusEvent) => { if (this.state.isFocused) { if (e.relatedTarget === null || (this.wrapperRef.current && e.relatedTarget instanceof Node && !this.wrapperRef.current.contains(e.relatedTarget)) ) { this.setState({ isFocused: false }); safeInvoke(this.props.onBlur, e); } } } handleWrapperClick = (e: React.MouseEvent) => { if (!this.state.isFocused) { this.element.current?.focus(); } }; handleWrapperClickOutside = (e: MouseEvent) => { if (this.state.isFocused) { const currentElement = this.element.current; if (currentElement !== null) { currentElement.focus(); currentElement.blur(); } } }; handleWrapperFocus = () => { this.element.current?.focus(); } onKeyUp = (e: React.KeyboardEvent) => { switch (e.keyCode) { case 13: safeInvoke(this.props.onPressEnter, e); break; case 27: safeInvoke(this.props.onPressEscape, e); break; } safeInvoke(this.props.onKeyUp, e); } createRef () { if (this.props.forwardRef) { this.element = this.props.forwardRef; } return this.element; } handleClearButtonMouseDown: React.MouseEventHandler = (e) => { e.preventDefault(); }; handleClearButtonClick: React.MouseEventHandler = () => { const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); if (this.element && this.element.current && descriptor && descriptor.set) { this.element.current.focus(); this.element.current.dispatchEvent(new Event('click', {bubbles: true})); descriptor.set.call(this.element.current, ''); this.element.current.dispatchEvent(new Event('input', {bubbles: true})); } }; override componentDidMount () { if (this.props.autoFocus && this.element.current) { this.element.current.focus(); } } override componentDidUpdate (prevProps: Readonly) { if (this.props.autoFocus === true && prevProps.autoFocus === false && this.element.current) { this.element.current.focus(); } } override render () { const {size = SIZE.MIDDLE} = this.props; const isLogicDisabled = this.props.readOnly || this.props.isDisabled; const wrapperProps: WrapperProps = { size: size, prefix: this.props.prefix, suffix: this.props.suffix, hasError: this.props.hasError, isDisabled: this.props.isDisabled, isReadOnly: this.props.readOnly, errorMessage: this.props.errorMessage, onPrefixClick: this.props.onPrefixClick, onSuffixClick: this.props.onSuffixClick, isFocused: this.state.isFocused, forwardRef: this.wrapperRef, onClickOutside: this.handleWrapperClickOutside, onClick: this.handleWrapperClick, onWrapperFocus: this.handleWrapperFocus, tabIndex: isLogicDisabled ? -1 : 0 }; const sizeThemeKey = getSizeThemeKey('', size); const classContainer = joinClassNames( styles.inputContainer, styles[sizeThemeKey], [styles.readonly, this.props.readOnly === true], [styles.disabled, this.props.isDisabled === true] ); return (
{this.renderInput()} {this.renderClearButton()}
); } renderInput () { const inputProps = { ...filterProps(this.props), ref: this.createRef(), onBlur: this.handleBlur, onKeyUp: this.onKeyUp, onFocus: this.handleFocus, onChange: this.handleChange, disabled: this.props.isDisabled || this.props.readOnly, value: this.props.value !== null ? this.props.value : undefined }; return ; } renderClearButton () { const {readOnly, isDisabled, size} = this.props; const handleClearButtonClick = readOnly || isDisabled ? undefined : this.handleClearButtonClick; const handleClearButtonMouseDown = readOnly || isDisabled ? undefined : this.handleClearButtonMouseDown; return this.props.allowClear ? (
) : null; } }