import classNames from "classnames" ; import * as React from "react" ; import DayPicker from "react-day-picker" ; import { polyfill } from "react-lifecycles-compat" ; import { AbstractPureComponent2 , Boundary , Classes , DISPLAYNAME_PREFIX , InputGroupProps2 , InputGroup , Intent , IPopoverProps , Props , Keys , Popover , Position , refHandler , setRef , } from "@blueprintjs/core" ; import { DateRange } from "./common/dateRange" ; import { areSameTime , isDateValid , isDayInRange } from "./common/dateUtils" ; import * as Errors from "./common/errors" ; import { getFormattedDateString , DateFormatProps } from "./dateFormat" ; import { getDefaultMaxDate , getDefaultMinDate , IDatePickerBaseProps } from "./datePickerCore" ; import { DateRangePicker } from "./dateRangePicker" ; import { DateRangeShortcut } from "./shortcuts" ; type InputEvent = React . MouseEvent < HTMLInputElement > | React . KeyboardEvent < HTMLInputElement > | React . FocusEvent < HTMLInputElement > | React . ChangeEvent < HTMLInputElement > ; export type DateRangeInputProps = IDateRangeInputProps ; deprecated export interface IDateRangeInputProps extends IDatePickerBaseProps , DateFormatProps , Props { default allowSingleDayRange ? : boolean ; default closeOnSelection ? : boolean ; default contiguousCalendarMonths ? : boolean ; /**
     * The default date range to be used in the component when uncontrolled.
     * This will be ignored if `value` is set.
     */ defaultValue ? : DateRange ; default disabled ? : boolean ; /**
     * Props to pass to the end-date [input group](#core/components/text-inputs.input-group).
     * `disabled` and `value` will be ignored in favor of the top-level props on this component.
     * `ref` is not supported; use `inputRef` instead.
     */ endInputProps ? : InputGroupProps2 ; /**
     * Called when the user selects a day.
     * If no days are selected, it will pass `[null, null]`.
     * If a start date is selected but not an end date, it will pass `[selectedDate, null]`.
     * If both a start and end date are selected, it will pass `[startDate, endDate]`.
     */ onChange ? : ( selectedRange : DateRange ) => void ; /**
     * Called when the user finishes typing in a new date and the date causes an error state.
     * If the date is invalid, `new Date(undefined)` will be returned for the corresponding
     * boundary of the date range.
     * If the date is out of range, the out-of-range date will be returned for the corresponding
     * boundary of the date range (`onChange` is not called in this case).
     */ onError ? : ( errorRange : DateRange ) => void ; default overlappingDatesMessage ? : string ; /**
     * The props to pass to the popover.
     * `autoFocus`, `content`, and `enforceFocus` will be ignored to avoid compromising usability.
     */ popoverProps ? : Partial < IPopoverProps > ; default selectAllOnFocus ? : boolean ; default shortcuts ? : boolean | DateRangeShortcut [ ] ; default singleMonthOnly ? : boolean ; /**
     * Props to pass to the start-date [input group](#core/components/text-inputs.input-group).
     * `disabled` and `value` will be ignored in favor of the top-level props on this component.
     * `ref` is not supported; use `inputRef` instead.
     */ startInputProps ? : InputGroupProps2 ; /**
     * The currently selected date range.
     * If the prop is strictly `undefined`, the component acts in an uncontrolled manner.
     * If this prop is anything else, the component acts in a controlled manner.
     * To display an empty value in the input fields in a controlled manner, pass `[null, null]`.
     * To display an invalid date error in either input field, pass `new Date(undefined)`
     * for the appropriate date in the value prop.
     */ value ? : DateRange ; } export interface IDateRangeInputState { isOpen ? : boolean ; boundaryToModify ? : Boundary ; lastFocusedField ? : Boundary ; formattedMinDateString ? : string ; formattedMaxDateString ? : string ; isStartInputFocused ? : boolean ; isEndInputFocused ? : boolean ; startInputString ? : string ; endInputString ? : string ; startHoverString ? : string ; endHoverString ? : string ; selectedEnd ? : Date ; selectedStart ? : Date ; shouldSelectAfterUpdate ? : boolean ; wasLastFocusChangeDueToHover ? : boolean ; selectedShortcutIndex ? : number ; } interface IStateKeysAndValuesObject { keys : { hoverString : "startHoverString" | "endHoverString" ; inputString : "startInputString" | "endInputString" ; isInputFocused : "isStartInputFocused" | "isEndInputFocused" ; selectedValue : "selectedStart" | "selectedEnd" ; } ; values : { controlledValue ? : Date ; hoverString ? : string ; inputString ? : string ; isInputFocused ? : boolean ; selectedValue ? : Date ; } ; } @ polyfill export class DateRangeInput extends AbstractPureComponent2 < DateRangeInputProps , IDateRangeInputState > { public static defaultProps : Partial < DateRangeInputProps > = { allowSingleDayRange : false , closeOnSelection : true , contiguousCalendarMonths : true , dayPickerProps : { } , disabled : false , endInputProps : { } , invalidDateMessage : "Invalid date" , maxDate : getDefaultMaxDate ( ) , minDate : getDefaultMinDate ( ) , outOfRangeMessage : "Out of range" , overlappingDatesMessage : "Overlapping dates" , popoverProps : { } , selectAllOnFocus : false , shortcuts : true , singleMonthOnly : false , startInputProps : { } , } ; public static displayName = `${ DISPLAYNAME_PREFIX }.DateRangeInput` ; public startInputElement : HTMLInputElement | null = null ; public endInputElement : HTMLInputElement | null = null ; private handleStartInputRef = refHandler < HTMLInputElement , "startInputElement" > ( this , "startInputElement" , this . props . startInputProps ?. inputRef ) ; private handleEndInputRef = refHandler < HTMLInputElement , "endInputElement" > ( this , "endInputElement" , this . props . endInputProps ?. inputRef ) ; public constructor ( props , context ? ) { super ( props , context ) ; this . reset ( props ) ; } /**
     * Public method intended for unit testing only. Do not use in feature work!
     */ public reset ( props = this . props ) { const [ selectedStart , selectedEnd ] = this . getInitialRange ( ) ; this . state = { formattedMaxDateString : this . getFormattedMinMaxDateString ( props , "maxDate" ) , formattedMinDateString : this . getFormattedMinMaxDateString ( props , "minDate" ) , isOpen : false , selectedEnd , selectedShortcutIndex : - 1 , selectedStart , } ; } public componentDidUpdate ( prevProps , prevState ) { super . componentDidUpdate ( prevProps , prevState ) ; const { isStartInputFocused , isEndInputFocused , shouldSelectAfterUpdate } = this . state ; if ( prevProps . startInputProps ?. inputRef !== this . props . startInputProps ?. inputRef ) { setRef ( prevProps . startInputProps ?. inputRef , null ) ; this . handleStartInputRef = refHandler ( this , "startInputElement" , this . props . startInputProps ?. inputRef ) ; setRef ( this . props . startInputProps ?. inputRef , this . startInputElement ) ; } if ( prevProps . endInputProps ?. inputRef !== this . props . endInputProps ?. inputRef ) { setRef ( prevProps . endInputProps ?. inputRef , null ) ; this . handleEndInputRef = refHandler ( this , "endInputElement" , this . props . endInputProps ?. inputRef ) ; setRef ( this . props . endInputProps ?. inputRef , this . endInputElement ) ; } const shouldFocusStartInput = this . shouldFocusInputRef ( isStartInputFocused , this . startInputElement ) ; const shouldFocusEndInput = this . shouldFocusInputRef ( isEndInputFocused , this . endInputElement ) ; if ( shouldFocusStartInput ) { this . startInputElement ?. focus ( ) ; } else if ( shouldFocusEndInput ) { this . endInputElement ?. focus ( ) ; } if ( isStartInputFocused && shouldSelectAfterUpdate ) { this . startInputElement ?. select ( ) ; } else if ( isEndInputFocused && shouldSelectAfterUpdate ) { this . endInputElement ?. select ( ) ; } let nextState = { } ; if ( this . props . value !== prevProps . value ) { const [ selectedStart , selectedEnd ] = this . getInitialRange ( this . props ) ; nextState = { ... nextState , selectedStart , selectedEnd } ; } if ( this . props . minDate !== prevProps . minDate ) { const formattedMinDateString = this . getFormattedMinMaxDateString ( this . props , "minDate" ) ; nextState = { ... nextState , formattedMinDateString } ; } if ( this . props . maxDate !== prevProps . maxDate ) { const formattedMaxDateString = this . getFormattedMinMaxDateString ( this . props , "maxDate" ) ; nextState = { ... nextState , formattedMaxDateString } ; } this . setState ( nextState ) ; } public render ( ) { const { selectedShortcutIndex } = this . state ; const { popoverProps = { } } = this . props ; const popoverContent = ( < DateRangePicker { ... this . props } selectedShortcutIndex = { selectedShortcutIndex } boundaryToModify = { this . state . boundaryToModify } onChange = { this . handleDateRangePickerChange } onShortcutChange = { this . handleShortcutChange } onHoverChange = { this . handleDateRangePickerHoverChange } value = { this . getSelectedRange ( ) } / > ) ; const popoverClassName = classNames ( popoverProps . className , this . props . className ) ; return ( < Popover isOpen = { this . state . isOpen } position = { Position . BOTTOM_LEFT } { ... this . props . popoverProps } autoFocus = { false } className = { popoverClassName } content = { popoverContent } enforceFocus = { false } onClose = { this . handlePopoverClose } > < div className = { Classes . CONTROL_GROUP } > { this . renderInputGroup ( Boundary . START ) } { this . renderInputGroup ( Boundary . END ) } < /div> { } < /Popover>); } protected validateProps ( props ) { if ( props . value === null ) { throw new Error ( Errors . DATERANGEINPUT_NULL_VALUE ) ; } } private renderInputGroup = ( boundary ) => { const inputProps = this . getInputProps ( boundary ) ; const handleInputEvent = boundary === Boundary . START ? this . handleStartInputEvent : this . handleEndInputEvent ; return ( < InputGroup autoComplete = "off" disabled = { inputProps . disabled || this . props . disabled } { ... inputProps } intent = { this . isInputInErrorState ( boundary ) ? Intent . DANGER : inputProps . intent } inputRef = { this . getInputRef ( boundary ) } onBlur = { handleInputEvent } onChange = { handleInputEvent } onClick = { handleInputEvent } onFocus = { handleInputEvent } onKeyDown = { handleInputEvent } onMouseDown = { handleInputEvent } placeholder = { this . getInputPlaceholderString ( boundary ) } value = { this . getInputDisplayString ( boundary ) } / > ) ; } ; private handleDateRangePickerChange = ( selectedRange , didSubmitWithEnter = false ) => { if ( ! this . state . isOpen ) { return ; } const [ selectedStart , selectedEnd ] = selectedRange ; let isOpen = true ; let isStartInputFocused ; let isEndInputFocused ; let startHoverString ; let endHoverString ; let boundaryToModify ; if ( selectedStart == null ) { if ( this . props . timePrecision == null ) { isStartInputFocused = true ; isEndInputFocused = false ; } else { isStartInputFocused = false ; isEndInputFocused = false ; boundaryToModify = Boundary . START ; } startHoverString = null ; } else if ( selectedEnd == null ) { if ( this . props . timePrecision == null ) { isStartInputFocused = false ; isEndInputFocused = true ; } else { isStartInputFocused = false ; isEndInputFocused = false ; boundaryToModify = Boundary . END ; } endHoverString = null ; } else if ( this . props . closeOnSelection ) { isOpen = this . getIsOpenValueWhenDateChanges ( selectedStart , selectedEnd ) ; isStartInputFocused = false ; if ( this . props . timePrecision == null && didSubmitWithEnter ) { isEndInputFocused = true ; } else { isEndInputFocused = false ; boundaryToModify = Boundary . END ; } } else if ( this . state . lastFocusedField === Boundary . START ) { if ( this . props . timePrecision == null ) { isStartInputFocused = true ; isEndInputFocused = false ; } else { isStartInputFocused = false ; isEndInputFocused = false ; boundaryToModify = Boundary . START ; } } else if ( this . props . timePrecision == null ) { isStartInputFocused = false ; isEndInputFocused = true ; } else { isStartInputFocused = false ; isEndInputFocused = false ; boundaryToModify = Boundary . END ; } const baseStateChange = { boundaryToModify , endHoverString , endInputString : this . formatDate ( selectedEnd ) , isEndInputFocused , isOpen , isStartInputFocused , startHoverString , startInputString : this . formatDate ( selectedStart ) , wasLastFocusChangeDueToHover : false , } ; if ( this . isControlled ( ) ) { this . setState ( baseStateChange ) ; } else { this . setState ( { ... baseStateChange , selectedEnd , selectedStart } ) ; } this . props . onChange ?. ( selectedRange ) ; } ; private handleShortcutChange = ( _ , selectedShortcutIndex ) => { this . setState ( { selectedShortcutIndex } ) ; } ; private handleDateRangePickerHoverChange = ( hoveredRange , _hoveredDay , hoveredBoundary ) => { if ( ! this . state . isOpen ) { return ; } if ( hoveredRange == null ) { const isEndInputFocused = this . state . boundaryToModify === Boundary . END ; this . setState ( { endHoverString : null , isEndInputFocused , isStartInputFocused : ! isEndInputFocused , lastFocusedField : this . state . boundaryToModify , startHoverString : null , } ) ; } else { const [ hoveredStart , hoveredEnd ] = hoveredRange ; const isStartInputFocused = hoveredBoundary != null ? hoveredBoundary === Boundary . START : this . state . isStartInputFocused ; const isEndInputFocused = hoveredBoundary != null ? hoveredBoundary === Boundary . END : this . state . isEndInputFocused ; this . setState ( { endHoverString : this . formatDate ( hoveredEnd ) , isEndInputFocused , isStartInputFocused , lastFocusedField : isStartInputFocused ? Boundary . START : Boundary . END , shouldSelectAfterUpdate : this . props . selectAllOnFocus , startHoverString : this . formatDate ( hoveredStart ) , wasLastFocusChangeDueToHover : true , } ) ; } } ; private handleStartInputEvent = ( e ) => { this . handleInputEvent ( e , Boundary . START ) ; } ; private handleEndInputEvent = ( e ) => { this . handleInputEvent ( e , Boundary . END ) ; } ; private handleInputEvent = ( e , boundary ) => { const inputProps = this . getInputProps ( boundary ) ; switch ( e . type ) { case "blur" : this . handleInputBlur ( e , boundary ) ; inputProps . onBlur ?. ( e as React . FocusEvent < HTMLInputElement > ) ; break ; case "change" : this . handleInputChange ( e , boundary ) ; inputProps . onChange ?. ( e as React . ChangeEvent < HTMLInputElement > ) ; break ; case "click" : e = e as React . MouseEvent < HTMLInputElement > ; this . handleInputClick ( e ) ; inputProps . onClick ?. ( e ) ; break ; case "focus" : this . handleInputFocus ( e , boundary ) ; inputProps . onFocus ?. ( e as React . FocusEvent < HTMLInputElement > ) ; break ; case "keydown" : e = e as React . KeyboardEvent < HTMLInputElement > ; this . handleInputKeyDown ( e ) ; inputProps . onKeyDown ?. ( e ) ; break ; case "mousedown" : e = e as React . MouseEvent < HTMLInputElement > ; this . handleInputMouseDown ( ) ; inputProps . onMouseDown ?. ( e ) ; break ; default : break ; } } ; private handleInputKeyDown = ( e ) => { const isTabPressed = e . which === Keys . TAB ; const isEnterPressed = e . which === Keys . ENTER ; const isShiftPressed = e . shiftKey ; const { selectedStart , selectedEnd } = this . state ; const wasStartFieldFocused = this . state . lastFocusedField === Boundary . START ; const wasEndFieldFocused = this . state . lastFocusedField === Boundary . END ; if ( isTabPressed ) { let isEndInputFocused ; let isStartInputFocused ; let isOpen = true ; if ( wasStartFieldFocused && ! isShiftPressed ) { isStartInputFocused = false ; isEndInputFocused = true ; e . preventDefault ( ) ; } else if ( wasEndFieldFocused && isShiftPressed ) { isStartInputFocused = true ; isEndInputFocused = false ; e . preventDefault ( ) ; } else { isStartInputFocused = false ; isEndInputFocused = false ; isOpen = false ; } this . setState ( { isEndInputFocused , isOpen , isStartInputFocused , wasLastFocusChangeDueToHover : false , } ) ; } else if ( wasStartFieldFocused && isEnterPressed ) { const nextStartDate = this . parseDate ( this . state . startInputString ) ; this . handleDateRangePickerChange ( [ nextStartDate , selectedEnd ] , true ) ; } else if ( wasEndFieldFocused && isEnterPressed ) { const nextEndDate = this . parseDate ( this . state . endInputString ) ; this . handleDateRangePickerChange ( [ selectedStart , nextEndDate ] as DateRange , true ) ; } else { return ; } } ; private handleInputMouseDown = ( ) => { this . setState ( { wasLastFocusChangeDueToHover : false } ) ; } ; private handleInputClick = ( e ) => { e . stopPropagation ( ) ; } ; private handleInputFocus = ( _e , boundary ) => { const { keys , values } = this . getStateKeysAndValuesForBoundary ( boundary ) ; const inputString = getFormattedDateString ( values . selectedValue , this . props , true ) ; const boundaryToModify = this . state . wasLastFocusChangeDueToHover ? this . state . boundaryToModify : boundary ; this . setState ( { [ keys . inputString ] : inputString , [ keys . isInputFocused ] : true , boundaryToModify , isOpen : true , lastFocusedField : boundary , shouldSelectAfterUpdate : this . props . selectAllOnFocus , wasLastFocusChangeDueToHover : false , } ) ; } ; private handleInputBlur = ( _e , boundary ) => { const { keys , values } = this . getStateKeysAndValuesForBoundary ( boundary ) ; const maybeNextDate = this . parseDate ( values . inputString ) ; const isValueControlled = this . isControlled ( ) ; let nextState = { [ keys . isInputFocused ] : false , shouldSelectAfterUpdate : false , } ; if ( this . isInputEmpty ( values . inputString ) ) { if ( isValueControlled ) { nextState = { ... nextState , [ keys . inputString ] : getFormattedDateString ( values . controlledValue , this . props ) , } ; } else { nextState = { ... nextState , [ keys . inputString ] : null , [ keys . selectedValue ] : null , } ; } } else if ( ! this . isNextDateRangeValid ( maybeNextDate , boundary ) ) { if ( ! isValueControlled ) { nextState = { ... nextState , [ keys . inputString ] : null , [ keys . selectedValue ] : maybeNextDate , } ; } this . props . onError ?. ( this . getDateRangeForCallback ( maybeNextDate , boundary ) ) ; } this . setState ( nextState ) ; } ; private handleInputChange = ( e , boundary ) => { const inputString = ( e . target as HTMLInputElement ) . value ; const { keys } = this . getStateKeysAndValuesForBoundary ( boundary ) ; const maybeNextDate = this . parseDate ( inputString ) ; const isValueControlled = this . isControlled ( ) ; let nextState = { shouldSelectAfterUpdate : false } ; if ( inputString . length === 0 ) { const baseState = { ... nextState , [ keys . inputString ] : "" } ; if ( isValueControlled ) { nextState = baseState ; } else { nextState = { ... baseState , [ keys . selectedValue ] : null } ; } this . props . onChange ?. ( this . getDateRangeForCallback ( null , boundary ) ) ; } else if ( this . isDateValidAndInRange ( maybeNextDate ) ) { const baseState = { ... nextState , [ keys . hoverString ] : null , [ keys . inputString ] : inputString , } ; if ( isValueControlled ) { nextState = baseState ; } else { nextState = { ... baseState , [ keys . selectedValue ] : maybeNextDate } ; } if ( this . isNextDateRangeValid ( maybeNextDate , boundary ) ) { this . props . onChange ?. ( this . getDateRangeForCallback ( maybeNextDate , boundary ) ) ; } } else { nextState = { ... nextState , [ keys . inputString ] : inputString , [ keys . hoverString ] : null } ; } this . setState ( nextState ) ; } ; private handlePopoverClose = ( event ) => { this . setState ( { isOpen : false } ) ; this . props . popoverProps . onClose ?. ( event ) ; } ; private shouldFocusInputRef ( isFocused , inputRef ) { return isFocused && inputRef !== undefined && document . activeElement !== inputRef ; } private getIsOpenValueWhenDateChanges = ( nextSelectedStart , nextSelectedEnd ) : boolean => { if ( this . props . closeOnSelection ) { if ( this . props . timePrecision == null ) { return false ; } const fallbackDate = new Date ( new Date ( ) . setHours ( 0 , 0 , 0 , 0 ) ) ; const [ selectedStart , selectedEnd ] = this . getSelectedRange ( [ fallbackDate , fallbackDate ] ) ; if ( areSameTime ( selectedStart , nextSelectedStart ) === true && areSameTime ( selectedEnd , nextSelectedEnd ) === true ) { return false ; } return true ; } return true ; } ; private getInitialRange = ( props = this . props ) : DateRange => { const { defaultValue , value } = props ; if ( value != null ) { return value ; } else if ( defaultValue != null ) { return defaultValue ; } else { return [ null , null ] ; } } ; private getSelectedRange = ( fallbackRange ? ) => { let selectedStart ; let selectedEnd ; if ( this . isControlled ( ) ) { [ selectedStart , selectedEnd ] = this . props . value ; } else { selectedStart = this . state . selectedStart ; selectedEnd = this . state . selectedEnd ; } const doBoundaryDatesOverlap = this . doBoundaryDatesOverlap ( selectedStart , Boundary . START ) ; const dateRange = [ selectedStart , doBoundaryDatesOverlap ? undefined : selectedEnd ] ; return dateRange . map ( ( selectedBound , index ) => { const fallbackDate = fallbackRange != null ? fallbackRange [ index ] : undefined ; return this . isDateValidAndInRange ( selectedBound ) ? selectedBound : fallbackDate ; } ) as DateRange ; } ; private getInputDisplayString = ( boundary ) => { const { values } = this . getStateKeysAndValuesForBoundary ( boundary ) ; const { isInputFocused , inputString , selectedValue , hoverString } = values ; if ( hoverString != null ) { return hoverString ; } else if ( isInputFocused ) { return inputString == null ? "" : inputString ; } else if ( selectedValue == null ) { return "" ; } else if ( this . doesEndBoundaryOverlapStartBoundary ( selectedValue , boundary ) ) { return this . props . overlappingDatesMessage ; } else { return getFormattedDateString ( selectedValue , this . props ) ; } } ; private getInputPlaceholderString = ( boundary ) => { const isStartBoundary = boundary === Boundary . START ; const isEndBoundary = boundary === Boundary . END ; const inputProps = this . getInputProps ( boundary ) ; const { isInputFocused } = this . getStateKeysAndValuesForBoundary ( boundary ) . values ; if ( inputProps . placeholder != null ) { return inputProps . placeholder ; } else if ( isStartBoundary ) { return isInputFocused ? this . state . formattedMinDateString : "Start date" ; } else if ( isEndBoundary ) { return isInputFocused ? this . state . formattedMaxDateString : "End date" ; } else { return "" ; } } ; private getInputProps = ( boundary ) => { return boundary === Boundary . START ? this . props . startInputProps : this . props . endInputProps ; } ; private getInputRef = ( boundary ) => { return boundary === Boundary . START ? this . handleStartInputRef : this . handleEndInputRef ; } ; private getStateKeysAndValuesForBoundary = ( boundary ) : IStateKeysAndValuesObject => { const controlledRange = this . props . value ; if ( boundary === Boundary . START ) { return { keys : { hoverString : "startHoverString" , inputString : "startInputString" , isInputFocused : "isStartInputFocused" , selectedValue : "selectedStart" , } , values : { controlledValue : controlledRange != null ? controlledRange [ 0 ] : undefined , hoverString : this . state . startHoverString , inputString : this . state . startInputString , isInputFocused : this . state . isStartInputFocused , selectedValue : this . state . selectedStart , } , } ; } else { return { keys : { hoverString : "endHoverString" , inputString : "endInputString" , isInputFocused : "isEndInputFocused" , selectedValue : "selectedEnd" , } , values : { controlledValue : controlledRange != null ? controlledRange [ 1 ] : undefined , hoverString : this . state . endHoverString , inputString : this . state . endInputString , isInputFocused : this . state . isEndInputFocused , selectedValue : this . state . selectedEnd , } , } ; } } ; private getDateRangeForCallback = ( currDate , currBoundary ? ) : DateRange => { const otherBoundary = this . getOtherBoundary ( currBoundary ) ; const otherDate = this . getStateKeysAndValuesForBoundary ( otherBoundary ) . values . selectedValue ; return currBoundary === Boundary . START ? [ currDate , otherDate ] : [ otherDate , currDate ] ; } ; private getOtherBoundary = ( boundary ? ) => { return boundary === Boundary . START ? Boundary . END : Boundary . START ; } ; private doBoundaryDatesOverlap = ( date , boundary ) => { const { allowSingleDayRange } = this . props ; const otherBoundary = this . getOtherBoundary ( boundary ) ; const otherBoundaryDate = this . getStateKeysAndValuesForBoundary ( otherBoundary ) . values . selectedValue ; if ( date == null || otherBoundaryDate == null ) { return false ; } if ( boundary === Boundary . START ) { const isAfter = date > otherBoundaryDate ; return isAfter || ( ! allowSingleDayRange && DayPicker . DateUtils . isSameDay ( date , otherBoundaryDate ) ) ; } else { const isBefore = date < otherBoundaryDate ; return isBefore || ( ! allowSingleDayRange && DayPicker . DateUtils . isSameDay ( date , otherBoundaryDate ) ) ; } } ; /**
     * Returns true if the provided boundary is an END boundary overlapping the
     * selected start date. (If the boundaries overlap, we consider the END
     * boundary to be erroneous.)
     */ private doesEndBoundaryOverlapStartBoundary = ( boundaryDate , boundary ) => { return boundary === Boundary . START ? false : this . doBoundaryDatesOverlap ( boundaryDate , boundary ) ; } ; private isControlled = ( ) => this . props . value !== undefined ; private isInputEmpty = ( inputString ) => inputString == null || inputString . length === 0 ; private isInputInErrorState = ( boundary ) => { const values = this . getStateKeysAndValuesForBoundary ( boundary ) . values ; const { isInputFocused , hoverString , inputString , selectedValue } = values ; if ( hoverString != null || this . isInputEmpty ( inputString ) ) { return false ; } const boundaryValue = isInputFocused ? this . parseDate ( inputString ) : selectedValue ; return ( boundaryValue != null && ( ! this . isDateValidAndInRange ( boundaryValue ) || this . doesEndBoundaryOverlapStartBoundary ( boundaryValue , boundary ) ) ) ; } ; private isDateValidAndInRange = ( date ) : date is Date => { return isDateValid ( date ) && isDayInRange ( date , [ this . props . minDate , this . props . maxDate ] ) ; } ; private isNextDateRangeValid ( nextDate , boundary ) { return this . isDateValidAndInRange ( nextDate ) && ! this . doBoundaryDatesOverlap ( nextDate , boundary ) ; } private getFormattedMinMaxDateString ( props , propName ) { const date = props [ propName ] ; const defaultDate = DateRangeInput . defaultProps [ propName ] ; return getFormattedDateString ( date === undefined ? defaultDate : date , this . props ) ; } private parseDate ( dateString ) { if ( dateString === this . props . outOfRangeMessage || dateString === this . props . invalidDateMessage ) { return null ; } const { locale , parseDate } = this . props ; const newDate = parseDate ( dateString , locale ) ; return newDate === false ? new Date ( undefined ) : newDate ; } private formatDate ( date ) { if ( ! this . isDateValidAndInRange ( date ) ) { return "" ; } const { locale , formatDate } = this . props ; return formatDate ( date , locale ) ; } }