UNPKG

@wordpress/components

Version:
209 lines (171 loc) 6.42 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _element = require("@wordpress/element"); var _moment = _interopRequireDefault(require("moment")); var _classnames = _interopRequireDefault(require("classnames")); var _DayPickerSingleDateController = _interopRequireDefault(require("react-dates/lib/components/DayPickerSingleDateController")); var _i18n = require("@wordpress/i18n"); /** * External dependencies */ // react-dates doesn't tree-shake correctly, so we import from the individual // component here, to avoid including too much of the library /** * WordPress dependencies */ /** * Module Constants */ const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; const ARIAL_LABEL_TIME_FORMAT = 'dddd, LL'; function DatePickerDay({ day, events = [] }) { const ref = (0, _element.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. */ (0, _element.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 = (0, _moment.default)(day).format(ARIAL_LABEL_TIME_FORMAT); if (!events.length) { // Set aria-label without event description. parentNode.setAttribute('aria-label', dayAriaLabel); return; } const dayWithEventsDescription = (0, _i18n.sprintf)( // translators: 1: Calendar day format, 2: Calendar event number. (0, _i18n._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 (0, _element.createElement)("div", { ref: ref, className: (0, _classnames.default)('components-datetime__date__day', { 'has-events': events === null || events === void 0 ? void 0 : events.length }) }, day.format('D')); } class DatePicker extends _element.Component { constructor() { super(...arguments); this.onChangeMoment = this.onChangeMoment.bind(this); this.nodeRef = (0, _element.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 ? (0, _moment.default)(currentDate) : (0, _moment.default)(); 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 ? (0, _moment.default)(currentDate) : (0, _moment.default)(); } 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 (0, _element.createElement)("div", { className: "components-datetime__date", ref: this.nodeRef }, (0, _element.createElement)(_DayPickerSingleDateController.default, { 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: (0, _i18n.isRTL)(), isOutsideRange: date => { return isInvalidDate && isInvalidDate(date.toDate()); }, onPrevMonthClick: this.onMonthPreviewedHandler, onNextMonthClick: this.onMonthPreviewedHandler, renderDayContents: day => (0, _element.createElement)(DatePickerDay, { day: day, events: this.getEventsPerDay(day) }) })); } } var _default = DatePicker; exports.default = _default; //# sourceMappingURL=date.js.map