| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 | /* eslint-disable no-unused-vars, max-len, @typescript-eslint/no-unused-vars */import React from 'react';import cls from 'classnames';import PropTypes from 'prop-types';import InputFoundation from '@douyinfe/semi-foundation/input/foundation';import { cssClasses, strings } from '@douyinfe/semi-foundation/input/constants';import { isSemiIcon } from '../_utils';import BaseComponent from '../_base/baseComponent';import '@douyinfe/semi-foundation/input/input.scss';import { isString, noop, isFunction } from 'lodash';import { IconClear, IconEyeOpened, IconEyeClosedSolid } from '@douyinfe/semi-icons';const prefixCls = cssClasses.PREFIX;const sizeSet = strings.SIZE;const statusSet = strings.STATUS;const modeSet = strings.MODE;export { InputGroupProps } from './inputGroup';export { TextAreaProps } from './textarea';export type InputSize = 'small' | 'large' | 'default';export type InputMode = 'password';// still keep success as ValidateStatus optional value because form will pass success as props.validateStatus in sometime// Although we do not consume success in the input to configure special styles, we should allow it as a legal props value, otherwise a warning will be thrownexport type ValidateStatus = "default" | "error" | "warning" | "success";export interface InputProps extends    Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'prefix' | 'size' | 'autoFocus' | 'placeholder' | 'onFocus' | 'onBlur'> {    'aria-label'?: React.AriaAttributes['aria-label'];    'aria-describedby'?: React.AriaAttributes['aria-describedby'];    'aria-errormessage'?: React.AriaAttributes['aria-errormessage'];    'aria-invalid'?: React.AriaAttributes['aria-invalid'];    'aria-labelledby'?: React.AriaAttributes['aria-labelledby'];    'aria-required'?: React.AriaAttributes['aria-required'];    addonBefore?: React.ReactNode;    addonAfter?: React.ReactNode;    prefix?: React.ReactNode;    suffix?: React.ReactNode;    mode?: InputMode;    value?: React.ReactText;    defaultValue?: React.ReactText;    disabled?: boolean;    readonly?: boolean;    autofocus?: boolean;    type?: string;    showClear?: boolean;    hideSuffix?: boolean;    placeholder?: React.ReactText;    insetLabel?: React.ReactNode;    insetLabelId?: string;    size?: InputSize;    className?: string;    style?: React.CSSProperties;    validateStatus?: ValidateStatus;    onClear?: (e: React.MouseEvent<HTMLDivElement>) => void;    onChange?: (value: string, e: React.ChangeEvent<HTMLInputElement>) => void;    onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;    onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;    onInput?: (e: React.MouseEvent<HTMLInputElement>) => void;    onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;    onKeyUp?: (e: React.KeyboardEvent<HTMLInputElement>) => void;    onKeyPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void;    onEnterPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void;    inputStyle?: React.CSSProperties;    getValueLength?: (value: string) => number;    forwardRef?: ((instance: any) => void) | React.MutableRefObject<any> | null;    preventScroll?: boolean;}export interface InputState {    value: React.ReactText;    cachedValue: React.ReactText;    disabled: boolean;    props: Record<string, any>;    paddingLeft: string;    isFocus: boolean;    isHovering: boolean;    eyeClosed: boolean;    minLength: number;}class Input extends BaseComponent<InputProps, InputState> {    static propTypes = {        'aria-label': PropTypes.string,        'aria-labelledby': PropTypes.string,        'aria-invalid': PropTypes.bool,        'aria-errormessage': PropTypes.string,        'aria-describedby': PropTypes.string,        'aria-required': PropTypes.bool,        addonBefore: PropTypes.node,        addonAfter: PropTypes.node,        prefix: PropTypes.node,        suffix: PropTypes.node,        mode: PropTypes.oneOf(modeSet),        value: PropTypes.any,        defaultValue: PropTypes.any,        disabled: PropTypes.bool,        readonly: PropTypes.bool,        autofocus: PropTypes.bool,        type: PropTypes.string,        showClear: PropTypes.bool,        hideSuffix: PropTypes.bool,        placeholder: PropTypes.any,        size: PropTypes.oneOf(sizeSet),        className: PropTypes.string,        style: PropTypes.object,        validateStatus: PropTypes.oneOf(statusSet),        onClear: PropTypes.func,        onChange: PropTypes.func,        onBlur: PropTypes.func,        onFocus: PropTypes.func,        onInput: PropTypes.func,        onKeyDown: PropTypes.func,        onKeyUp: PropTypes.func,        onKeyPress: PropTypes.func,        onEnterPress: PropTypes.func,        insetLabel: PropTypes.node,        insetLabelId: PropTypes.string,        inputStyle: PropTypes.object,        getValueLength: PropTypes.func,        preventScroll: PropTypes.bool,    };    static defaultProps = {        addonBefore: '',        addonAfter: '',        prefix: '',        suffix: '',        readonly: false,        type: 'text',        showClear: false,        hideSuffix: false,        placeholder: '',        size: 'default',        className: '',        onClear: noop,        onChange: noop,        onBlur: noop,        onFocus: noop,        onInput: noop,        onKeyDown: noop,        onKeyUp: noop,        onKeyPress: noop,        onEnterPress: noop,        validateStatus: 'default',    };    inputRef!: React.RefObject<HTMLInputElement>;    prefixRef!: React.RefObject<React.ReactNode>;    suffixRef!: React.RefObject<React.ReactNode>;    foundation!: InputFoundation;    constructor(props: InputProps) {        super(props);        this.state = {            value: '',            cachedValue: null, // Cache current props.value value            disabled: false,            props: {},            paddingLeft: '',            isFocus: false,            isHovering: false,            eyeClosed: props.mode === 'password',            minLength: props.minLength,        };        this.inputRef = React.createRef();        this.prefixRef = React.createRef();        this.suffixRef = React.createRef();        this.foundation = new InputFoundation(this.adapter);    }    get adapter() {        return {            ...super.adapter,            setValue: (value: string) => this.setState({ value }),            setEyeClosed: (value: boolean) => this.setState({ eyeClosed: value }),            toggleFocusing: (isFocus: boolean) => {                const { preventScroll } = this.props;                const input = this.inputRef && this.inputRef.current;                if (isFocus) {                    input && input.focus({ preventScroll });                } else {                    input && input.blur();                }                this.setState({ isFocus });            },            toggleHovering: (isHovering: boolean) => this.setState({ isHovering }),            getIfFocusing: () => this.state.isFocus,            notifyChange: (cbValue: string, e: React.ChangeEvent<HTMLInputElement>) => this.props.onChange(cbValue, e),            notifyBlur: (val: string, e: React.FocusEvent<HTMLInputElement>) => this.props.onBlur(e),            notifyFocus: (val: string, e: React.FocusEvent<HTMLInputElement>) => this.props.onFocus(e),            notifyInput: (e: React.MouseEvent<HTMLInputElement>) => this.props.onInput(e),            notifyKeyPress: (e: React.KeyboardEvent<HTMLInputElement>) => this.props.onKeyPress(e),            notifyKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => this.props.onKeyDown(e),            notifyKeyUp: (e: React.KeyboardEvent<HTMLInputElement>) => this.props.onKeyUp(e),            notifyEnterPress: (e: React.KeyboardEvent<HTMLInputElement>) => this.props.onEnterPress(e),            notifyClear: (e: React.MouseEvent<HTMLDivElement>) => this.props.onClear(e),            setPaddingLeft: (paddingLeft: string) => this.setState({ paddingLeft }),            setMinLength: (minLength: number) => this.setState({ minLength }),            isEventTarget: (e: React.MouseEvent) => e && e.target === e.currentTarget,        };    }    static getDerivedStateFromProps(props: InputProps, state: InputState) {        const willUpdateStates: Partial<InputState> = {};        if (props.value !== state.cachedValue) {            willUpdateStates.value = props.value;            willUpdateStates.cachedValue = props.value;        }        return willUpdateStates;    }    componentDidUpdate(prevProps: InputProps) {        const { mode } = this.props;        if (prevProps.mode !== mode) {            this.handleModeChange(mode);        }    }    handleClear = (e: React.MouseEvent<HTMLInputElement>) => {        this.foundation.handleClear(e);    };    handleClick = (e: React.MouseEvent<HTMLDivElement>) => {        this.foundation.handleClick(e);    };    handleMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {        this.setState({ isHovering: true });    };    handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {        this.setState({ isHovering: false });    };    handleModeChange = (mode: string) => {        this.foundation.handleModeChange(mode);    };    handleClickEye = (e: React.MouseEvent<HTMLInputElement>) => {        this.foundation.handleClickEye(e);    };    handleMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {        this.foundation.handleMouseDown(e);    };    handleMouseUp = (e: React.MouseEvent<HTMLInputElement>) => {        this.foundation.handleMouseUp(e);    };    handleModeEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {        this.foundation.handleModeEnterPress(e);    };    handleClickPrefixOrSuffix = (e: React.MouseEvent<HTMLInputElement>) => {        this.foundation.handleClickPrefixOrSuffix(e);    };    handlePreventMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {        this.foundation.handlePreventMouseDown(e);    };    renderPrepend() {        const { addonBefore } = this.props;        if (addonBefore) {            const prefixWrapperCls = cls({                [`${prefixCls}-prepend`]: true,                [`${prefixCls}-prepend-text`]: addonBefore && isString(addonBefore),                [`${prefixCls}-prepend-icon`]: isSemiIcon(addonBefore),            });            return (                <div className={prefixWrapperCls} x-semi-prop="addonBefore">                    {addonBefore}                </div>            );        }        return null;    }    renderAppend() {        const { addonAfter } = this.props;        if (addonAfter) {            const prefixWrapperCls = cls({                [`${prefixCls}-append`]: true,                [`${prefixCls}-append-text`]: addonAfter && isString(addonAfter),                [`${prefixCls}-append-icon`]: isSemiIcon(addonAfter),            });            return (                <div className={prefixWrapperCls} x-semi-prop="addonAfter">                    {addonAfter}                </div>            );        }        return null;    }    renderClearBtn() {        const clearCls = cls(`${prefixCls}-clearbtn`);        const allowClear = this.foundation.isAllowClear();        // use onMouseDown to fix issue 1203        if (allowClear) {            return (                // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions                <div                    className={clearCls}                    onMouseDown={this.handleClear}                >                    <IconClear />                </div>            );        }        return null;    }    renderModeBtn() {        const { eyeClosed } = this.state;        const { mode, disabled } = this.props;        const modeCls = cls(`${prefixCls}-modebtn`);        const modeIcon = eyeClosed ? <IconEyeClosedSolid /> : <IconEyeOpened />;        // alway show password button for a11y        const showModeBtn = mode === 'password' && !disabled;        const ariaLabel = eyeClosed ? 'Show password' : 'Hidden password';        if (showModeBtn) {            return (                <div                    role="button"                    tabIndex={0}                    aria-label={ariaLabel}                    className={modeCls}                    onClick={this.handleClickEye}                    onMouseDown={this.handleMouseDown}                    onMouseUp={this.handleMouseUp}                    onKeyPress={this.handleModeEnterPress}                >                    {modeIcon}                </div>            );        }        return null;    }    renderPrefix() {        const { prefix, insetLabel, insetLabelId } = this.props;        const labelNode = prefix || insetLabel;        if (!labelNode) {            return null;        }        const prefixWrapperCls = cls({            [`${prefixCls}-prefix`]: true,            [`${prefixCls}-inset-label`]: insetLabel,            [`${prefixCls}-prefix-text`]: labelNode && isString(labelNode),            [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),        });        return (            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions            <div                className={prefixWrapperCls}                onMouseDown={this.handlePreventMouseDown}                onClick={this.handleClickPrefixOrSuffix}                id={insetLabelId}                x-semi-prop="prefix,insetLabel"            >                {labelNode}            </div>        );    }    showClearBtn() {        const { value, isFocus, isHovering } = this.state;        const { disabled, showClear } = this.props;        return Boolean(value) && showClear && !disabled && (isFocus || isHovering);    }    renderSuffix(suffixAllowClear: boolean) {        const { suffix, hideSuffix } = this.props;        if (!suffix) {            return null;        }        const suffixWrapperCls = cls({            [`${prefixCls}-suffix`]: true,            [`${prefixCls}-suffix-text`]: suffix && isString(suffix),            [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),            [`${prefixCls}-suffix-hidden`]: suffixAllowClear && Boolean(hideSuffix),        });        return (            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions            <div                className={suffixWrapperCls}                onMouseDown={this.handlePreventMouseDown}                onClick={this.handleClickPrefixOrSuffix}                x-semi-prop="suffix"            >                {suffix}            </div>        );    }    render() {        const {            addonAfter,            addonBefore,            autofocus,            className,            disabled,            defaultValue,            placeholder,            prefix,            mode,            insetLabel,            insetLabelId,            validateStatus,            type,            readonly,            size,            suffix,            style,            showClear,            onEnterPress,            onClear,            hideSuffix,            inputStyle,            forwardRef,            maxLength,            getValueLength,            preventScroll,            ...rest        } = this.props;        const { value, paddingLeft, isFocus, minLength: stateMinLength } = this.state;        const suffixAllowClear = this.showClearBtn();        const suffixIsIcon = isSemiIcon(suffix);        const ref = forwardRef || this.inputRef;        const wrapperPrefix = `${prefixCls}-wrapper`;        const wrapperCls = cls(wrapperPrefix, className, {            [`${prefixCls}-wrapper__with-prefix`]: prefix || insetLabel,            [`${prefixCls}-wrapper__with-suffix`]: suffix,            [`${prefixCls}-wrapper__with-suffix-hidden`]: suffixAllowClear && Boolean(hideSuffix),            [`${prefixCls}-wrapper__with-suffix-icon`]: suffixIsIcon,            [`${prefixCls}-wrapper__with-append`]: addonBefore,            [`${prefixCls}-wrapper__with-prepend`]: addonAfter,            [`${prefixCls}-wrapper__with-append-only`]: addonBefore && !addonAfter,            [`${prefixCls}-wrapper__with-prepend-only`]: !addonBefore && addonAfter,            [`${wrapperPrefix}-readonly`]: readonly,            [`${wrapperPrefix}-disabled`]: disabled,            [`${wrapperPrefix}-warning`]: validateStatus === 'warning',            [`${wrapperPrefix}-error`]: validateStatus === 'error',            [`${wrapperPrefix}-focus`]: isFocus,            [`${wrapperPrefix}-clearable`]: showClear,            [`${wrapperPrefix}-modebtn`]: mode === 'password',            [`${wrapperPrefix}-hidden`]: type === 'hidden',            [`${wrapperPrefix}-${size}`]: size,        });        const inputCls = cls(prefixCls, {            [`${prefixCls}-${size}`]: size,            [`${prefixCls}-disabled`]: disabled,            [`${prefixCls}-sibling-clearbtn`]: this.foundation.isAllowClear(),            [`${prefixCls}-sibling-modebtn`]: mode === 'password',        });        const inputValue = value === null || value === undefined ? '' : value;        const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {            ...rest,            style: { paddingLeft, ...inputStyle },            autoFocus: autofocus,            className: inputCls,            disabled,            readOnly: readonly,            type: this.foundation.handleInputType(type),            placeholder: placeholder as string,            onInput: e => this.foundation.handleInput(e),            onChange: e => this.foundation.handleChange(e.target.value, e),            onFocus: e => this.foundation.handleFocus(e),            onBlur: e => this.foundation.handleBlur(e),            onKeyUp: e => this.foundation.handleKeyUp(e),            onKeyDown: e => this.foundation.handleKeyDown(e),            onKeyPress: e => this.foundation.handleKeyPress(e),            value: inputValue,        };        if (!isFunction(getValueLength)) {            inputProps.maxLength = maxLength;        }        if (stateMinLength) {            inputProps.minLength = stateMinLength;        }        if (validateStatus === 'error') {            inputProps['aria-invalid'] = 'true';        }        return (            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions            <div                className={wrapperCls}                style={style}                onMouseEnter={e => this.handleMouseOver(e)}                onMouseLeave={e => this.handleMouseLeave(e)}                onClick={e => this.handleClick(e)}            >                {this.renderPrepend()}                {this.renderPrefix()}                <input {...inputProps} ref={ref} />                {this.renderClearBtn()}                {this.renderSuffix(suffixAllowClear)}                {this.renderModeBtn()}                {this.renderAppend()}            </div>        );    }}const ForwardInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'forwardRef'>>((props, ref) => <Input {...props} forwardRef={ref} />);export default ForwardInput;export { Input };
 |