22
33import dayjs from 'dayjs'
44import {
5+ type ChangeEvent ,
56 type ComponentProps ,
67 type ComponentPropsWithRef ,
78 type FocusEventHandler ,
9+ type MouseEvent ,
810 type ReactNode ,
911 forwardRef ,
1012 memo ,
@@ -28,6 +30,7 @@ import { Portal } from './Portal'
2830import { parseJpnDateString } from './datePickerHelper'
2931import { useGlobalKeyDown } from './useGlobalKeyDown'
3032
33+ type ChangeLikeEvent = ChangeEvent | React . KeyboardEvent | MouseEvent
3134type Props = {
3235 /** input 要素の `value` 属性の値 */
3336 value ?: string | null
@@ -53,13 +56,17 @@ type Props = {
5356 formatDate ?: ( date : Date | null ) => string
5457 /** 入出力用文字列と併記する別フォーマット処理を記述する関数 */
5558 showAlternative ?: ( date : Date | null ) => ReactNode
56- /** 選択された日付が変わった時に発火するコールバック関数 */
59+ /** @deprecated onChangeDate は非推奨です。onChange を使ってください。 */
5760 onChangeDate ?: ( date : Date | null , value : string , other : { errors : string [ ] } ) => void
61+ /** 選択された日付が変わった時に発火するコールバック関数 */
62+ onChange ?: (
63+ e : ChangeEvent < HTMLInputElement > ,
64+ other : { date : Date | null ; formatValue : string ; errors : string [ ] } ,
65+ ) => void
5866}
5967type OmitInputAttributes =
6068 | keyof Props
6169 | 'type'
62- | 'onChange'
6370 | 'onKeyPress'
6471 | 'onFocus'
6572 | 'aria-expanded'
@@ -101,6 +108,7 @@ export const DatePicker = forwardRef<HTMLInputElement, Props & InputAttributes>(
101108 formatDate,
102109 showAlternative,
103110 onChangeDate,
111+ onChange,
104112 onBlur,
105113 ...inputAttrs
106114 } ,
@@ -172,31 +180,54 @@ export const DatePicker = forwardRef<HTMLInputElement, Props & InputAttributes>(
172180 }
173181
174182 const nextDate = isValid ? newDate : null
175- const currentValue = dateToString ( nextDate )
183+ const formatValue = dateToString ( nextDate )
176184
177- inputRef . current . value = currentValue
185+ inputRef . current . value = formatValue
178186 setAlternativeFormat ( dateToAlternativeFormat ( nextDate ) )
179187 setSelectedDate ( nextDate )
180188
181- return [ nextDate , currentValue , errors ] as [ Date | null , string , string [ ] ]
189+ return [ nextDate , formatValue , errors ] as [ Date | null , string , string [ ] ]
182190 } ,
183191 [ selectedDate , dateToString , dateToAlternativeFormat ] ,
184192 )
185- const updateDate = useMemo (
186- ( ) =>
187- onChangeDate
188- ? ( newDate : Date | null ) => {
189- const result = baseUpdateDate ( newDate )
193+ const updateDate = useMemo ( ( ) => {
194+ if ( onChange ) {
195+ return ( e : ChangeLikeEvent , newDate : Date | null ) => {
196+ const result = baseUpdateDate ( newDate )
197+
198+ if ( result ) {
199+ e . preventDefault ( )
200+ e . stopPropagation ( )
201+
202+ const [ nextDate , formatValue , errors ] = result
203+ const event = new Event ( 'change' , { bubbles : true } )
204+ // HINT: resultが存在する時点でinputRef.currentは必ず存在する
205+ const input = inputRef . current as HTMLInputElement
206+
207+ input . dispatchEvent ( event )
208+ onChange (
209+ // HINT: Eventのままではtargetがnullの場合もあり得ることになるので強制的に型変換する
210+ event as ChangeEvent < HTMLInputElement > ,
211+ { date : nextDate , formatValue, errors } ,
212+ )
213+ }
214+ }
215+ }
216+
217+ return onChangeDate
218+ ? ( _e : ChangeLikeEvent , newDate : Date | null ) => {
219+ const result = baseUpdateDate ( newDate )
190220
191- if ( result ) {
192- const [ nextDate , currentValue , errors ] = result
221+ if ( result ) {
222+ const [ nextDate , formatValue , errors ] = result
193223
194- onChangeDate ( nextDate , currentValue , { errors } )
195- }
224+ onChangeDate ( nextDate , formatValue , { errors } )
196225 }
197- : baseUpdateDate ,
198- [ onChangeDate , baseUpdateDate ] ,
199- )
226+ }
227+ : ( _e : ChangeLikeEvent , newDate : Date | null ) => {
228+ baseUpdateDate ( newDate )
229+ }
230+ } , [ onChange , onChangeDate , baseUpdateDate ] )
200231
201232 const closeCalendar = useCallback ( ( ) => setIsCalendarShown ( false ) , [ ] )
202233 const openCalendar = useCallback ( ( ) => {
@@ -288,7 +319,7 @@ export const DatePicker = forwardRef<HTMLInputElement, Props & InputAttributes>(
288319 const baseHandleBlur = useCallback < FocusEventHandler < HTMLInputElement > > (
289320 ( e ) => {
290321 setIsInputFocused ( false )
291- updateDate ( e . target . value ? stringToDate ( e . target . value ) : null )
322+ updateDate ( e , e . target . value ? stringToDate ( e . target . value ) : null )
292323 } ,
293324 [ stringToDate , updateDate ] ,
294325 )
@@ -328,7 +359,7 @@ export const DatePicker = forwardRef<HTMLInputElement, Props & InputAttributes>(
328359 ( e : React . KeyboardEvent < HTMLInputElement > ) => {
329360 if ( e . key === 'Enter' ) {
330361 ; ( isCalendarShown ? openCalendar : closeCalendar ) ( )
331- updateDate ( stringToDate ( e . currentTarget . value ) )
362+ updateDate ( e , stringToDate ( e . currentTarget . value ) )
332363 }
333364 } ,
334365 [ isCalendarShown , updateDate , closeCalendar , openCalendar , stringToDate ] ,
@@ -338,8 +369,8 @@ export const DatePicker = forwardRef<HTMLInputElement, Props & InputAttributes>(
338369 openCalendar ( )
339370 } , [ openCalendar ] )
340371 const onSelectDateCalendar = useCallback (
341- ( _ : any , selected : Date | null ) => {
342- updateDate ( selected )
372+ ( e : ChangeLikeEvent , selected : Date | null ) => {
373+ updateDate ( e , selected )
343374 // delay hiding calendar because calendar will be displayed when input is focused
344375 requestAnimationFrame ( closeCalendar )
345376
0 commit comments