react-day-picker 9.6.2 → 9.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/DayPicker.js +15 -3
- package/dist/cjs/DayPicker.js.map +1 -1
- package/dist/cjs/classes/DateLib.js +4 -4
- package/dist/cjs/classes/DateLib.js.map +1 -1
- package/dist/cjs/useAnimation.js +74 -42
- package/dist/cjs/useAnimation.js.map +1 -1
- package/dist/esm/DayPicker.js +15 -3
- package/dist/esm/DayPicker.js.map +1 -1
- package/dist/esm/classes/DateLib.js +1 -1
- package/dist/esm/classes/DateLib.js.map +1 -1
- package/dist/esm/useAnimation.js +74 -42
- package/dist/esm/useAnimation.js.map +1 -1
- package/package.json +2 -4
- package/src/.eslintignore +1 -0
- package/src/.eslintrc.cjs +27 -0
- package/src/DayPicker.test.tsx +199 -0
- package/src/DayPicker.tsx +630 -0
- package/src/UI.ts +365 -0
- package/src/classes/CalendarDay.test.ts +17 -0
- package/src/classes/CalendarDay.ts +61 -0
- package/src/classes/CalendarMonth.test.ts +28 -0
- package/src/classes/CalendarMonth.ts +15 -0
- package/src/classes/CalendarWeek.test.ts +21 -0
- package/src/classes/CalendarWeek.ts +13 -0
- package/src/classes/DateLib.ts +615 -0
- package/src/classes/index.ts +4 -0
- package/src/components/Button.tsx +13 -0
- package/src/components/CaptionLabel.tsx +13 -0
- package/src/components/Chevron.tsx +42 -0
- package/src/components/Day.tsx +28 -0
- package/src/components/DayButton.tsx +29 -0
- package/src/components/Dropdown.tsx +71 -0
- package/src/components/DropdownNav.tsx +13 -0
- package/src/components/Footer.tsx +13 -0
- package/src/components/Month.tsx +24 -0
- package/src/components/MonthCaption.tsx +23 -0
- package/src/components/MonthGrid.tsx +13 -0
- package/src/components/Months.tsx +13 -0
- package/src/components/MonthsDropdown.tsx +16 -0
- package/src/components/Nav.tsx +90 -0
- package/src/components/NextMonthButton.tsx +18 -0
- package/src/components/Option.tsx +13 -0
- package/src/components/PreviousMonthButton.tsx +20 -0
- package/src/components/Root.tsx +19 -0
- package/src/components/Select.tsx +13 -0
- package/src/components/Week.tsx +20 -0
- package/src/components/WeekNumber.tsx +21 -0
- package/src/components/WeekNumberHeader.tsx +15 -0
- package/src/components/Weekday.tsx +13 -0
- package/src/components/Weekdays.tsx +17 -0
- package/src/components/Weeks.tsx +13 -0
- package/src/components/YearsDropdown.tsx +16 -0
- package/src/components/custom-components.tsx +26 -0
- package/src/formatters/formatCaption.test.ts +27 -0
- package/src/formatters/formatCaption.ts +23 -0
- package/src/formatters/formatDay.test.ts +7 -0
- package/src/formatters/formatDay.ts +16 -0
- package/src/formatters/formatMonthDropdown.test.ts +19 -0
- package/src/formatters/formatMonthDropdown.ts +15 -0
- package/src/formatters/formatWeekNumber.test.ts +5 -0
- package/src/formatters/formatWeekNumber.ts +13 -0
- package/src/formatters/formatWeekNumberHeader.ts +10 -0
- package/src/formatters/formatWeekdayName.test.ts +15 -0
- package/src/formatters/formatWeekdayName.ts +16 -0
- package/src/formatters/formatYearDropdown.test.ts +7 -0
- package/src/formatters/formatYearDropdown.ts +21 -0
- package/src/formatters/index.ts +7 -0
- package/src/helpers/broadcastCalendar.test.ts +43 -0
- package/src/helpers/calculateFocusTarget.ts +51 -0
- package/src/helpers/endOfBroadcastWeek.test.ts +25 -0
- package/src/helpers/endOfBroadcastWeek.ts +16 -0
- package/src/helpers/getBroadcastWeeksInMonth.test.ts +23 -0
- package/src/helpers/getBroadcastWeeksInMonth.ts +31 -0
- package/src/helpers/getClassNamesForModifiers.ts +26 -0
- package/src/helpers/getComponents.ts +11 -0
- package/src/helpers/getDataAttributes.test.tsx +48 -0
- package/src/helpers/getDataAttributes.tsx +21 -0
- package/src/helpers/getDates.test.ts +190 -0
- package/src/helpers/getDates.ts +64 -0
- package/src/helpers/getDays.test.ts +30 -0
- package/src/helpers/getDays.ts +16 -0
- package/src/helpers/getDefaultClassNames.test.ts +47 -0
- package/src/helpers/getDefaultClassNames.ts +33 -0
- package/src/helpers/getDisplayMonths.test.ts +44 -0
- package/src/helpers/getDisplayMonths.ts +20 -0
- package/src/helpers/getFocusableDate.ts +59 -0
- package/src/helpers/getFormatters.test.ts +48 -0
- package/src/helpers/getFormatters.ts +19 -0
- package/src/helpers/getInitialMonth.test.ts +79 -0
- package/src/helpers/getInitialMonth.ts +41 -0
- package/src/helpers/getLabels.ts +10 -0
- package/src/helpers/getMonthOptions.test.ts +226 -0
- package/src/helpers/getMonthOptions.ts +37 -0
- package/src/helpers/getMonths.test.ts +88 -0
- package/src/helpers/getMonths.ts +90 -0
- package/src/helpers/getNavMonth.test.ts +253 -0
- package/src/helpers/getNavMonth.ts +70 -0
- package/src/helpers/getNextFocus.test.tsx +99 -0
- package/src/helpers/getNextFocus.tsx +67 -0
- package/src/helpers/getNextMonth.test.ts +101 -0
- package/src/helpers/getNextMonth.ts +45 -0
- package/src/helpers/getPossibleFocusDate.test.ts +144 -0
- package/src/helpers/getPreviousMonth.test.ts +77 -0
- package/src/helpers/getPreviousMonth.ts +40 -0
- package/src/helpers/getStyleForModifiers.test.ts +92 -0
- package/src/helpers/getStyleForModifiers.ts +21 -0
- package/src/helpers/getWeekdays.test.ts +44 -0
- package/src/helpers/getWeekdays.ts +29 -0
- package/src/helpers/getWeeks.test.ts +30 -0
- package/src/helpers/getWeeks.ts +9 -0
- package/src/helpers/getYearOptions.test.ts +46 -0
- package/src/helpers/getYearOptions.ts +34 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/startOfBroadcastWeek.test.ts +24 -0
- package/src/helpers/startOfBroadcastWeek.ts +19 -0
- package/src/helpers/useControlledValue.test.ts +45 -0
- package/src/helpers/useControlledValue.ts +33 -0
- package/src/index.ts +15 -0
- package/src/jalali.tsx +2 -0
- package/src/labels/index.ts +12 -0
- package/src/labels/labelDayButton.test.ts +41 -0
- package/src/labels/labelDayButton.ts +31 -0
- package/src/labels/labelGrid.test.ts +7 -0
- package/src/labels/labelGrid.ts +23 -0
- package/src/labels/labelGridcell.test.ts +7 -0
- package/src/labels/labelGridcell.ts +22 -0
- package/src/labels/labelMonthDropdown.test.ts +5 -0
- package/src/labels/labelMonthDropdown.ts +12 -0
- package/src/labels/labelNav.test.ts +5 -0
- package/src/labels/labelNav.ts +10 -0
- package/src/labels/labelNext.test.ts +5 -0
- package/src/labels/labelNext.ts +13 -0
- package/src/labels/labelPrevious.test.ts +5 -0
- package/src/labels/labelPrevious.ts +13 -0
- package/src/labels/labelWeekNumber.test.ts +5 -0
- package/src/labels/labelWeekNumber.ts +15 -0
- package/src/labels/labelWeekNumberHeader.test.ts +5 -0
- package/src/labels/labelWeekNumberHeader.ts +12 -0
- package/src/labels/labelWeekday.test.ts +15 -0
- package/src/labels/labelWeekday.ts +16 -0
- package/src/labels/labelYearDropdown.test.ts +5 -0
- package/src/labels/labelYearDropdown.ts +12 -0
- package/src/locale.ts +1 -0
- package/src/persian.tsx +86 -0
- package/src/selection/useMulti.test.tsx +41 -0
- package/src/selection/useMulti.tsx +74 -0
- package/src/selection/useRange.test.tsx +154 -0
- package/src/selection/useRange.tsx +73 -0
- package/src/selection/useSingle.test.tsx +38 -0
- package/src/selection/useSingle.tsx +69 -0
- package/src/types/deprecated.ts +230 -0
- package/src/types/index.ts +4 -0
- package/src/types/props.test.tsx +71 -0
- package/src/types/props.ts +675 -0
- package/src/types/selection.ts +57 -0
- package/src/types/shared.ts +442 -0
- package/src/useAnimation.test.tsx +190 -0
- package/src/useAnimation.ts +236 -0
- package/src/useCalendar.ts +178 -0
- package/src/useDayPicker.test.tsx +142 -0
- package/src/useDayPicker.ts +93 -0
- package/src/useFocus.ts +87 -0
- package/src/useGetModifiers.test.tsx +154 -0
- package/src/useGetModifiers.tsx +122 -0
- package/src/useSelection.ts +26 -0
- package/src/utc.tsx +10 -0
- package/src/utils/addToRange.test.ts +117 -0
- package/src/utils/addToRange.ts +87 -0
- package/src/utils/dateMatchModifiers.test.ts +120 -0
- package/src/utils/dateMatchModifiers.ts +88 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/rangeContainsDayOfWeek.test.ts +48 -0
- package/src/utils/rangeContainsDayOfWeek.ts +35 -0
- package/src/utils/rangeContainsModifiers.test.ts +230 -0
- package/src/utils/rangeContainsModifiers.ts +125 -0
- package/src/utils/rangeIncludesDate.test.ts +46 -0
- package/src/utils/rangeIncludesDate.ts +43 -0
- package/src/utils/rangeOverlaps.test.ts +60 -0
- package/src/utils/rangeOverlaps.ts +22 -0
- package/src/utils/typeguards.test.ts +83 -0
- package/src/utils/typeguards.ts +70 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type DateLib } from "../classes/DateLib.js";
|
|
2
|
+
import { DropdownOption } from "../components/Dropdown.js";
|
|
3
|
+
import type { Formatters } from "../types/index.js";
|
|
4
|
+
|
|
5
|
+
/** Return the months to show in the dropdown. */
|
|
6
|
+
export function getMonthOptions(
|
|
7
|
+
displayMonth: Date,
|
|
8
|
+
navStart: Date | undefined,
|
|
9
|
+
navEnd: Date | undefined,
|
|
10
|
+
formatters: Pick<Formatters, "formatMonthDropdown">,
|
|
11
|
+
dateLib: DateLib
|
|
12
|
+
): DropdownOption[] | undefined {
|
|
13
|
+
const {
|
|
14
|
+
startOfMonth,
|
|
15
|
+
startOfYear,
|
|
16
|
+
endOfYear,
|
|
17
|
+
eachMonthOfInterval,
|
|
18
|
+
getMonth
|
|
19
|
+
} = dateLib;
|
|
20
|
+
|
|
21
|
+
const months = eachMonthOfInterval({
|
|
22
|
+
start: startOfYear(displayMonth),
|
|
23
|
+
end: endOfYear(displayMonth)
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const options = months.map((month) => {
|
|
27
|
+
const label = formatters.formatMonthDropdown(month, dateLib);
|
|
28
|
+
const value = getMonth(month);
|
|
29
|
+
const disabled =
|
|
30
|
+
(navStart && month < startOfMonth(navStart)) ||
|
|
31
|
+
(navEnd && month > startOfMonth(navEnd)) ||
|
|
32
|
+
false;
|
|
33
|
+
return { value, label, disabled };
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return options;
|
|
37
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CalendarMonth } from "../classes";
|
|
2
|
+
import { DateLib } from "../classes/DateLib";
|
|
3
|
+
import type { DayPickerProps } from "../types";
|
|
4
|
+
|
|
5
|
+
import { getMonths } from "./getMonths";
|
|
6
|
+
|
|
7
|
+
const mockDates = [
|
|
8
|
+
new Date(2023, 4, 27), // May 1, 2023
|
|
9
|
+
new Date(2023, 5, 1), // June 1, 2023
|
|
10
|
+
new Date(2023, 5, 8), // June 2, 2023
|
|
11
|
+
new Date(2023, 5, 15), // June 15, 2023
|
|
12
|
+
new Date(2023, 5, 22), // June 22, 2023
|
|
13
|
+
new Date(2023, 5, 30), // June 30, 2023
|
|
14
|
+
new Date(2023, 6, 6) // June 30, 2023
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const mockProps: Pick<
|
|
18
|
+
DayPickerProps,
|
|
19
|
+
"fixedWeeks" | "ISOWeek" | "reverseMonths"
|
|
20
|
+
> = {
|
|
21
|
+
fixedWeeks: false,
|
|
22
|
+
ISOWeek: false,
|
|
23
|
+
reverseMonths: false
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const dateLib = new DateLib({
|
|
27
|
+
weekStartsOn: 0, // Sunday
|
|
28
|
+
firstWeekContainsDate: 1
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should return the correct months without ISO weeks and reverse months", () => {
|
|
32
|
+
const displayMonths = [new Date(2023, 5, 1)]; // June 2023
|
|
33
|
+
|
|
34
|
+
const result = getMonths(displayMonths, mockDates, mockProps, dateLib);
|
|
35
|
+
|
|
36
|
+
expect(result).toHaveLength(1);
|
|
37
|
+
expect(result[0]).toBeInstanceOf(CalendarMonth);
|
|
38
|
+
expect(result[0].weeks).toHaveLength(5); // June 2023 has 5 weeks
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle ISO weeks", () => {
|
|
42
|
+
const displayMonths = [new Date(2023, 5, 1)]; // June 2023
|
|
43
|
+
|
|
44
|
+
const isoProps = { ...mockProps, ISOWeek: true };
|
|
45
|
+
|
|
46
|
+
const result = getMonths(displayMonths, mockDates, isoProps, dateLib);
|
|
47
|
+
|
|
48
|
+
expect(result).toHaveLength(1);
|
|
49
|
+
expect(result[0]).toBeInstanceOf(CalendarMonth);
|
|
50
|
+
expect(result[0].weeks).toHaveLength(5); // June 2023 has 5 ISO weeks
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should handle reverse months", () => {
|
|
54
|
+
const displayMonths = [
|
|
55
|
+
new Date(2023, 4, 1), // May 2023
|
|
56
|
+
new Date(2023, 5, 1) // June 2023
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const reverseProps = { ...mockProps, reverseMonths: true };
|
|
60
|
+
|
|
61
|
+
const result = getMonths(displayMonths, mockDates, reverseProps, dateLib);
|
|
62
|
+
|
|
63
|
+
expect(result).toHaveLength(2);
|
|
64
|
+
expect(result[0].date).toEqual(new Date(2023, 5, 1)); // June 2023
|
|
65
|
+
expect(result[1].date).toEqual(new Date(2023, 4, 1)); // May 2023
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should handle fixed weeks", () => {
|
|
69
|
+
const displayMonths = [new Date(2023, 5, 1)]; // June 2023
|
|
70
|
+
|
|
71
|
+
const fixedWeeksProps = { ...mockProps, fixedWeeks: true };
|
|
72
|
+
|
|
73
|
+
const result = getMonths(displayMonths, mockDates, fixedWeeksProps, dateLib);
|
|
74
|
+
|
|
75
|
+
expect(result).toHaveLength(1);
|
|
76
|
+
expect(result[0]).toBeInstanceOf(CalendarMonth);
|
|
77
|
+
expect(result[0].weeks).toHaveLength(6); // Fixed weeks should ensure 6 weeks in the month view
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should handle months with no dates", () => {
|
|
81
|
+
const displayMonths = [new Date(2023, 5, 1)]; // June 2023
|
|
82
|
+
|
|
83
|
+
const result = getMonths(displayMonths, [], mockProps, dateLib);
|
|
84
|
+
|
|
85
|
+
expect(result).toHaveLength(1);
|
|
86
|
+
expect(result[0]).toBeInstanceOf(CalendarMonth);
|
|
87
|
+
expect(result[0].weeks).toHaveLength(0); // No dates should result in no weeks
|
|
88
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { DateLib } from "../classes/DateLib.js";
|
|
2
|
+
import { CalendarWeek, CalendarDay, CalendarMonth } from "../classes/index.js";
|
|
3
|
+
import type { DayPickerProps } from "../types/index.js";
|
|
4
|
+
|
|
5
|
+
/** Return the months to display in the calendar. */
|
|
6
|
+
export function getMonths(
|
|
7
|
+
/** The months (as dates) to display in the calendar. */
|
|
8
|
+
displayMonths: Date[],
|
|
9
|
+
/** The dates to display in the calendar. */
|
|
10
|
+
dates: Date[],
|
|
11
|
+
/** Options from the props context. */
|
|
12
|
+
props: Pick<
|
|
13
|
+
DayPickerProps,
|
|
14
|
+
"broadcastCalendar" | "fixedWeeks" | "ISOWeek" | "reverseMonths"
|
|
15
|
+
>,
|
|
16
|
+
dateLib: DateLib
|
|
17
|
+
): CalendarMonth[] {
|
|
18
|
+
const {
|
|
19
|
+
addDays,
|
|
20
|
+
endOfBroadcastWeek,
|
|
21
|
+
endOfISOWeek,
|
|
22
|
+
endOfMonth,
|
|
23
|
+
endOfWeek,
|
|
24
|
+
getISOWeek,
|
|
25
|
+
getWeek,
|
|
26
|
+
startOfBroadcastWeek,
|
|
27
|
+
startOfISOWeek,
|
|
28
|
+
startOfWeek
|
|
29
|
+
} = dateLib;
|
|
30
|
+
const dayPickerMonths = displayMonths.reduce<CalendarMonth[]>(
|
|
31
|
+
(months, month) => {
|
|
32
|
+
const firstDateOfFirstWeek = props.broadcastCalendar
|
|
33
|
+
? startOfBroadcastWeek(month, dateLib)
|
|
34
|
+
: props.ISOWeek
|
|
35
|
+
? startOfISOWeek(month)
|
|
36
|
+
: startOfWeek(month);
|
|
37
|
+
|
|
38
|
+
const lastDateOfLastWeek = props.broadcastCalendar
|
|
39
|
+
? endOfBroadcastWeek(month, dateLib)
|
|
40
|
+
: props.ISOWeek
|
|
41
|
+
? endOfISOWeek(endOfMonth(month))
|
|
42
|
+
: endOfWeek(endOfMonth(month));
|
|
43
|
+
|
|
44
|
+
/** The dates to display in the month. */
|
|
45
|
+
const monthDates = dates.filter((date) => {
|
|
46
|
+
return date >= firstDateOfFirstWeek && date <= lastDateOfLastWeek;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const nrOfDaysWithFixedWeeks = props.broadcastCalendar ? 35 : 42;
|
|
50
|
+
|
|
51
|
+
if (props.fixedWeeks && monthDates.length < nrOfDaysWithFixedWeeks) {
|
|
52
|
+
const extraDates = dates.filter((date) => {
|
|
53
|
+
const daysToAdd = nrOfDaysWithFixedWeeks - monthDates.length;
|
|
54
|
+
return (
|
|
55
|
+
date > lastDateOfLastWeek &&
|
|
56
|
+
date <= addDays(lastDateOfLastWeek, daysToAdd)
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
monthDates.push(...extraDates);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const weeks: CalendarWeek[] = monthDates.reduce<CalendarWeek[]>(
|
|
63
|
+
(weeks, date) => {
|
|
64
|
+
const weekNumber = props.ISOWeek ? getISOWeek(date) : getWeek(date);
|
|
65
|
+
const week = weeks.find((week) => week.weekNumber === weekNumber);
|
|
66
|
+
|
|
67
|
+
const day = new CalendarDay(date, month, dateLib);
|
|
68
|
+
if (!week) {
|
|
69
|
+
weeks.push(new CalendarWeek(weekNumber, [day]));
|
|
70
|
+
} else {
|
|
71
|
+
week.days.push(day);
|
|
72
|
+
}
|
|
73
|
+
return weeks;
|
|
74
|
+
},
|
|
75
|
+
[]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const dayPickerMonth = new CalendarMonth(month, weeks);
|
|
79
|
+
months.push(dayPickerMonth);
|
|
80
|
+
return months;
|
|
81
|
+
},
|
|
82
|
+
[]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!props.reverseMonths) {
|
|
86
|
+
return dayPickerMonths;
|
|
87
|
+
} else {
|
|
88
|
+
return dayPickerMonths.reverse();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addYears,
|
|
3
|
+
endOfMonth,
|
|
4
|
+
endOfYear,
|
|
5
|
+
startOfDay,
|
|
6
|
+
startOfMonth,
|
|
7
|
+
startOfYear
|
|
8
|
+
} from "date-fns";
|
|
9
|
+
import { DayPickerProps } from "react-day-picker/types";
|
|
10
|
+
|
|
11
|
+
import { defaultDateLib } from "../classes/DateLib";
|
|
12
|
+
|
|
13
|
+
import { getNavMonths } from "./getNavMonth";
|
|
14
|
+
|
|
15
|
+
describe('when "startMonth" is not set', () => {
|
|
16
|
+
test('"startMonth" should be undefined', () => {
|
|
17
|
+
const [navStartMonth] = getNavMonths({}, defaultDateLib);
|
|
18
|
+
expect(navStartMonth).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('when "startMonth" is set', () => {
|
|
22
|
+
const startMonth = new Date(2021, 4, 3);
|
|
23
|
+
const props: DayPickerProps = { startMonth };
|
|
24
|
+
test('"startMonth" should be the start of that month', () => {
|
|
25
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
26
|
+
const startOfThatMonth = startOfMonth(startMonth);
|
|
27
|
+
expect(navStartMonth).toEqual(startOfThatMonth);
|
|
28
|
+
});
|
|
29
|
+
describe('when "fromYear" is set', () => {
|
|
30
|
+
const fromYear = 2021;
|
|
31
|
+
test('"startMonth" should be the start of that month', () => {
|
|
32
|
+
const [navStartMonth] = getNavMonths(
|
|
33
|
+
{ ...props, fromYear },
|
|
34
|
+
defaultDateLib
|
|
35
|
+
);
|
|
36
|
+
const startOfThatMonth = startOfMonth(startMonth);
|
|
37
|
+
expect(navStartMonth).toEqual(startOfThatMonth);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('when "fromYear" is set', () => {
|
|
42
|
+
const fromYear = 2021;
|
|
43
|
+
const props: DayPickerProps = { fromYear };
|
|
44
|
+
test('"startMonth" should be the start of that year', () => {
|
|
45
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
46
|
+
const startOfThatYear = new Date(fromYear, 0, 1);
|
|
47
|
+
expect(navStartMonth).toEqual(startOfThatYear);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('when "endMonth" is set', () => {
|
|
51
|
+
const endMonth = new Date(2021, 4, 3);
|
|
52
|
+
const props: DayPickerProps = { endMonth };
|
|
53
|
+
test('"endMonth" should be the end of that month', () => {
|
|
54
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
55
|
+
const endOfThatMonth = startOfDay(endOfMonth(endMonth));
|
|
56
|
+
expect(navEndMonth).toEqual(endOfThatMonth);
|
|
57
|
+
});
|
|
58
|
+
describe('when "fromYear" is set', () => {
|
|
59
|
+
const fromYear = 2021;
|
|
60
|
+
test('"endMonth" should be the end of that month', () => {
|
|
61
|
+
const [, navEndMonth] = getNavMonths(
|
|
62
|
+
{ ...props, fromYear },
|
|
63
|
+
defaultDateLib
|
|
64
|
+
);
|
|
65
|
+
const endOfThatMonth = startOfDay(endOfMonth(endMonth));
|
|
66
|
+
expect(navEndMonth).toEqual(endOfThatMonth);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('when "toYear" is set', () => {
|
|
72
|
+
const toYear = 2021;
|
|
73
|
+
const props: DayPickerProps = { toYear };
|
|
74
|
+
test('"endMonth" should be the end of that year', () => {
|
|
75
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
76
|
+
expect(navEndMonth).toEqual(new Date(toYear, 11, 31));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe.each([["dropdown" as const], ["dropdown-years" as const]])(
|
|
81
|
+
'when "captionLayout" is "%s"',
|
|
82
|
+
(captionLayout) => {
|
|
83
|
+
const today = new Date(2024, 4, 3);
|
|
84
|
+
const props: DayPickerProps = { captionLayout, today };
|
|
85
|
+
|
|
86
|
+
test('"startMonth" should be 100 years ago', () => {
|
|
87
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
88
|
+
const startOf100YearsAgo = startOfYear(addYears(today, -100));
|
|
89
|
+
expect(navStartMonth).toEqual(startOf100YearsAgo);
|
|
90
|
+
});
|
|
91
|
+
test('"endMonth" should be the end of this year', () => {
|
|
92
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
93
|
+
const endOfThisYear = startOfDay(endOfYear(today));
|
|
94
|
+
expect(navEndMonth).toEqual(endOfThisYear);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('when "startMonth" is set', () => {
|
|
98
|
+
const today = new Date(2024, 4, 3);
|
|
99
|
+
const startMonth = new Date(2021, 4, 3);
|
|
100
|
+
const props: DayPickerProps = { captionLayout, startMonth, today };
|
|
101
|
+
test('"startMonth" should be the start of that month', () => {
|
|
102
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
103
|
+
const startOfThatMonth = startOfMonth(startMonth);
|
|
104
|
+
expect(navStartMonth).toEqual(startOfThatMonth);
|
|
105
|
+
});
|
|
106
|
+
test('"endMonth" should be the end of this year', () => {
|
|
107
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
108
|
+
const endOfThisYear = startOfDay(endOfYear(today));
|
|
109
|
+
expect(navEndMonth).toEqual(endOfThisYear);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('when "endMonth" is set', () => {
|
|
114
|
+
const today = new Date(2021, 4, 3);
|
|
115
|
+
const endMonth = new Date(2022, 4, 3);
|
|
116
|
+
const props: DayPickerProps = { captionLayout, endMonth, today };
|
|
117
|
+
|
|
118
|
+
test('"startMonth" should be 100 years ago', () => {
|
|
119
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
120
|
+
const startOf100YearsAgo = startOfYear(addYears(today, -100));
|
|
121
|
+
expect(navStartMonth).toEqual(startOf100YearsAgo);
|
|
122
|
+
});
|
|
123
|
+
test('"endMonth" should be the end of that month', () => {
|
|
124
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
125
|
+
expect(navEndMonth).toEqual(startOfDay(endOfMonth(endMonth)));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('when "fromYear" is set', () => {
|
|
130
|
+
const today = new Date(2024, 4, 3);
|
|
131
|
+
const fromYear = 2022;
|
|
132
|
+
const props: DayPickerProps = { captionLayout, fromYear, today };
|
|
133
|
+
|
|
134
|
+
test('"startMonth" should be equal to the "fromYear"', () => {
|
|
135
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
136
|
+
const startOfThatYear = new Date(fromYear, 0, 1);
|
|
137
|
+
expect(navStartMonth).toEqual(startOfThatYear);
|
|
138
|
+
});
|
|
139
|
+
test('"endMonth" should be the end of this year', () => {
|
|
140
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
141
|
+
const endOfThisYear = startOfDay(endOfYear(today));
|
|
142
|
+
expect(navEndMonth).toEqual(endOfThisYear);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('when "toYear" is set', () => {
|
|
147
|
+
const today = new Date(2021, 4, 3);
|
|
148
|
+
const toYear = 2022;
|
|
149
|
+
const props: DayPickerProps = { captionLayout, toYear, today };
|
|
150
|
+
|
|
151
|
+
test('"startMonth" should be 100 years ago', () => {
|
|
152
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
153
|
+
expect(navStartMonth).toEqual(startOfYear(addYears(today, -100)));
|
|
154
|
+
});
|
|
155
|
+
test('"endMonth" should be equal the last day of the year', () => {
|
|
156
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
157
|
+
expect(navEndMonth).toEqual(new Date(toYear, 11, 31));
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
describe('when "captionLayout" is "dropdown-months"', () => {
|
|
164
|
+
const today = new Date(2024, 4, 3);
|
|
165
|
+
const props: DayPickerProps = {
|
|
166
|
+
captionLayout: "dropdown-months",
|
|
167
|
+
today
|
|
168
|
+
};
|
|
169
|
+
test('"startMonth" should be undefined', () => {
|
|
170
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
171
|
+
expect(navStartMonth).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
test('"endMonth" should be undefined', () => {
|
|
174
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
175
|
+
expect(navEndMonth).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('when "startMonth" is set', () => {
|
|
179
|
+
const today = new Date(2024, 4, 3);
|
|
180
|
+
const startMonth = new Date(2021, 4, 3);
|
|
181
|
+
const props: DayPickerProps = {
|
|
182
|
+
captionLayout: "dropdown-months",
|
|
183
|
+
startMonth,
|
|
184
|
+
today
|
|
185
|
+
};
|
|
186
|
+
test('"startMonth" should be the start of that month', () => {
|
|
187
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
188
|
+
const startOfThatMonth = startOfMonth(startMonth);
|
|
189
|
+
expect(navStartMonth).toEqual(startOfThatMonth);
|
|
190
|
+
});
|
|
191
|
+
test('"endMonth" should be undefined', () => {
|
|
192
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
193
|
+
expect(navEndMonth).toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('when "endMonth" is set', () => {
|
|
198
|
+
const today = new Date(2021, 4, 3);
|
|
199
|
+
const endMonth = new Date(2022, 4, 3);
|
|
200
|
+
const props: DayPickerProps = {
|
|
201
|
+
captionLayout: "dropdown-months",
|
|
202
|
+
endMonth,
|
|
203
|
+
today
|
|
204
|
+
};
|
|
205
|
+
test('"startMonth" should be undefined', () => {
|
|
206
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
207
|
+
expect(navStartMonth).toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
test('"endMonth" should be the end of that month', () => {
|
|
210
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
211
|
+
const endOfThatMonth = startOfDay(endOfMonth(endMonth));
|
|
212
|
+
expect(navEndMonth).toEqual(endOfThatMonth);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('when "fromYear" is set', () => {
|
|
217
|
+
const today = new Date(2024, 4, 3);
|
|
218
|
+
const fromYear = 2022;
|
|
219
|
+
const props: DayPickerProps = {
|
|
220
|
+
captionLayout: "dropdown-months",
|
|
221
|
+
fromYear,
|
|
222
|
+
today
|
|
223
|
+
};
|
|
224
|
+
test('"startMonth" should be equal to the start of that year', () => {
|
|
225
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
226
|
+
const startOfThatYear = new Date(fromYear, 0, 1);
|
|
227
|
+
expect(navStartMonth).toEqual(startOfThatYear);
|
|
228
|
+
});
|
|
229
|
+
test('"endMonth" should be undefined', () => {
|
|
230
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
231
|
+
expect(navEndMonth).toBeUndefined();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('when "toYear" is set', () => {
|
|
236
|
+
const today = new Date(2021, 4, 3);
|
|
237
|
+
const toYear = 2022;
|
|
238
|
+
const props: DayPickerProps = {
|
|
239
|
+
captionLayout: "dropdown-months",
|
|
240
|
+
toYear,
|
|
241
|
+
today
|
|
242
|
+
};
|
|
243
|
+
test('"startMonth" should be undefined', () => {
|
|
244
|
+
const [navStartMonth] = getNavMonths(props, defaultDateLib);
|
|
245
|
+
expect(navStartMonth).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
test('"endMonth" should be equal to the end of that year', () => {
|
|
248
|
+
const [, navEndMonth] = getNavMonths(props, defaultDateLib);
|
|
249
|
+
const endOfThatYear = new Date(2022, 11, 31);
|
|
250
|
+
expect(navEndMonth).toEqual(endOfThatYear);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type DateLib } from "../classes/DateLib.js";
|
|
2
|
+
import type { DayPickerProps } from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
/** Return the start and end months for the calendar navigation. */
|
|
5
|
+
export function getNavMonths(
|
|
6
|
+
props: Pick<
|
|
7
|
+
DayPickerProps,
|
|
8
|
+
| "captionLayout"
|
|
9
|
+
| "endMonth"
|
|
10
|
+
| "startMonth"
|
|
11
|
+
| "today"
|
|
12
|
+
| "timeZone"
|
|
13
|
+
// Deprecated:
|
|
14
|
+
| "fromMonth"
|
|
15
|
+
| "fromYear"
|
|
16
|
+
| "toMonth"
|
|
17
|
+
| "toYear"
|
|
18
|
+
>,
|
|
19
|
+
dateLib: DateLib
|
|
20
|
+
): [start: Date | undefined, end: Date | undefined] {
|
|
21
|
+
let { startMonth, endMonth } = props;
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
startOfYear,
|
|
25
|
+
startOfDay,
|
|
26
|
+
startOfMonth,
|
|
27
|
+
endOfMonth,
|
|
28
|
+
addYears,
|
|
29
|
+
endOfYear,
|
|
30
|
+
newDate,
|
|
31
|
+
today
|
|
32
|
+
} = dateLib;
|
|
33
|
+
|
|
34
|
+
// Handle deprecated code
|
|
35
|
+
const { fromYear, toYear, fromMonth, toMonth } = props;
|
|
36
|
+
if (!startMonth && fromMonth) {
|
|
37
|
+
startMonth = fromMonth;
|
|
38
|
+
}
|
|
39
|
+
if (!startMonth && fromYear) {
|
|
40
|
+
startMonth = dateLib.newDate(fromYear, 0, 1);
|
|
41
|
+
}
|
|
42
|
+
if (!endMonth && toMonth) {
|
|
43
|
+
endMonth = toMonth;
|
|
44
|
+
}
|
|
45
|
+
if (!endMonth && toYear) {
|
|
46
|
+
endMonth = newDate(toYear, 11, 31);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const hasYearDropdown =
|
|
50
|
+
props.captionLayout === "dropdown" ||
|
|
51
|
+
props.captionLayout === "dropdown-years";
|
|
52
|
+
if (startMonth) {
|
|
53
|
+
startMonth = startOfMonth(startMonth);
|
|
54
|
+
} else if (fromYear) {
|
|
55
|
+
startMonth = newDate(fromYear, 0, 1);
|
|
56
|
+
} else if (!startMonth && hasYearDropdown) {
|
|
57
|
+
startMonth = startOfYear(addYears(props.today ?? today(), -100));
|
|
58
|
+
}
|
|
59
|
+
if (endMonth) {
|
|
60
|
+
endMonth = endOfMonth(endMonth);
|
|
61
|
+
} else if (toYear) {
|
|
62
|
+
endMonth = newDate(toYear, 11, 31);
|
|
63
|
+
} else if (!endMonth && hasYearDropdown) {
|
|
64
|
+
endMonth = endOfYear(props.today ?? today());
|
|
65
|
+
}
|
|
66
|
+
return [
|
|
67
|
+
startMonth ? startOfDay(startMonth) : startMonth,
|
|
68
|
+
endMonth ? startOfDay(endMonth) : endMonth
|
|
69
|
+
];
|
|
70
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CalendarDay } from "../classes";
|
|
2
|
+
import { defaultDateLib } from "../classes/DateLib";
|
|
3
|
+
import type { DayPickerProps, MoveFocusBy, MoveFocusDir } from "../types";
|
|
4
|
+
|
|
5
|
+
import { getNextFocus } from "./getNextFocus";
|
|
6
|
+
|
|
7
|
+
const props: Pick<
|
|
8
|
+
DayPickerProps,
|
|
9
|
+
"disabled" | "hidden" | "startMonth" | "endMonth"
|
|
10
|
+
> = {
|
|
11
|
+
disabled: [],
|
|
12
|
+
hidden: []
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
it("should return `undefined` if `attempt` exceeds 365", () => {
|
|
16
|
+
const focusedDay = new CalendarDay(
|
|
17
|
+
new Date(2020, 0, 1),
|
|
18
|
+
new Date(2020, 0, 1),
|
|
19
|
+
defaultDateLib
|
|
20
|
+
);
|
|
21
|
+
const moveBy: MoveFocusBy = "day";
|
|
22
|
+
const moveDir: MoveFocusDir = "after";
|
|
23
|
+
const result = getNextFocus(
|
|
24
|
+
moveBy,
|
|
25
|
+
moveDir,
|
|
26
|
+
focusedDay,
|
|
27
|
+
undefined,
|
|
28
|
+
undefined,
|
|
29
|
+
props,
|
|
30
|
+
defaultDateLib,
|
|
31
|
+
366
|
|
32
|
+
);
|
|
33
|
+
expect(result).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should return the focus date if it is not disabled or hidden", () => {
|
|
37
|
+
const focusedDay = new CalendarDay(
|
|
38
|
+
new Date(2020, 0, 1),
|
|
39
|
+
new Date(2020, 0, 1),
|
|
40
|
+
defaultDateLib
|
|
41
|
+
);
|
|
42
|
+
const expectedDate = new Date(2020, 0, 2);
|
|
43
|
+
const result = getNextFocus(
|
|
44
|
+
"day",
|
|
45
|
+
"after",
|
|
46
|
+
focusedDay,
|
|
47
|
+
undefined,
|
|
48
|
+
undefined,
|
|
49
|
+
props,
|
|
50
|
+
defaultDateLib
|
|
51
|
+
);
|
|
52
|
+
expect(result?.date).toEqual(expectedDate);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should return the next focus date if it is disabled", () => {
|
|
56
|
+
const focusedDay = new CalendarDay(
|
|
57
|
+
new Date(2020, 0, 1),
|
|
58
|
+
new Date(2020, 0, 1),
|
|
59
|
+
defaultDateLib
|
|
60
|
+
);
|
|
61
|
+
const disabledDate = new Date(2020, 0, 2);
|
|
62
|
+
const expectedDate = new Date(2020, 0, 3);
|
|
63
|
+
const result = getNextFocus(
|
|
64
|
+
"day",
|
|
65
|
+
"after",
|
|
66
|
+
focusedDay,
|
|
67
|
+
undefined,
|
|
68
|
+
undefined,
|
|
69
|
+
{
|
|
70
|
+
...props,
|
|
71
|
+
disabled: [disabledDate]
|
|
72
|
+
},
|
|
73
|
+
defaultDateLib
|
|
74
|
+
);
|
|
75
|
+
expect(result?.date).toEqual(expectedDate);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should return the next focus date if it is hidden", () => {
|
|
79
|
+
const focusedDay = new CalendarDay(
|
|
80
|
+
new Date(2020, 0, 1),
|
|
81
|
+
new Date(2020, 0, 1),
|
|
82
|
+
defaultDateLib
|
|
83
|
+
);
|
|
84
|
+
const hiddenDate = new Date(2020, 0, 2);
|
|
85
|
+
const expectedDate = new Date(2020, 0, 3);
|
|
86
|
+
const result = getNextFocus(
|
|
87
|
+
"day",
|
|
88
|
+
"after",
|
|
89
|
+
focusedDay,
|
|
90
|
+
undefined,
|
|
91
|
+
undefined,
|
|
92
|
+
{
|
|
93
|
+
...props,
|
|
94
|
+
hidden: [hiddenDate]
|
|
95
|
+
},
|
|
96
|
+
defaultDateLib
|
|
97
|
+
);
|
|
98
|
+
expect(result?.date).toEqual(expectedDate);
|
|
99
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { DateLib } from "../classes/DateLib.js";
|
|
2
|
+
import { CalendarDay } from "../classes/index.js";
|
|
3
|
+
import type {
|
|
4
|
+
DayPickerProps,
|
|
5
|
+
MoveFocusBy,
|
|
6
|
+
MoveFocusDir
|
|
7
|
+
} from "../types/index.js";
|
|
8
|
+
import { dateMatchModifiers } from "../utils/dateMatchModifiers.js";
|
|
9
|
+
|
|
10
|
+
import { getFocusableDate } from "./getFocusableDate.js";
|
|
11
|
+
|
|
12
|
+
export function getNextFocus(
|
|
13
|
+
moveBy: MoveFocusBy,
|
|
14
|
+
moveDir: MoveFocusDir,
|
|
15
|
+
/** The date that is currently focused. */
|
|
16
|
+
refDay: CalendarDay,
|
|
17
|
+
calendarStartMonth: Date | undefined,
|
|
18
|
+
calendarEndMonth: Date | undefined,
|
|
19
|
+
props: Pick<
|
|
20
|
+
DayPickerProps,
|
|
21
|
+
"disabled" | "hidden" | "modifiers" | "ISOWeek" | "timeZone"
|
|
22
|
+
>,
|
|
23
|
+
dateLib: DateLib,
|
|
24
|
+
attempt: number = 0
|
|
25
|
+
): CalendarDay | undefined {
|
|
26
|
+
if (attempt > 365) {
|
|
27
|
+
// Limit the recursion to 365 attempts
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const focusableDate = getFocusableDate(
|
|
32
|
+
moveBy,
|
|
33
|
+
moveDir,
|
|
34
|
+
refDay.date, // should be refDay? or refDay.date?
|
|
35
|
+
calendarStartMonth,
|
|
36
|
+
calendarEndMonth,
|
|
37
|
+
props,
|
|
38
|
+
dateLib
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const isDisabled = Boolean(
|
|
42
|
+
props.disabled && dateMatchModifiers(focusableDate, props.disabled, dateLib)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const isHidden = Boolean(
|
|
46
|
+
props.hidden && dateMatchModifiers(focusableDate, props.hidden, dateLib)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const targetMonth = focusableDate;
|
|
50
|
+
const focusDay = new CalendarDay(focusableDate, targetMonth, dateLib);
|
|
51
|
+
|
|
52
|
+
if (!isDisabled && !isHidden) {
|
|
53
|
+
return focusDay;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Recursively attempt to find the next focusable date
|
|
57
|
+
return getNextFocus(
|
|
58
|
+
moveBy,
|
|
59
|
+
moveDir,
|
|
60
|
+
focusDay,
|
|
61
|
+
calendarStartMonth,
|
|
62
|
+
calendarEndMonth,
|
|
63
|
+
props,
|
|
64
|
+
dateLib,
|
|
65
|
+
attempt + 1
|
|
66
|
+
);
|
|
67
|
+
}
|