Skip to content

Commit 3a82fd1

Browse files
fix: allow spreading props with selectsRange and selectsMultiple
Changed selectsRange and selectsMultiple from required literal `true` to optional `true` in the union type branches. This allows spreading props where these values might be `boolean | undefined` (e.g., from wrapper components using `Omit<DatePickerProps, 'onChange'>`). Added internal type aliases (OnChangeSingle, OnChangeRange, OnChangeMultiple) and type assertions in the component methods to maintain type safety while allowing the more flexible public API. Updated test files to use explicit type annotations for onChange callbacks since TypeScript can no longer infer the parameter type from the union. Fixes #6131 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ce17245 commit 3a82fd1

5 files changed

Lines changed: 54 additions & 35 deletions

File tree

src/index.tsx

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export type DatePickerProps = OmitUnion<
257257
) => void;
258258
}
259259
| {
260-
selectsRange: true;
260+
selectsRange?: true;
261261
selectsMultiple?: false | undefined;
262262
formatMultipleDates?: never;
263263
onChange?: (
@@ -269,7 +269,7 @@ export type DatePickerProps = OmitUnion<
269269
}
270270
| {
271271
selectsRange?: false | undefined;
272-
selectsMultiple: true;
272+
selectsMultiple?: true;
273273
formatMultipleDates?: (
274274
dates: Date[],
275275
formatDate: (date: Date) => string,
@@ -283,6 +283,22 @@ export type DatePickerProps = OmitUnion<
283283
}
284284
);
285285

286+
// Internal types for onChange handlers - used for type assertions within the component
287+
type OnChangeSingle = (
288+
date: Date | null,
289+
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
290+
) => void;
291+
292+
type OnChangeRange = (
293+
date: [Date | null, Date | null],
294+
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
295+
) => void;
296+
297+
type OnChangeMultiple = (
298+
dates: Date[] | null,
299+
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
300+
) => void;
301+
286302
interface DatePickerState {
287303
open: boolean;
288304
wasHidden: boolean;
@@ -953,38 +969,40 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
953969
}
954970

955971
if (selectsRange) {
972+
const onChangeRange = onChange as OnChangeRange | undefined;
956973
const noRanges = !startDate && !endDate;
957974
const hasStartRange = startDate && !endDate;
958975
const hasOnlyEndRange = !startDate && !!endDate;
959976
const isRangeFilled = startDate && endDate;
960977
if (noRanges) {
961-
onChange?.([changedDate, null], event);
978+
onChangeRange?.([changedDate, null], event);
962979
} else if (hasStartRange) {
963980
if (changedDate === null) {
964-
onChange?.([null, null], event);
981+
onChangeRange?.([null, null], event);
965982
} else if (isDateBefore(changedDate, startDate)) {
966983
if (swapRange) {
967-
onChange?.([changedDate, startDate], event);
984+
onChangeRange?.([changedDate, startDate], event);
968985
} else {
969-
onChange?.([changedDate, null], event);
986+
onChangeRange?.([changedDate, null], event);
970987
}
971988
} else {
972-
onChange?.([startDate, changedDate], event);
989+
onChangeRange?.([startDate, changedDate], event);
973990
}
974991
} else if (hasOnlyEndRange) {
975992
if (changedDate && isDateBefore(changedDate, endDate)) {
976-
onChange?.([changedDate, endDate], event);
993+
onChangeRange?.([changedDate, endDate], event);
977994
} else {
978-
onChange?.([changedDate, null], event);
995+
onChangeRange?.([changedDate, null], event);
979996
}
980997
}
981998
if (isRangeFilled) {
982-
onChange?.([changedDate, null], event);
999+
onChangeRange?.([changedDate, null], event);
9831000
}
9841001
} else if (selectsMultiple) {
1002+
const onChangeMultiple = onChange as OnChangeMultiple | undefined;
9851003
if (changedDate !== null) {
9861004
if (!selectedDates?.length) {
987-
onChange?.([changedDate], event);
1005+
onChangeMultiple?.([changedDate], event);
9881006
} else {
9891007
const isChangedDateAlreadySelected = selectedDates.some(
9901008
(selectedDate) => isSameDay(selectedDate, changedDate),
@@ -995,14 +1013,14 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
9951013
(selectedDate) => !isSameDay(selectedDate, changedDate),
9961014
);
9971015

998-
onChange?.(nextDates, event);
1016+
onChangeMultiple?.(nextDates, event);
9991017
} else {
1000-
onChange?.([...selectedDates, changedDate], event);
1018+
onChangeMultiple?.([...selectedDates, changedDate], event);
10011019
}
10021020
}
10031021
}
10041022
} else {
1005-
onChange?.(changedDate, event);
1023+
(onChange as OnChangeSingle | undefined)?.(changedDate, event);
10061024
}
10071025
}
10081026

@@ -1058,6 +1076,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
10581076
const { selectsRange, startDate, endDate, onChange, timeZone } = this.props;
10591077

10601078
if (selectsRange) {
1079+
const onChangeRange = onChange as OnChangeRange | undefined;
10611080
// In range mode, apply time to the appropriate date
10621081
// If modifyDateType is specified, use that to determine which date to modify
10631082
// Otherwise, use the legacy behavior:
@@ -1078,7 +1097,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
10781097
if (timeZone) {
10791098
changedStartDate = fromZonedTime(changedStartDate, timeZone);
10801099
}
1081-
onChange?.(
1100+
onChangeRange?.(
10821101
[
10831102
changedStartDate,
10841103
endDate
@@ -1104,7 +1123,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
11041123
if (timeZone) {
11051124
changedEndDate = fromZonedTime(changedEndDate, timeZone);
11061125
}
1107-
onChange?.(
1126+
onChangeRange?.(
11081127
[
11091128
startDate
11101129
? timeZone
@@ -1133,7 +1152,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
11331152
if (timeZone) {
11341153
changedStartDate = fromZonedTime(changedStartDate, timeZone);
11351154
}
1136-
onChange?.([changedStartDate, null], undefined);
1155+
onChangeRange?.([changedStartDate, null], undefined);
11371156
} else if (startDate && endDate) {
11381157
// Apply time to endDate
11391158
let changedEndDate = setTime(endDate, {
@@ -1147,7 +1166,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
11471166
if (timeZone) {
11481167
changedEndDate = fromZonedTime(changedEndDate, timeZone);
11491168
}
1150-
onChange?.(
1169+
onChangeRange?.(
11511170
[
11521171
timeZone ? fromZonedTime(startDate, timeZone) : startDate,
11531172
changedEndDate,
@@ -1186,7 +1205,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
11861205
changedDate = fromZonedTime(changedDate, timeZone);
11871206
}
11881207

1189-
this.props.onChange?.(changedDate);
1208+
(this.props.onChange as OnChangeSingle | undefined)?.(changedDate);
11901209
}
11911210

11921211
if (this.props.shouldCloseOnSelect && !this.props.showTimeInput) {
@@ -1255,7 +1274,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
12551274
minute: getMinutes(newTime),
12561275
});
12571276

1258-
this.props.onChange?.(changedDate);
1277+
(this.props.onChange as OnChangeSingle | undefined)?.(changedDate);
12591278

12601279
if (this.props.showTimeSelectOnly || this.props.showTimeSelect) {
12611280
this.setState({ isRenderAriaLiveMessage: true });
@@ -1652,9 +1671,9 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
16521671

16531672
const { selectsRange, onChange } = this.props;
16541673
if (selectsRange) {
1655-
onChange?.([null, null], event);
1674+
(onChange as OnChangeRange | undefined)?.([null, null], event);
16561675
} else {
1657-
onChange?.(null, event);
1676+
(onChange as OnChangeSingle | undefined)?.(null, event);
16581677
}
16591678

16601679
this.setState({ inputValue: null });

src/test/calendar_test.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ describe("Calendar", () => {
13731373
<DatePicker
13741374
selected={newDate("2017-07-28")}
13751375
adjustDateOnChange
1376-
onChange={(d) => {
1376+
onChange={(d: Date | null) => {
13771377
date = d;
13781378
}}
13791379
/>,
@@ -1397,7 +1397,7 @@ describe("Calendar", () => {
13971397
<DatePicker
13981398
selected={newDate("2017-07-28")}
13991399
adjustDateOnChange
1400-
onChange={(d) => {
1400+
onChange={(d: Date | null) => {
14011401
date = d;
14021402
}}
14031403
/>,
@@ -1421,7 +1421,7 @@ describe("Calendar", () => {
14211421
<DatePicker
14221422
selected={newDate("2017-12-31")}
14231423
adjustDateOnChange
1424-
onChange={(d) => {
1424+
onChange={(d: Date | null) => {
14251425
date = d;
14261426
}}
14271427
/>,

src/test/datepicker_test.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ describe("DatePicker", () => {
11501150
<DatePicker
11511151
inline
11521152
selected={selected}
1153-
onChange={(d) => {
1153+
onChange={(d: Date | null) => {
11541154
date = d;
11551155
}}
11561156
/>,
@@ -1172,7 +1172,7 @@ describe("DatePicker", () => {
11721172
const { container } = render(
11731173
<DatePicker
11741174
selected={selected}
1175-
onChange={(d) => {
1175+
onChange={(d: Date | null) => {
11761176
date = d;
11771177
}}
11781178
/>,
@@ -4487,7 +4487,7 @@ describe("DatePicker", () => {
44874487
const { container } = render(
44884488
<DatePicker
44894489
selected={selected}
4490-
onChange={(d) => {
4490+
onChange={(d: Date | null) => {
44914491
date = d;
44924492
}}
44934493
showTimeSelect
@@ -4518,7 +4518,7 @@ describe("DatePicker", () => {
45184518
const { container: datepicker } = render(
45194519
<DatePicker
45204520
selected={selected}
4521-
onChange={(d) => {
4521+
onChange={(d: Date | null) => {
45224522
date = d;
45234523
}}
45244524
showTimeSelect
@@ -4544,7 +4544,7 @@ describe("DatePicker", () => {
45444544
const { container: datepicker } = render(
45454545
<DatePicker
45464546
selected={selected}
4547-
onChange={(d) => {
4547+
onChange={(d: Date | null) => {
45484548
date = d;
45494549
}}
45504550
showTimeSelectOnly
@@ -4568,7 +4568,7 @@ describe("DatePicker", () => {
45684568
const { container } = render(
45694569
<DatePicker
45704570
selected={selected}
4571-
onChange={(d) => (date = d)}
4571+
onChange={(d: Date | null) => (date = d)}
45724572
dateFormat="MM/yyyy"
45734573
minDate={newDate("2022-12-31")}
45744574
showMonthYearPicker
@@ -4597,7 +4597,7 @@ describe("DatePicker", () => {
45974597
const { container } = render(
45984598
<DatePicker
45994599
selected={selected}
4600-
onChange={(d) => (date = d)}
4600+
onChange={(d: Date | null) => (date = d)}
46014601
dateFormat="yyyy"
46024602
minDate={newDate("2022-12-31")}
46034603
showYearPicker

src/test/timezone_test.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ describe("DatePicker with timeZone prop", () => {
303303
const { container } = render(
304304
<DatePicker
305305
selected={utcDate}
306-
onChange={(date) => {
306+
onChange={(date: Date | null) => {
307307
selectedDate = date;
308308
}}
309309
timeZone="America/New_York"

src/test/year_picker_test.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ describe("YearPicker", () => {
642642
selected={newDate("2020-01-01")}
643643
adjustDateOnChange
644644
showYearPicker
645-
onChange={(d) => {
645+
onChange={(d: Date | null) => {
646646
date = d;
647647
}}
648648
/>,
@@ -674,7 +674,7 @@ describe("YearPicker", () => {
674674
selected={newDate("2020-01-01")}
675675
adjustDateOnChange
676676
showYearPicker
677-
onChange={(d) => {
677+
onChange={(d: Date | null) => {
678678
date = d;
679679
}}
680680
/>,

0 commit comments

Comments
 (0)