UNPKG

baseui

Version:

A React Component library implementing the Base design language

655 lines (649 loc) • 26.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var React = _interopRequireWildcard(require("react")); var _formControl = require("../form-control"); var _locale = require("../locale"); var _select = require("../select"); var _calendarHeader = _interopRequireDefault(require("./calendar-header")); var _month = _interopRequireDefault(require("./month")); var _timepicker = _interopRequireDefault(require("../timepicker/timepicker")); var _styledComponents = require("./styled-components"); var _dateFnsAdapter = _interopRequireDefault(require("./utils/date-fns-adapter")); var _dateHelpers = _interopRequireDefault(require("./utils/date-helpers")); var _overrides = require("../helpers/overrides"); var _constants = require("./constants"); var _button = require("../button"); var _buttonDock = require("../button-dock"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ class Calendar extends React.Component { constructor(props) { super(props); _defineProperty(this, "dateHelpers", void 0); // @ts-ignore _defineProperty(this, "calendar", void 0); _defineProperty(this, "getDateInView", () => { const { highlightedDate, value } = this.props; // @ts-ignore const minDate = this.dateHelpers.getEffectiveMinDate(this.props); // @ts-ignore const maxDate = this.dateHelpers.getEffectiveMaxDate(this.props); const current = this.dateHelpers.date(); const initialDate = this.getSingleDate(value) || highlightedDate; if (initialDate) { return initialDate; } else { if (minDate && this.dateHelpers.isBefore(current, minDate)) { return minDate; } else if (maxDate && this.dateHelpers.isAfter(current, maxDate)) { return maxDate; } } return current; }); _defineProperty(this, "handleMonthChange", date => { this.setHighlightedDate(this.dateHelpers.getStartOfMonth(date)); if (this.props.onMonthChange) { this.props.onMonthChange({ date }); } }); _defineProperty(this, "handleYearChange", date => { this.setHighlightedDate(date); if (this.props.onYearChange) { this.props.onYearChange({ date }); } }); _defineProperty(this, "changeMonth", ({ date }) => { this.setState({ date: date }, () => this.handleMonthChange(this.state.date)); }); _defineProperty(this, "changeYear", ({ date }) => { this.setState({ date: date }, () => this.handleYearChange(this.state.date)); }); _defineProperty(this, "renderCalendarHeader", (date = this.state.date, order) => { return /*#__PURE__*/React.createElement(_calendarHeader.default, _extends({}, this.props, { key: `month-header-${order}`, date: date, order: order, onMonthChange: this.changeMonth, onYearChange: this.changeYear })); }); _defineProperty(this, "onKeyDown", event => { switch (event.key) { case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': case 'Home': case 'End': case 'PageUp': case 'PageDown': this.handleArrowKey(event.key); event.preventDefault(); event.stopPropagation(); break; } }); _defineProperty(this, "handleArrowKey", key => { const { highlightedDate: oldDate } = this.state; let highlightedDate = oldDate; const currentDate = this.dateHelpers.date(); switch (key) { case 'ArrowLeft': // adding `new Date()` as the last option to satisfy Flow highlightedDate = this.dateHelpers.subDays(highlightedDate ? highlightedDate : currentDate, 1); break; case 'ArrowRight': highlightedDate = this.dateHelpers.addDays( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate, 1); break; case 'ArrowUp': highlightedDate = this.dateHelpers.subWeeks( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate, 1); break; case 'ArrowDown': highlightedDate = this.dateHelpers.addWeeks( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate, 1); break; case 'Home': highlightedDate = this.dateHelpers.getStartOfWeek( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate); break; case 'End': highlightedDate = this.dateHelpers.getEndOfWeek( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate); break; case 'PageUp': highlightedDate = this.dateHelpers.subMonths( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate, 1); break; case 'PageDown': highlightedDate = this.dateHelpers.addMonths( // adding `new Date()` as the last option to satisfy Flow highlightedDate ? highlightedDate : currentDate, 1); break; } this.setState({ highlightedDate, date: highlightedDate }); }); _defineProperty(this, "focusCalendar", () => { if (!this.state.focused) { this.setState({ focused: true }); } }); _defineProperty(this, "blurCalendar", () => { if (typeof document !== 'undefined') { const activeElm = document.activeElement; if (this.calendar && !this.calendar.contains(activeElm)) { this.setState({ focused: false }); } } }); _defineProperty(this, "handleTabbing", event => { if (typeof document !== 'undefined') { if (event.keyCode === 9) { const activeElm = document.activeElement; // need to look for any tabindex >= 0 and ideally for not disabled // focusable by default elements like input, button, etc. const focusable = this.state.rootElement ? this.state.rootElement.querySelectorAll('[tabindex="0"]') : null; const length = focusable ? focusable.length : 0; if (event.shiftKey) { if (focusable && activeElm === focusable[0]) { event.preventDefault(); focusable[length - 1].focus(); } } else { if (focusable && activeElm === focusable[length - 1]) { event.preventDefault(); focusable[0].focus(); } } } } }); _defineProperty(this, "onDayFocus", data => { const { date } = data; this.setState({ highlightedDate: date }); this.focusCalendar(); this.props.onDayFocus && this.props.onDayFocus(data); }); _defineProperty(this, "onDayMouseOver", data => { const { date } = data; this.setState({ highlightedDate: date }); this.props.onDayMouseOver && this.props.onDayMouseOver(data); }); _defineProperty(this, "onDayMouseLeave", data => { const { date } = data; const { value } = this.props; const selected = this.getSingleDate(value); this.setState({ highlightedDate: selected || date }); this.props.onDayMouseLeave && this.props.onDayMouseLeave(data); }); /** Responsible for merging time values into date values. Note: the 'Day' component * determines how the days themselves change when a new day is selected. */ _defineProperty(this, "handleDateChange", data => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { onChange = params => {} } = this.props; let updatedDate = data.date; // Apply the currently selected time values (saved in state) to the updated date if (Array.isArray(data.date)) { // We'll need to update the date in time values of internal state const newTimeState = [...this.state.time]; const start = data.date[0] ? this.dateHelpers.applyDateToTime(newTimeState[0], data.date[0]) : null; const end = data.date[1] ? this.dateHelpers.applyDateToTime(newTimeState[1], data.date[1]) : null; newTimeState[0] = start; if (end) { updatedDate = [start, end]; newTimeState[1] = end; } else { updatedDate = [start]; } // Update the date in time values of internal state this.setState({ time: newTimeState }); } else if (!Array.isArray(this.props.value) && data.date) { const newTimeState = this.dateHelpers.applyDateToTime(this.state.time[0], data.date); updatedDate = newTimeState; // Update the date in time values of internal state this.setState({ time: [newTimeState] }); } onChange({ date: updatedDate }); }); _defineProperty(this, "handleTimeChange", (time, index) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { onChange = params => {} } = this.props; // Save/update the time value in internal state const newTimeState = [...this.state.time]; newTimeState[index] = this.dateHelpers.applyTimeToDate(newTimeState[index], time); this.setState({ time: newTimeState }); // Time change calls calendar's onChange handler // with the date value set to the date with updated time if (Array.isArray(this.props.value)) { const dates = this.props.value.map((date, i) => { if (date && index === i) { return this.dateHelpers.applyTimeToDate(date, time); } return date; }); onChange({ date: [dates[0], dates[1]] }); } else { const date = this.dateHelpers.applyTimeToDate(this.props.value, time); onChange({ date }); } }); _defineProperty(this, "renderMonths", translations => { const { overrides = {}, orientation } = this.props; const monthList = []; const [CalendarContainer, calendarContainerProps] = (0, _overrides.getOverrides)(overrides.CalendarContainer, _styledComponents.StyledCalendarContainer); const [MonthContainer, monthContainerProps] = (0, _overrides.getOverrides)(overrides.MonthContainer, _styledComponents.StyledMonthContainer); for (let i = 0; i < (this.props.monthsShown || 1); ++i) { const monthSubComponents = []; const monthDate = this.dateHelpers.addMonths(this.state.date, i); const monthKey = `month-${i}`; // @ts-ignore monthSubComponents.push(this.renderCalendarHeader(monthDate, i)); monthSubComponents.push( /*#__PURE__*/ // @ts-ignore React.createElement(CalendarContainer, _extends({ key: monthKey // @ts-ignore , ref: calendar => { this.calendar = calendar; }, role: "grid", "aria-roledescription": translations.ariaRoleDescCalMonth, "aria-multiselectable": this.props.range || null, onKeyDown: this.onKeyDown }, calendarContainerProps, { $density: this.props.density }), /*#__PURE__*/React.createElement(_month.default, { adapter: this.props.adapter, date: monthDate, dateLabel: this.props.dateLabel, density: this.props.density, excludeDates: this.props.excludeDates, filterDate: this.props.filterDate, highlightedDate: this.state.highlightedDate, includeDates: this.props.includeDates, focusedCalendar: this.state.focused, range: this.props.range, locale: this.props.locale, maxDate: this.props.maxDate, minDate: this.props.minDate, month: this.dateHelpers.getMonth(this.state.date), onDayBlur: this.blurCalendar, onDayFocus: this.onDayFocus, onDayClick: this.props.onDayClick, onDayMouseOver: this.onDayMouseOver, onDayMouseLeave: this.onDayMouseLeave, onChange: this.handleDateChange, overrides: overrides, value: this.props.value, peekNextMonth: this.props.peekNextMonth, fixedHeight: this.props.fixedHeight, hasLockedBehavior: !!this.props.hasLockedBehavior, selectedInput: this.props.selectedInput }))); // @ts-ignore monthList.push( /*#__PURE__*/React.createElement("div", { key: `month-component-${i}` }, monthSubComponents)); } return /*#__PURE__*/React.createElement(MonthContainer, _extends({ $orientation: orientation }, monthContainerProps), monthList); }); _defineProperty(this, "renderTimeSelect", (value, onChange, label) => { const { overrides = {} } = this.props; const [TimeSelectContainer, timeSelectContainerProps] = (0, _overrides.getOverrides)(overrides.TimeSelectContainer, _styledComponents.StyledSelectorContainer); const [TimeSelectFormControl, timeSelectFormControlProps] = (0, _overrides.getOverrides)(overrides.TimeSelectFormControl, _formControl.FormControl); const [TimeSelect, timeSelectProps] = (0, _overrides.getOverrides)(overrides.TimeSelect, _timepicker.default); return /*#__PURE__*/React.createElement(TimeSelectContainer, timeSelectContainerProps, /*#__PURE__*/React.createElement(TimeSelectFormControl, _extends({ label: label }, timeSelectFormControlProps), /*#__PURE__*/React.createElement(TimeSelect, _extends({ value: value ? this.dateHelpers.date(value) : value, onChange: onChange, nullable: true }, timeSelectProps)))); }); _defineProperty(this, "renderQuickSelect", () => { const { overrides = {} } = this.props; const [QuickSelectContainer, quickSelectContainerProps] = (0, _overrides.getOverrides)(overrides.QuickSelectContainer, _styledComponents.StyledSelectorContainer); const [QuickSelectFormControl, quickSelectFormControlProps] = (0, _overrides.getOverrides)(overrides.QuickSelectFormControl, _formControl.FormControl); const [QuickSelect, { overrides: quickSelectOverrides, ...restQuickSelectProps }] = (0, _overrides.getOverrides)( // overrides.QuickSelect, _select.Select); if (!this.props.quickSelect) { return null; } const NOW = this.dateHelpers.set(this.dateHelpers.date(), { hours: 12, minutes: 0, seconds: 0 }); return /*#__PURE__*/React.createElement(_locale.LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(QuickSelectContainer, quickSelectContainerProps, /*#__PURE__*/React.createElement(QuickSelectFormControl, _extends({ label: locale.datepicker.quickSelectLabel }, quickSelectFormControlProps), /*#__PURE__*/React.createElement(QuickSelect, _extends({ "aria-label": locale.datepicker.quickSelectAriaLabel, labelKey: "id" // @ts-ignore , onChange: params => { if (!params.option) { this.setState({ quickSelectId: null }); this.props.onChange && this.props.onChange({ date: [] }); } else { this.setState({ quickSelectId: params.option.id }); if (this.props.onChange) { if (this.props.range) { this.props.onChange({ date: [params.option.beginDate, params.option.endDate || NOW] }); } else { this.props.onChange({ date: params.option.beginDate }); } } } if (this.props.onQuickSelectChange) { this.props.onQuickSelectChange(params.option); } }, options: this.props.quickSelectOptions || [{ id: locale.datepicker.pastWeek, beginDate: this.dateHelpers.subWeeks(NOW, 1) }, { id: locale.datepicker.pastMonth, beginDate: this.dateHelpers.subMonths(NOW, 1) }, { id: locale.datepicker.pastThreeMonths, beginDate: this.dateHelpers.subMonths(NOW, 3) }, { id: locale.datepicker.pastSixMonths, beginDate: this.dateHelpers.subMonths(NOW, 6) }, { id: locale.datepicker.pastYear, beginDate: this.dateHelpers.subYears(NOW, 1) }, { id: locale.datepicker.pastTwoYears, beginDate: this.dateHelpers.subYears(NOW, 2) }], placeholder: locale.datepicker.quickSelectPlaceholder, value: this.state.quickSelectId && [{ id: this.state.quickSelectId }], overrides: (0, _overrides.mergeOverrides)({ Dropdown: { style: { textAlign: 'start' } } }, quickSelectOverrides) }, restQuickSelectProps))))); }); _defineProperty(this, "renderActionBar", () => { const { overrides = {}, primaryButton, secondaryButton } = this.props; const [ButtonDockComponent, buttonDockProps] = (0, _overrides.getOverrides)(overrides.ButtonDock, _buttonDock.ButtonDock); const [PrimaryButtonComponent, primaryButtonProps] = (0, _overrides.getOverrides)(overrides.PrimaryButton, _button.Button); const [SecondaryButtonComponent, secondaryButtonProps] = (0, _overrides.getOverrides)(overrides.SecondaryButton, _button.Button); const primaryButtonComponent = primaryButton != null ? /*#__PURE__*/React.createElement(PrimaryButtonComponent, _extends({ onClick: () => primaryButton.onClick() }, primaryButtonProps), primaryButton.label) : null; const secondaryButtonComponent = secondaryButton != null ? /*#__PURE__*/React.createElement(SecondaryButtonComponent, _extends({ onClick: () => secondaryButton.onClick(), kind: _button.KIND.tertiary }, secondaryButtonProps), secondaryButton.label) : null; if (primaryButtonComponent || secondaryButtonComponent) { return /*#__PURE__*/React.createElement(ButtonDockComponent, _extends({ primaryAction: primaryButtonComponent, dismissiveAction: secondaryButtonComponent, overrides: (0, _overrides.mergeOverrides)({ ActionSubContainer: { style: { flexDirection: 'row-reverse' } } }, buttonDockProps.overrides) }, buttonDockProps)); } return null; }); const { highlightedDate: _highlightedDate, value: _value, adapter } = this.props; // @ts-ignore this.dateHelpers = new _dateHelpers.default(adapter); const dateInView = this.getDateInView(); // @ts-ignore let _time = []; if (Array.isArray(_value)) { // @ts-ignore _time = [..._value]; } else if (_value) { // @ts-ignore _time = [_value]; } this.state = { highlightedDate: this.getSingleDate(_value) || (_highlightedDate && this.dateHelpers.isSameMonth(dateInView, _highlightedDate) ? _highlightedDate : this.dateHelpers.date()), focused: false, date: dateInView, quickSelectId: null, rootElement: null, // @ts-ignore time: _time }; } componentDidMount() { if (this.props.autoFocusCalendar) { this.focusCalendar(); } } componentDidUpdate(prevProps) { if (this.props.highlightedDate && !this.dateHelpers.isSameDay(this.props.highlightedDate, prevProps.highlightedDate)) { this.setState({ date: this.props.highlightedDate }); } if (this.props.autoFocusCalendar && this.props.autoFocusCalendar !== prevProps.autoFocusCalendar) { this.focusCalendar(); } if (prevProps.value !== this.props.value) { const nextDate = this.getDateInView(); if (!this.isInView(nextDate)) { this.setState({ date: nextDate }); } } } isInView(date) { // we calculate the month delta between the date arg and the date in the state. const currentDate = this.state.date; // First we get the year delta const yearDelta = this.dateHelpers.getYear(date) - this.dateHelpers.getYear(currentDate); // then we convert it to months. Then we simply add the date-without-year month delta back in. const monthDelta = yearDelta * 12 + this.dateHelpers.getMonth(date) - this.dateHelpers.getMonth(currentDate); // we just check that the delta is between the range given by "this month" (i.e. 0) and "the last month" (i.e. monthsShown) return monthDelta >= 0 && monthDelta < (this.props.monthsShown || 1); } getSingleDate(value) { // need to check this.props.range but flow would complain // at the return value in the else clause if (Array.isArray(value)) { return value[0] || null; } return value; } setHighlightedDate(date) { const { value } = this.props; const selected = this.getSingleDate(value); let nextState; if (selected && this.dateHelpers.isSameMonth(selected, date) && this.dateHelpers.isSameYear(selected, date)) { nextState = { highlightedDate: selected }; } else { nextState = { highlightedDate: date }; } this.setState(nextState); } render() { const { overrides = {} } = this.props; const [Root, rootProps] = (0, _overrides.getOverrides)(overrides.Root, _styledComponents.StyledRoot); // @ts-ignore const [startDate, endDate] = [].concat(this.props.value); return /*#__PURE__*/React.createElement(_locale.LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(Root, _extends({ $density: this.props.density, "data-baseweb": "calendar", role: "dialog", "aria-roledescription": "date picker", id: this.props.id // @ts-ignore , ref: root => { if (root && root instanceof HTMLElement && !this.state.rootElement) { this.setState({ rootElement: root }); } }, "aria-label": locale.datepicker.ariaLabelCalendar, onKeyDown: this.props.trapTabbing ? this.handleTabbing : null }, rootProps), this.renderMonths({ ariaRoleDescCalMonth: locale.datepicker.ariaRoleDescriptionCalendarMonth }), this.props.timeSelectStart && this.renderTimeSelect(startDate, // @ts-ignore time => this.handleTimeChange(time, 0), locale.datepicker.timeSelectStartLabel), this.props.timeSelectEnd && this.props.range && this.renderTimeSelect(endDate, // @ts-ignore time => this.handleTimeChange(time, 1), locale.datepicker.timeSelectEndLabel), this.renderQuickSelect(), this.renderActionBar())); } } exports.default = Calendar; _defineProperty(Calendar, "defaultProps", { autoFocusCalendar: false, dateLabel: null, density: _constants.DENSITY.default, excludeDates: null, filterDate: null, highlightedDate: null, includeDates: null, range: false, locale: null, maxDate: null, minDate: null, onDayClick: () => {}, onDayFocus: () => {}, onDayMouseOver: () => {}, onDayMouseLeave: () => {}, onMonthChange: () => {}, onYearChange: () => {}, onChange: () => {}, orientation: _constants.ORIENTATION.horizontal, overrides: {}, peekNextMonth: false, // @ts-ignore adapter: _dateFnsAdapter.default, value: null, trapTabbing: false });