2022-03-01 14:17:48 +08:00
import React , { forwardRef , useContext , useEffect , useRef } from 'react' ;
import RcInput , { InputProps as RcInputProps , InputRef } from 'rc-input' ;
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled' ;
2016-03-31 17:46:35 +08:00
import classNames from 'classnames' ;
2022-03-01 14:17:48 +08:00
import { composeRef } from 'rc-util/lib/ref' ;
2020-01-03 13:38:16 +08:00
import SizeContext , { SizeType } from '../config-provider/SizeContext' ;
2022-03-01 14:17:48 +08:00
import {
getFeedbackIcon ,
getMergedStatus ,
getStatusClassNames ,
InputStatus ,
} from '../_util/statusUtils' ;
import { ConfigContext } from '../config-provider' ;
2022-02-14 17:09:35 +08:00
import { FormItemStatusContext } from '../form/context' ;
2022-03-01 14:17:48 +08:00
import { hasPrefixSuffix } from './utils' ;
import devWarning from '../_util/devWarning' ;
2016-03-31 17:46:35 +08:00
2020-12-29 22:42:04 +08:00
export interface InputFocusOptions extends FocusOptions {
cursor ? : 'start' | 'end' | 'all' ;
}
2022-03-01 14:17:48 +08:00
export type { InputRef } ;
2016-08-19 17:11:06 +08:00
2019-11-01 18:19:29 +08:00
export function fixControlledValue < T > ( value : T ) {
if ( typeof value === 'undefined' || value === null ) {
return '' ;
}
2021-12-29 11:32:49 +08:00
return String ( value ) ;
2019-11-01 18:19:29 +08:00
}
2021-06-03 09:36:04 +08:00
export function resolveOnChange < E extends HTMLInputElement | HTMLTextAreaElement > (
target : E ,
2019-11-01 18:19:29 +08:00
e :
2021-06-03 09:36:04 +08:00
| React . ChangeEvent < E >
2021-03-23 15:03:47 +08:00
| React . MouseEvent < HTMLElement , MouseEvent >
| React . CompositionEvent < HTMLElement > ,
2021-08-04 20:07:28 +08:00
onChange : undefined | ( ( event : React.ChangeEvent < E > ) = > void ) ,
2021-03-23 15:03:47 +08:00
targetValue? : string ,
2019-11-01 18:19:29 +08:00
) {
2021-02-19 18:26:53 +08:00
if ( ! onChange ) {
return ;
}
let event = e ;
2021-03-23 15:03:47 +08:00
2021-02-19 18:26:53 +08:00
if ( e . type === 'click' ) {
2021-08-30 13:25:32 +08:00
// Clone a new target for event.
// Avoid the following usage, the setQuery method gets the original value.
//
// const [query, setQuery] = React.useState('');
// <Input
// allowClear
// value={query}
// onChange={(e)=> {
// setQuery((prevStatus) => e.target.value);
// }}
// />
const currentTarget = target . cloneNode ( true ) as E ;
2022-01-04 22:41:12 +08:00
// click clear icon
event = Object . create ( e , {
target : { value : currentTarget } ,
currentTarget : { value : currentTarget } ,
} ) ;
2021-08-30 13:25:32 +08:00
currentTarget . value = '' ;
2021-06-03 09:36:04 +08:00
onChange ( event as React . ChangeEvent < E > ) ;
2021-02-19 18:26:53 +08:00
return ;
2019-11-01 18:19:29 +08:00
}
2021-03-23 15:03:47 +08:00
// Trigger by composition event, this means we need force change the input value
if ( targetValue !== undefined ) {
2022-01-04 22:41:12 +08:00
event = Object . create ( e , {
target : { value : target } ,
currentTarget : { value : target } ,
} ) ;
2021-03-23 15:03:47 +08:00
target . value = targetValue ;
2021-06-03 09:36:04 +08:00
onChange ( event as React . ChangeEvent < E > ) ;
2021-03-23 15:03:47 +08:00
return ;
}
2021-06-03 09:36:04 +08:00
onChange ( event as React . ChangeEvent < E > ) ;
2019-11-01 18:19:29 +08:00
}
2020-12-29 22:42:04 +08:00
export function triggerFocus (
element? : HTMLInputElement | HTMLTextAreaElement ,
option? : InputFocusOptions ,
) {
if ( ! element ) return ;
element . focus ( option ) ;
// Selection content
const { cursor } = option || { } ;
if ( cursor ) {
const len = element . value . length ;
switch ( cursor ) {
case 'start' :
element . setSelectionRange ( 0 , 0 ) ;
break ;
case 'end' :
element . setSelectionRange ( len , len ) ;
break ;
default :
element . setSelectionRange ( 0 , len ) ;
}
}
}
2022-03-01 14:17:48 +08:00
export interface InputProps
extends Omit <
RcInputProps ,
'wrapperClassName' | 'groupClassName' | 'inputClassName' | 'affixWrapperClassName' | 'clearIcon'
> {
size? : SizeType ;
status? : InputStatus ;
bordered? : boolean ;
2019-11-01 18:19:29 +08:00
}
2022-03-01 14:17:48 +08:00
const Input = forwardRef < InputRef , InputProps > ( ( props , ref ) = > {
const {
prefixCls : customizePrefixCls ,
bordered = true ,
status : customStatus ,
size : customSize ,
onBlur ,
onFocus ,
suffix ,
. . . rest
} = props ;
const { getPrefixCls , direction , input } = React . useContext ( ConfigContext ) ;
const prefixCls = getPrefixCls ( 'input' , customizePrefixCls ) ;
const inputRef = useRef < InputRef > ( null ) ;
// ===================== Status =====================
const size = React . useContext ( SizeContext ) ;
const mergedSize = customSize || size ;
// ===================== Status =====================
const { status : contextStatus , hasFeedback } = useContext ( FormItemStatusContext ) ;
const mergedStatus = getMergedStatus ( contextStatus , customStatus ) ;
// ===================== Focus warning =====================
const inputHasPrefixSuffix = hasPrefixSuffix ( props ) ;
const prevHasPrefixSuffix = useRef < boolean > ( inputHasPrefixSuffix ) ;
useEffect ( ( ) = > {
if ( inputHasPrefixSuffix && ! prevHasPrefixSuffix . current ) {
2020-05-14 15:57:04 +08:00
devWarning (
2022-03-01 14:17:48 +08:00
document . activeElement === inputRef . current ? . input ,
2019-02-27 15:32:29 +08:00
'Input' ,
2019-02-20 15:12:29 +08:00
` When Input is focused, dynamic add or remove prefix / suffix will make it lose focus caused by dom structure change. Read more: https://ant.design/components/input/#FAQ ` ,
) ;
}
2022-03-01 14:17:48 +08:00
prevHasPrefixSuffix . current = inputHasPrefixSuffix ;
} , [ inputHasPrefixSuffix ] ) ;
// ===================== Remove Password value =====================
const removePasswordTimeoutRef = useRef < number [ ] > ( [ ] ) ;
const removePasswordTimeout = ( ) = > {
removePasswordTimeoutRef . current . push (
window . setTimeout ( ( ) = > {
if (
inputRef . current ? . input &&
inputRef . current ? . input . getAttribute ( 'type' ) === 'password' &&
inputRef . current ? . input . hasAttribute ( 'value' )
) {
inputRef . current ? . input . removeAttribute ( 'value' ) ;
}
} ) ,
2017-05-22 14:44:58 +08:00
) ;
2019-11-01 18:19:29 +08:00
} ;
2022-03-01 14:17:48 +08:00
useEffect ( ( ) = > {
removePasswordTimeout ( ) ;
return ( ) = > removePasswordTimeoutRef . current . forEach ( item = > window . clearTimeout ( item ) ) ;
} , [ ] ) ;
2022-02-14 17:09:35 +08:00
2022-03-01 14:17:48 +08:00
const handleBlur = ( e : React.FocusEvent < HTMLInputElement > ) = > {
removePasswordTimeout ( ) ;
onBlur ? . ( e ) ;
2022-02-14 17:09:35 +08:00
} ;
2022-03-01 14:17:48 +08:00
const handleFocus = ( e : React.FocusEvent < HTMLInputElement > ) = > {
removePasswordTimeout ( ) ;
onFocus ? . ( e ) ;
2018-12-05 19:12:18 +08:00
} ;
2022-03-01 14:17:48 +08:00
const suffixNode = ( hasFeedback || suffix ) && (
< >
{ suffix }
{ hasFeedback && getFeedbackIcon ( prefixCls , mergedStatus ) }
< / >
) ;
2022-03-04 12:07:25 +08:00
const withPrefixSuffix = hasPrefixSuffix ( props ) || hasFeedback ;
2022-03-01 14:17:48 +08:00
return (
< RcInput
ref = { composeRef ( ref , inputRef ) }
prefixCls = { prefixCls }
autoComplete = { input ? . autoComplete }
{ . . . rest }
onBlur = { handleBlur }
onFocus = { handleFocus }
suffix = { suffixNode }
clearIcon = { < CloseCircleFilled / > }
inputClassName = { classNames (
2022-03-04 12:07:25 +08:00
! withPrefixSuffix && {
2022-03-01 14:17:48 +08:00
[ ` ${ prefixCls } -sm ` ] : mergedSize === 'small' ,
[ ` ${ prefixCls } -lg ` ] : mergedSize === 'large' ,
[ ` ${ prefixCls } -rtl ` ] : direction === 'rtl' ,
[ ` ${ prefixCls } -borderless ` ] : ! bordered ,
} ,
2022-03-04 12:07:25 +08:00
! withPrefixSuffix && getStatusClassNames ( prefixCls , mergedStatus ) ,
2022-03-01 14:17:48 +08:00
) }
affixWrapperClassName = { classNames (
{
[ ` ${ prefixCls } -affix-wrapper-sm ` ] : mergedSize === 'small' ,
[ ` ${ prefixCls } -affix-wrapper-lg ` ] : mergedSize === 'large' ,
[ ` ${ prefixCls } -affix-wrapper-rtl ` ] : direction === 'rtl' ,
[ ` ${ prefixCls } -affix-wrapper-borderless ` ] : ! bordered ,
} ,
getStatusClassNames ( ` ${ prefixCls } -affix-wrapper ` , mergedStatus , hasFeedback ) ,
) }
wrapperClassName = { classNames ( {
[ ` ${ prefixCls } -group-rtl ` ] : direction === 'rtl' ,
} ) }
groupClassName = { classNames (
{
[ ` ${ prefixCls } -group-wrapper-sm ` ] : mergedSize === 'small' ,
[ ` ${ prefixCls } -group-wrapper-lg ` ] : mergedSize === 'large' ,
[ ` ${ prefixCls } -group-wrapper-rtl ` ] : direction === 'rtl' ,
} ,
getStatusClassNames ( ` ${ prefixCls } -group-wrapper ` , mergedStatus , hasFeedback ) ,
) }
/ >
) ;
} ) ;
2018-12-26 22:47:55 +08:00
export default Input ;