import * as React from 'react' ;
import * as PropTypes from 'prop-types' ;
import classNames from 'classnames' ;
import omit from 'omit.js' ;
import { polyfill } from 'react-lifecycles-compat' ;
import Group from './Group' ;
import Search from './Search' ;
import TextArea from './TextArea' ;
import { ConfigConsumer , ConfigConsumerProps } from '../config-provider' ;
import Password from './Password' ;
import Icon from '../icon' ;
import { Omit , tuple } from '../_util/type' ;
import warning from '../_util/warning' ;
function fixControlledValue < T > ( value : T ) {
if ( typeof value === 'undefined' || value === null ) {
return '' ;
}
return value ;
}
function hasPrefixSuffix ( props : InputProps ) {
return ! ! ( 'prefix' in props || props . suffix || props . allowClear ) ;
}
const InputSizes = tuple ( 'small' , 'default' , 'large' ) ;
export interface InputProps
extends Omit < React.InputHTMLAttributes < HTMLInputElement > , 'size' | 'prefix' > {
prefixCls? : string ;
size ? : ( typeof InputSizes ) [ number ] ;
onPressEnter? : React.KeyboardEventHandler < HTMLInputElement > ;
addonBefore? : React.ReactNode ;
addonAfter? : React.ReactNode ;
prefix? : React.ReactNode ;
suffix? : React.ReactNode ;
allowClear? : boolean ;
}
class Input extends React . Component < InputProps , any > {
static Group : typeof Group ;
static Search : typeof Search ;
static TextArea : typeof TextArea ;
static Password : typeof Password ;
static defaultProps = {
type : 'text' ,
} ;
static propTypes = {
type : PropTypes . string ,
id : PropTypes.string ,
size : PropTypes.oneOf ( InputSizes ) ,
maxLength : PropTypes.number ,
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 ,
onKeyUp : PropTypes.func ,
onFocus : PropTypes.func ,
onBlur : PropTypes.func ,
prefix : PropTypes.node ,
suffix : PropTypes.node ,
allowClear : PropTypes.bool ,
} ;
static getDerivedStateFromProps ( nextProps : InputProps ) {
if ( 'value' in nextProps ) {
return {
value : nextProps.value ,
} ;
}
return null ;
}
input : HTMLInputElement ;
constructor ( props : InputProps ) {
super ( props ) ;
const value = typeof props . value === 'undefined' ? props.defaultValue : props.value ;
this . state = {
value ,
} ;
}
getSnapshotBeforeUpdate ( prevProps : InputProps ) {
if ( hasPrefixSuffix ( prevProps ) !== hasPrefixSuffix ( this . props ) ) {
warning (
this . input !== document . activeElement ,
'Input' ,
` 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() { }
handleKeyDown = ( e : React.KeyboardEvent < HTMLInputElement > ) = > {
const { onPressEnter , onKeyDown } = this . props ;
if ( e . keyCode === 13 && onPressEnter ) {
onPressEnter ( e ) ;
}
if ( onKeyDown ) {
onKeyDown ( e ) ;
}
} ;
focus() {
this . input . focus ( ) ;
}
blur() {
this . input . blur ( ) ;
}
select() {
this . input . select ( ) ;
}
getInputClassName ( prefixCls : string ) {
const { size , disabled } = this . props ;
return classNames ( prefixCls , {
[ ` ${ prefixCls } -sm ` ] : size === 'small' ,
[ ` ${ prefixCls } -lg ` ] : size === 'large' ,
[ ` ${ prefixCls } -disabled ` ] : disabled ,
} ) ;
}
saveInput = ( node : HTMLInputElement ) = > {
this . input = node ;
} ;
setValue (
value : string ,
e : React.ChangeEvent < HTMLInputElement > | React . MouseEvent < HTMLElement , MouseEvent > ,
callback ? : ( ) = > void ,
) {
if ( ! ( 'value' in this . props ) ) {
this . setState ( { value } , callback ) ;
}
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 = '' ;
onChange ( event as React . ChangeEvent < HTMLInputElement > ) ;
// reset input value
this . input . value = originalInputValue ;
return ;
}
onChange ( event as React . ChangeEvent < HTMLInputElement > ) ;
}
}
handleReset = ( e : React.MouseEvent < HTMLElement , MouseEvent > ) = > {
this . setValue ( '' , e , ( ) = > {
this . focus ( ) ;
} ) ;
} ;
handleChange = ( e : React.ChangeEvent < HTMLInputElement > ) = > {
this . setValue ( e . target . value , e ) ;
} ;
renderClearIcon ( prefixCls : string ) {
const { allowClear } = this . props ;
const { value } = this . state ;
if ( ! allowClear || value === undefined || value === null || value === '' ) {
return null ;
}
return (
< Icon
type = "close-circle"
theme = "filled"
onClick = { this . handleReset }
className = { ` ${ prefixCls } -clear-icon ` }
role = "button"
/ >
) ;
}
renderSuffix ( prefixCls : string ) {
const { suffix , allowClear } = this . props ;
if ( suffix || allowClear ) {
return (
< span className = { ` ${ prefixCls } -suffix ` } >
{ this . renderClearIcon ( prefixCls ) }
{ suffix }
< / span >
) ;
}
return null ;
}
renderLabeledInput ( prefixCls : string , children : React.ReactElement < any > ) {
const { addonBefore , addonAfter , style , size , className } = this . props ;
// Not wrap when there is not addons
if ( ! addonBefore && ! addonAfter ) {
return children ;
}
const wrapperClassName = ` ${ prefixCls } -group ` ;
const addonClassName = ` ${ wrapperClassName } -addon ` ;
const addonBeforeNode = addonBefore ? (
< span className = { addonClassName } > { addonBefore } < / span >
) : null ;
const addonAfterNode = addonAfter ? < span className = { addonClassName } > { addonAfter } < / span > : null ;
const mergedWrapperClassName = classNames ( ` ${ prefixCls } -wrapper ` , {
[ wrapperClassName ] : addonBefore || addonAfter ,
} ) ;
const mergedGroupClassName = classNames ( className , ` ${ prefixCls } -group-wrapper ` , {
[ ` ${ prefixCls } -group-wrapper-sm ` ] : size === 'small' ,
[ ` ${ prefixCls } -group-wrapper-lg ` ] : size === 'large' ,
} ) ;
// Need another wrapper for changing display:table to display:inline-block
// and put style prop in wrapper
return (
< span className = { mergedGroupClassName } style = { style } >
< span className = { mergedWrapperClassName } >
{ addonBeforeNode }
{ React . cloneElement ( children , { style : null } ) }
{ addonAfterNode }
< / span >
< / span >
) ;
}
renderLabeledIcon ( prefixCls : string , children : React.ReactElement < any > ) {
const { props } = this ;
const suffix = this . renderSuffix ( prefixCls ) ;
if ( ! hasPrefixSuffix ( props ) ) {
return children ;
}
const prefix = props . prefix ? (
< span className = { ` ${ prefixCls } -prefix ` } > { props . prefix } < / span >
) : null ;
const affixWrapperCls = classNames ( props . className , ` ${ prefixCls } -affix-wrapper ` , {
[ ` ${ prefixCls } -affix-wrapper-sm ` ] : props . size === 'small' ,
[ ` ${ prefixCls } -affix-wrapper-lg ` ] : props . size === 'large' ,
} ) ;
return (
< span className = { affixWrapperCls } style = { props . style } >
{ prefix }
{ React . cloneElement ( children , {
style : null ,
className : this.getInputClassName ( prefixCls ) ,
} ) }
{ suffix }
< / span >
) ;
}
renderInput ( prefixCls : string ) {
const { className , addonBefore , addonAfter } = this . props ;
const { value } = this . state ;
// Fix https://fb.me/react-unknown-prop
const otherProps = omit ( this . props , [
'prefixCls' ,
'onPressEnter' ,
'addonBefore' ,
'addonAfter' ,
'prefix' ,
'suffix' ,
'allowClear' ,
// Input elements must be either controlled or uncontrolled,
// specify either the value prop, or the defaultValue prop, but not both.
'defaultValue' ,
] ) ;
return this . renderLabeledIcon (
prefixCls ,
< input
{ . . . otherProps }
value = { fixControlledValue ( value ) }
onChange = { this . handleChange }
className = { classNames ( this . getInputClassName ( prefixCls ) , {
[ className ! ] : className && ! addonBefore && ! addonAfter ,
} ) }
onKeyDown = { this . handleKeyDown }
ref = { this . saveInput }
/ > ,
) ;
}
renderComponent = ( { getPrefixCls } : ConfigConsumerProps ) = > {
const { prefixCls : customizePrefixCls } = this . props ;
const prefixCls = getPrefixCls ( 'input' , customizePrefixCls ) ;
return this . renderLabeledInput ( prefixCls , this . renderInput ( prefixCls ) ) ;
} ;
render() {
return < ConfigConsumer > { this . renderComponent } < / ConfigConsumer > ;
}
}
polyfill ( Input ) ;
export default Input ;