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,236 @@
|
|
|
1
|
+
import React, { useLayoutEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Animation } from "./UI.js";
|
|
4
|
+
import type { CalendarDay } from "./classes/CalendarDay.js";
|
|
5
|
+
import { CalendarMonth } from "./classes/CalendarMonth.js";
|
|
6
|
+
import type { DateLib } from "./classes/DateLib.js";
|
|
7
|
+
import { ClassNames } from "./types/shared.js";
|
|
8
|
+
|
|
9
|
+
const asHtmlElement = (element: Element | null): HTMLElement | null => {
|
|
10
|
+
if (element instanceof HTMLElement) return element;
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const queryMonthEls = (element: HTMLElement) => [
|
|
15
|
+
...(element.querySelectorAll("[data-animated-month]") ?? [])
|
|
16
|
+
];
|
|
17
|
+
const queryMonthEl = (element: HTMLElement) =>
|
|
18
|
+
asHtmlElement(element.querySelector("[data-animated-month]"));
|
|
19
|
+
const queryCaptionEl = (element: HTMLElement) =>
|
|
20
|
+
asHtmlElement(element.querySelector("[data-animated-caption]"));
|
|
21
|
+
const queryWeeksEl = (element: HTMLElement) =>
|
|
22
|
+
asHtmlElement(element.querySelector("[data-animated-weeks]"));
|
|
23
|
+
const queryNavEl = (element: HTMLElement) =>
|
|
24
|
+
asHtmlElement(element.querySelector("[data-animated-nav]"));
|
|
25
|
+
const queryWeekdaysEl = (element: HTMLElement) =>
|
|
26
|
+
asHtmlElement(element.querySelector("[data-animated-weekdays]"));
|
|
27
|
+
|
|
28
|
+
/** @private */
|
|
29
|
+
export function useAnimation(
|
|
30
|
+
rootElRef: React.RefObject<HTMLDivElement | null>,
|
|
31
|
+
enabled: boolean,
|
|
32
|
+
{
|
|
33
|
+
classNames,
|
|
34
|
+
months,
|
|
35
|
+
focused,
|
|
36
|
+
dateLib
|
|
37
|
+
}: {
|
|
38
|
+
classNames: ClassNames;
|
|
39
|
+
months: CalendarMonth[];
|
|
40
|
+
focused: CalendarDay | undefined;
|
|
41
|
+
dateLib: DateLib;
|
|
42
|
+
}
|
|
43
|
+
): void {
|
|
44
|
+
const previousRootElSnapshotRef = useRef<HTMLElement>(null);
|
|
45
|
+
const previousMonthsRef = useRef(months);
|
|
46
|
+
const animatingRef = useRef(false);
|
|
47
|
+
|
|
48
|
+
useLayoutEffect(() => {
|
|
49
|
+
// get previous months before updating the previous months ref
|
|
50
|
+
const previousMonths = previousMonthsRef.current;
|
|
51
|
+
// update previous months ref for next effect trigger
|
|
52
|
+
previousMonthsRef.current = months;
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
!enabled ||
|
|
56
|
+
!rootElRef.current ||
|
|
57
|
+
// safety check because the ref can be set to anything by consumers
|
|
58
|
+
!(rootElRef.current instanceof HTMLElement) ||
|
|
59
|
+
// validation required for the animation to work as expected
|
|
60
|
+
months.length === 0 ||
|
|
61
|
+
previousMonths.length === 0 ||
|
|
62
|
+
months.length !== previousMonths.length
|
|
63
|
+
) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isSameMonth = dateLib.isSameMonth(
|
|
68
|
+
months[0].date,
|
|
69
|
+
previousMonths[0].date
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const isAfterPreviousMonth = dateLib.isAfter(
|
|
73
|
+
months[0].date,
|
|
74
|
+
previousMonths[0].date
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const captionAnimationClass = isAfterPreviousMonth
|
|
78
|
+
? classNames[Animation.caption_after_enter]
|
|
79
|
+
: classNames[Animation.caption_before_enter];
|
|
80
|
+
|
|
81
|
+
const weeksAnimationClass = isAfterPreviousMonth
|
|
82
|
+
? classNames[Animation.weeks_after_enter]
|
|
83
|
+
: classNames[Animation.weeks_before_enter];
|
|
84
|
+
|
|
85
|
+
// get previous root element snapshot before updating the snapshot ref
|
|
86
|
+
const previousRootElSnapshot = previousRootElSnapshotRef.current;
|
|
87
|
+
|
|
88
|
+
// update snapshot for next effect trigger
|
|
89
|
+
const rootElSnapshot = rootElRef.current.cloneNode(true);
|
|
90
|
+
if (rootElSnapshot instanceof HTMLElement) {
|
|
91
|
+
// if this effect is triggered while animating, we need to clean up the new root snapshot
|
|
92
|
+
// to put it in the same state as when not animating, to correctly animate the next month change
|
|
93
|
+
const currentMonthElsSnapshot = queryMonthEls(rootElSnapshot);
|
|
94
|
+
currentMonthElsSnapshot.forEach((currentMonthElSnapshot) => {
|
|
95
|
+
if (!(currentMonthElSnapshot instanceof HTMLElement)) return;
|
|
96
|
+
|
|
97
|
+
// remove the old month snapshots from the new root snapshot
|
|
98
|
+
const previousMonthElSnapshot = queryMonthEl(currentMonthElSnapshot);
|
|
99
|
+
if (
|
|
100
|
+
previousMonthElSnapshot &&
|
|
101
|
+
currentMonthElSnapshot.contains(previousMonthElSnapshot)
|
|
102
|
+
) {
|
|
103
|
+
currentMonthElSnapshot.removeChild(previousMonthElSnapshot);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// remove animation classes from the new month snapshots
|
|
107
|
+
const captionEl = queryCaptionEl(currentMonthElSnapshot);
|
|
108
|
+
if (captionEl) {
|
|
109
|
+
captionEl.classList.remove(captionAnimationClass);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const weeksEl = queryWeeksEl(currentMonthElSnapshot);
|
|
113
|
+
if (weeksEl) {
|
|
114
|
+
weeksEl.classList.remove(weeksAnimationClass);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
previousRootElSnapshotRef.current = rootElSnapshot;
|
|
119
|
+
} else {
|
|
120
|
+
previousRootElSnapshotRef.current = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
animatingRef.current ||
|
|
125
|
+
isSameMonth ||
|
|
126
|
+
// skip animation if a day is focused because it can cause issues to the animation and is better for a11y
|
|
127
|
+
focused
|
|
128
|
+
) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const previousMonthEls =
|
|
133
|
+
previousRootElSnapshot instanceof HTMLElement
|
|
134
|
+
? queryMonthEls(previousRootElSnapshot)
|
|
135
|
+
: [];
|
|
136
|
+
|
|
137
|
+
const currentMonthEls = queryMonthEls(rootElRef.current);
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
currentMonthEls &&
|
|
141
|
+
currentMonthEls.every((el) => el instanceof HTMLElement) &&
|
|
142
|
+
previousMonthEls &&
|
|
143
|
+
previousMonthEls.every((el) => el instanceof HTMLElement)
|
|
144
|
+
) {
|
|
145
|
+
animatingRef.current = true;
|
|
146
|
+
const cleanUpFunctions: (() => void)[] = [];
|
|
147
|
+
|
|
148
|
+
// set isolation to isolate to isolate the stacking context during animation
|
|
149
|
+
rootElRef.current.style.isolation = "isolate";
|
|
150
|
+
// set z-index to 1 to ensure the nav is clickable over the other elements being animated
|
|
151
|
+
const navEl = queryNavEl(rootElRef.current);
|
|
152
|
+
if (navEl) {
|
|
153
|
+
navEl.style.zIndex = "1";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
currentMonthEls.forEach((currentMonthEl, index) => {
|
|
157
|
+
const previousMonthEl = previousMonthEls[index];
|
|
158
|
+
|
|
159
|
+
if (!previousMonthEl) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// animate new displayed month
|
|
164
|
+
currentMonthEl.style.position = "relative";
|
|
165
|
+
currentMonthEl.style.overflow = "hidden";
|
|
166
|
+
const captionEl = queryCaptionEl(currentMonthEl);
|
|
167
|
+
if (captionEl) {
|
|
168
|
+
captionEl.classList.add(captionAnimationClass);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const weeksEl = queryWeeksEl(currentMonthEl);
|
|
172
|
+
if (weeksEl) {
|
|
173
|
+
weeksEl.classList.add(weeksAnimationClass);
|
|
174
|
+
}
|
|
175
|
+
// animate new displayed month end
|
|
176
|
+
|
|
177
|
+
const cleanUp = () => {
|
|
178
|
+
animatingRef.current = false;
|
|
179
|
+
|
|
180
|
+
if (rootElRef.current) {
|
|
181
|
+
rootElRef.current.style.isolation = "";
|
|
182
|
+
}
|
|
183
|
+
if (navEl) {
|
|
184
|
+
navEl.style.zIndex = "";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (captionEl) {
|
|
188
|
+
captionEl.classList.remove(captionAnimationClass);
|
|
189
|
+
}
|
|
190
|
+
if (weeksEl) {
|
|
191
|
+
weeksEl.classList.remove(weeksAnimationClass);
|
|
192
|
+
}
|
|
193
|
+
currentMonthEl.style.position = "";
|
|
194
|
+
currentMonthEl.style.overflow = "";
|
|
195
|
+
if (currentMonthEl.contains(previousMonthEl)) {
|
|
196
|
+
currentMonthEl.removeChild(previousMonthEl);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
cleanUpFunctions.push(cleanUp);
|
|
200
|
+
|
|
201
|
+
// animate old displayed month
|
|
202
|
+
previousMonthEl.style.pointerEvents = "none";
|
|
203
|
+
previousMonthEl.style.position = "absolute";
|
|
204
|
+
previousMonthEl.style.overflow = "hidden";
|
|
205
|
+
previousMonthEl.setAttribute("aria-hidden", "true");
|
|
206
|
+
|
|
207
|
+
// hide the weekdays container of the old month and only the new one
|
|
208
|
+
const previousWeekdaysEl = queryWeekdaysEl(previousMonthEl);
|
|
209
|
+
if (previousWeekdaysEl) {
|
|
210
|
+
previousWeekdaysEl.style.opacity = "0";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const previousCaptionEl = queryCaptionEl(previousMonthEl);
|
|
214
|
+
if (previousCaptionEl) {
|
|
215
|
+
previousCaptionEl.classList.add(
|
|
216
|
+
isAfterPreviousMonth
|
|
217
|
+
? classNames[Animation.caption_before_exit]
|
|
218
|
+
: classNames[Animation.caption_after_exit]
|
|
219
|
+
);
|
|
220
|
+
previousCaptionEl.addEventListener("animationend", cleanUp);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const previousWeeksEl = queryWeeksEl(previousMonthEl);
|
|
224
|
+
if (previousWeeksEl) {
|
|
225
|
+
previousWeeksEl.classList.add(
|
|
226
|
+
isAfterPreviousMonth
|
|
227
|
+
? classNames[Animation.weeks_before_exit]
|
|
228
|
+
: classNames[Animation.weeks_after_exit]
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
currentMonthEl.insertBefore(previousMonthEl, currentMonthEl.firstChild);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
CalendarWeek,
|
|
5
|
+
CalendarDay,
|
|
6
|
+
CalendarMonth,
|
|
7
|
+
DateLib
|
|
8
|
+
} from "./classes/index.js";
|
|
9
|
+
import { getDates } from "./helpers/getDates.js";
|
|
10
|
+
import { getDays } from "./helpers/getDays.js";
|
|
11
|
+
import { getDisplayMonths } from "./helpers/getDisplayMonths.js";
|
|
12
|
+
import { getInitialMonth } from "./helpers/getInitialMonth.js";
|
|
13
|
+
import { getMonths } from "./helpers/getMonths.js";
|
|
14
|
+
import { getNavMonths } from "./helpers/getNavMonth.js";
|
|
15
|
+
import { getNextMonth } from "./helpers/getNextMonth.js";
|
|
16
|
+
import { getPreviousMonth } from "./helpers/getPreviousMonth.js";
|
|
17
|
+
import { getWeeks } from "./helpers/getWeeks.js";
|
|
18
|
+
import { useControlledValue } from "./helpers/useControlledValue.js";
|
|
19
|
+
import type { DayPickerProps } from "./types/props.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Return the calendar object to work with the calendar in custom components.
|
|
23
|
+
*
|
|
24
|
+
* @see https://daypicker.dev/guides/custom-components
|
|
25
|
+
*/
|
|
26
|
+
export interface Calendar {
|
|
27
|
+
/**
|
|
28
|
+
* All the days displayed in the calendar. As opposite from
|
|
29
|
+
* {@link CalendarContext.dates}, it may return duplicated dates when shown
|
|
30
|
+
* outside the month.
|
|
31
|
+
*/
|
|
32
|
+
days: CalendarDay[];
|
|
33
|
+
/** The months displayed in the calendar. */
|
|
34
|
+
weeks: CalendarWeek[];
|
|
35
|
+
/** The months displayed in the calendar. */
|
|
36
|
+
months: CalendarMonth[];
|
|
37
|
+
|
|
38
|
+
/** The next month to display. */
|
|
39
|
+
nextMonth: Date | undefined;
|
|
40
|
+
/** The previous month to display. */
|
|
41
|
+
previousMonth: Date | undefined;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The month where the navigation starts. `undefined` if the calendar can be
|
|
45
|
+
* navigated indefinitely to the past.
|
|
46
|
+
*/
|
|
47
|
+
navStart: Date | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The month where the navigation ends. `undefined` if the calendar can be
|
|
50
|
+
* navigated indefinitely to the past.
|
|
51
|
+
*/
|
|
52
|
+
navEnd: Date | undefined;
|
|
53
|
+
|
|
54
|
+
/** Navigate to the specified month. Will fire the `onMonthChange` callback. */
|
|
55
|
+
goToMonth: (month: Date) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Navigate to the specified date. If the second parameter (refDate) is
|
|
58
|
+
* provided and the date is before the refDate, then the month is set to one
|
|
59
|
+
* month before the date.
|
|
60
|
+
*
|
|
61
|
+
* @param day - The date to navigate to.
|
|
62
|
+
* @param dateToCompare - Optional. If `date` is before `dateToCompare`, the
|
|
63
|
+
* month is set to one month before the date.
|
|
64
|
+
*/
|
|
65
|
+
goToDay: (day: CalendarDay) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @private */
|
|
69
|
+
export function useCalendar(
|
|
70
|
+
props: Pick<
|
|
71
|
+
DayPickerProps,
|
|
72
|
+
| "captionLayout"
|
|
73
|
+
| "endMonth"
|
|
74
|
+
| "startMonth"
|
|
75
|
+
| "today"
|
|
76
|
+
| "fixedWeeks"
|
|
77
|
+
| "ISOWeek"
|
|
78
|
+
| "numberOfMonths"
|
|
79
|
+
| "disableNavigation"
|
|
80
|
+
| "onMonthChange"
|
|
81
|
+
| "month"
|
|
82
|
+
| "defaultMonth"
|
|
83
|
+
| "timeZone"
|
|
84
|
+
| "broadcastCalendar"
|
|
85
|
+
// Deprecated:
|
|
86
|
+
| "fromMonth"
|
|
87
|
+
| "fromYear"
|
|
88
|
+
| "toMonth"
|
|
89
|
+
| "toYear"
|
|
90
|
+
>,
|
|
91
|
+
dateLib: DateLib
|
|
92
|
+
): Calendar {
|
|
93
|
+
const [navStart, navEnd] = getNavMonths(props, dateLib);
|
|
94
|
+
|
|
95
|
+
const { startOfMonth, endOfMonth } = dateLib;
|
|
96
|
+
const initialMonth = getInitialMonth(props, dateLib);
|
|
97
|
+
const [firstMonth, setFirstMonth] = useControlledValue(
|
|
98
|
+
initialMonth,
|
|
99
|
+
// initialMonth is always computed from props.month if provided
|
|
100
|
+
props.month ? initialMonth : undefined
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const newInitialMonth = getInitialMonth(props, dateLib);
|
|
105
|
+
setFirstMonth(newInitialMonth);
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
|
+
}, [props.timeZone]);
|
|
108
|
+
|
|
109
|
+
/** The months displayed in the calendar. */
|
|
110
|
+
const displayMonths = getDisplayMonths(firstMonth, navEnd, props, dateLib);
|
|
111
|
+
|
|
112
|
+
/** The dates displayed in the calendar. */
|
|
113
|
+
const dates = getDates(
|
|
114
|
+
displayMonths,
|
|
115
|
+
props.endMonth ? endOfMonth(props.endMonth) : undefined,
|
|
116
|
+
props,
|
|
117
|
+
dateLib
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/** The Months displayed in the calendar. */
|
|
121
|
+
const months = getMonths(displayMonths, dates, props, dateLib);
|
|
122
|
+
|
|
123
|
+
/** The Weeks displayed in the calendar. */
|
|
124
|
+
const weeks = getWeeks(months);
|
|
125
|
+
|
|
126
|
+
/** The Days displayed in the calendar. */
|
|
127
|
+
const days = getDays(months);
|
|
128
|
+
|
|
129
|
+
const previousMonth = getPreviousMonth(firstMonth, navStart, props, dateLib);
|
|
130
|
+
const nextMonth = getNextMonth(firstMonth, navEnd, props, dateLib);
|
|
131
|
+
|
|
132
|
+
const { disableNavigation, onMonthChange } = props;
|
|
133
|
+
|
|
134
|
+
const isDayInCalendar = (day: CalendarDay) =>
|
|
135
|
+
weeks.some((week: CalendarWeek) => week.days.some((d) => d.isEqualTo(day)));
|
|
136
|
+
|
|
137
|
+
const goToMonth = (date: Date) => {
|
|
138
|
+
if (disableNavigation) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let newMonth = startOfMonth(date);
|
|
142
|
+
// if month is before start, use the first month instead
|
|
143
|
+
if (navStart && newMonth < startOfMonth(navStart)) {
|
|
144
|
+
newMonth = startOfMonth(navStart);
|
|
145
|
+
}
|
|
146
|
+
// if month is after endMonth, use the last month instead
|
|
147
|
+
if (navEnd && newMonth > startOfMonth(navEnd)) {
|
|
148
|
+
newMonth = startOfMonth(navEnd);
|
|
149
|
+
}
|
|
150
|
+
setFirstMonth(newMonth);
|
|
151
|
+
onMonthChange?.(newMonth);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const goToDay = (day: CalendarDay) => {
|
|
155
|
+
// is this check necessary?
|
|
156
|
+
if (isDayInCalendar(day)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
goToMonth(day.date);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const calendar = {
|
|
163
|
+
months,
|
|
164
|
+
weeks,
|
|
165
|
+
days,
|
|
166
|
+
|
|
167
|
+
navStart,
|
|
168
|
+
navEnd,
|
|
169
|
+
|
|
170
|
+
previousMonth,
|
|
171
|
+
nextMonth,
|
|
172
|
+
|
|
173
|
+
goToMonth,
|
|
174
|
+
goToDay
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return calendar;
|
|
178
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { renderHook } from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
import { Animation, DayFlag, SelectionState, UI } from "./UI";
|
|
6
|
+
import { CalendarDay } from "./classes/CalendarDay";
|
|
7
|
+
import { CalendarMonth } from "./classes/CalendarMonth";
|
|
8
|
+
import { DayPickerProps } from "./types/props";
|
|
9
|
+
import { Modifiers } from "./types/shared";
|
|
10
|
+
import {
|
|
11
|
+
DayPickerContext,
|
|
12
|
+
dayPickerContext,
|
|
13
|
+
useDayPicker
|
|
14
|
+
} from "./useDayPicker";
|
|
15
|
+
|
|
16
|
+
describe("useDayPicker", () => {
|
|
17
|
+
const mockContextValue: DayPickerContext<{
|
|
18
|
+
required: false;
|
|
19
|
+
mode: "single";
|
|
20
|
+
}> = {
|
|
21
|
+
months: [new CalendarMonth(new Date(), [])],
|
|
22
|
+
nextMonth: new Date(),
|
|
23
|
+
previousMonth: new Date(),
|
|
24
|
+
goToMonth: jest.fn(),
|
|
25
|
+
getModifiers: jest.fn((day: CalendarDay) => ({}) as Modifiers),
|
|
26
|
+
selected: undefined,
|
|
27
|
+
select: jest.fn(),
|
|
28
|
+
isSelected: jest.fn((date: Date) => false),
|
|
29
|
+
components: {
|
|
30
|
+
Button: jest.fn(),
|
|
31
|
+
Chevron: jest.fn(),
|
|
32
|
+
CaptionLabel: jest.fn(),
|
|
33
|
+
Day: jest.fn(),
|
|
34
|
+
DayButton: jest.fn(),
|
|
35
|
+
Dropdown: jest.fn(),
|
|
36
|
+
DropdownNav: jest.fn(),
|
|
37
|
+
Footer: jest.fn(),
|
|
38
|
+
Month: jest.fn(),
|
|
39
|
+
MonthCaption: jest.fn(),
|
|
40
|
+
MonthGrid: jest.fn(),
|
|
41
|
+
Months: jest.fn(),
|
|
42
|
+
Nav: jest.fn(),
|
|
43
|
+
Option: jest.fn(),
|
|
44
|
+
PreviousMonthButton: jest.fn(),
|
|
45
|
+
NextMonthButton: jest.fn(),
|
|
46
|
+
Root: jest.fn(),
|
|
47
|
+
Select: jest.fn(),
|
|
48
|
+
Weeks: jest.fn(),
|
|
49
|
+
Week: jest.fn(),
|
|
50
|
+
Weekday: jest.fn(),
|
|
51
|
+
Weekdays: jest.fn(),
|
|
52
|
+
WeekNumber: jest.fn(),
|
|
53
|
+
WeekNumberHeader: jest.fn(),
|
|
54
|
+
MonthsDropdown: jest.fn(),
|
|
55
|
+
YearsDropdown: jest.fn()
|
|
56
|
+
},
|
|
57
|
+
classNames: {
|
|
58
|
+
[UI.Root]: "",
|
|
59
|
+
[UI.Chevron]: "",
|
|
60
|
+
[UI.Day]: "",
|
|
61
|
+
[UI.DayButton]: "",
|
|
62
|
+
[UI.CaptionLabel]: "",
|
|
63
|
+
[UI.Dropdowns]: "",
|
|
64
|
+
[UI.Dropdown]: "",
|
|
65
|
+
[UI.DropdownRoot]: "",
|
|
66
|
+
[UI.Footer]: "",
|
|
67
|
+
[UI.MonthGrid]: "",
|
|
68
|
+
[UI.MonthCaption]: "",
|
|
69
|
+
[UI.MonthsDropdown]: "",
|
|
70
|
+
[UI.Month]: "",
|
|
71
|
+
[UI.Months]: "",
|
|
72
|
+
[UI.Nav]: "",
|
|
73
|
+
[UI.NextMonthButton]: "",
|
|
74
|
+
[UI.PreviousMonthButton]: "",
|
|
75
|
+
[UI.Week]: "",
|
|
76
|
+
[UI.Weeks]: "",
|
|
77
|
+
[UI.Weekday]: "",
|
|
78
|
+
[UI.Weekdays]: "",
|
|
79
|
+
[UI.WeekNumber]: "",
|
|
80
|
+
[UI.WeekNumberHeader]: "",
|
|
81
|
+
[UI.YearsDropdown]: "",
|
|
82
|
+
[SelectionState.range_end]: "",
|
|
83
|
+
[SelectionState.range_middle]: "",
|
|
84
|
+
[SelectionState.range_start]: "",
|
|
85
|
+
[SelectionState.selected]: "",
|
|
86
|
+
[DayFlag.disabled]: "",
|
|
87
|
+
[DayFlag.hidden]: "",
|
|
88
|
+
[DayFlag.outside]: "",
|
|
89
|
+
[DayFlag.focused]: "",
|
|
90
|
+
[DayFlag.today]: "",
|
|
91
|
+
[Animation.weeks_after_enter]: "",
|
|
92
|
+
[Animation.weeks_before_exit]: "",
|
|
93
|
+
[Animation.weeks_before_enter]: "",
|
|
94
|
+
[Animation.weeks_after_exit]: "",
|
|
95
|
+
[Animation.caption_after_enter]: "",
|
|
96
|
+
[Animation.caption_before_exit]: "",
|
|
97
|
+
[Animation.caption_before_enter]: "",
|
|
98
|
+
[Animation.caption_after_exit]: ""
|
|
99
|
+
},
|
|
100
|
+
styles: {},
|
|
101
|
+
labels: {
|
|
102
|
+
labelNav: jest.fn(),
|
|
103
|
+
labelGrid: jest.fn(),
|
|
104
|
+
labelGridcell: jest.fn(),
|
|
105
|
+
labelMonthDropdown: jest.fn(),
|
|
106
|
+
labelYearDropdown: jest.fn(),
|
|
107
|
+
labelNext: jest.fn(),
|
|
108
|
+
labelPrevious: jest.fn(),
|
|
109
|
+
labelDayButton: jest.fn(),
|
|
110
|
+
labelDay: jest.fn(),
|
|
111
|
+
labelWeekday: jest.fn(),
|
|
112
|
+
labelWeekNumber: jest.fn(),
|
|
113
|
+
labelWeekNumberHeader: jest.fn()
|
|
114
|
+
},
|
|
115
|
+
formatters: {
|
|
116
|
+
formatCaption: jest.fn(),
|
|
117
|
+
formatDay: jest.fn(),
|
|
118
|
+
formatMonthDropdown: jest.fn(),
|
|
119
|
+
formatMonthCaption: jest.fn(),
|
|
120
|
+
formatWeekNumber: jest.fn(),
|
|
121
|
+
formatWeekNumberHeader: jest.fn(),
|
|
122
|
+
formatWeekdayName: jest.fn(),
|
|
123
|
+
formatYearDropdown: jest.fn(),
|
|
124
|
+
formatYearCaption: jest.fn()
|
|
125
|
+
},
|
|
126
|
+
dayPickerProps: {
|
|
127
|
+
mode: "single",
|
|
128
|
+
required: false
|
|
129
|
+
} as DayPickerProps
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
it("should return the context value when used within a DayPicker provider", () => {
|
|
133
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
134
|
+
<dayPickerContext.Provider value={mockContextValue}>
|
|
135
|
+
{children}
|
|
136
|
+
</dayPickerContext.Provider>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const { result } = renderHook(() => useDayPicker(), { wrapper });
|
|
140
|
+
expect(result.current).toEqual(mockContextValue);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import { CalendarDay } from "./classes/CalendarDay.js";
|
|
4
|
+
import { CalendarMonth } from "./classes/CalendarMonth.js";
|
|
5
|
+
import { DayPickerProps } from "./types/props.js";
|
|
6
|
+
import type { SelectedValue, SelectHandler } from "./types/selection.js";
|
|
7
|
+
import {
|
|
8
|
+
ClassNames,
|
|
9
|
+
CustomComponents,
|
|
10
|
+
Formatters,
|
|
11
|
+
Labels,
|
|
12
|
+
Mode,
|
|
13
|
+
Modifiers,
|
|
14
|
+
Styles
|
|
15
|
+
} from "./types/shared.js";
|
|
16
|
+
|
|
17
|
+
/** @ignore */
|
|
18
|
+
export const dayPickerContext = createContext<
|
|
19
|
+
| DayPickerContext<{
|
|
20
|
+
mode?: Mode | undefined;
|
|
21
|
+
required?: boolean | undefined;
|
|
22
|
+
}>
|
|
23
|
+
| undefined
|
|
24
|
+
>(undefined);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Represents the context for the DayPicker component, providing various
|
|
28
|
+
* properties and methods to interact with the calendar.
|
|
29
|
+
*
|
|
30
|
+
* @template T - The type of the DayPicker props, which must optionally include
|
|
31
|
+
* `mode` and `required` properties. This type can be used to refine the type
|
|
32
|
+
* returned by the hook.
|
|
33
|
+
*/
|
|
34
|
+
export type DayPickerContext<
|
|
35
|
+
T extends { mode?: Mode | undefined; required?: boolean | undefined }
|
|
36
|
+
> = {
|
|
37
|
+
/** The months displayed in the calendar. */
|
|
38
|
+
months: CalendarMonth[];
|
|
39
|
+
/** The next month to display. */
|
|
40
|
+
nextMonth: Date | undefined;
|
|
41
|
+
/** The previous month to display. */
|
|
42
|
+
previousMonth: Date | undefined;
|
|
43
|
+
/** Navigate to the specified month. Will fire the `onMonthChange` callback. */
|
|
44
|
+
goToMonth: (month: Date) => void;
|
|
45
|
+
/** Returns the modifiers for the given day. */
|
|
46
|
+
getModifiers: (day: CalendarDay) => Modifiers;
|
|
47
|
+
/** The selected date(s). */
|
|
48
|
+
selected: SelectedValue<T> | undefined;
|
|
49
|
+
/** Set a selection. */
|
|
50
|
+
select: SelectHandler<T> | undefined;
|
|
51
|
+
/** Whether the given date is selected. */
|
|
52
|
+
isSelected: ((date: Date) => boolean) | undefined;
|
|
53
|
+
/** The components used internally by DayPicker. */
|
|
54
|
+
components: CustomComponents;
|
|
55
|
+
/** The class names for the UI elements. */
|
|
56
|
+
classNames: ClassNames;
|
|
57
|
+
/** The styles for the UI elements. */
|
|
58
|
+
styles: Partial<Styles> | undefined;
|
|
59
|
+
/** The labels used in the user interface. */
|
|
60
|
+
labels: Labels;
|
|
61
|
+
/** The formatters used to format the UI elements. */
|
|
62
|
+
formatters: Formatters;
|
|
63
|
+
/**
|
|
64
|
+
* The props as passed to the DayPicker component.
|
|
65
|
+
*
|
|
66
|
+
* @since 9.3.0
|
|
67
|
+
*/
|
|
68
|
+
dayPickerProps: DayPickerProps;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns the context to work with `<DayPicker />` inside custom components.
|
|
73
|
+
*
|
|
74
|
+
* This hook provides access to the DayPicker context, which includes various
|
|
75
|
+
* properties and methods to interact with the DayPicker component. It must be
|
|
76
|
+
* used within a custom component.
|
|
77
|
+
*
|
|
78
|
+
* @template T - Use this type to refine the returned context type with a
|
|
79
|
+
* specific selection mode.
|
|
80
|
+
* @returns {DayPickerContext<T>} The context to work with DayPicker.
|
|
81
|
+
* @throws {Error} If the hook is used outside of a DayPicker provider.
|
|
82
|
+
* @group Hooks
|
|
83
|
+
* @see https://daypicker.dev/guides/custom-components
|
|
84
|
+
*/
|
|
85
|
+
export function useDayPicker<
|
|
86
|
+
T extends { mode?: Mode | undefined; required?: boolean | undefined }
|
|
87
|
+
>(): DayPickerContext<T> {
|
|
88
|
+
const context = useContext(dayPickerContext);
|
|
89
|
+
if (context === undefined) {
|
|
90
|
+
throw new Error("useDayPicker() must be used within a custom component.");
|
|
91
|
+
}
|
|
92
|
+
return context;
|
|
93
|
+
}
|