Skip to content

Commit ec2034d

Browse files
Merge pull request #6156 from Hacker0x01/fix/mindate-maxdate-typescript-types
fix: TypeScript type improvements for DatePicker props
2 parents 09e785c + 3a82fd1 commit ec2034d

8 files changed

Lines changed: 88 additions & 43 deletions

src/calendar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ type CalendarProps = React.PropsWithChildren<
226226
} & (
227227
| ({
228228
showMonthYearDropdown: true;
229-
} & Pick<MonthYearDropdownProps, "maxDate" | "minDate">)
229+
} & Pick<YearDropdownProps, "maxDate" | "minDate">)
230230
| ({
231231
showMonthYearDropdown?: never;
232232
} & Pick<YearDropdownProps, "maxDate" | "minDate"> &

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/month_year_dropdown.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React, { Component } from "react";
22

33
import {
44
addMonths,
5+
addYears,
6+
subYears,
57
formatDate,
68
getStartOfMonth,
79
isAfter,
@@ -11,8 +13,12 @@ import {
1113
getTime,
1214
type Locale,
1315
} from "./date_utils";
16+
1417
import MonthYearDropdownOptions from "./month_year_dropdown_options";
1518

19+
// Default range: 5 years before and after current date
20+
const DEFAULT_YEAR_RANGE = 5;
21+
1622
interface MonthYearDropdownOptionsProps extends React.ComponentPropsWithoutRef<
1723
typeof MonthYearDropdownOptions
1824
> {}
@@ -39,8 +45,14 @@ export default class MonthYearDropdown extends Component<
3945
};
4046

4147
renderSelectOptions = (): React.ReactElement[] => {
42-
let currDate = getStartOfMonth(this.props.minDate);
43-
const lastDate = getStartOfMonth(this.props.maxDate);
48+
// Use defaults if minDate/maxDate not provided
49+
const minDate =
50+
this.props.minDate ?? subYears(this.props.date, DEFAULT_YEAR_RANGE);
51+
const maxDate =
52+
this.props.maxDate ?? addYears(this.props.date, DEFAULT_YEAR_RANGE);
53+
54+
let currDate = getStartOfMonth(minDate);
55+
const lastDate = getStartOfMonth(maxDate);
4456
const options = [];
4557

4658
while (!isAfter(currDate, lastDate)) {

src/month_year_dropdown_options.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import React, { Component } from "react";
44
import { ClickOutsideWrapper } from "./click_outside_wrapper";
55
import {
66
addMonths,
7+
addYears,
8+
subYears,
79
formatDate,
810
getStartOfMonth,
911
newDate,
@@ -14,11 +16,22 @@ import {
1416
type Locale,
1517
} from "./date_utils";
1618

17-
function generateMonthYears(minDate: Date, maxDate: Date): Date[] {
19+
// Default range: 5 years before and after current date
20+
const DEFAULT_YEAR_RANGE = 5;
21+
22+
function generateMonthYears(
23+
minDate: Date | undefined,
24+
maxDate: Date | undefined,
25+
currentDate: Date,
26+
): Date[] {
1827
const list = [];
1928

20-
let currDate = getStartOfMonth(minDate);
21-
const lastDate = getStartOfMonth(maxDate);
29+
// Use defaults if minDate/maxDate not provided
30+
const effectiveMinDate = minDate ?? subYears(currentDate, DEFAULT_YEAR_RANGE);
31+
const effectiveMaxDate = maxDate ?? addYears(currentDate, DEFAULT_YEAR_RANGE);
32+
33+
let currDate = getStartOfMonth(effectiveMinDate);
34+
const lastDate = getStartOfMonth(effectiveMaxDate);
2235

2336
while (!isAfter(currDate, lastDate)) {
2437
list.push(newDate(currDate));
@@ -29,8 +42,8 @@ function generateMonthYears(minDate: Date, maxDate: Date): Date[] {
2942
}
3043

3144
interface MonthYearDropdownOptionsProps {
32-
minDate: Date;
33-
maxDate: Date;
45+
minDate?: Date;
46+
maxDate?: Date;
3447
onCancel: VoidFunction;
3548
onChange: (monthYear: number) => void;
3649
scrollableMonthYearDropdown?: boolean;
@@ -54,6 +67,7 @@ export default class MonthYearDropdownOptions extends Component<
5467
monthYearsList: generateMonthYears(
5568
this.props.minDate,
5669
this.props.maxDate,
70+
this.props.date,
5771
),
5872
};
5973
}

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"

0 commit comments

Comments
 (0)