Skip to content

Commit 3e6abe7

Browse files
committed
feat: DatePickerを内部的に利用する用になるため、onChangeDateを非推奨にし、onChangeを追加する
1 parent 2db4e0e commit 3e6abe7

File tree

1 file changed

+52
-21
lines changed

1 file changed

+52
-21
lines changed

packages/smarthr-ui/src/components/DatePicker/DatePicker.tsx

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import dayjs from 'dayjs'
44
import {
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'
2830
import { parseJpnDateString } from './datePickerHelper'
2931
import { useGlobalKeyDown } from './useGlobalKeyDown'
3032

33+
type ChangeLikeEvent = ChangeEvent | React.KeyboardEvent | MouseEvent
3134
type 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
}
5967
type 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

Comments
 (0)