UNPKG

@wordpress/components

Version:
196 lines (164 loc) 5.97 kB
import { createElement } from "@wordpress/element"; /** * External dependencies */ import moment from 'moment'; import classnames from 'classnames'; // react-dates doesn't tree-shake correctly, so we import from the individual // component here, to avoid including too much of the library import DayPickerSingleDateController from 'react-dates/lib/components/DayPickerSingleDateController'; /** * WordPress dependencies */ import { Component, createRef, useEffect, useRef } from '@wordpress/element'; import { isRTL, _n, sprintf } from '@wordpress/i18n'; /** * Module Constants */ const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; const ARIAL_LABEL_TIME_FORMAT = 'dddd, LL'; function DatePickerDay({ day, events = [] }) { const ref = useRef(); /* * a11y hack to make the `There is/are n events` string * available speaking for readers, * re-defining the aria-label attribute. * This attribute is handled by the react-dates component. */ useEffect(() => { var _ref$current; // Bail when no parent node. if (!(ref !== null && ref !== void 0 && (_ref$current = ref.current) !== null && _ref$current !== void 0 && _ref$current.parentNode)) { return; } const { parentNode } = ref.current; const dayAriaLabel = moment(day).format(ARIAL_LABEL_TIME_FORMAT); if (!events.length) { // Set aria-label without event description. parentNode.setAttribute('aria-label', dayAriaLabel); return; } const dayWithEventsDescription = sprintf( // translators: 1: Calendar day format, 2: Calendar event number. _n('%1$s. There is %2$d event.', '%1$s. There are %2$d events.', events.length), dayAriaLabel, events.length); parentNode.setAttribute('aria-label', dayWithEventsDescription); }, [events.length]); return createElement("div", { ref: ref, className: classnames('components-datetime__date__day', { 'has-events': events === null || events === void 0 ? void 0 : events.length }) }, day.format('D')); } class DatePicker extends Component { constructor() { super(...arguments); this.onChangeMoment = this.onChangeMoment.bind(this); this.nodeRef = createRef(); this.onMonthPreviewedHandler = this.onMonthPreviewedHandler.bind(this); } onMonthPreviewedHandler(newMonthDate) { var _this$props; (_this$props = this.props) === null || _this$props === void 0 ? void 0 : _this$props.onMonthPreviewed(newMonthDate.toISOString()); this.keepFocusInside(); } /* * Todo: We should remove this function ASAP. * It is kept because focus is lost when we click on the previous and next month buttons. * This focus loss closes the date picker popover. * Ideally we should add an upstream commit on react-dates to fix this issue. */ keepFocusInside() { if (!this.nodeRef.current) { return; } const { ownerDocument } = this.nodeRef.current; const { activeElement } = ownerDocument; // If focus was lost. if (!activeElement || !this.nodeRef.current.contains(ownerDocument.activeElement)) { // Retrieve the focus region div. const focusRegion = this.nodeRef.current.querySelector('.DayPicker_focusRegion'); if (!focusRegion) { return; } // Keep the focus on focus region. focusRegion.focus(); } } onChangeMoment(newDate) { const { currentDate, onChange } = this.props; // If currentDate is null, use now as momentTime to designate hours, minutes, seconds. const momentDate = currentDate ? moment(currentDate) : moment(); const momentTime = { hours: momentDate.hours(), minutes: momentDate.minutes(), seconds: 0 }; onChange(newDate.set(momentTime).format(TIMEZONELESS_FORMAT)); // Keep focus on the date picker. this.keepFocusInside(); } /** * Create a Moment object from a date string. With no currentDate supplied, default to a Moment * object representing now. If a null value is passed, return a null value. * * @param {?string} currentDate Date representing the currently selected date or null to signify no selection. * @return {?moment.Moment} Moment object for selected date or null. */ getMomentDate(currentDate) { if (null === currentDate) { return null; } return currentDate ? moment(currentDate) : moment(); } getEventsPerDay(day) { var _this$props$events; if (!((_this$props$events = this.props.events) !== null && _this$props$events !== void 0 && _this$props$events.length)) { return []; } return this.props.events.filter(eventDay => day.isSame(eventDay.date, 'day')); } render() { const { currentDate, isInvalidDate } = this.props; const momentDate = this.getMomentDate(currentDate); return createElement("div", { className: "components-datetime__date", ref: this.nodeRef }, createElement(DayPickerSingleDateController, { date: momentDate, daySize: 30, focused: true, hideKeyboardShortcutsPanel: true // This is a hack to force the calendar to update on month or year change // https://github.com/airbnb/react-dates/issues/240#issuecomment-361776665 , key: `datepicker-controller-${momentDate ? momentDate.format('MM-YYYY') : 'null'}`, noBorder: true, numberOfMonths: 1, onDateChange: this.onChangeMoment, transitionDuration: 0, weekDayFormat: "ddd", dayAriaLabelFormat: ARIAL_LABEL_TIME_FORMAT, isRTL: isRTL(), isOutsideRange: date => { return isInvalidDate && isInvalidDate(date.toDate()); }, onPrevMonthClick: this.onMonthPreviewedHandler, onNextMonthClick: this.onMonthPreviewedHandler, renderDayContents: day => createElement(DatePickerDay, { day: day, events: this.getEventsPerDay(day) }) })); } } export default DatePicker; //# sourceMappingURL=date.js.map