1
1
import { useState , useRef , useEffect } from 'react' ;
2
2
3
3
import type { DatePickerProps } from './DatePicker' ;
4
- import { computeNewSelectedSlot , formatInputValue , formatValueToSlot } from './utils' ;
4
+ import {
5
+ computeNewSelectedSlot ,
6
+ formatInputValue ,
7
+ formatValueToSlot ,
8
+ isWithinInterval ,
9
+ } from './utils' ;
5
10
import { useModalPosition } from '../../../hooks/useModalPosition' ;
6
11
import useOutsideClick from '../../../hooks/useOutsideClick' ;
7
12
8
13
const MODAL_HORIZONTAL_OFFSET = - 24 ;
9
14
const MODAL_VERTICAL_OFFSET = 3 ;
10
15
16
+ // Regex for "xx/xx/xx"
17
+ const SINGLE_DATE_REGEX = / ^ \d { 2 } \/ \d { 2 } \/ \d { 2 } $ / ;
18
+
11
19
export default function useDatePicker ( datePickerProps : DatePickerProps ) {
12
- const { inputProps , value, isRangeMode, onDateChange } = datePickerProps ;
20
+ const { value, isRangeMode, selectableSlot , onDateChange } = datePickerProps ;
13
21
const [ showPicker , setShowPicker ] = useState ( false ) ;
14
22
const [ inputValue , setInputValue ] = useState ( formatInputValue ( datePickerProps ) ) ;
15
23
const [ selectedSlot , setSelectedSlot ] = useState ( formatValueToSlot ( datePickerProps ) ) ;
24
+ const [ inputIsValid , setInputIsValid ] = useState ( true ) ;
16
25
17
26
const calendarPickerRef = useRef < HTMLDivElement > ( null ) ;
18
27
const inputRef = useRef < HTMLInputElement > ( null ) ;
19
28
useOutsideClick ( calendarPickerRef , ( e ) => {
20
29
// Do not close the picker if any children in input wrapper is clicked.
21
30
// This wrapper include, the input itself, the trailing content (which contains the calendar icon) and the leading content
22
- if ( inputRef . current && inputRef . current . parentElement ?. contains ( e . target as Node ) ) return ;
31
+ if (
32
+ inputRef . current &&
33
+ inputRef . current . parentElement ?. parentElement ?. contains ( e . target as Node )
34
+ )
35
+ return ;
23
36
setShowPicker ( false ) ;
24
37
} ) ;
25
38
const { calculatePosition, modalPosition } = useModalPosition (
@@ -29,9 +42,10 @@ export default function useDatePicker(datePickerProps: DatePickerProps) {
29
42
MODAL_HORIZONTAL_OFFSET
30
43
) ;
31
44
32
- const handleInputClick = ( e : React . MouseEvent < HTMLInputElement , MouseEvent > ) => {
33
- setShowPicker ( true ) ;
34
- inputProps . onClick ?.( e ) ;
45
+ const handleInputClick = ( ) => {
46
+ if ( isRangeMode ) {
47
+ setShowPicker ( true ) ;
48
+ }
35
49
} ;
36
50
37
51
const handleDayClick = ( clickedDate : Date ) => {
@@ -43,25 +57,53 @@ export default function useDatePicker(datePickerProps: DatePickerProps) {
43
57
}
44
58
} ;
45
59
60
+ const handleInputOnChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
61
+ if ( isRangeMode ) {
62
+ return ;
63
+ }
64
+
65
+ const inputIsValid = SINGLE_DATE_REGEX . test ( e . target . value ) ;
66
+ setInputValue ( e . target . value ) ;
67
+
68
+ if ( inputIsValid ) {
69
+ const [ day , month , year ] = e . target . value . split ( '/' ) . map ( Number ) ;
70
+ const date = new Date ( year + 2000 , month - 1 , day ) ;
71
+
72
+ if ( ! isNaN ( date . getTime ( ) ) && isWithinInterval ( date , selectableSlot ) && onDateChange ) {
73
+ onDateChange ( date ) ;
74
+ } else {
75
+ setInputIsValid ( false ) ;
76
+ }
77
+ }
78
+ } ;
79
+
46
80
useEffect ( ( ) => {
47
81
if ( showPicker ) calculatePosition ( ) ;
48
82
} , [ showPicker , calculatePosition ] ) ;
49
83
50
84
useEffect ( ( ) => {
51
- setInputValue ( formatInputValue ( datePickerProps ) ) ;
85
+ const newInput = formatInputValue ( datePickerProps ) ;
86
+ if ( newInput !== inputValue ) {
87
+ // we only set the input value if it has changed
88
+ // otherwise the user loses the focus
89
+ setInputValue ( newInput ) ;
90
+ }
91
+ setInputIsValid ( true ) ;
52
92
setSelectedSlot ( formatValueToSlot ( datePickerProps ) ) ;
53
93
// eslint-disable-next-line react-hooks/exhaustive-deps
54
94
} , [ value ] ) ;
55
95
56
96
return {
57
97
showPicker,
58
98
inputValue,
99
+ inputIsValid,
59
100
selectedSlot,
60
101
modalPosition,
61
102
inputRef,
62
103
calendarPickerRef,
63
104
setShowPicker,
64
105
handleDayClick,
65
106
handleInputClick,
107
+ handleInputOnChange,
66
108
} ;
67
109
}
0 commit comments