@@ -15,6 +15,7 @@ import {
1515 getMinutes ,
1616 getHours ,
1717 addDays ,
18+ addMinutes ,
1819 addMonths ,
1920 addWeeks ,
2021 subDays ,
@@ -27,6 +28,7 @@ import {
2728 getEffectiveMinDate ,
2829 getEffectiveMaxDate ,
2930 parseDate ,
31+ formatDate ,
3032 safeDateFormat ,
3133 safeDateRangeFormat ,
3234 getHighLightDaysMap ,
@@ -46,6 +48,7 @@ import {
4648 isDateBefore ,
4749 getStartOfDay ,
4850 getEndOfDay ,
51+ isSameMinute ,
4952 type HighlightDate ,
5053 type HolidayItem ,
5154 KeyType ,
@@ -978,6 +981,165 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
978981 this . props . onInputClick ?.( ) ;
979982 } ;
980983
984+ handleTimeOnlyArrowKey = ( eventKey : string ) : void => {
985+ const currentTime =
986+ this . props . selected || this . state . preSelection || newDate ( ) ;
987+ const timeIntervals = this . props . timeIntervals ?? 30 ;
988+ const dateFormat =
989+ this . props . dateFormat ?? DatePicker . defaultProps . dateFormat ;
990+ const formatStr = Array . isArray ( dateFormat ) ? dateFormat [ 0 ] : dateFormat ;
991+
992+ const baseDate = getStartOfDay ( currentTime ) ;
993+ const currentMinutes = getHours ( currentTime ) * 60 + getMinutes ( currentTime ) ;
994+
995+ const maxMinutes = 23 * 60 + 60 - timeIntervals ; // Cap at last valid interval of the day
996+ let newTime : Date ;
997+ if ( eventKey === KeyType . ArrowUp ) {
998+ const newMinutes = Math . max ( 0 , currentMinutes - timeIntervals ) ;
999+ newTime = addMinutes ( baseDate , newMinutes ) ;
1000+ } else {
1001+ const newMinutes = Math . min ( maxMinutes , currentMinutes + timeIntervals ) ;
1002+ newTime = addMinutes ( baseDate , newMinutes ) ;
1003+ }
1004+
1005+ const formattedTime = formatDate (
1006+ newTime ,
1007+ formatStr || DatePicker . defaultProps . dateFormat ,
1008+ this . props . locale ,
1009+ ) ;
1010+ this . setState ( {
1011+ preSelection : newTime ,
1012+ inputValue : formattedTime ,
1013+ } ) ;
1014+
1015+ if ( this . props . selectsRange || this . props . selectsMultiple ) {
1016+ return ;
1017+ }
1018+
1019+ const selected = this . props . selected
1020+ ? this . props . selected
1021+ : this . getPreSelection ( ) ;
1022+ const changedDate = this . props . selected
1023+ ? newTime
1024+ : setTime ( selected , {
1025+ hour : getHours ( newTime ) ,
1026+ minute : getMinutes ( newTime ) ,
1027+ } ) ;
1028+
1029+ this . props . onChange ?.( changedDate ) ;
1030+
1031+ if ( this . props . showTimeSelectOnly || this . props . showTimeSelect ) {
1032+ this . setState ( { isRenderAriaLiveMessage : true } ) ;
1033+ }
1034+
1035+ requestAnimationFrame ( ( ) => {
1036+ this . scrollToTimeOption ( newTime ) ;
1037+ } ) ;
1038+ } ;
1039+
1040+ handleTimeOnlyEnterKey = ( event : React . KeyboardEvent < HTMLElement > ) : void => {
1041+ const inputElement = event . target as HTMLInputElement ;
1042+ const inputValue = inputElement . value ;
1043+ const dateFormat =
1044+ this . props . dateFormat ?? DatePicker . defaultProps . dateFormat ;
1045+ const timeFormat = this . props . timeFormat || "p" ;
1046+
1047+ const defaultTime =
1048+ this . state . preSelection || this . props . selected || newDate ( ) ;
1049+ const parsedDate = parseDate (
1050+ inputValue ,
1051+ dateFormat ,
1052+ this . props . locale ,
1053+ this . props . strictParsing ?? false ,
1054+ defaultTime ,
1055+ ) ;
1056+
1057+ let timeToCommit : Date = defaultTime ;
1058+
1059+ if ( parsedDate && isValid ( parsedDate ) ) {
1060+ timeToCommit = parsedDate ;
1061+ } else {
1062+ const highlightedItem =
1063+ this . calendar ?. containerRef . current instanceof Element &&
1064+ this . calendar . containerRef . current . querySelector (
1065+ ".react-datepicker__time-list-item[tabindex='0']" ,
1066+ ) ;
1067+
1068+ if ( highlightedItem instanceof HTMLElement ) {
1069+ const itemText = highlightedItem . textContent ?. trim ( ) ;
1070+ if ( itemText ) {
1071+ const itemTime = parseDate (
1072+ itemText ,
1073+ timeFormat ,
1074+ this . props . locale ,
1075+ false ,
1076+ defaultTime ,
1077+ ) ;
1078+ if ( itemTime && isValid ( itemTime ) ) {
1079+ timeToCommit = itemTime ;
1080+ }
1081+ }
1082+ }
1083+ }
1084+
1085+ this . handleTimeChange ( timeToCommit ) ;
1086+ this . setOpen ( false ) ;
1087+ this . sendFocusBackToInput ( ) ;
1088+ } ;
1089+
1090+ scrollToTimeOption = ( time : Date ) : void => {
1091+ if ( ! this . calendar ?. containerRef . current ) {
1092+ return ;
1093+ }
1094+
1095+ const container = this . calendar . containerRef . current ;
1096+ const timeListItems = Array . from (
1097+ container . querySelectorAll < HTMLLIElement > (
1098+ ".react-datepicker__time-list-item" ,
1099+ ) ,
1100+ ) ;
1101+
1102+ let targetItem : HTMLLIElement | null = null ;
1103+ let closestTimeDiff = Infinity ;
1104+ const timeFormat = this . props . timeFormat || "p" ;
1105+
1106+ for ( const item of timeListItems ) {
1107+ const itemText = item . textContent ?. trim ( ) ;
1108+ if ( itemText ) {
1109+ const itemTime = parseDate (
1110+ itemText ,
1111+ timeFormat ,
1112+ this . props . locale ,
1113+ false ,
1114+ time ,
1115+ ) ;
1116+ if ( itemTime && isValid ( itemTime ) ) {
1117+ if ( isSameMinute ( itemTime , time ) ) {
1118+ targetItem = item ;
1119+ break ;
1120+ }
1121+ const timeDiff = Math . abs ( itemTime . getTime ( ) - time . getTime ( ) ) ;
1122+ if ( timeDiff < closestTimeDiff ) {
1123+ closestTimeDiff = timeDiff ;
1124+ targetItem = item ;
1125+ }
1126+ }
1127+ }
1128+ }
1129+
1130+ if ( targetItem ) {
1131+ timeListItems . forEach ( ( item ) => {
1132+ item . setAttribute ( "tabindex" , "-1" ) ;
1133+ } ) ;
1134+ targetItem . setAttribute ( "tabindex" , "0" ) ;
1135+
1136+ targetItem . scrollIntoView ( {
1137+ behavior : "smooth" ,
1138+ block : "center" ,
1139+ } ) ;
1140+ }
1141+ } ;
1142+
9811143 onInputKeyDown = ( event : React . KeyboardEvent < HTMLElement > ) : void => {
9821144 this . props . onKeyDown ?.( event ) ;
9831145 const eventKey = event . key ;
@@ -997,6 +1159,20 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
9971159 return ;
9981160 }
9991161
1162+ if ( this . state . open && this . props . showTimeSelectOnly ) {
1163+ if ( eventKey === KeyType . ArrowDown || eventKey === KeyType . ArrowUp ) {
1164+ event . preventDefault ( ) ;
1165+ this . handleTimeOnlyArrowKey ( eventKey ) ;
1166+ return ;
1167+ }
1168+
1169+ if ( eventKey === KeyType . Enter ) {
1170+ event . preventDefault ( ) ;
1171+ this . handleTimeOnlyEnterKey ( event ) ;
1172+ return ;
1173+ }
1174+ }
1175+
10001176 // if calendar is open, these keys will focus the selected item
10011177 if ( this . state . open ) {
10021178 if ( eventKey === KeyType . ArrowDown || eventKey === KeyType . ArrowUp ) {
0 commit comments