UNPKG

@fremtind/jkl-datepicker-react

Version:
497 lines (496 loc) 16.7 kB
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