88 ArrowRightIcon ,
99} from '@radix-ui/react-icons' ;
1010import AutosizeInput from 'react-input-autosize' ;
11- import Calendar from '../utils/calendar/Calendar' ;
11+ import Calendar , { CalendarHandle } from '../utils/calendar/Calendar' ;
1212import { DatePickerRangeProps , CalendarDirection } from '../types' ;
1313import {
1414 dateAsStr ,
@@ -59,7 +59,11 @@ const DatePickerRange = ({
5959 const direction = is_RTL
6060 ? CalendarDirection . RightToLeft
6161 : CalendarDirection . LeftToRight ;
62- const initialMonth = strAsDate ( initial_visible_month ) ;
62+ const initialCalendarDate =
63+ strAsDate ( initial_visible_month ) ||
64+ internalStartDate ||
65+ internalEndDate ;
66+
6367 const minDate = strAsDate ( min_date_allowed ) ;
6468 const maxDate = strAsDate ( max_date_allowed ) ;
6569 const disabledDates = useMemo ( ( ) => {
@@ -101,6 +105,7 @@ const DatePickerRange = ({
101105 const containerRef = useRef < HTMLDivElement > ( null ) ;
102106 const startInputRef = useRef < HTMLInputElement | null > ( null ) ;
103107 const endInputRef = useRef < HTMLInputElement | null > ( null ) ;
108+ const calendarRef = useRef < CalendarHandle > ( null ) ;
104109
105110 useEffect ( ( ) => {
106111 setInternalStartDate ( strAsDate ( start_date ) ) ;
@@ -145,77 +150,107 @@ const DatePickerRange = ({
145150 }
146151 } , [ isCalendarOpen , startInputValue ] ) ;
147152
148- const sendStartInputAsDate = useCallback ( ( ) => {
149- const parsed = strAsDate ( startInputValue , display_format ) ;
150- const isValid =
151- parsed && ! isDateDisabled ( parsed , minDate , maxDate , disabledDates ) ;
153+ const sendStartInputAsDate = useCallback (
154+ ( focusCalendar = false ) => {
155+ if ( startInputValue ) {
156+ setInternalStartDate ( undefined ) ;
157+ }
158+ const parsed = strAsDate ( startInputValue , display_format ) ;
159+ const isValid =
160+ parsed &&
161+ ! isDateDisabled ( parsed , minDate , maxDate , disabledDates ) ;
152162
153- if ( isValid ) {
154- setInternalStartDate ( parsed ) ;
155- } else {
156- // Invalid or disabled input: revert to previous valid date with proper formatting
157- const previousDate = strAsDate ( start_date ) ;
158- setStartInputValue (
159- previousDate ? formatDate ( previousDate , display_format ) : ''
160- ) ;
161- }
162- } , [
163- startInputValue ,
164- display_format ,
165- start_date ,
166- minDate ,
167- maxDate ,
168- disabledDates ,
169- ] ) ;
163+ if ( isValid ) {
164+ setInternalStartDate ( parsed ) ;
165+ if ( focusCalendar ) {
166+ calendarRef . current ?. focusDate ( parsed ) ;
167+ } else {
168+ calendarRef . current ?. setVisibleDate ( parsed ) ;
169+ }
170+ } else {
171+ // Invalid or disabled input: revert to previous valid date with proper formatting
172+ const previousDate = strAsDate ( start_date ) ;
173+ setStartInputValue (
174+ previousDate ? formatDate ( previousDate , display_format ) : ''
175+ ) ;
176+ if ( focusCalendar ) {
177+ calendarRef . current ?. focusDate ( previousDate ) ;
178+ }
179+ }
180+ } ,
181+ [
182+ startInputValue ,
183+ display_format ,
184+ start_date ,
185+ minDate ,
186+ maxDate ,
187+ disabledDates ,
188+ ]
189+ ) ;
170190
171- const sendEndInputAsDate = useCallback ( ( ) => {
172- const parsed = strAsDate ( endInputValue , display_format ) ;
173- const isValid =
174- parsed && ! isDateDisabled ( parsed , minDate , maxDate , disabledDates ) ;
191+ const sendEndInputAsDate = useCallback (
192+ ( focusCalendar = false ) => {
193+ if ( endInputValue === '' ) {
194+ setInternalEndDate ( undefined ) ;
195+ }
196+ const parsed = strAsDate ( endInputValue , display_format ) ;
197+ const isValid =
198+ parsed &&
199+ ! isDateDisabled ( parsed , minDate , maxDate , disabledDates ) ;
175200
176- if ( isValid ) {
177- setInternalEndDate ( parsed ) ;
178- } else {
179- // Invalid or disabled input: revert to previous valid date with proper formatting
180- const previousDate = strAsDate ( end_date ) ;
181- setEndInputValue (
182- previousDate ? formatDate ( previousDate , display_format ) : ''
183- ) ;
184- }
185- } , [
186- endInputValue ,
187- display_format ,
188- end_date ,
189- minDate ,
190- maxDate ,
191- disabledDates ,
192- ] ) ;
201+ if ( isValid ) {
202+ setInternalEndDate ( parsed ) ;
203+ if ( focusCalendar ) {
204+ calendarRef . current ?. focusDate ( parsed ) ;
205+ } else {
206+ calendarRef . current ?. setVisibleDate ( parsed ) ;
207+ }
208+ } else {
209+ // Invalid or disabled input: revert to previous valid date with proper formatting
210+ const previousDate = strAsDate ( end_date ) ;
211+ setEndInputValue (
212+ previousDate ? formatDate ( previousDate , display_format ) : ''
213+ ) ;
214+ if ( focusCalendar ) {
215+ calendarRef . current ?. focusDate ( previousDate ) ;
216+ }
217+ }
218+ } ,
219+ [
220+ endInputValue ,
221+ display_format ,
222+ end_date ,
223+ minDate ,
224+ maxDate ,
225+ disabledDates ,
226+ ]
227+ ) ;
193228
194229 const clearSelection = useCallback (
195230 e => {
196- e . preventDefault ( ) ;
197231 setInternalStartDate ( undefined ) ;
198232 setInternalEndDate ( undefined ) ;
233+ startInputRef . current ?. focus ( ) ;
234+ e . preventDefault ( ) ;
235+ e . stopPropagation ( ) ;
199236 if ( reopen_calendar_on_clear ) {
200237 setIsCalendarOpen ( true ) ;
201- } else {
202- startInputRef . current ?. focus ( ) ;
203238 }
204239 } ,
205240 [ reopen_calendar_on_clear ]
206241 ) ;
207242
208243 const handleStartInputKeyDown = useCallback (
209244 ( e : React . KeyboardEvent < HTMLInputElement > ) => {
210- if ( e . key === 'ArrowDown' ) {
245+ if ( [ 'ArrowUp' , 'ArrowDown' ] . includes ( e . key ) ) {
211246 e . preventDefault ( ) ;
247+ sendStartInputAsDate ( true ) ;
212248 if ( ! isCalendarOpen ) {
213- sendStartInputAsDate ( ) ;
214249 // open the calendar after resolving prop changes, so that
215250 // it opens with the correct date showing
216251 setTimeout ( ( ) => setIsCalendarOpen ( true ) , 0 ) ;
217252 }
218- } else if ( e . key === 'Enter' ) {
253+ } else if ( [ 'Enter' , 'Tab' ] . includes ( e . key ) ) {
219254 sendStartInputAsDate ( ) ;
220255 }
221256 } ,
@@ -224,15 +259,15 @@ const DatePickerRange = ({
224259
225260 const handleEndInputKeyDown = useCallback (
226261 ( e : React . KeyboardEvent < HTMLInputElement > ) => {
227- if ( e . key === 'ArrowDown' ) {
262+ if ( [ 'ArrowUp' , 'ArrowDown' ] . includes ( e . key ) ) {
228263 e . preventDefault ( ) ;
264+ sendEndInputAsDate ( true ) ;
229265 if ( ! isCalendarOpen ) {
230- sendEndInputAsDate ( ) ;
231266 // open the calendar after resolving prop changes, so that
232267 // it opens with the correct date showing
233268 setTimeout ( ( ) => setIsCalendarOpen ( true ) , 0 ) ;
234269 }
235- } else if ( e . key === 'Enter' ) {
270+ } else if ( [ 'Enter' , 'Tab' ] . includes ( e . key ) ) {
236271 sendEndInputAsDate ( ) ;
237272 }
238273 } ,
@@ -248,9 +283,6 @@ const DatePickerRange = ({
248283 classNames += ' ' + className ;
249284 }
250285
251- const initialCalendarDate =
252- initialMonth || internalStartDate || internalEndDate ;
253-
254286 const ArrowIcon =
255287 direction === CalendarDirection . LeftToRight
256288 ? ArrowRightIcon
@@ -299,6 +331,12 @@ const DatePickerRange = ({
299331 aria-haspopup = "dialog"
300332 aria-expanded = { isCalendarOpen }
301333 aria-disabled = { disabled }
334+ onClick = { e => {
335+ e . preventDefault ( ) ;
336+ if ( ! isCalendarOpen && ! disabled ) {
337+ setIsCalendarOpen ( true ) ;
338+ }
339+ } }
302340 >
303341 < CalendarIcon className = "dash-datepicker-trigger-icon" />
304342 < AutosizeInput
@@ -311,10 +349,11 @@ const DatePickerRange = ({
311349 value = { startInputValue }
312350 onChange = { e => setStartInputValue ( e . target . value ) }
313351 onKeyDown = { handleStartInputKeyDown }
314- onBlur = { sendStartInputAsDate }
315- onClick = { ( ) => {
316- if ( ! isCalendarOpen && ! disabled ) {
317- setIsCalendarOpen ( true ) ;
352+ onFocus = { ( ) => {
353+ if ( internalStartDate ) {
354+ calendarRef . current ?. setVisibleDate (
355+ internalStartDate
356+ ) ;
318357 }
319358 } }
320359 placeholder = { start_date_placeholder_text }
@@ -333,10 +372,11 @@ const DatePickerRange = ({
333372 value = { endInputValue }
334373 onChange = { e => setEndInputValue ( e . target . value ) }
335374 onKeyDown = { handleEndInputKeyDown }
336- onBlur = { sendEndInputAsDate }
337- onClick = { ( ) => {
338- if ( ! isCalendarOpen && ! disabled ) {
339- setIsCalendarOpen ( true ) ;
375+ onFocus = { ( ) => {
376+ if ( internalEndDate ) {
377+ calendarRef . current ?. setVisibleDate (
378+ internalEndDate
379+ ) ;
340380 }
341381 } }
342382 placeholder = { end_date_placeholder_text }
@@ -365,6 +405,7 @@ const DatePickerRange = ({
365405 onOpenAutoFocus = { e => e . preventDefault ( ) }
366406 >
367407 < Calendar
408+ ref = { calendarRef }
368409 initialVisibleDate = { initialCalendarDate }
369410 selectionStart = { internalStartDate }
370411 selectionEnd = { internalEndDate }
0 commit comments