Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ type CalendarProps = React.PropsWithChildren<
} & (
| ({
showMonthYearDropdown: true;
} & Pick<MonthYearDropdownProps, "maxDate" | "minDate">)
} & Pick<YearDropdownProps, "maxDate" | "minDate">)
| ({
showMonthYearDropdown?: never;
} & Pick<YearDropdownProps, "maxDate" | "minDate"> &
Expand Down
63 changes: 41 additions & 22 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export type DatePickerProps = OmitUnion<
) => void;
}
| {
selectsRange: true;
selectsRange?: true;
selectsMultiple?: false | undefined;
formatMultipleDates?: never;
onChange?: (
Expand All @@ -269,7 +269,7 @@ export type DatePickerProps = OmitUnion<
}
| {
selectsRange?: false | undefined;
selectsMultiple: true;
selectsMultiple?: true;
formatMultipleDates?: (
dates: Date[],
formatDate: (date: Date) => string,
Expand All @@ -283,6 +283,22 @@ export type DatePickerProps = OmitUnion<
}
);

// Internal types for onChange handlers - used for type assertions within the component
type OnChangeSingle = (
date: Date | null,
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
) => void;

type OnChangeRange = (
date: [Date | null, Date | null],
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
) => void;

type OnChangeMultiple = (
dates: Date[] | null,
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
) => void;

interface DatePickerState {
open: boolean;
wasHidden: boolean;
Expand Down Expand Up @@ -953,38 +969,40 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
}

if (selectsRange) {
const onChangeRange = onChange as OnChangeRange | undefined;
const noRanges = !startDate && !endDate;
const hasStartRange = startDate && !endDate;
const hasOnlyEndRange = !startDate && !!endDate;
const isRangeFilled = startDate && endDate;
if (noRanges) {
onChange?.([changedDate, null], event);
onChangeRange?.([changedDate, null], event);
} else if (hasStartRange) {
if (changedDate === null) {
onChange?.([null, null], event);
onChangeRange?.([null, null], event);
} else if (isDateBefore(changedDate, startDate)) {
if (swapRange) {
onChange?.([changedDate, startDate], event);
onChangeRange?.([changedDate, startDate], event);
} else {
onChange?.([changedDate, null], event);
onChangeRange?.([changedDate, null], event);
}
} else {
onChange?.([startDate, changedDate], event);
onChangeRange?.([startDate, changedDate], event);
}
} else if (hasOnlyEndRange) {
if (changedDate && isDateBefore(changedDate, endDate)) {
onChange?.([changedDate, endDate], event);
onChangeRange?.([changedDate, endDate], event);
} else {
onChange?.([changedDate, null], event);
onChangeRange?.([changedDate, null], event);
}
}
if (isRangeFilled) {
onChange?.([changedDate, null], event);
onChangeRange?.([changedDate, null], event);
}
} else if (selectsMultiple) {
const onChangeMultiple = onChange as OnChangeMultiple | undefined;
if (changedDate !== null) {
if (!selectedDates?.length) {
onChange?.([changedDate], event);
onChangeMultiple?.([changedDate], event);
} else {
const isChangedDateAlreadySelected = selectedDates.some(
(selectedDate) => isSameDay(selectedDate, changedDate),
Expand All @@ -995,14 +1013,14 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
(selectedDate) => !isSameDay(selectedDate, changedDate),
);

onChange?.(nextDates, event);
onChangeMultiple?.(nextDates, event);
} else {
onChange?.([...selectedDates, changedDate], event);
onChangeMultiple?.([...selectedDates, changedDate], event);
}
}
}
} else {
onChange?.(changedDate, event);
(onChange as OnChangeSingle | undefined)?.(changedDate, event);
}
}

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

if (selectsRange) {
const onChangeRange = onChange as OnChangeRange | undefined;
// In range mode, apply time to the appropriate date
// If modifyDateType is specified, use that to determine which date to modify
// Otherwise, use the legacy behavior:
Expand All @@ -1078,7 +1097,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
if (timeZone) {
changedStartDate = fromZonedTime(changedStartDate, timeZone);
}
onChange?.(
onChangeRange?.(
[
changedStartDate,
endDate
Expand All @@ -1104,7 +1123,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
if (timeZone) {
changedEndDate = fromZonedTime(changedEndDate, timeZone);
}
onChange?.(
onChangeRange?.(
[
startDate
? timeZone
Expand Down Expand Up @@ -1133,7 +1152,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
if (timeZone) {
changedStartDate = fromZonedTime(changedStartDate, timeZone);
}
onChange?.([changedStartDate, null], undefined);
onChangeRange?.([changedStartDate, null], undefined);
} else if (startDate && endDate) {
// Apply time to endDate
let changedEndDate = setTime(endDate, {
Expand All @@ -1147,7 +1166,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
if (timeZone) {
changedEndDate = fromZonedTime(changedEndDate, timeZone);
}
onChange?.(
onChangeRange?.(
[
timeZone ? fromZonedTime(startDate, timeZone) : startDate,
changedEndDate,
Expand Down Expand Up @@ -1186,7 +1205,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
changedDate = fromZonedTime(changedDate, timeZone);
}

this.props.onChange?.(changedDate);
(this.props.onChange as OnChangeSingle | undefined)?.(changedDate);
}

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

this.props.onChange?.(changedDate);
(this.props.onChange as OnChangeSingle | undefined)?.(changedDate);

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

const { selectsRange, onChange } = this.props;
if (selectsRange) {
onChange?.([null, null], event);
(onChange as OnChangeRange | undefined)?.([null, null], event);
} else {
onChange?.(null, event);
(onChange as OnChangeSingle | undefined)?.(null, event);
}

this.setState({ inputValue: null });
Expand Down
16 changes: 14 additions & 2 deletions src/month_year_dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { Component } from "react";

import {
addMonths,
addYears,
subYears,
formatDate,
getStartOfMonth,
isAfter,
Expand All @@ -11,8 +13,12 @@ import {
getTime,
type Locale,
} from "./date_utils";

import MonthYearDropdownOptions from "./month_year_dropdown_options";

// Default range: 5 years before and after current date
const DEFAULT_YEAR_RANGE = 5;

interface MonthYearDropdownOptionsProps extends React.ComponentPropsWithoutRef<
typeof MonthYearDropdownOptions
> {}
Expand All @@ -39,8 +45,14 @@ export default class MonthYearDropdown extends Component<
};

renderSelectOptions = (): React.ReactElement[] => {
let currDate = getStartOfMonth(this.props.minDate);
const lastDate = getStartOfMonth(this.props.maxDate);
// Use defaults if minDate/maxDate not provided
const minDate =
this.props.minDate ?? subYears(this.props.date, DEFAULT_YEAR_RANGE);
const maxDate =
this.props.maxDate ?? addYears(this.props.date, DEFAULT_YEAR_RANGE);

let currDate = getStartOfMonth(minDate);
const lastDate = getStartOfMonth(maxDate);
const options = [];

while (!isAfter(currDate, lastDate)) {
Expand Down
24 changes: 19 additions & 5 deletions src/month_year_dropdown_options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import React, { Component } from "react";
import { ClickOutsideWrapper } from "./click_outside_wrapper";
import {
addMonths,
addYears,
subYears,
formatDate,
getStartOfMonth,
newDate,
Expand All @@ -14,11 +16,22 @@ import {
type Locale,
} from "./date_utils";

function generateMonthYears(minDate: Date, maxDate: Date): Date[] {
// Default range: 5 years before and after current date
const DEFAULT_YEAR_RANGE = 5;

function generateMonthYears(
minDate: Date | undefined,
maxDate: Date | undefined,
currentDate: Date,
): Date[] {
const list = [];

let currDate = getStartOfMonth(minDate);
const lastDate = getStartOfMonth(maxDate);
// Use defaults if minDate/maxDate not provided
const effectiveMinDate = minDate ?? subYears(currentDate, DEFAULT_YEAR_RANGE);
const effectiveMaxDate = maxDate ?? addYears(currentDate, DEFAULT_YEAR_RANGE);

let currDate = getStartOfMonth(effectiveMinDate);
const lastDate = getStartOfMonth(effectiveMaxDate);

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

interface MonthYearDropdownOptionsProps {
minDate: Date;
maxDate: Date;
minDate?: Date;
maxDate?: Date;
onCancel: VoidFunction;
onChange: (monthYear: number) => void;
scrollableMonthYearDropdown?: boolean;
Expand All @@ -54,6 +67,7 @@ export default class MonthYearDropdownOptions extends Component<
monthYearsList: generateMonthYears(
this.props.minDate,
this.props.maxDate,
this.props.date,
),
};
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/calendar_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1373,7 +1373,7 @@ describe("Calendar", () => {
<DatePicker
selected={newDate("2017-07-28")}
adjustDateOnChange
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
/>,
Expand All @@ -1397,7 +1397,7 @@ describe("Calendar", () => {
<DatePicker
selected={newDate("2017-07-28")}
adjustDateOnChange
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
/>,
Expand All @@ -1421,7 +1421,7 @@ describe("Calendar", () => {
<DatePicker
selected={newDate("2017-12-31")}
adjustDateOnChange
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
/>,
Expand Down
14 changes: 7 additions & 7 deletions src/test/datepicker_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ describe("DatePicker", () => {
<DatePicker
inline
selected={selected}
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
/>,
Expand All @@ -1172,7 +1172,7 @@ describe("DatePicker", () => {
const { container } = render(
<DatePicker
selected={selected}
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
/>,
Expand Down Expand Up @@ -4487,7 +4487,7 @@ describe("DatePicker", () => {
const { container } = render(
<DatePicker
selected={selected}
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
showTimeSelect
Expand Down Expand Up @@ -4518,7 +4518,7 @@ describe("DatePicker", () => {
const { container: datepicker } = render(
<DatePicker
selected={selected}
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
showTimeSelect
Expand All @@ -4544,7 +4544,7 @@ describe("DatePicker", () => {
const { container: datepicker } = render(
<DatePicker
selected={selected}
onChange={(d) => {
onChange={(d: Date | null) => {
date = d;
}}
showTimeSelectOnly
Expand All @@ -4568,7 +4568,7 @@ describe("DatePicker", () => {
const { container } = render(
<DatePicker
selected={selected}
onChange={(d) => (date = d)}
onChange={(d: Date | null) => (date = d)}
dateFormat="MM/yyyy"
minDate={newDate("2022-12-31")}
showMonthYearPicker
Expand Down Expand Up @@ -4597,7 +4597,7 @@ describe("DatePicker", () => {
const { container } = render(
<DatePicker
selected={selected}
onChange={(d) => (date = d)}
onChange={(d: Date | null) => (date = d)}
dateFormat="yyyy"
minDate={newDate("2022-12-31")}
showYearPicker
Expand Down
2 changes: 1 addition & 1 deletion src/test/timezone_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ describe("DatePicker with timeZone prop", () => {
const { container } = render(
<DatePicker
selected={utcDate}
onChange={(date) => {
onChange={(date: Date | null) => {
selectedDate = date;
}}
timeZone="America/New_York"
Expand Down
Loading
Loading