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' ;
2016-09-10 17:17:55 +08:00
import omit from 'omit.js' ;
2017-05-22 14:44:58 +08:00
import Group from './Group' ;
import Search from './Search' ;
import TextArea from './TextArea' ;
2018-11-29 10:03:16 +08:00
import Password from './Password' ;
2020-01-03 13:38:16 +08:00
import { Omit } from '../_util/type' ;
2019-11-01 18:19:29 +08:00
import ClearableLabeledInput , { hasPrefixSuffix } from './ClearableLabeledInput' ;
import { ConfigConsumer , ConfigConsumerProps } from '../config-provider' ;
2020-01-03 13:38:16 +08:00
import SizeContext , { SizeType } from '../config-provider/SizeContext' ;
2019-02-20 15:12:29 +08:00
import warning from '../_util/warning' ;
2016-03-31 17:46:35 +08:00
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 ;
2020-01-03 13:38:16 +08:00
size? : SizeType ;
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
}
2019-11-01 18:19:29 +08:00
export function fixControlledValue < T > ( value : T ) {
if ( typeof value === 'undefined' || value === null ) {
return '' ;
}
return value ;
}
export function resolveOnChange (
target : HTMLInputElement | HTMLTextAreaElement ,
e :
| React . ChangeEvent < HTMLTextAreaElement | HTMLInputElement >
| React . MouseEvent < HTMLElement , MouseEvent > ,
onChange ? : ( event : React.ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ) = > void ,
) {
if ( onChange ) {
let event = e ;
if ( e . type === 'click' ) {
// click clear icon
event = Object . create ( e ) ;
event . target = target ;
event . currentTarget = target ;
const originalInputValue = target . value ;
// change target ref value cause e.target.value should be '' when clear input
target . value = '' ;
onChange ( event as React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ) ;
// reset target ref value
target . value = originalInputValue ;
return ;
}
onChange ( event as React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ) ;
}
}
export function getInputClassName (
prefixCls : string ,
2020-01-03 13:38:16 +08:00
size? : SizeType ,
2019-11-01 18:19:29 +08:00
disabled? : boolean ,
2020-01-02 19:10:16 +08:00
direction? : any ,
2019-11-01 18:19:29 +08:00
) {
return classNames ( prefixCls , {
[ ` ${ prefixCls } -sm ` ] : size === 'small' ,
[ ` ${ prefixCls } -lg ` ] : size === 'large' ,
[ ` ${ prefixCls } -disabled ` ] : disabled ,
2020-01-02 19:10:16 +08:00
[ ` ${ prefixCls } -rtl ` ] : direction === 'rtl' ,
2019-11-01 18:19:29 +08:00
} ) ;
}
export interface InputState {
value : any ;
}
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
2017-09-08 09:43:21 +08:00
input : HTMLInputElement ;
2016-06-10 16:44:01 +08:00
2019-11-01 18:19:29 +08:00
clearableInput : ClearableLabeledInput ;
2019-12-31 14:41:09 +08:00
removePasswordTimeout : number ;
2020-01-02 19:10:16 +08:00
direction : any = 'ltr' ;
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-11-01 18:19:29 +08:00
static getDerivedStateFromProps ( nextProps : InputProps ) {
if ( 'value' in nextProps ) {
return {
value : nextProps.value ,
} ;
}
return null ;
}
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 ) ) {
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 ;
}
2019-12-31 14:41:09 +08:00
componentWillUnmount() {
if ( this . removePasswordTimeout ) {
clearTimeout ( this . removePasswordTimeout ) ;
}
}
2019-08-05 18:38:10 +08:00
focus() {
this . input . focus ( ) ;
}
blur() {
this . input . blur ( ) ;
}
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
2019-11-01 18:19:29 +08:00
setValue ( value : string , callback ? : ( ) = > void ) {
if ( ! ( 'value' in this . props ) ) {
this . setState ( { value } , 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-01-03 13:38:16 +08:00
renderInput = ( prefixCls : string , size? : SizeType ) = > {
const { className , addonBefore , addonAfter , size : customizeSize , disabled } = this . props ;
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' ,
2019-09-26 22:07:34 +08:00
'size' ,
2019-11-01 18:19:29 +08:00
'inputType' ,
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
{ . . . otherProps }
2018-12-26 22:34:29 +08:00
onChange = { this . handleChange }
2019-11-01 18:19:29 +08:00
onKeyDown = { this . handleKeyDown }
2020-01-03 13:38:16 +08:00
className = { classNames (
getInputClassName ( prefixCls , customizeSize || size , disabled , this . direction ) ,
{
[ className ! ] : className && ! addonBefore && ! addonAfter ,
} ,
) }
2017-09-08 09:43:21 +08:00
ref = { this . saveInput }
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 ;
if ( e . keyCode === 13 && onPressEnter ) {
onPressEnter ( e ) ;
}
if ( onKeyDown ) {
onKeyDown ( e ) ;
}
} ;
2016-03-31 17:46:35 +08:00
2020-01-02 19:10:16 +08:00
renderComponent = ( { getPrefixCls , direction } : ConfigConsumerProps ) = > {
2019-11-01 18:19:29 +08:00
const { value } = this . state ;
2018-12-05 19:12:18 +08:00
const { prefixCls : customizePrefixCls } = this . props ;
const prefixCls = getPrefixCls ( 'input' , customizePrefixCls ) ;
2020-01-02 19:10:16 +08:00
this . direction = direction ;
2019-11-01 18:19:29 +08:00
return (
2020-01-03 13:38:16 +08:00
< SizeContext.Consumer >
{ size = > (
< ClearableLabeledInput
{ . . . this . props }
prefixCls = { prefixCls }
inputType = "input"
value = { fixControlledValue ( value ) }
element = { this . renderInput ( prefixCls , size ) }
handleReset = { this . handleReset }
ref = { this . saveClearableInput }
direction = { direction }
/ >
) }
< / 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 ;