@@ -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,163 @@ 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+ let newTime : Date ;
996+ if ( eventKey === KeyType . ArrowUp ) {
997+ const newMinutes = Math . max ( 0 , currentMinutes - timeIntervals ) ;
998+ newTime = addMinutes ( baseDate , newMinutes ) ;
999+ } else {
1000+ newTime = addMinutes ( baseDate , currentMinutes + timeIntervals ) ;
1001+ }
1002+
1003+ const formattedTime = formatDate (
1004+ newTime ,
1005+ formatStr || DatePicker . defaultProps . dateFormat ,
1006+ this . props . locale ,
1007+ ) ;
1008+ this . setState ( {
1009+ preSelection : newTime ,
1010+ inputValue : formattedTime ,
1011+ } ) ;
1012+
1013+ if ( this . props . selectsRange || this . props . selectsMultiple ) {
1014+ return ;
1015+ }
1016+
1017+ const selected = this . props . selected
1018+ ? this . props . selected
1019+ : this . getPreSelection ( ) ;
1020+ const changedDate = this . props . selected
1021+ ? newTime
1022+ : setTime ( selected , {
1023+ hour : getHours ( newTime ) ,
1024+ minute : getMinutes ( newTime ) ,
1025+ } ) ;
1026+
1027+ this . props . onChange ?.( changedDate ) ;
1028+
1029+ if ( this . props . showTimeSelectOnly || this . props . showTimeSelect ) {
1030+ this . setState ( { isRenderAriaLiveMessage : true } ) ;
1031+ }
1032+
1033+ requestAnimationFrame ( ( ) => {
1034+ this . scrollToTimeOption ( newTime ) ;
1035+ } ) ;
1036+ } ;
1037+
1038+ handleTimeOnlyEnterKey = ( event : React . KeyboardEvent < HTMLElement > ) : void => {
1039+ const inputElement = event . target as HTMLInputElement ;
1040+ const inputValue = inputElement . value ;
1041+ const dateFormat =
1042+ this . props . dateFormat ?? DatePicker . defaultProps . dateFormat ;
1043+ const timeFormat = this . props . timeFormat || "p" ;
1044+
1045+ const defaultTime =
1046+ this . state . preSelection || this . props . selected || newDate ( ) ;
1047+ const parsedDate = parseDate (
1048+ inputValue ,
1049+ dateFormat ,
1050+ this . props . locale ,
1051+ this . props . strictParsing ?? false ,
1052+ defaultTime ,
1053+ ) ;
1054+
1055+ let timeToCommit : Date = defaultTime ;
1056+
1057+ if ( parsedDate && isValid ( parsedDate ) ) {
1058+ timeToCommit = parsedDate ;
1059+ } else {
1060+ const highlightedItem =
1061+ this . calendar ?. containerRef . current instanceof Element &&
1062+ this . calendar . containerRef . current . querySelector (
1063+ ".react-datepicker__time-list-item[tabindex='0']" ,
1064+ ) ;
1065+
1066+ if ( highlightedItem instanceof HTMLElement ) {
1067+ const itemText = highlightedItem . textContent ?. trim ( ) ;
1068+ if ( itemText ) {
1069+ const itemTime = parseDate (
1070+ itemText ,
1071+ timeFormat ,
1072+ this . props . locale ,
1073+ false ,
1074+ defaultTime ,
1075+ ) ;
1076+ if ( itemTime && isValid ( itemTime ) ) {
1077+ timeToCommit = itemTime ;
1078+ }
1079+ }
1080+ }
1081+ }
1082+
1083+ this . handleTimeChange ( timeToCommit ) ;
1084+ this . setOpen ( false ) ;
1085+ this . sendFocusBackToInput ( ) ;
1086+ } ;
1087+
1088+ scrollToTimeOption = ( time : Date ) : void => {
1089+ if ( ! this . calendar ?. containerRef . current ) {
1090+ return ;
1091+ }
1092+
1093+ const container = this . calendar . containerRef . current ;
1094+ const timeListItems = Array . from (
1095+ container . querySelectorAll < HTMLLIElement > (
1096+ ".react-datepicker__time-list-item" ,
1097+ ) ,
1098+ ) ;
1099+
1100+ let targetItem : HTMLLIElement | null = null ;
1101+ let closestTimeDiff = Infinity ;
1102+ const timeFormat = this . props . timeFormat || "p" ;
1103+
1104+ for ( const item of timeListItems ) {
1105+ const itemText = item . textContent ?. trim ( ) ;
1106+ if ( itemText ) {
1107+ const itemTime = parseDate (
1108+ itemText ,
1109+ timeFormat ,
1110+ this . props . locale ,
1111+ false ,
1112+ time ,
1113+ ) ;
1114+ if ( itemTime && isValid ( itemTime ) ) {
1115+ if ( isSameMinute ( itemTime , time ) ) {
1116+ targetItem = item ;
1117+ break ;
1118+ }
1119+ const timeDiff = Math . abs ( itemTime . getTime ( ) - time . getTime ( ) ) ;
1120+ if ( timeDiff < closestTimeDiff ) {
1121+ closestTimeDiff = timeDiff ;
1122+ targetItem = item ;
1123+ }
1124+ }
1125+ }
1126+ }
1127+
1128+ if ( targetItem ) {
1129+ timeListItems . forEach ( ( item ) => {
1130+ item . setAttribute ( "tabindex" , "-1" ) ;
1131+ } ) ;
1132+ targetItem . setAttribute ( "tabindex" , "0" ) ;
1133+
1134+ targetItem . scrollIntoView ( {
1135+ behavior : "smooth" ,
1136+ block : "center" ,
1137+ } ) ;
1138+ }
1139+ } ;
1140+
9811141 onInputKeyDown = ( event : React . KeyboardEvent < HTMLElement > ) : void => {
9821142 this . props . onKeyDown ?.( event ) ;
9831143 const eventKey = event . key ;
@@ -997,6 +1157,20 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
9971157 return ;
9981158 }
9991159
1160+ if ( this . state . open && this . props . showTimeSelectOnly ) {
1161+ if ( eventKey === KeyType . ArrowDown || eventKey === KeyType . ArrowUp ) {
1162+ event . preventDefault ( ) ;
1163+ this . handleTimeOnlyArrowKey ( eventKey ) ;
1164+ return ;
1165+ }
1166+
1167+ if ( eventKey === KeyType . Enter ) {
1168+ event . preventDefault ( ) ;
1169+ this . handleTimeOnlyEnterKey ( event ) ;
1170+ return ;
1171+ }
1172+ }
1173+
10001174 // if calendar is open, these keys will focus the selected item
10011175 if ( this . state . open ) {
10021176 if ( eventKey === KeyType . ArrowDown || eventKey === KeyType . ArrowUp ) {
0 commit comments