1 | import { __rest } from "tslib";
|
2 | import React, { useEffect } from 'react';
|
3 | import { TextInput } from '../TextInput/TextInput';
|
4 | import { Button } from '../Button/Button';
|
5 | import { Select, SelectOption } from '../Select';
|
6 | import ArrowLeftIcon from '@patternfly/react-icons/dist/esm/icons/arrow-left-icon';
|
7 | import ArrowRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-right-icon';
|
8 | import { css } from '@patternfly/react-styles';
|
9 | import styles from '@patternfly/react-styles/css/components/CalendarMonth/calendar-month';
|
10 | import { getUniqueId } from '../../helpers/util';
|
11 | export var Weekday;
|
12 | (function (Weekday) {
|
13 | Weekday[Weekday["Sunday"] = 0] = "Sunday";
|
14 | Weekday[Weekday["Monday"] = 1] = "Monday";
|
15 | Weekday[Weekday["Tuesday"] = 2] = "Tuesday";
|
16 | Weekday[Weekday["Wednesday"] = 3] = "Wednesday";
|
17 | Weekday[Weekday["Thursday"] = 4] = "Thursday";
|
18 | Weekday[Weekday["Friday"] = 5] = "Friday";
|
19 | Weekday[Weekday["Saturday"] = 6] = "Saturday";
|
20 | })(Weekday || (Weekday = {}));
|
21 |
|
22 | const yearFormat = (date) => date.getFullYear();
|
23 | const buildCalendar = (year, month, weekStart, validators) => {
|
24 | const defaultDate = new Date(year, month);
|
25 | const firstDayOfWeek = new Date(defaultDate);
|
26 | firstDayOfWeek.setDate(firstDayOfWeek.getDate() - firstDayOfWeek.getDay() + weekStart);
|
27 |
|
28 |
|
29 | const calendarWeeks = [];
|
30 | for (let i = 0; i < 6; i++) {
|
31 | const week = [];
|
32 | for (let j = 0; j < 7; j++) {
|
33 | const date = new Date(firstDayOfWeek);
|
34 | week.push({
|
35 | date,
|
36 | isValid: validators.every(validator => validator(date))
|
37 | });
|
38 | firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 1);
|
39 | }
|
40 | calendarWeeks.push(week);
|
41 | }
|
42 | return calendarWeeks;
|
43 | };
|
44 | const isSameDate = (d1, d2) => d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
|
45 | export const isValidDate = (date) => Boolean(date && !isNaN(date));
|
46 | const today = new Date();
|
47 | export const CalendarMonth = (_a) => {
|
48 | var { date: dateProp, locale = undefined, monthFormat = date => date.toLocaleDateString(locale, { month: 'long' }), weekdayFormat = date => date.toLocaleDateString(locale, { weekday: 'narrow' }), longWeekdayFormat = date => date.toLocaleDateString(locale, { weekday: 'long' }), dayFormat = date => date.getDate(), weekStart = 0,
|
49 | onChange = () => { }, validators = [() => true], className, onSelectToggle = () => { }, rangeStart, prevMonthAriaLabel = 'Previous month', nextMonthAriaLabel = 'Next month', yearInputAriaLabel = 'Select year', cellAriaLabel } = _a, props = __rest(_a, ["date", "locale", "monthFormat", "weekdayFormat", "longWeekdayFormat", "dayFormat", "weekStart", "onChange", "validators", "className", "onSelectToggle", "rangeStart", "prevMonthAriaLabel", "nextMonthAriaLabel", "yearInputAriaLabel", "cellAriaLabel"]);
|
50 | const longMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(monthNum => new Date(1990, monthNum)).map(monthFormat);
|
51 | const [isSelectOpen, setIsSelectOpen] = React.useState(false);
|
52 |
|
53 | let [focusedDate, setFocusedDate] = React.useState(new Date(dateProp));
|
54 | if (!isValidDate(focusedDate)) {
|
55 | if (isValidDate(rangeStart)) {
|
56 | focusedDate = rangeStart;
|
57 | }
|
58 | else {
|
59 | focusedDate = today;
|
60 | }
|
61 | }
|
62 | const [hoveredDate, setHoveredDate] = React.useState(new Date(focusedDate));
|
63 | const focusRef = React.useRef();
|
64 | const [hiddenMonthId] = React.useState(getUniqueId('hidden-month-span'));
|
65 | const [shouldFocus, setShouldFocus] = React.useState(false);
|
66 | const isValidated = (date) => validators.every(validator => validator(date));
|
67 | const focusedDateValidated = isValidated(focusedDate);
|
68 | useEffect(() => {
|
69 | if (!(isValidDate(dateProp) && isSameDate(focusedDate, dateProp))) {
|
70 | setFocusedDate(dateProp);
|
71 | }
|
72 | }, [dateProp]);
|
73 | useEffect(() => {
|
74 |
|
75 | if (shouldFocus) {
|
76 | if (focusRef.current && focusedDateValidated) {
|
77 | focusRef.current.focus();
|
78 | }
|
79 | }
|
80 | else {
|
81 | setShouldFocus(true);
|
82 | }
|
83 | }, [focusedDate]);
|
84 | const onMonthClick = (newDate) => {
|
85 | setFocusedDate(newDate);
|
86 | setHoveredDate(newDate);
|
87 | setShouldFocus(false);
|
88 | };
|
89 | const onKeyDown = (ev) => {
|
90 | const newDate = new Date(focusedDate);
|
91 | if (ev.key === 'ArrowUp') {
|
92 | newDate.setDate(newDate.getDate() - 7);
|
93 | }
|
94 | else if (ev.key === 'ArrowRight') {
|
95 | newDate.setDate(newDate.getDate() + 1);
|
96 | }
|
97 | else if (ev.key === 'ArrowDown') {
|
98 | newDate.setDate(newDate.getDate() + 7);
|
99 | }
|
100 | else if (ev.key === 'ArrowLeft') {
|
101 | newDate.setDate(newDate.getDate() - 1);
|
102 | }
|
103 | if (newDate.getTime() !== focusedDate.getTime() && isValidated(newDate)) {
|
104 | ev.preventDefault();
|
105 | setFocusedDate(newDate);
|
106 | setHoveredDate(newDate);
|
107 | setShouldFocus(true);
|
108 | }
|
109 | };
|
110 | const addMonth = (toAdd) => {
|
111 | const newDate = new Date(focusedDate);
|
112 | newDate.setMonth(newDate.getMonth() + toAdd);
|
113 | return newDate;
|
114 | };
|
115 | const prevMonth = addMonth(-1);
|
116 | const nextMonth = addMonth(1);
|
117 | const focusedYear = focusedDate.getFullYear();
|
118 | const focusedMonth = focusedDate.getMonth();
|
119 | const calendar = React.useMemo(() => buildCalendar(focusedYear, focusedMonth, weekStart, validators), [
|
120 | focusedYear,
|
121 | focusedMonth,
|
122 | weekStart,
|
123 | validators
|
124 | ]);
|
125 | if (!focusedDateValidated) {
|
126 | const toFocus = calendar
|
127 | .reduce((acc, cur) => [...acc, ...cur], [])
|
128 | .filter(({ date, isValid }) => isValid && date.getMonth() === focusedMonth)
|
129 | .map(({ date }) => ({ date, days: Math.abs(focusedDate.getTime() - date.getTime()) }))
|
130 | .sort((o1, o2) => o1.days - o2.days)
|
131 | .map(({ date }) => date)[0];
|
132 | if (toFocus) {
|
133 | setFocusedDate(toFocus);
|
134 | setHoveredDate(toFocus);
|
135 | }
|
136 | }
|
137 | const isHoveredDateValid = isValidated(hoveredDate);
|
138 | const monthFormatted = monthFormat(focusedDate);
|
139 | const yearFormatted = yearFormat(focusedDate);
|
140 | return (React.createElement("div", Object.assign({ className: css(styles.calendarMonth, className) }, props),
|
141 | React.createElement("div", { className: styles.calendarMonthHeader },
|
142 | React.createElement("div", { className: css(styles.calendarMonthHeaderNavControl, styles.modifiers.prevMonth) },
|
143 | React.createElement(Button, { variant: "plain", "aria-label": prevMonthAriaLabel, onClick: () => onMonthClick(prevMonth) },
|
144 | React.createElement(ArrowLeftIcon, { "aria-hidden": true }))),
|
145 | React.createElement("div", { className: styles.calendarMonthHeaderMonth },
|
146 | React.createElement("span", { id: hiddenMonthId, hidden: true }, "Month"),
|
147 | React.createElement(Select
|
148 |
|
149 | , {
|
150 |
|
151 | width: "140px", "aria-labelledby": hiddenMonthId, isOpen: isSelectOpen, onToggle: () => {
|
152 | setIsSelectOpen(!isSelectOpen);
|
153 | onSelectToggle(!isSelectOpen);
|
154 | }, onSelect: (_ev, monthNum) => {
|
155 |
|
156 |
|
157 | setTimeout(() => {
|
158 | setIsSelectOpen(false);
|
159 | onSelectToggle(false);
|
160 | const newDate = new Date(focusedDate);
|
161 | newDate.setMonth(Number(monthNum));
|
162 | setFocusedDate(newDate);
|
163 | setHoveredDate(newDate);
|
164 | setShouldFocus(false);
|
165 | }, 0);
|
166 | }, variant: "single", selections: monthFormatted }, longMonths.map((longMonth, index) => (React.createElement(SelectOption, { key: index, value: index, isSelected: longMonth === monthFormatted }, longMonth))))),
|
167 | React.createElement("div", { className: styles.calendarMonthHeaderYear },
|
168 | React.createElement(TextInput, { "aria-label": yearInputAriaLabel, type: "number", value: yearFormatted, onChange: year => {
|
169 | const newDate = new Date(focusedDate);
|
170 | newDate.setFullYear(+year);
|
171 | setFocusedDate(newDate);
|
172 | setHoveredDate(newDate);
|
173 | setShouldFocus(false);
|
174 | } })),
|
175 | React.createElement("div", { className: css(styles.calendarMonthHeaderNavControl, styles.modifiers.nextMonth) },
|
176 | React.createElement(Button, { variant: "plain", "aria-label": nextMonthAriaLabel, onClick: () => onMonthClick(nextMonth) },
|
177 | React.createElement(ArrowRightIcon, { "aria-hidden": true })))),
|
178 | React.createElement("table", { className: styles.calendarMonthCalendar },
|
179 | React.createElement("thead", { className: styles.calendarMonthDays },
|
180 | React.createElement("tr", null, calendar[0].map(({ date }, index) => (React.createElement("th", { key: index, className: styles.calendarMonthDay, scope: "col" },
|
181 | React.createElement("span", { className: "pf-screen-reader" }, longWeekdayFormat(date)),
|
182 | React.createElement("span", { "aria-hidden": true }, weekdayFormat(date))))))),
|
183 | React.createElement("tbody", { onKeyDown: onKeyDown }, calendar.map((week, index) => (React.createElement("tr", { key: index, className: styles.calendarMonthDatesRow }, week.map(({ date, isValid }, index) => {
|
184 | const dayFormatted = dayFormat(date);
|
185 | const isToday = isSameDate(date, today);
|
186 | const isSelected = isValidDate(dateProp) && isSameDate(date, dateProp);
|
187 | const isFocused = isSameDate(date, focusedDate);
|
188 | const isAdjacentMonth = date.getMonth() !== focusedDate.getMonth();
|
189 | const isRangeStart = isValidDate(rangeStart) && isSameDate(date, rangeStart);
|
190 | let isInRange = false;
|
191 | let isRangeEnd = false;
|
192 | if (isValidDate(rangeStart) && isValidDate(dateProp)) {
|
193 | isInRange = date > rangeStart && date < dateProp;
|
194 | isRangeEnd = isSameDate(date, dateProp);
|
195 | }
|
196 | else if (isValidDate(rangeStart) && isHoveredDateValid) {
|
197 | if (hoveredDate > rangeStart || isSameDate(hoveredDate, rangeStart)) {
|
198 | isInRange = date > rangeStart && date < hoveredDate;
|
199 | isRangeEnd = isSameDate(date, hoveredDate);
|
200 | }
|
201 |
|
202 |
|
203 | }
|
204 | return (React.createElement("td", { key: index, className: css(styles.calendarMonthDatesCell, isAdjacentMonth && styles.modifiers.adjacentMonth, isToday && styles.modifiers.current, (isSelected || isRangeStart) && styles.modifiers.selected, !isValid && styles.modifiers.disabled, (isInRange || isRangeStart || isRangeEnd) && styles.modifiers.inRange, isRangeStart && styles.modifiers.startRange, isRangeEnd && styles.modifiers.endRange) },
|
205 | React.createElement("button", Object.assign({ className: css(styles.calendarMonthDate, isRangeEnd && styles.modifiers.hover, !isValid && styles.modifiers.disabled), type: "button", onClick: () => onChange(date), onMouseOver: () => setHoveredDate(date), tabIndex: isFocused ? 0 : -1, disabled: !isValid, "aria-label": cellAriaLabel ? cellAriaLabel(date) : `${dayFormatted} ${monthFormatted} ${yearFormatted}` }, (isFocused && { ref: focusRef })), dayFormatted)));
|
206 | }))))))));
|
207 | };
|
208 |
|
\ | No newline at end of file |