2017-11-17 14:38:54 +08:00
import * as React from 'react' ;
2018-08-07 21:07:52 +08:00
import * as PropTypes from 'prop-types' ;
2016-03-31 17:46:35 +08:00
import classNames from 'classnames' ;
2016-09-10 17:17:55 +08:00
import omit from 'omit.js' ;
2018-12-26 22:47:55 +08:00
import { polyfill } from 'react-lifecycles-compat' ;
2017-05-22 14:44:58 +08:00
import Group from './Group' ;
import Search from './Search' ;
import TextArea from './TextArea' ;
2018-12-05 19:12:18 +08:00
import { ConfigConsumer , ConfigConsumerProps } from '../config-provider' ;
2018-11-29 10:03:16 +08:00
import Password from './Password' ;
2018-12-26 22:34:29 +08:00
import Icon from '../icon' ;
2018-12-22 21:21:42 +08:00
import { Omit , tuple } from '../_util/type' ;
2019-02-20 15:12:29 +08:00
import warning from '../_util/warning' ;
2016-03-31 17:46:35 +08:00
2018-05-21 21:30:37 +08:00
function fixControlledValue < T > ( value : T ) {
2016-03-31 17:46:35 +08:00
if ( typeof value === 'undefined' || value === null ) {
return '' ;
}
return value ;
}
2019-02-20 15:12:29 +08:00
function hasPrefixSuffix ( props : InputProps ) {
2019-03-07 15:47:43 +08:00
return ! ! ( 'prefix' in props || props . suffix || props . allowClear ) ;
2019-02-20 15:12:29 +08:00
}
2018-12-22 21:21:42 +08:00
const InputSizes = tuple ( 'small' , 'default' , 'large' ) ;
2018-12-07 20:02:01 +08:00
export interface InputProps
extends Omit < React.InputHTMLAttributes < HTMLInputElement > , 'size' | 'prefix' > {
2016-08-19 17:11:06 +08:00
prefixCls? : string ;
2018-12-22 21:21:42 +08:00
size ? : ( typeof InputSizes ) [ number ] ;
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 ;
2016-08-19 17:11:06 +08:00
}
2018-12-26 22:47:55 +08:00
class Input extends React . Component < InputProps , any > {
2017-05-22 14:44:58 +08:00
static Group : typeof Group ;
static Search : typeof Search ;
static TextArea : typeof TextArea ;
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
static propTypes = {
2016-06-10 15:29:01 +08:00
type : PropTypes . string ,
2018-12-22 21:21:42 +08:00
id : PropTypes.string ,
size : PropTypes.oneOf ( InputSizes ) ,
maxLength : PropTypes.number ,
2016-06-10 15:29:01 +08:00
disabled : PropTypes.bool ,
value : PropTypes.any ,
defaultValue : PropTypes.any ,
className : PropTypes.string ,
addonBefore : PropTypes.node ,
addonAfter : PropTypes.node ,
prefixCls : PropTypes.string ,
onPressEnter : PropTypes.func ,
onKeyDown : PropTypes.func ,
2017-12-28 09:27:11 +08:00
onKeyUp : PropTypes.func ,
2017-01-01 01:06:19 +08:00
onFocus : PropTypes.func ,
onBlur : PropTypes.func ,
prefix : PropTypes.node ,
suffix : PropTypes.node ,
2018-12-26 22:34:29 +08:00
allowClear : PropTypes.bool ,
} ;
2018-12-28 17:07:02 +08:00
static getDerivedStateFromProps ( nextProps : InputProps ) {
2018-12-26 22:47:55 +08:00
if ( 'value' in nextProps ) {
return {
value : nextProps.value ,
} ;
}
return null ;
}
2016-04-06 18:12:41 +08:00
2017-09-08 09:43:21 +08:00
input : HTMLInputElement ;
2016-06-10 16:44:01 +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 ,
} ;
}
2019-02-20 15:12:29 +08:00
getSnapshotBeforeUpdate ( prevProps : InputProps ) {
if ( hasPrefixSuffix ( prevProps ) !== hasPrefixSuffix ( this . props ) ) {
warning (
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 ;
}
// Since polyfill `getSnapshotBeforeUpdate` need work with `componentDidUpdate`.
// We keep an empty function here.
componentDidUpdate() { }
2017-11-21 19:51:31 +08:00
handleKeyDown = ( e : React.KeyboardEvent < HTMLInputElement > ) = > {
2016-10-24 12:04:26 +08:00
const { onPressEnter , onKeyDown } = this . props ;
if ( e . keyCode === 13 && onPressEnter ) {
onPressEnter ( e ) ;
}
if ( onKeyDown ) {
onKeyDown ( e ) ;
2016-04-06 18:12:41 +08:00
}
2018-12-07 20:02:01 +08:00
} ;
2016-03-31 17:46:35 +08:00
2016-12-07 11:08:30 +08:00
focus() {
2017-09-08 09:43:21 +08:00
this . input . focus ( ) ;
2016-12-07 11:08:30 +08:00
}
2017-05-22 14:44:58 +08:00
blur() {
2017-09-08 09:43:21 +08:00
this . input . blur ( ) ;
2017-05-22 14:44:58 +08:00
}
2018-08-28 18:54:21 +08:00
select() {
this . input . select ( ) ;
}
2018-12-05 19:12:18 +08:00
getInputClassName ( prefixCls : string ) {
const { size , disabled } = this . props ;
2017-07-12 21:43:06 +08:00
return classNames ( prefixCls , {
[ ` ${ prefixCls } -sm ` ] : size === 'small' ,
[ ` ${ prefixCls } -lg ` ] : size === 'large' ,
[ ` ${ prefixCls } -disabled ` ] : disabled ,
} ) ;
}
2017-11-21 19:51:31 +08:00
saveInput = ( node : HTMLInputElement ) = > {
2017-09-08 09:43:21 +08:00
this . input = node ;
2018-12-07 20:02:01 +08:00
} ;
2017-09-08 09:43:21 +08:00
2018-12-26 22:34:29 +08:00
setValue (
value : string ,
e : React.ChangeEvent < HTMLInputElement > | React . MouseEvent < HTMLElement , MouseEvent > ,
2019-03-04 22:19:40 +08:00
callback ? : ( ) = > void ,
2018-12-26 22:34:29 +08:00
) {
if ( ! ( 'value' in this . props ) ) {
2019-03-04 22:19:40 +08:00
this . setState ( { value } , callback ) ;
2018-12-26 22:34:29 +08:00
}
2018-12-28 17:08:38 +08:00
const { onChange } = this . props ;
if ( onChange ) {
let event = e ;
if ( e . type === 'click' ) {
// click clear icon
event = Object . create ( e ) ;
event . target = this . input ;
event . currentTarget = this . input ;
const originalInputValue = this . input . value ;
// change input value cause e.target.value should be '' when clear input
this . input . value = '' ;
2018-12-28 15:48:05 +08:00
onChange ( event as React . ChangeEvent < HTMLInputElement > ) ;
2018-12-28 17:08:38 +08:00
// reset input value
this . input . value = originalInputValue ;
return ;
2018-12-28 15:48:05 +08:00
}
onChange ( event as React . ChangeEvent < HTMLInputElement > ) ;
2018-12-26 22:34:29 +08:00
}
}
handleReset = ( e : React.MouseEvent < HTMLElement , MouseEvent > ) = > {
2019-03-04 22:19:40 +08:00
this . setValue ( '' , e , ( ) = > {
this . focus ( ) ;
} ) ;
2018-12-26 22:34:29 +08:00
} ;
handleChange = ( e : React.ChangeEvent < HTMLInputElement > ) = > {
this . setValue ( e . target . value , e ) ;
} ;
renderClearIcon ( prefixCls : string ) {
const { allowClear } = this . props ;
const { value } = this . state ;
2019-02-07 11:39:16 +08:00
if ( ! allowClear || value === undefined || value === null || value === '' ) {
2018-12-26 22:34:29 +08:00
return null ;
}
return (
< Icon
type = "close-circle"
theme = "filled"
onClick = { this . handleReset }
className = { ` ${ prefixCls } -clear-icon ` }
2018-12-28 17:07:02 +08:00
role = "button"
2018-12-26 22:34:29 +08:00
/ >
) ;
}
renderSuffix ( prefixCls : string ) {
const { suffix , allowClear } = this . props ;
if ( suffix || allowClear ) {
return (
< span className = { ` ${ prefixCls } -suffix ` } >
{ this . renderClearIcon ( prefixCls ) }
{ suffix }
< / span >
) ;
}
return null ;
}
2018-12-05 19:12:18 +08:00
renderLabeledInput ( prefixCls : string , children : React.ReactElement < any > ) {
2019-01-25 11:06:05 +08:00
const { addonBefore , addonAfter , style , size , className } = this . props ;
2016-12-04 16:42:49 +08:00
// Not wrap when there is not addons
2019-01-25 11:06:05 +08:00
if ( ! addonBefore && ! addonAfter ) {
2016-12-04 16:42:49 +08:00
return children ;
}
2018-12-05 19:12:18 +08:00
const wrapperClassName = ` ${ prefixCls } -group ` ;
2016-03-31 17:46:35 +08:00
const addonClassName = ` ${ wrapperClassName } -addon ` ;
2019-01-25 11:06:05 +08:00
const addonBeforeNode = addonBefore ? (
< span className = { addonClassName } > { addonBefore } < / span >
2016-03-31 17:46:35 +08:00
) : null ;
2019-01-25 11:06:05 +08:00
const addonAfterNode = addonAfter ? < span className = { addonClassName } > { addonAfter } < / span > : null ;
2016-03-31 17:46:35 +08:00
2019-01-25 11:06:05 +08:00
const mergedWrapperClassName = classNames ( ` ${ prefixCls } -wrapper ` , {
2018-12-07 20:02:01 +08:00
[ wrapperClassName ] : addonBefore || addonAfter ,
2016-03-31 17:46:35 +08:00
} ) ;
2019-01-25 11:06:05 +08:00
const mergedGroupClassName = classNames ( className , ` ${ prefixCls } -group-wrapper ` , {
[ ` ${ prefixCls } -group-wrapper-sm ` ] : size === 'small' ,
[ ` ${ prefixCls } -group-wrapper-lg ` ] : size === 'large' ,
2017-11-29 11:20:38 +08:00
} ) ;
2017-06-03 00:55:30 +08:00
// Need another wrapper for changing display:table to display:inline-block
// and put style prop in wrapper
return (
2019-01-25 11:06:05 +08:00
< span className = { mergedGroupClassName } style = { style } >
< span className = { mergedWrapperClassName } >
{ addonBeforeNode }
2018-07-31 16:40:48 +08:00
{ React . cloneElement ( children , { style : null } ) }
2019-01-25 11:06:05 +08:00
{ addonAfterNode }
2018-07-31 16:40:48 +08:00
< / span >
2016-03-31 17:46:35 +08:00
< / span >
) ;
}
2018-12-05 19:12:18 +08:00
renderLabeledIcon ( prefixCls : string , children : React.ReactElement < any > ) {
2017-01-01 01:06:19 +08:00
const { props } = this ;
2018-12-26 22:34:29 +08:00
const suffix = this . renderSuffix ( prefixCls ) ;
2019-02-20 15:12:29 +08:00
if ( ! hasPrefixSuffix ( props ) ) {
2017-01-01 01:06:19 +08:00
return children ;
}
const prefix = props . prefix ? (
2018-12-07 20:02:01 +08:00
< span className = { ` ${ prefixCls } -prefix ` } > { props . prefix } < / span >
2017-01-01 01:06:19 +08:00
) : null ;
2018-12-05 19:12:18 +08:00
const affixWrapperCls = classNames ( props . className , ` ${ prefixCls } -affix-wrapper ` , {
[ ` ${ prefixCls } -affix-wrapper-sm ` ] : props . size === 'small' ,
[ ` ${ prefixCls } -affix-wrapper-lg ` ] : props . size === 'large' ,
2017-12-08 20:57:30 +08:00
} ) ;
2017-01-01 01:06:19 +08:00
return (
2018-12-07 20:02:01 +08:00
< span className = { affixWrapperCls } style = { props . style } >
2017-01-01 01:06:19 +08:00
{ prefix }
2018-12-07 20:02:01 +08:00
{ React . cloneElement ( children , {
style : null ,
className : this.getInputClassName ( prefixCls ) ,
} ) }
2017-01-01 01:06:19 +08:00
{ suffix }
< / span >
) ;
}
2018-12-05 19:12:18 +08:00
renderInput ( prefixCls : string ) {
2019-01-25 11:06:05 +08:00
const { className , addonBefore , addonAfter } = this . props ;
2018-12-26 22:34:29 +08:00
const { value } = this . state ;
2016-07-06 12:21:49 +08:00
// Fix https://fb.me/react-unknown-prop
2017-07-12 21:43:06 +08:00
const otherProps = omit ( this . props , [
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' ,
] ) ;
2017-05-22 14:44:58 +08:00
return this . renderLabeledIcon (
2018-12-05 19:12:18 +08:00
prefixCls ,
2017-05-22 14:44:58 +08:00
< input
{ . . . otherProps }
2018-12-27 11:41:30 +08:00
value = { fixControlledValue ( value ) }
2018-12-26 22:34:29 +08:00
onChange = { this . handleChange }
2019-01-25 11:06:05 +08:00
className = { classNames ( this . getInputClassName ( prefixCls ) , {
[ className ! ] : className && ! addonBefore && ! addonAfter ,
} ) }
2017-05-22 14:44:58 +08:00
onKeyDown = { this . handleKeyDown }
2017-09-08 09:43:21 +08:00
ref = { this . saveInput }
2017-05-22 14:44:58 +08:00
/ > ,
) ;
2016-03-31 17:46:35 +08:00
}
2018-12-05 19:12:18 +08:00
renderComponent = ( { getPrefixCls } : ConfigConsumerProps ) = > {
const { prefixCls : customizePrefixCls } = this . props ;
const prefixCls = getPrefixCls ( 'input' , customizePrefixCls ) ;
return this . renderLabeledInput ( prefixCls , this . renderInput ( prefixCls ) ) ;
} ;
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
polyfill ( Input ) ;
export default Input ;