UNPKG

5.35 kBPlain TextView Raw
1import { useEffect, useState } from "react";
2
3import type {
4 CalendarWeek,
5 CalendarDay,
6 CalendarMonth
7} from "./classes/index.js";
8import { getDates } from "./helpers/getDates.js";
9import { getDays } from "./helpers/getDays.js";
10import { getDisplayMonths } from "./helpers/getDisplayMonths.js";
11import { getInitialMonth } from "./helpers/getInitialMonth.js";
12import { getMonths } from "./helpers/getMonths.js";
13import { getNavMonths } from "./helpers/getNavMonth.js";
14import { getNextMonth } from "./helpers/getNextMonth.js";
15import { getPreviousMonth } from "./helpers/getPreviousMonth.js";
16import { getWeeks } from "./helpers/getWeeks.js";
17import type { DayPickerProps } from "./types/props.js";
18import type { DateLib } from "./types/shared.js";
19
20/**
21 * Return the calendar object to work with the calendar in custom components.
22 *
23 * @see https://daypicker.dev/guides/custom-components
24 */
25export interface Calendar {
26 /**
27 * All the days displayed in the calendar. As opposite from
28 * {@link CalendarContext.dates}, it may return duplicated dates when shown
29 * outside the month.
30 */
31 days: CalendarDay[];
32 /** The months displayed in the calendar. */
33 weeks: CalendarWeek[];
34 /** The months displayed in the calendar. */
35 months: CalendarMonth[];
36
37 /** The next month to display. */
38 nextMonth: Date | undefined;
39 /** The previous month to display. */
40 previousMonth: Date | undefined;
41
42 /**
43 * The month where the navigation starts. `undefined` if the calendar can be
44 * navigated indefinitely to the past.
45 */
46 navStart: Date | undefined;
47 /**
48 * The month where the navigation ends. `undefined` if the calendar can be
49 * navigated indefinitely to the past.
50 */
51 navEnd: Date | undefined;
52
53 /** Navigate to the specified month. Will fire the `onMonthChange` callback. */
54 goToMonth: (month: Date) => void;
55 /**
56 * Navigate to the specified date. If the second parameter (refDate) is
57 * provided and the date is before the refDate, then the month is set to one
58 * month before the date.
59 *
60 * @param day - The date to navigate to.
61 * @param dateToCompare - Optional. If `date` is before `dateToCompare`, the
62 * month is set to one month before the date.
63 */
64 goToDay: (day: CalendarDay) => void;
65}
66
67/** @private */
68export function useCalendar(
69 props: Pick<
70 DayPickerProps,
71 | "captionLayout"
72 | "endMonth"
73 | "startMonth"
74 | "today"
75 | "fixedWeeks"
76 | "ISOWeek"
77 | "weekStartsOn"
78 | "numberOfMonths"
79 | "disableNavigation"
80 | "onMonthChange"
81 | "month"
82 | "defaultMonth"
83 // Deprecated:
84 | "fromMonth"
85 | "fromYear"
86 | "toMonth"
87 | "toYear"
88 >,
89 dateLib: DateLib
90): Calendar {
91 const [navStart, navEnd] = getNavMonths(props, dateLib);
92
93 const { startOfMonth, endOfMonth } = dateLib;
94
95 const initialMonth = getInitialMonth(props, dateLib);
96
97 const [firstMonth, setFirstMonth] = useState(initialMonth);
98
99 // Update the displayed month if `month` changes
100 useEffect(() => {
101 const initialDisplayMonth = getInitialMonth(props, dateLib);
102 setFirstMonth(initialDisplayMonth);
103 // eslint-disable-next-line react-hooks/exhaustive-deps
104 }, [props.month]);
105
106 // Update the displayed month if start/end month changes
107 useEffect(() => {
108 // TOFIX: this effect should do nothing if the current firstMonth is between
109 // startMonth and endMonth
110 const initialDisplayMonth = getInitialMonth(props, dateLib);
111 setFirstMonth(initialDisplayMonth);
112 // eslint-disable-next-line react-hooks/exhaustive-deps
113 }, [props.startMonth, props.endMonth]);
114
115 /** The months displayed in the calendar. */
116 const displayMonths = getDisplayMonths(firstMonth, navEnd, props, dateLib);
117
118 /** The dates displayed in the calendar. */
119 const dates = getDates(
120 displayMonths,
121 props.endMonth ? endOfMonth(props.endMonth) : undefined,
122 props,
123 dateLib
124 );
125
126 /** The Months displayed in the calendar. */
127 const months = getMonths(displayMonths, dates, props, dateLib);
128
129 /** The Weeks displayed in the calendar. */
130 const weeks = getWeeks(months);
131
132 /** The Days displayed in the calendar. */
133 const days = getDays(months);
134
135 const previousMonth = getPreviousMonth(firstMonth, navStart, props, dateLib);
136 const nextMonth = getNextMonth(firstMonth, navEnd, props, dateLib);
137
138 const { disableNavigation, onMonthChange } = props;
139
140 const isDayInCalendar = (day: CalendarDay) =>
141 weeks.some((week: CalendarWeek) => week.days.some((d) => d.isEqualTo(day)));
142
143 const goToMonth = (date: Date) => {
144 if (disableNavigation) {
145 return;
146 }
147 let newMonth = startOfMonth(date);
148 // if month is before start, use the first month instead
149 if (navStart && newMonth < startOfMonth(navStart)) {
150 newMonth = startOfMonth(navStart);
151 }
152 // if month is after endMonth, use the last month instead
153 if (navEnd && newMonth > startOfMonth(navEnd)) {
154 newMonth = startOfMonth(navEnd);
155 }
156 setFirstMonth(newMonth);
157 onMonthChange?.(newMonth);
158 };
159
160 const goToDay = (day: CalendarDay) => {
161 // is this check necessary?
162 if (isDayInCalendar(day)) {
163 return;
164 }
165 goToMonth(day.date);
166 };
167
168 const calendar = {
169 months,
170 weeks,
171 days,
172
173 navStart,
174 navEnd,
175
176 previousMonth,
177 nextMonth,
178
179 goToMonth,
180 goToDay
181 };
182
183 return calendar;
184}