import * as React from 'react' ;
import classNames from 'classnames' ;
import omit from 'rc-util/lib/omit' ;
import type Group from './Group' ;
import type Search from './Search' ;
import type TextArea from './TextArea' ;
import type Password from './Password' ;
import { LiteralUnion } from '../_util/type' ;
import ClearableLabeledInput from './ClearableLabeledInput' ;
import { ConfigConsumer , ConfigConsumerProps , DirectionType } from '../config-provider' ;
import SizeContext , { SizeType } from '../config-provider/SizeContext' ;
import devWarning from '../_util/devWarning' ;
import { getInputClassName , hasPrefixSuffix } from './utils' ;
export interface InputFocusOptions extends FocusOptions {
cursor ? : 'start' | 'end' | 'all' ;
}
export interface ShowCountProps {
formatter : ( args : { count : number ; maxLength? : number } ) = > React . ReactNode ;
}
export interface InputProps
extends Omit < React.InputHTMLAttributes < HTMLInputElement > , 'size' | 'prefix' | 'type' > {
prefixCls? : string ;
size? : SizeType ;
// 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
> ;
onPressEnter? : React.KeyboardEventHandler < HTMLInputElement > ;
addonBefore? : React.ReactNode ;
addonAfter? : React.ReactNode ;
prefix? : React.ReactNode ;
suffix? : React.ReactNode ;
allowClear? : boolean ;
showCount? : boolean | ShowCountProps ;
bordered? : boolean ;
htmlSize? : number ;
}
export function fixControlledValue < T > ( value : T ) {
if ( typeof value === 'undefined' || value === null ) {
return '' ;
}
return String ( value ) ;
}
export function resolveOnChange < E extends HTMLInputElement | HTMLTextAreaElement > (
target : E ,
e :
| React . ChangeEvent < E >
| React . MouseEvent < HTMLElement , MouseEvent >
| React . CompositionEvent < HTMLElement > ,
onChange : undefined | ( ( event : React.ChangeEvent < E > ) = > void ) ,
targetValue? : string ,
) {
if ( ! onChange ) {
return ;
}
let event = e ;
if ( e . type === 'click' ) {
// click clear icon
event = Object . create ( e ) ;
// 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 = '' ;
onChange ( event as React . ChangeEvent < E > ) ;
return ;
}
// 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 ;
onChange ( event as React . ChangeEvent < E > ) ;
return ;
}
onChange ( event as React . ChangeEvent < E > ) ;
}
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 ) ;
}
}
}
export interface InputState {
value : any ;
focused : boolean ;
/** `value` from prev props */
prevValue : any ;
}
class Input extends React . Component < InputProps , InputState > {
static Group : typeof Group ;
static Search : typeof Search ;
static TextArea : typeof TextArea ;
static Password : typeof Password ;
static defaultProps = {
type : 'text' ,
} ;
input ! : HTMLInputElement ;
clearableInput ! : ClearableLabeledInput ;
removePasswordTimeout : any ;
direction : DirectionType = 'ltr' ;
constructor ( props : InputProps ) {
super ( props ) ;
const value = typeof props . value === 'undefined' ? props.defaultValue : props.value ;
this . state = {
value ,
focused : false ,
// eslint-disable-next-line react/no-unused-state
prevValue : props.value ,
} ;
}
static getDerivedStateFromProps ( nextProps : InputProps , { prevValue } : InputState ) {
const newState : Partial < InputState > = { prevValue : nextProps.value } ;
if ( nextProps . value !== undefined || prevValue !== nextProps . value ) {
newState . value = nextProps . value ;
}
if ( nextProps . disabled ) {
newState . focused = false ;
}
return newState ;
}
componentDidMount() {
this . clearPasswordValueAttribute ( ) ;
}
// Since polyfill `getSnapshotBeforeUpdate` need work with `componentDidUpdate`.
// We keep an empty function here.
componentDidUpdate() { }
getSnapshotBeforeUpdate ( prevProps : InputProps ) {
if ( hasPrefixSuffix ( prevProps ) !== hasPrefixSuffix ( this . props ) ) {
devWarning (
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 ;
}
componentWillUnmount() {
if ( this . removePasswordTimeout ) {
clearTimeout ( this . removePasswordTimeout ) ;
}
}
focus = ( option? : InputFocusOptions ) = > {
triggerFocus ( this . input , option ) ;
} ;
blur() {
this . input . blur ( ) ;
}
setSelectionRange ( start : number , end : number , direction ? : 'forward' | 'backward' | 'none' ) {
this . input . setSelectionRange ( start , end , direction ) ;
}
select() {
this . input . select ( ) ;
}
saveClearableInput = ( input : ClearableLabeledInput ) = > {
this . clearableInput = input ;
} ;
saveInput = ( input : HTMLInputElement ) = > {
this . input = input ;
} ;
onFocus : React.FocusEventHandler < HTMLInputElement > = e = > {
const { onFocus } = this . props ;
this . setState ( { focused : true } , this . clearPasswordValueAttribute ) ;
onFocus ? . ( e ) ;
} ;
onBlur : React.FocusEventHandler < HTMLInputElement > = e = > {
const { onBlur } = this . props ;
this . setState ( { focused : false } , this . clearPasswordValueAttribute ) ;
onBlur ? . ( e ) ;
} ;