Skip to content

Commit 5e81443

Browse files
fix: support time selection with selectsRange for showTimeInput
Fixes #3673 and #4649 - When selectsRange is true with showTimeInput, render two time inputs (one for start date, one for end date) - Add modifyDateType parameter to handleTimeChange to explicitly specify which date to modify - Each time input independently updates its corresponding date - Add 7 new tests for showTimeInput with selectsRange functionality Changes: - src/index.tsx: Updated handleTimeChange to accept modifyDateType param - src/calendar.tsx: Updated renderInputTimeSection to render two inputs - src/test/datepicker_test.test.tsx: Added tests for new functionality
1 parent a9517e4 commit 5e81443

File tree

3 files changed

+347
-46
lines changed

3 files changed

+347
-46
lines changed

src/calendar.tsx

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ type CalendarProps = React.PropsWithChildren<
149149
> &
150150
Omit<TimeProps, "onChange" | "format" | "intervals" | "monthRef"> &
151151
Omit<InputTimeProps, "date" | "timeString" | "onChange"> & {
152+
selectsRange?: boolean;
153+
startDate?: Date | null;
154+
endDate?: Date | null;
152155
className?: string;
153156
container?: React.ElementType;
154157
showYearPicker?: boolean;
@@ -205,7 +208,7 @@ type CalendarProps = React.PropsWithChildren<
205208
| React.KeyboardEvent<HTMLLIElement>
206209
| React.KeyboardEvent<HTMLButtonElement>,
207210
) => void;
208-
onTimeChange?: TimeProps["onChange"] | InputTimeProps["onChange"];
211+
onTimeChange?: (time: Date, modifyDateType?: "start" | "end") => void;
209212
timeFormat?: TimeProps["format"];
210213
timeIntervals?: TimeProps["intervals"];
211214
} & (
@@ -1107,25 +1110,72 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
11071110
};
11081111

11091112
renderInputTimeSection = (): React.ReactElement | undefined => {
1113+
if (!this.props.showTimeInput) {
1114+
return;
1115+
}
1116+
1117+
// Handle selectsRange mode - render two time inputs
1118+
if (this.props.selectsRange) {
1119+
const { startDate, endDate } = this.props;
1120+
1121+
const startTime = startDate ? new Date(startDate) : undefined;
1122+
const startTimeValid =
1123+
startTime && isValid(startTime) && Boolean(startDate);
1124+
const startTimeString = startTimeValid
1125+
? `${addZero(startTime.getHours())}:${addZero(startTime.getMinutes())}`
1126+
: "";
1127+
1128+
const endTime = endDate ? new Date(endDate) : undefined;
1129+
const endTimeValid = endTime && isValid(endTime) && Boolean(endDate);
1130+
const endTimeString = endTimeValid
1131+
? `${addZero(endTime.getHours())}:${addZero(endTime.getMinutes())}`
1132+
: "";
1133+
1134+
return (
1135+
<>
1136+
<InputTime
1137+
{...Calendar.defaultProps}
1138+
{...this.props}
1139+
date={startTime}
1140+
timeString={startTimeString}
1141+
onChange={(time: Date) => {
1142+
this.props.onTimeChange?.(time, "start");
1143+
}}
1144+
timeInputLabel={(this.props.timeInputLabel ?? "Time") + " (Start)"}
1145+
/>
1146+
<InputTime
1147+
{...Calendar.defaultProps}
1148+
{...this.props}
1149+
date={endTime}
1150+
timeString={endTimeString}
1151+
onChange={(time: Date) => {
1152+
this.props.onTimeChange?.(time, "end");
1153+
}}
1154+
timeInputLabel={(this.props.timeInputLabel ?? "Time") + " (End)"}
1155+
/>
1156+
</>
1157+
);
1158+
}
1159+
1160+
// Single date mode (original behavior)
11101161
const time = this.props.selected
11111162
? new Date(this.props.selected)
11121163
: undefined;
11131164
const timeValid = time && isValid(time) && Boolean(this.props.selected);
11141165
const timeString = timeValid
11151166
? `${addZero(time.getHours())}:${addZero(time.getMinutes())}`
11161167
: "";
1117-
if (this.props.showTimeInput) {
1118-
return (
1119-
<InputTime
1120-
{...Calendar.defaultProps}
1121-
{...this.props}
1122-
date={time}
1123-
timeString={timeString}
1124-
onChange={this.props.onTimeChange}
1125-
/>
1126-
);
1127-
}
1128-
return;
1168+
return (
1169+
<InputTime
1170+
{...Calendar.defaultProps}
1171+
{...this.props}
1172+
date={time}
1173+
timeString={timeString}
1174+
onChange={(time: Date) => {
1175+
this.props.onTimeChange?.(time);
1176+
}}
1177+
/>
1178+
);
11291179
};
11301180

11311181
renderAriaLiveRegion = (): React.ReactElement => {

src/index.tsx

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
963963
this.setOpen(!this.state.open);
964964
};
965965

966-
handleTimeChange = (time: Date): void => {
966+
handleTimeChange = (time: Date, modifyDateType?: "start" | "end"): void => {
967967
if (this.props.selectsMultiple) {
968968
return;
969969
}
@@ -972,39 +972,69 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
972972

973973
if (selectsRange) {
974974
// In range mode, apply time to the appropriate date
975-
// If we have a startDate but no endDate, apply time to startDate
976-
// If we have both, apply time to endDate
977-
const hasStartRange = startDate && !endDate;
978-
979-
if (hasStartRange) {
980-
// Apply time to startDate
981-
const changedStartDate = setTime(startDate, {
982-
hour: getHours(time),
983-
minute: getMinutes(time),
984-
});
985-
this.setState({
986-
preSelection: changedStartDate,
987-
});
988-
onChange?.([changedStartDate, null], undefined);
989-
} else if (startDate && endDate) {
990-
// Apply time to endDate
991-
const changedEndDate = setTime(endDate, {
992-
hour: getHours(time),
993-
minute: getMinutes(time),
994-
});
995-
this.setState({
996-
preSelection: changedEndDate,
997-
});
998-
onChange?.([startDate, changedEndDate], undefined);
975+
// If modifyDateType is specified, use that to determine which date to modify
976+
// Otherwise, use the legacy behavior:
977+
// - If we have a startDate but no endDate, apply time to startDate
978+
// - If we have both, apply time to endDate
979+
980+
if (modifyDateType === "start") {
981+
// Explicitly modify start date
982+
if (startDate) {
983+
const changedStartDate = setTime(startDate, {
984+
hour: getHours(time),
985+
minute: getMinutes(time),
986+
});
987+
this.setState({
988+
preSelection: changedStartDate,
989+
});
990+
onChange?.([changedStartDate, endDate ?? null], undefined);
991+
}
992+
} else if (modifyDateType === "end") {
993+
// Explicitly modify end date
994+
if (endDate) {
995+
const changedEndDate = setTime(endDate, {
996+
hour: getHours(time),
997+
minute: getMinutes(time),
998+
});
999+
this.setState({
1000+
preSelection: changedEndDate,
1001+
});
1002+
onChange?.([startDate ?? null, changedEndDate], undefined);
1003+
}
9991004
} else {
1000-
// No dates selected yet, just update preSelection
1001-
const changedDate = setTime(this.getPreSelection(), {
1002-
hour: getHours(time),
1003-
minute: getMinutes(time),
1004-
});
1005-
this.setState({
1006-
preSelection: changedDate,
1007-
});
1005+
// Legacy behavior for showTimeSelect (single time picker)
1006+
const hasStartRange = startDate && !endDate;
1007+
1008+
if (hasStartRange) {
1009+
// Apply time to startDate
1010+
const changedStartDate = setTime(startDate, {
1011+
hour: getHours(time),
1012+
minute: getMinutes(time),
1013+
});
1014+
this.setState({
1015+
preSelection: changedStartDate,
1016+
});
1017+
onChange?.([changedStartDate, null], undefined);
1018+
} else if (startDate && endDate) {
1019+
// Apply time to endDate
1020+
const changedEndDate = setTime(endDate, {
1021+
hour: getHours(time),
1022+
minute: getMinutes(time),
1023+
});
1024+
this.setState({
1025+
preSelection: changedEndDate,
1026+
});
1027+
onChange?.([startDate, changedEndDate], undefined);
1028+
} else {
1029+
// No dates selected yet, just update preSelection
1030+
const changedDate = setTime(this.getPreSelection(), {
1031+
hour: getHours(time),
1032+
minute: getMinutes(time),
1033+
});
1034+
this.setState({
1035+
preSelection: changedDate,
1036+
});
1037+
}
10081038
}
10091039
} else {
10101040
// Single date mode (original behavior)

0 commit comments

Comments
 (0)