2017-11-17 14:38:54 +08:00
import * as React from 'react' ;
2016-03-31 17:46:35 +08:00
import classNames from 'classnames' ;
2021-01-13 21:00:30 +08:00
import omit from 'rc-util/lib/omit' ;
2021-08-17 16:22:42 +08:00
import type Group from './Group' ;
import type Search from './Search' ;
import type TextArea from './TextArea' ;
import type Password from './Password' ;
2021-08-04 20:07:28 +08:00
import { LiteralUnion } from '../_util/type' ;
2021-08-17 16:22:42 +08:00
import ClearableLabeledInput from './ClearableLabeledInput' ;
2020-11-04 14:12:45 +08:00
import { ConfigConsumer , ConfigConsumerProps , DirectionType } from '../config-provider' ;
2020-01-03 13:38:16 +08:00
import SizeContext , { SizeType } from '../config-provider/SizeContext' ;
2020-05-14 15:57:04 +08:00
import devWarning from '../_util/devWarning' ;
2021-08-17 16:22:42 +08:00
import { getInputClassName , hasPrefixSuffix } from './utils' ;
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' ;
}
2021-10-15 16:23:47 +08:00
export interface ShowCountProps {
formatter : ( args : { count : number ; maxLength? : number } ) = > React . ReactNode ;
}
2018-12-07 20:02:01 +08:00
export interface InputProps
2020-04-08 10:43:37 +08:00
extends Omit < React.InputHTMLAttributes < HTMLInputElement > , 'size' | 'prefix' | 'type' > {
2016-08-19 17:11:06 +08:00
prefixCls? : string ;
2020-01-03 13:38:16 +08:00
size? : SizeType ;
2020-04-08 10:43:37 +08:00
// ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#%3Cinput%3E_types
type ? : LiteralUnion <
| 'button'
| 'checkbox'
| 'color'
| 'date'
| 'datetime-local'
| 'email'
| 'file'
| 'hidden'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'reset'
| 'search'
| 'submit'
| 'tel'
| 'text'
| 'time'
| 'url'
| 'week' ,
string
> ;
2018-05-21 21:30:37 +08:00
onPressEnter? : React.KeyboardEventHandler < HTMLInputElement > ;
2016-08-19 17:11:06 +08:00
addonBefore? : React.ReactNode ;
addonAfter? : React.ReactNode ;
2017-01-01 01:06:19 +08:00
prefix? : React.ReactNode ;
suffix? : React.ReactNode ;
2018-12-28 12:27:26 +08:00
allowClear? : boolean ;
2021-10-15 16:23:47 +08:00
showCount? : boolean | ShowCountProps ;
2020-07-16 00:25:47 +08:00
bordered? : boolean ;
2021-09-01 10:49:52 +08:00
htmlSize? : number ;
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 '' ;
}
return value ;
}
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' ) {
// click clear icon
event = Object . create ( e ) ;
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 ;
event . target = currentTarget ;
event . currentTarget = currentTarget ;
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 ) {
event = Object . create ( e ) ;
event . target = target ;
event . currentTarget = target ;
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 ) ;
}
}
}
2019-11-01 18:19:29 +08:00
export interface InputState {
value : any ;
2020-01-14 11:59:38 +08:00
focused : boolean ;
2020-01-09 11:13:07 +08:00
/** `value` from prev props */
prevValue : any ;
2019-11-01 18:19:29 +08:00
}
class Input extends React . Component < InputProps , InputState > {
2017-05-22 14:44:58 +08:00
static Group : typeof Group ;
2019-08-05 18:38:10 +08:00
2017-05-22 14:44:58 +08:00
static Search : typeof Search ;
2019-08-05 18:38:10 +08:00
2017-05-22 14:44:58 +08:00
static TextArea : typeof TextArea ;
2019-08-05 18:38:10 +08:00
2018-11-29 10:03:16 +08:00
static Password : typeof Password ;
2017-05-22 14:44:58 +08:00
2016-03-31 17:46:35 +08:00
static defaultProps = {
type : 'text' ,
2016-07-13 11:14:24 +08:00
} ;
2016-03-31 17:46:35 +08:00
2021-06-03 09:36:04 +08:00
input ! : HTMLInputElement ;
2016-06-10 16:44:01 +08:00
2021-06-03 09:36:04 +08:00
clearableInput ! : ClearableLabeledInput ;
2019-11-01 18:19:29 +08:00
2021-02-20 14:08:49 +08:00
removePasswordTimeout : any ;
2019-12-31 14:41:09 +08:00
2020-11-04 14:12:45 +08:00
direction : DirectionType = 'ltr' ;
2020-01-02 19:10:16 +08:00
2018-12-26 22:47:55 +08:00
constructor ( props : InputProps ) {
super ( props ) ;
2018-12-28 17:31:17 +08:00
const value = typeof props . value === 'undefined' ? props.defaultValue : props.value ;
2018-12-26 22:47:55 +08:00
this . state = {
value ,
2020-01-14 11:59:38 +08:00
focused : false ,
2020-01-09 11:13:07 +08:00
// eslint-disable-next-line react/no-unused-state
prevValue : props.value ,
2018-12-26 22:47:55 +08:00
} ;
}
2020-01-09 11:13:07 +08:00
static getDerivedStateFromProps ( nextProps : InputProps , { prevValue } : InputState ) {
const newState : Partial < InputState > = { prevValue : nextProps.value } ;
if ( nextProps . value !== undefined || prevValue !== nextProps . value ) {
newState . value = nextProps . value ;
2019-11-01 18:19:29 +08:00
}
2021-11-04 16:49:57 +08:00
if ( nextProps . disabled ) {
newState . focused = false ;
}
2020-01-09 11:13:07 +08:00
return newState ;
2019-11-01 18:19:29 +08:00
}
2019-12-31 14:41:09 +08:00
componentDidMount() {
this . clearPasswordValueAttribute ( ) ;
}
2019-08-05 18:38:10 +08:00
// Since polyfill `getSnapshotBeforeUpdate` need work with `componentDidUpdate`.
// We keep an empty function here.
componentDidUpdate() { }
2019-02-20 15:12:29 +08:00
getSnapshotBeforeUpdate ( prevProps : InputProps ) {
if ( hasPrefixSuffix ( prevProps ) !== hasPrefixSuffix ( this . props ) ) {
2020-05-14 15:57:04 +08:00
devWarning (
2019-02-20 15:12:29 +08:00
this . input !== document . activeElement ,
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 ` ,
) ;
}
return null ;
}
2019-12-31 14:41:09 +08:00
componentWillUnmount() {
if ( this . removePasswordTimeout ) {
clearTimeout ( this . removePasswordTimeout ) ;
}
}
2020-12-29 22:42:04 +08:00
focus = ( option? : InputFocusOptions ) = > {
triggerFocus ( this . input , option ) ;
2020-02-17 19:12:42 +08:00
} ;
2019-08-05 18:38:10 +08:00
blur() {
this . input . blur ( ) ;
}
2020-11-06 11:48:50 +08:00
setSelectionRange ( start : number , end : number , direction ? : 'forward' | 'backward' | 'none' ) {
this . input . setSelectionRange ( start , end , direction ) ;
}
2019-08-05 18:38:10 +08:00
select() {
this . input . select ( ) ;
}
2019-11-01 18:19:29 +08:00
saveClearableInput = ( input : ClearableLabeledInput ) = > {
this . clearableInput = input ;
} ;
2018-12-26 22:34:29 +08:00
2019-11-01 18:19:29 +08:00
saveInput = ( input : HTMLInputElement ) = > {
this . input = input ;
} ;
2018-12-26 22:34:29 +08:00
2020-01-14 11:59:38 +08:00
onFocus : React.FocusEventHandler < HTMLInputElement > = e = > {
const { onFocus } = this . props ;
2020-05-28 14:49:03 +08:00
this . setState ( { focused : true } , this . clearPasswordValueAttribute ) ;
2021-02-19 18:26:53 +08:00
onFocus ? . ( e ) ;
2020-01-14 11:59:38 +08:00
} ;
onBlur : React.FocusEventHandler < HTMLInputElement > = e = > {
const { onBlur } = this . props ;
2020-05-28 14:49:03 +08:00
this . setState ( { focused : false } , this . clearPasswordValueAttribute ) ;
2021-02-19 18:26:53 +08:00
onBlur ? . ( e ) ;
2020-01-14 11:59:38 +08:00
} ;
2019-11-01 18:19:29 +08:00
setValue ( value : string , callback ? : ( ) = > void ) {
2020-01-09 11:13:07 +08:00
if ( this . props . value === undefined ) {
2019-11-01 18:19:29 +08:00
this . setState ( { value } , callback ) ;
2020-10-16 18:05:13 +08:00
} else {
callback ? . ( ) ;
2016-12-04 16:42:49 +08:00
}
2016-03-31 17:46:35 +08:00
}
2019-11-01 18:19:29 +08:00
handleReset = ( e : React.MouseEvent < HTMLElement , MouseEvent > ) = > {
this . setValue ( '' , ( ) = > {
this . focus ( ) ;
2017-12-08 20:57:30 +08:00
} ) ;
2019-11-01 18:19:29 +08:00
resolveOnChange ( this . input , e , this . props . onChange ) ;
} ;
2017-01-01 01:06:19 +08:00
2020-04-22 10:38:43 +08:00
renderInput = (
prefixCls : string ,
size : SizeType | undefined ,
2020-07-16 00:25:47 +08:00
bordered : boolean ,
2020-04-22 10:38:43 +08:00
input : ConfigConsumerProps [ 'input' ] = { } ,
) = > {
2021-09-01 10:49:52 +08:00
const {
className ,
addonBefore ,
addonAfter ,
size : customizeSize ,
disabled ,
htmlSize ,
} = this . props ;
2016-07-06 12:21:49 +08:00
// Fix https://fb.me/react-unknown-prop
2021-01-13 21:00:30 +08:00
const otherProps = omit ( this . props as InputProps & { inputType : any } , [
2016-07-06 12:21:49 +08:00
'prefixCls' ,
'onPressEnter' ,
'addonBefore' ,
'addonAfter' ,
2017-01-01 01:06:19 +08:00
'prefix' ,
'suffix' ,
2018-12-26 22:34:29 +08:00
'allowClear' ,
2016-04-14 11:58:01 +08:00
// Input elements must be either controlled or uncontrolled,
// specify either the value prop, or the defaultValue prop, but not both.
2018-12-27 11:41:30 +08:00
'defaultValue' ,
2019-09-26 22:07:34 +08:00
'size' ,
2019-11-01 18:19:29 +08:00
'inputType' ,
2020-07-16 00:25:47 +08:00
'bordered' ,
2021-09-01 10:49:52 +08:00
'htmlSize' ,
2018-12-27 11:41:30 +08:00
] ) ;
2019-11-01 18:19:29 +08:00
return (
2017-05-22 14:44:58 +08:00
< input
2020-04-22 10:38:43 +08:00
autoComplete = { input . autoComplete }
2017-05-22 14:44:58 +08:00
{ . . . otherProps }
2018-12-26 22:34:29 +08:00
onChange = { this . handleChange }
2020-01-14 11:59:38 +08:00
onFocus = { this . onFocus }
onBlur = { this . onBlur }
2019-11-01 18:19:29 +08:00
onKeyDown = { this . handleKeyDown }
2020-01-03 13:38:16 +08:00
className = { classNames (
2020-07-16 00:25:47 +08:00
getInputClassName ( prefixCls , bordered , customizeSize || size , disabled , this . direction ) ,
2020-01-03 13:38:16 +08:00
{
[ className ! ] : className && ! addonBefore && ! addonAfter ,
} ,
) }
2017-09-08 09:43:21 +08:00
ref = { this . saveInput }
2021-09-01 10:49:52 +08:00
size = { htmlSize }
2019-11-01 18:19:29 +08:00
/ >
2017-05-22 14:44:58 +08:00
) ;
2019-11-01 18:19:29 +08:00
} ;
2019-12-31 14:41:09 +08:00
clearPasswordValueAttribute = ( ) = > {
// https://github.com/ant-design/ant-design/issues/20541
this . removePasswordTimeout = setTimeout ( ( ) = > {
if (
this . input &&
this . input . getAttribute ( 'type' ) === 'password' &&
this . input . hasAttribute ( 'value' )
) {
this . input . removeAttribute ( 'value' ) ;
}
} ) ;
} ;
2019-11-01 18:19:29 +08:00
handleChange = ( e : React.ChangeEvent < HTMLInputElement > ) = > {
2019-12-31 14:41:09 +08:00
this . setValue ( e . target . value , this . clearPasswordValueAttribute ) ;
2019-11-01 18:19:29 +08:00
resolveOnChange ( this . input , e , this . props . onChange ) ;
} ;
handleKeyDown = ( e : React.KeyboardEvent < HTMLInputElement > ) = > {
const { onPressEnter , onKeyDown } = this . props ;
2021-02-19 18:26:53 +08:00
if ( onPressEnter && e . keyCode === 13 ) {
2019-11-01 18:19:29 +08:00
onPressEnter ( e ) ;
}
2021-02-19 18:26:53 +08:00
onKeyDown ? . ( e ) ;
2019-11-01 18:19:29 +08:00
} ;
2016-03-31 17:46:35 +08:00
2021-10-15 16:23:47 +08:00
renderShowCountSuffix = ( prefixCls : string ) = > {
const { value } = this . state ;
const { maxLength , suffix , showCount } = this . props ;
// Max length value
const hasMaxLength = Number ( maxLength ) > 0 ;
if ( suffix || showCount ) {
const valueLength = [ . . . fixControlledValue ( value ) ] . length ;
let dataCount = null ;
if ( typeof showCount === 'object' ) {
dataCount = showCount . formatter ( { count : valueLength , maxLength } ) ;
} else {
dataCount = ` ${ valueLength } ${ hasMaxLength ? ` / ${ maxLength } ` : '' } ` ;
}
return (
< >
{ ! ! showCount && (
< span
className = { classNames ( ` ${ prefixCls } -show-count-suffix ` , {
[ ` ${ prefixCls } -show-count-has-suffix ` ] : ! ! suffix ,
} ) }
>
{ dataCount }
< / span >
) }
{ suffix }
< / >
) ;
}
return null ;
} ;
2020-04-22 10:38:43 +08:00
renderComponent = ( { getPrefixCls , direction , input } : ConfigConsumerProps ) = > {
2020-01-14 11:59:38 +08:00
const { value , focused } = this . state ;
2020-07-16 00:25:47 +08:00
const { prefixCls : customizePrefixCls , bordered = true } = this . props ;
2018-12-05 19:12:18 +08:00
const prefixCls = getPrefixCls ( 'input' , customizePrefixCls ) ;
2020-01-02 19:10:16 +08:00
this . direction = direction ;
2020-04-22 10:38:43 +08:00
2021-10-15 16:23:47 +08:00
const showCountSuffix = this . renderShowCountSuffix ( prefixCls ) ;
2019-11-01 18:19:29 +08:00
return (
2020-01-03 13:38:16 +08:00
< SizeContext.Consumer >
{ size = > (
< ClearableLabeledInput
2020-02-09 11:57:42 +08:00
size = { size }
2020-01-03 13:38:16 +08:00
{ . . . this . props }
prefixCls = { prefixCls }
inputType = "input"
value = { fixControlledValue ( value ) }
2020-07-16 00:25:47 +08:00
element = { this . renderInput ( prefixCls , size , bordered , input ) }
2020-01-03 13:38:16 +08:00
handleReset = { this . handleReset }
ref = { this . saveClearableInput }
direction = { direction }
2020-01-14 11:59:38 +08:00
focused = { focused }
2020-02-17 19:12:42 +08:00
triggerFocus = { this . focus }
2020-07-16 00:25:47 +08:00
bordered = { bordered }
2021-10-15 16:23:47 +08:00
suffix = { showCountSuffix }
2020-01-03 13:38:16 +08:00
/ >
) }
< / SizeContext.Consumer >
2019-11-01 18:19:29 +08:00
) ;
2018-12-05 19:12:18 +08:00
} ;
2016-03-31 17:46:35 +08:00
render() {
2018-12-07 20:02:01 +08:00
return < ConfigConsumer > { this . renderComponent } < / ConfigConsumer > ;
2016-03-31 17:46:35 +08:00
}
}
2018-12-26 22:47:55 +08:00
export default Input ;