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.
Files changed (181) hide show
  1. package/dist/cjs/DayPicker.js +15 -3
  2. package/dist/cjs/DayPicker.js.map +1 -1
  3. package/dist/cjs/classes/DateLib.js +4 -4
  4. package/dist/cjs/classes/DateLib.js.map +1 -1
  5. package/dist/cjs/useAnimation.js +74 -42
  6. package/dist/cjs/useAnimation.js.map +1 -1
  7. package/dist/esm/DayPicker.js +15 -3
  8. package/dist/esm/DayPicker.js.map +1 -1
  9. package/dist/esm/classes/DateLib.js +1 -1
  10. package/dist/esm/classes/DateLib.js.map +1 -1
  11. package/dist/esm/useAnimation.js +74 -42
  12. package/dist/esm/useAnimation.js.map +1 -1
  13. package/package.json +2 -4
  14. package/src/.eslintignore +1 -0
  15. package/src/.eslintrc.cjs +27 -0
  16. package/src/DayPicker.test.tsx +199 -0
  17. package/src/DayPicker.tsx +630 -0
  18. package/src/UI.ts +365 -0
  19. package/src/classes/CalendarDay.test.ts +17 -0
  20. package/src/classes/CalendarDay.ts +61 -0
  21. package/src/classes/CalendarMonth.test.ts +28 -0
  22. package/src/classes/CalendarMonth.ts +15 -0
  23. package/src/classes/CalendarWeek.test.ts +21 -0
  24. package/src/classes/CalendarWeek.ts +13 -0
  25. package/src/classes/DateLib.ts +615 -0
  26. package/src/classes/index.ts +4 -0
  27. package/src/components/Button.tsx +13 -0
  28. package/src/components/CaptionLabel.tsx +13 -0
  29. package/src/components/Chevron.tsx +42 -0
  30. package/src/components/Day.tsx +28 -0
  31. package/src/components/DayButton.tsx +29 -0
  32. package/src/components/Dropdown.tsx +71 -0
  33. package/src/components/DropdownNav.tsx +13 -0
  34. package/src/components/Footer.tsx +13 -0
  35. package/src/components/Month.tsx +24 -0
  36. package/src/components/MonthCaption.tsx +23 -0
  37. package/src/components/MonthGrid.tsx +13 -0
  38. package/src/components/Months.tsx +13 -0
  39. package/src/components/MonthsDropdown.tsx +16 -0
  40. package/src/components/Nav.tsx +90 -0
  41. package/src/components/NextMonthButton.tsx +18 -0
  42. package/src/components/Option.tsx +13 -0
  43. package/src/components/PreviousMonthButton.tsx +20 -0
  44. package/src/components/Root.tsx +19 -0
  45. package/src/components/Select.tsx +13 -0
  46. package/src/components/Week.tsx +20 -0
  47. package/src/components/WeekNumber.tsx +21 -0
  48. package/src/components/WeekNumberHeader.tsx +15 -0
  49. package/src/components/Weekday.tsx +13 -0
  50. package/src/components/Weekdays.tsx +17 -0
  51. package/src/components/Weeks.tsx +13 -0
  52. package/src/components/YearsDropdown.tsx +16 -0
  53. package/src/components/custom-components.tsx +26 -0
  54. package/src/formatters/formatCaption.test.ts +27 -0
  55. package/src/formatters/formatCaption.ts +23 -0
  56. package/src/formatters/formatDay.test.ts +7 -0
  57. package/src/formatters/formatDay.ts +16 -0
  58. package/src/formatters/formatMonthDropdown.test.ts +19 -0
  59. package/src/formatters/formatMonthDropdown.ts +15 -0
  60. package/src/formatters/formatWeekNumber.test.ts +5 -0
  61. package/src/formatters/formatWeekNumber.ts +13 -0
  62. package/src/formatters/formatWeekNumberHeader.ts +10 -0
  63. package/src/formatters/formatWeekdayName.test.ts +15 -0
  64. package/src/formatters/formatWeekdayName.ts +16 -0
  65. package/src/formatters/formatYearDropdown.test.ts +7 -0
  66. package/src/formatters/formatYearDropdown.ts +21 -0
  67. package/src/formatters/index.ts +7 -0
  68. package/src/helpers/broadcastCalendar.test.ts +43 -0
  69. package/src/helpers/calculateFocusTarget.ts +51 -0
  70. package/src/helpers/endOfBroadcastWeek.test.ts +25 -0
  71. package/src/helpers/endOfBroadcastWeek.ts +16 -0
  72. package/src/helpers/getBroadcastWeeksInMonth.test.ts +23 -0
  73. package/src/helpers/getBroadcastWeeksInMonth.ts +31 -0
  74. package/src/helpers/getClassNamesForModifiers.ts +26 -0
  75. package/src/helpers/getComponents.ts +11 -0
  76. package/src/helpers/getDataAttributes.test.tsx +48 -0
  77. package/src/helpers/getDataAttributes.tsx +21 -0
  78. package/src/helpers/getDates.test.ts +190 -0
  79. package/src/helpers/getDates.ts +64 -0
  80. package/src/helpers/getDays.test.ts +30 -0
  81. package/src/helpers/getDays.ts +16 -0
  82. package/src/helpers/getDefaultClassNames.test.ts +47 -0
  83. package/src/helpers/getDefaultClassNames.ts +33 -0
  84. package/src/helpers/getDisplayMonths.test.ts +44 -0
  85. package/src/helpers/getDisplayMonths.ts +20 -0
  86. package/src/helpers/getFocusableDate.ts +59 -0
  87. package/src/helpers/getFormatters.test.ts +48 -0
  88. package/src/helpers/getFormatters.ts +19 -0
  89. package/src/helpers/getInitialMonth.test.ts +79 -0
  90. package/src/helpers/getInitialMonth.ts +41 -0
  91. package/src/helpers/getLabels.ts +10 -0
  92. package/src/helpers/getMonthOptions.test.ts +226 -0
  93. package/src/helpers/getMonthOptions.ts +37 -0
  94. package/src/helpers/getMonths.test.ts +88 -0
  95. package/src/helpers/getMonths.ts +90 -0
  96. package/src/helpers/getNavMonth.test.ts +253 -0
  97. package/src/helpers/getNavMonth.ts +70 -0
  98. package/src/helpers/getNextFocus.test.tsx +99 -0
  99. package/src/helpers/getNextFocus.tsx +67 -0
  100. package/src/helpers/getNextMonth.test.ts +101 -0
  101. package/src/helpers/getNextMonth.ts +45 -0
  102. package/src/helpers/getPossibleFocusDate.test.ts +144 -0
  103. package/src/helpers/getPreviousMonth.test.ts +77 -0
  104. package/src/helpers/getPreviousMonth.ts +40 -0
  105. package/src/helpers/getStyleForModifiers.test.ts +92 -0
  106. package/src/helpers/getStyleForModifiers.ts +21 -0
  107. package/src/helpers/getWeekdays.test.ts +44 -0
  108. package/src/helpers/getWeekdays.ts +29 -0
  109. package/src/helpers/getWeeks.test.ts +30 -0
  110. package/src/helpers/getWeeks.ts +9 -0
  111. package/src/helpers/getYearOptions.test.ts +46 -0
  112. package/src/helpers/getYearOptions.ts +34 -0
  113. package/src/helpers/index.ts +2 -0
  114. package/src/helpers/startOfBroadcastWeek.test.ts +24 -0
  115. package/src/helpers/startOfBroadcastWeek.ts +19 -0
  116. package/src/helpers/useControlledValue.test.ts +45 -0
  117. package/src/helpers/useControlledValue.ts +33 -0
  118. package/src/index.ts +15 -0
  119. package/src/jalali.tsx +2 -0
  120. package/src/labels/index.ts +12 -0
  121. package/src/labels/labelDayButton.test.ts +41 -0
  122. package/src/labels/labelDayButton.ts +31 -0
  123. package/src/labels/labelGrid.test.ts +7 -0
  124. package/src/labels/labelGrid.ts +23 -0
  125. package/src/labels/labelGridcell.test.ts +7 -0
  126. package/src/labels/labelGridcell.ts +22 -0
  127. package/src/labels/labelMonthDropdown.test.ts +5 -0
  128. package/src/labels/labelMonthDropdown.ts +12 -0
  129. package/src/labels/labelNav.test.ts +5 -0
  130. package/src/labels/labelNav.ts +10 -0
  131. package/src/labels/labelNext.test.ts +5 -0
  132. package/src/labels/labelNext.ts +13 -0
  133. package/src/labels/labelPrevious.test.ts +5 -0
  134. package/src/labels/labelPrevious.ts +13 -0
  135. package/src/labels/labelWeekNumber.test.ts +5 -0
  136. package/src/labels/labelWeekNumber.ts +15 -0
  137. package/src/labels/labelWeekNumberHeader.test.ts +5 -0
  138. package/src/labels/labelWeekNumberHeader.ts +12 -0
  139. package/src/labels/labelWeekday.test.ts +15 -0
  140. package/src/labels/labelWeekday.ts +16 -0
  141. package/src/labels/labelYearDropdown.test.ts +5 -0
  142. package/src/labels/labelYearDropdown.ts +12 -0
  143. package/src/locale.ts +1 -0
  144. package/src/persian.tsx +86 -0
  145. package/src/selection/useMulti.test.tsx +41 -0
  146. package/src/selection/useMulti.tsx +74 -0
  147. package/src/selection/useRange.test.tsx +154 -0
  148. package/src/selection/useRange.tsx +73 -0
  149. package/src/selection/useSingle.test.tsx +38 -0
  150. package/src/selection/useSingle.tsx +69 -0
  151. package/src/types/deprecated.ts +230 -0
  152. package/src/types/index.ts +4 -0
  153. package/src/types/props.test.tsx +71 -0
  154. package/src/types/props.ts +675 -0
  155. package/src/types/selection.ts +57 -0
  156. package/src/types/shared.ts +442 -0
  157. package/src/useAnimation.test.tsx +190 -0
  158. package/src/useAnimation.ts +236 -0
  159. package/src/useCalendar.ts +178 -0
  160. package/src/useDayPicker.test.tsx +142 -0
  161. package/src/useDayPicker.ts +93 -0
  162. package/src/useFocus.ts +87 -0
  163. package/src/useGetModifiers.test.tsx +154 -0
  164. package/src/useGetModifiers.tsx +122 -0
  165. package/src/useSelection.ts +26 -0
  166. package/src/utc.tsx +10 -0
  167. package/src/utils/addToRange.test.ts +117 -0
  168. package/src/utils/addToRange.ts +87 -0
  169. package/src/utils/dateMatchModifiers.test.ts +120 -0
  170. package/src/utils/dateMatchModifiers.ts +88 -0
  171. package/src/utils/index.ts +7 -0
  172. package/src/utils/rangeContainsDayOfWeek.test.ts +48 -0
  173. package/src/utils/rangeContainsDayOfWeek.ts +35 -0
  174. package/src/utils/rangeContainsModifiers.test.ts +230 -0
  175. package/src/utils/rangeContainsModifiers.ts +125 -0
  176. package/src/utils/rangeIncludesDate.test.ts +46 -0
  177. package/src/utils/rangeIncludesDate.ts +43 -0
  178. package/src/utils/rangeOverlaps.test.ts +60 -0
  179. package/src/utils/rangeOverlaps.ts +22 -0
  180. package/src/utils/typeguards.test.ts +83 -0
  181. 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
+ }