import * as React from 'react' ; import debounce from '../debounce' ; import { componentDefaultProps } from './component-common' ; import ErrorMessages from './error-messages' ; import Help from './help' ; import InputControl , { InputControlProps } from './controls/input' ; import InputGroup , { InputGroupProps } from './input-group' ; import Row from './row' ; type InputControlPropsCleaned = Omit < InputControlProps , 'required' | 'value' | 'className' | 'elementRef' | 'id' | 'name' | 'type' > ; type InputGroupPropsCleaned = Omit < InputGroupProps , 'children' > ; type SupportedInputType = 'color' | 'date' | 'datetime' | 'datetime-local' | 'email' | 'hidden' | 'month' | 'number' | 'password' | 'range' | 'search' | 'tel' | 'text' | 'time' | 'url' | 'week' ; const defaultProps = { ... componentDefaultProps , blurCallback : ( name , value ) : void => { } , blurDebounceInterval : 0 , changeDebounceInterval : 500 , className : '' , elementRef : React . createRef < HTMLInputElement > ( ) , keyDownCallback : ( event ) : void => { } , required : false , type : 'text' as SupportedInputType , updateOnBlur : true , updateOnChange : true , value : '' , } ; type InputProps = InputControlPropsCleaned & InputGroupPropsCleaned & typeof componentDefaultProps & InputGroupProps & typeof defaultProps & { name : string ; } ; interface State { currentValue : string ; incomingPropValue : string ; valueIsChanging : boolean ; } class Input extends React . Component < InputProps , State > { public static defaultProps = defaultProps ; public changeDebounced ; public blurDebounced ; public constructor ( props ) { super ( props ) ; const { value , changeDebounceInterval , blurDebounceInterval } = props ; this . state = { currentValue : value , incomingPropValue : value , valueIsChanging : false , } ; this . changeDebounced = debounce ( this . setValue , changeDebounceInterval ) ; this . blurDebounced = debounce ( this . setValue , blurDebounceInterval ) ; } public setValue = ( value ) : void => { if ( this . state . incomingPropValue !== value ) { this . blurDebounced . cancel ( ) ; this . changeDebounced . cancel ( ) ; this . props . onSetValue ( value ) ; } } ; public static getDerivedStateFromProps ( props , state ) { const { value : incomingPropValue } = props ; if ( incomingPropValue !== state . incomingPropValue ) { return { valueIsChanging : true , incomingPropValue , } ; } return null ; } public shouldComponentUpdate ( nextProps , nextState ) { const { valueIsChanging , incomingPropValue } = nextState ; if ( valueIsChanging === true ) { this . setState ( { valueIsChanging : false , currentValue : incomingPropValue , } , ( ) => { this . setValue ( incomingPropValue ) ; } ) ; return false ; } return true ; } public handleBlur = ( event ) : void => { const { value } = event . currentTarget ; const { updateOnBlur , isPristine , value : propValue , blurCallback , name , } = this . props ; this . setState ( { currentValue : value } ) ; if ( updateOnBlur ) { this . changeDebounced . cancel ( ) ; if ( isPristine ) { this . blurDebounced ( value ) ; } else if ( propValue !== value ) { this . blurDebounced ( value ) ; } } blurCallback ( name , value ) ; } ; public handleChange = ( event ) : void => { const { value } = event . currentTarget ; const { updateOnChange , changeCallback , name } = this . props ; this . setState ( { currentValue : value } ) ; if ( updateOnChange ) { this . changeDebounced ( value ) ; } changeCallback ( name , value ) ; } ; public handleKeyDown = ( event ) : void => { const { keyDownCallback } = this . props ; if ( event . key === 'Enter' ) { this . changeDebounced . flush ( ) ; } keyDownCallback ( event ) ; } ; public render ( ) { const { blurCallback , blurDebounceInterval , changeCallback , changeDebounceInterval , isPristine , keyDownCallback , name , onSetValue , updateOnBlur , updateOnChange , value , addonAfter , addonBefore , buttonAfter , buttonBefore , className , elementRef , elementWrapperClassName , errorMessages , help , id , label , labelClassName , layout , required , rowClassName , showErrors , type , ... passthoughProps } = this . props ; const markAsInvalid = showErrors && ( errorMessages . length > 0 || required ) ; let control = ( < InputControl { ... passthoughProps } className = { markAsInvalid ? `is-invalid ${ className }` : className } id = { id } onBlur = { this . handleBlur } onChange = { this . handleChange } onKeyDown = { this . handleKeyDown } elementRef = { elementRef } type = { type } value = { this . state . currentValue } / > ) ; if ( type === 'hidden' ) { return control ; } const inputGroupProps = { addonAfter , addonBefore , buttonAfter , buttonBefore , } ; const rowProps = { elementWrapperClassName , labelClassName , rowClassName , label , errorMessages , htmlFor : id , layout , required , showErrors , } ; if ( addonBefore || addonAfter || buttonBefore || buttonAfter ) { control = < InputGroup { ... inputGroupProps } > { control } < /InputGroup>; } if ( layout === 'elementOnly' ) { return control ; } return ( < Row { ... rowProps } > < > { control } { help ? < Help help = { help } /> : null} { showErrors ? < ErrorMessages messages = { errorMessages } /> : null} < /> < /Row>); } } export default Input ;