@wordpress/components
Version:
UI components for WordPress.
209 lines (171 loc) • 6.42 kB
JavaScript
"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