@fremtind/jkl-datepicker-react
Version:
Jøkul react datepicker component
497 lines (496 loc) • 16.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
import { Button } from "@fremtind/jkl-button-react";
import {
ArrowLeftIcon,
ArrowRightIcon,
ChevronDownIcon
} from "@fremtind/jkl-icons-react";
import { useId } from "@fremtind/jkl-react-hooks";
import React, {
forwardRef,
useCallback,
useEffect,
useReducer,
useRef
} from "react";
import { flushSync } from "react-dom";
import { calendarInitializer, calendarReducer } from "./calendarReducer";
import { useCalendar } from "./useCalendar";
import {
addMonth,
subtractMonth,
isBackDisabled,
isForwardDisabled,
getYearSelectOptions,
getMonthSelectOptions,
getInitialDateShown,
DEFAULT_YEARS_TO_SHOW
} from "./utils";
const defaultMonths = [
"Januar",
"Februar",
"Mars",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Desember"
];
const defaultDays = ["man", "tir", "ons", "tor", "fre", "l\xF8r", "s\xF8n"];
const Calendar = forwardRef(
(props, ref) => {
const _a = props, {
date,
defaultSelected,
density,
minDate,
maxDate,
days = defaultDays,
months = defaultMonths,
monthLabel = "Velg m\xE5ned",
yearLabel = "Velg \xE5r",
yearsToShow = DEFAULT_YEARS_TO_SHOW,
onTabOutside
} = _a, rest = __objRest(_a, [
"date",
"defaultSelected",
"density",
"minDate",
"maxDate",
"days",
"months",
"monthLabel",
"yearLabel",
"yearsToShow",
"onTabOutside"
]);
const id = useId("jkl-calendar");
const [{ offset, selectedDate, shownDate }, dispatch] = useReducer(
calendarReducer,
getInitialDateShown(date, defaultSelected, minDate, maxDate),
calendarInitializer
);
const shownMonth = shownDate.getMonth();
const shownYear = shownDate.getFullYear();
useEffect(() => {
dispatch({
type: "SET_SELECTED_DATE",
newDate: getInitialDateShown(
date,
defaultSelected,
minDate,
maxDate
)
});
}, [date, defaultSelected, minDate, maxDate]);
const onOffsetChanged = useCallback((newOffset) => {
dispatch({
type: "SET_OFFSET",
newOffset
});
}, []);
const {
calendars,
getBackProps,
getDateProps,
getForwardProps,
handleOffsetChanged
} = useCalendar(__spreadValues({
date: selectedDate,
selected: selectedDate,
minDate,
maxDate,
offset,
onOffsetChanged,
firstDayOfWeek: 1
}, rest));
const calendarPaddingRef = useRef(null);
const doFocusChange = useCallback(
(offsetDiff) => {
if (!calendarPaddingRef.current) {
return;
}
const e = document.activeElement;
const buttons = calendarPaddingRef.current.querySelectorAll(
'button.jkl-calendar-date-button:not([data-adjacent="true"]'
);
const changeFocusTo = async (nextButton) => {
e == null ? void 0 : e.setAttribute("tabindex", "-1");
nextButton.setAttribute("tabindex", "0");
nextButton.focus();
};
buttons.forEach((el, i) => {
const newNodeKey = i + offsetDiff;
if (el == e) {
if (newNodeKey <= buttons.length - 1 && newNodeKey >= 0) {
changeFocusTo(buttons[newNodeKey]);
} else if (offsetDiff < 0) {
if (isBackDisabled({ calendars, minDate })) {
return;
}
flushSync(() => {
handleOffsetChanged(
offset - subtractMonth({
calendars,
offset: 1,
minDate
})
);
});
if (!calendarPaddingRef.current) {
return;
}
const newButtons = calendarPaddingRef.current.querySelectorAll(
'button.jkl-calendar-date-button:not([data-adjacent="true"]'
);
if (newButtons[newButtons.length + newNodeKey]) {
newButtons[0].setAttribute("tabindex", "-1");
changeFocusTo(
newButtons[newButtons.length + newNodeKey]
);
}
} else {
if (isForwardDisabled({ calendars, maxDate })) {
return;
}
flushSync(() => {
handleOffsetChanged(
offset + addMonth({
calendars,
offset: 1,
maxDate
})
);
});
if (!calendarPaddingRef.current) {
return;
}
const newButtons = calendarPaddingRef.current.querySelectorAll(
'button.jkl-calendar-date-button:not([data-adjacent="true"]'
);
if (newButtons[newNodeKey - buttons.length]) {
newButtons[0].setAttribute("tabindex", "-1");
changeFocusTo(
newButtons[newNodeKey - buttons.length]
);
}
}
}
});
},
[
handleOffsetChanged,
calendarPaddingRef,
offset,
calendars,
maxDate,
minDate
]
);
const handleArrowNavigation = useCallback(
(event) => {
switch (event.key) {
case "ArrowUp":
doFocusChange(-7);
event.preventDefault();
break;
case "ArrowRight":
doFocusChange(1);
event.preventDefault();
break;
case "ArrowDown":
doFocusChange(7);
event.preventDefault();
break;
case "ArrowLeft":
doFocusChange(-1);
event.preventDefault();
break;
default:
break;
}
},
[doFocusChange]
);
const handleTabInside = useCallback(
(event) => {
var _a2;
if (event.key !== "Tab")
return;
const focusableElements = (_a2 = calendarPaddingRef.current) == null ? void 0 : _a2.querySelectorAll(
'button:not([disabled]):not([tabindex="-1"]), select'
);
if (!focusableElements)
return;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (!event.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
event.preventDefault();
} else if (event.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
event.preventDefault();
}
},
[]
);
const isFocusableDate = useCallback(
(dateInfo) => {
const { date: date2, selected, selectable, prevMonth, nextMonth } = dateInfo;
if (!selectable) {
return false;
}
if (selected) {
return true;
}
if (date2.toString() === (minDate == null ? void 0 : minDate.toString())) {
return true;
}
if (!prevMonth && !nextMonth && shownDate.getFullYear() === date2.getFullYear() && selectedDate.getMonth() !== date2.getMonth() && date2.getDate() === 1) {
return true;
}
return false;
},
[shownDate, minDate, selectedDate]
);
const handleGotoEdgeMonth = useCallback(() => {
if (
// Vi er i ferd med å gå til første måned
minDate && shownDate.getFullYear() - minDate.getFullYear() === 0 && shownDate.getMonth() - minDate.getMonth() === 1
) {
document.querySelectorAll(
".jkl-calendar-navigation__arrow"
)[1].focus();
} else if (
// Vi er i ferd med å gå til siste måned
maxDate && maxDate.getFullYear() - shownDate.getFullYear() === 0 && maxDate.getMonth() - shownDate.getMonth() === 1
) {
document.querySelectorAll(
".jkl-calendar-navigation__arrow"
)[0].focus();
}
}, [minDate, maxDate, shownDate]);
const handleYearChange = useCallback(
(event) => {
if (event.target.value.length !== 4) {
return;
}
const year = Number.parseInt(event.target.value);
if (Number.isNaN(year)) {
return;
}
let offset2 = (year - shownDate.getFullYear()) * 12;
const expectedDate = new Date(
shownDate.getFullYear(),
shownDate.getMonth() + offset2,
shownDate.getDate()
);
if (maxDate && maxDate.getFullYear() === expectedDate.getFullYear() && maxDate.getMonth() < expectedDate.getMonth()) {
offset2 -= expectedDate.getMonth() - maxDate.getMonth();
} else if (minDate && minDate.getFullYear() === expectedDate.getFullYear() && minDate.getMonth() > expectedDate.getMonth()) {
offset2 += minDate.getMonth() - expectedDate.getMonth();
}
dispatch({
type: "ADD_OFFSET",
addedOffset: offset2
});
return;
},
[shownDate, minDate, maxDate]
);
const handleMonthChange = useCallback(
(event) => {
if (!selectedDate && !date) {
return;
}
const yearDiff = shownDate.getFullYear() - (selectedDate || /* @__PURE__ */ new Date()).getFullYear();
const monthDiff = Number.parseInt(event.target.value) - (selectedDate || /* @__PURE__ */ new Date()).getMonth();
dispatch({
type: "SET_OFFSET",
newOffset: yearDiff * 12 + monthDiff
});
return;
},
[selectedDate, date, shownDate]
);
const yearSelectOptions = getYearSelectOptions(
shownYear,
minDate,
maxDate,
yearsToShow
);
const monthSelectOptions = getMonthSelectOptions(
shownYear,
months,
minDate,
maxDate
);
return /* @__PURE__ */ React.createElement(
"div",
{
ref,
id,
className: "jkl-calendar",
"data-testid": "jkl-calendar"
},
/* @__PURE__ */ React.createElement(
"div",
{
className: "jkl-calendar__padding",
ref: calendarPaddingRef,
onKeyDown: handleTabInside
},
/* @__PURE__ */ React.createElement("fieldset", { className: "jkl-calendar-navigation" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
Button,
__spreadProps(__spreadValues({}, getBackProps({
calendars,
onClick: handleGotoEdgeMonth
})), {
variant: "ghost",
icon: /* @__PURE__ */ React.createElement(ArrowLeftIcon, { variant: "medium", bold: true })
})
), /* @__PURE__ */ React.createElement(
Button,
__spreadProps(__spreadValues({}, getForwardProps({
calendars,
onClick: handleGotoEdgeMonth
})), {
variant: "ghost",
icon: /* @__PURE__ */ React.createElement(ArrowRightIcon, { variant: "medium", bold: true })
})
)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "jkl-calendar-navigation-dropdown" }, /* @__PURE__ */ React.createElement(
"select",
{
onChange: handleMonthChange,
className: "jkl-calendar-navigation-dropdown__select",
"aria-label": monthLabel,
value: shownMonth.toString()
},
monthSelectOptions.map(
({ label, value }) => /* @__PURE__ */ React.createElement("option", { key: value, value }, label)
)
), /* @__PURE__ */ React.createElement(
ChevronDownIcon,
{
bold: true,
className: "jkl-calendar-navigation-dropdown__chevron"
}
)), /* @__PURE__ */ React.createElement("div", { className: "jkl-calendar-navigation-dropdown" }, /* @__PURE__ */ React.createElement(
"select",
{
onChange: handleYearChange,
className: "jkl-calendar-navigation-dropdown__select",
"aria-label": yearLabel,
value: shownYear.toString()
},
yearSelectOptions.map((year) => /* @__PURE__ */ React.createElement("option", { key: year, value: year }, year))
), /* @__PURE__ */ React.createElement(
ChevronDownIcon,
{
bold: true,
className: "jkl-calendar-navigation-dropdown__chevron"
}
)))),
calendars.map((calendar) => /* @__PURE__ */ React.createElement(
"table",
{
className: "jkl-calendar-table",
key: "".concat(calendar.month).concat(calendar.year),
"data-testid": "jkl-datepicker-calendar"
},
/* @__PURE__ */ React.createElement("caption", { className: "jkl-sr-only" }, months[calendar.month], ", ", calendar.year),
/* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, days.map((weekday) => /* @__PURE__ */ React.createElement(
"th",
{
key: "".concat(calendar.month).concat(calendar.year).concat(weekday)
},
weekday
)))),
/* @__PURE__ */ React.createElement("tbody", { "data-testid": "jkl-datepicker-dates" }, calendar.weeks.map((week, weekIndex) => /* @__PURE__ */ React.createElement(
"tr",
{
key: "".concat(calendar.month).concat(calendar.year).concat(weekIndex)
},
week.map((dateInfo, index) => {
const key = "".concat(calendar.month).concat(calendar.year).concat(weekIndex).concat(index);
if (typeof dateInfo === "string") {
return /* @__PURE__ */ React.createElement(
"td",
{
className: "jkl-calendar__date jkl-calendar__date--empty",
key
},
dateInfo
);
}
const {
date: date2,
selectable,
today,
prevMonth,
nextMonth
} = dateInfo;
return /* @__PURE__ */ React.createElement("td", { key }, /* @__PURE__ */ React.createElement(
"button",
__spreadProps(__spreadValues({}, getDateProps({
dateObj: dateInfo
})), {
type: "button",
className: "jkl-calendar-date-button",
tabIndex: isFocusableDate(
dateInfo
) ? 0 : -1,
"aria-label": "".concat(date2.getDate(), ". ").concat(months[date2.getMonth()].toLowerCase()),
"aria-current": today ? "date" : void 0,
"data-adjacent": prevMonth || nextMonth ? "true" : void 0,
disabled: !selectable,
onKeyDown: handleArrowNavigation
}),
/* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, date2.getDate())
));
})
)))
))
)
);
}
);
Calendar.displayName = "Calendar";
export {
Calendar
};
//# sourceMappingURL=Calendar.js.map