baseui
Version:
A React Component library implementing the Base design language
537 lines (530 loc) • 23.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.DEFAULT_DATE_FORMAT = void 0;
var React = _interopRequireWildcard(require("react"));
var _reactUid = require("react-uid");
var _input = require("../input");
var _popover = require("../popover");
var _calendar = _interopRequireDefault(require("./calendar"));
var _overrides = require("../helpers/overrides");
var _locale = require("../locale");
var _styledComponents = require("./styled-components");
var _dateHelpers = _interopRequireDefault(require("./utils/date-helpers"));
var _dateFnsAdapter = _interopRequireDefault(require("./utils/date-fns-adapter"));
var _constants = require("./constants");
var _select = require("../select");
var _icon = require("../icon");
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.
*/
const DEFAULT_DATE_FORMAT = exports.DEFAULT_DATE_FORMAT = 'yyyy/MM/dd';
const INPUT_DELIMITER = '–';
// @ts-ignore
const combineSeparatedInputs = (newInputValue, prevCombinedInputValue = '', inputRole) => {
let inputValue = newInputValue;
const [prevStartDate = '', prevEndDate = ''] = prevCombinedInputValue.split(` ${INPUT_DELIMITER} `);
if (inputRole === _constants.INPUT_ROLE.startDate && prevEndDate) {
inputValue = `${inputValue} ${INPUT_DELIMITER} ${prevEndDate}`;
}
if (inputRole === _constants.INPUT_ROLE.endDate) {
inputValue = `${prevStartDate} ${INPUT_DELIMITER} ${inputValue}`;
}
return inputValue;
};
class Datepicker_DO_NOT_USE extends React.Component {
constructor(props) {
super(props);
// @ts-ignore
_defineProperty(this, "calendar", void 0);
_defineProperty(this, "dateHelpers", void 0);
_defineProperty(this, "calendarID", void 0);
_defineProperty(this, "handleChange", date => {
const onChange = this.props.onChange;
const onRangeChange = this.props.onRangeChange;
if (Array.isArray(date)) {
if (onChange && date.every(Boolean)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange({
date: date
});
}
if (onRangeChange) {
onRangeChange({
date: [...date]
});
}
} else {
if (onChange) {
onChange({
date
});
}
if (onRangeChange) {
onRangeChange({
date
});
}
}
});
_defineProperty(this, "onCalendarSelect", data => {
let isOpen = false;
let isPseudoFocused = false;
let calendarFocused = false;
let nextDate = data.date;
if (Array.isArray(nextDate) && this.props.range) {
if (!nextDate[0] || !nextDate[1]) {
isOpen = true;
isPseudoFocused = true;
// @ts-ignore
calendarFocused = null;
} else if (nextDate[0] && nextDate[1]) {
const [start, end] = nextDate;
if (this.dateHelpers.isAfter(start, end)) {
if (this.hasLockedBehavior()) {
nextDate = this.props.value;
isOpen = true;
} else {
nextDate = [start, start];
}
} else if (this.dateHelpers.dateRangeIncludesDates(nextDate, this.props.excludeDates)) {
nextDate = this.props.value;
isOpen = true;
}
if (this.state.lastActiveElm) {
this.state.lastActiveElm.focus();
}
}
} else if (this.state.lastActiveElm) {
this.state.lastActiveElm.focus();
}
// Time selectors previously caused the calendar popover to close.
// The check below refrains from closing the popover if only times changed.
const onlyTimeChanged = (prev, next) => {
if (!prev || !next) return false;
const p = this.dateHelpers.format(prev, 'keyboardDate');
const n = this.dateHelpers.format(next, 'keyboardDate');
if (p === n) {
return this.dateHelpers.getHours(prev) !== this.dateHelpers.getHours(next) || this.dateHelpers.getMinutes(prev) !== this.dateHelpers.getMinutes(next);
}
return false;
};
const prevValue = this.props.value;
if (Array.isArray(nextDate) && Array.isArray(prevValue)) {
if (nextDate.some((d, i) => onlyTimeChanged(prevValue[i], d))) {
isOpen = true;
}
} else if (!Array.isArray(nextDate) && !Array.isArray(prevValue)) {
if (onlyTimeChanged(prevValue, nextDate)) {
isOpen = true;
}
}
// If nextDate is an array but the datepicker is not ranged, we assign
// nextDate directly to the Date value to avoid formatting issues
if (Array.isArray(nextDate) && !this.props.range) {
nextDate = nextDate[0];
}
this.setState({
isOpen,
isPseudoFocused,
...(calendarFocused === null ? {} : {
calendarFocused
}),
inputValue: this.formatDisplayValue(nextDate)
});
this.handleChange(nextDate);
});
_defineProperty(this, "formatDisplayValue", date => {
const {
displayValueAtRangeIndex,
formatDisplayValue,
range
} = this.props;
// @ts-ignore
const formatString = this.normalizeDashes(this.props.formatString);
if (typeof displayValueAtRangeIndex === 'number') {
if (process.env.NODE_ENV !== "production") {
if (!range) {
console.error('displayValueAtRangeIndex only applies if range');
}
if (range && displayValueAtRangeIndex > 1) {
console.error('displayValueAtRangeIndex value must be 0 or 1');
}
}
if (date && Array.isArray(date)) {
const value = date[displayValueAtRangeIndex];
if (formatDisplayValue) {
return formatDisplayValue(value, formatString);
}
return this.formatDate(value, formatString);
}
}
if (formatDisplayValue) {
return formatDisplayValue(date, formatString);
}
return this.formatDate(date, formatString);
});
_defineProperty(this, "open", inputRole => {
this.setState({
isOpen: true,
isPseudoFocused: true,
calendarFocused: true,
selectedInput: inputRole
}, this.props.onOpen);
});
_defineProperty(this, "close", () => {
const isPseudoFocused = false;
this.setState({
isOpen: false,
selectedInput: null,
isPseudoFocused,
calendarFocused: false
}, this.props.onClose);
});
_defineProperty(this, "handleEsc", () => {
if (this.state.lastActiveElm) {
this.state.lastActiveElm.focus();
}
this.close();
});
_defineProperty(this, "handleInputBlur", () => {
if (!this.state.isPseudoFocused) {
this.close();
}
});
_defineProperty(this, "getMask", () => {
const {
formatString,
mask,
range
} = this.props;
if (mask === null || mask === undefined && formatString !== DEFAULT_DATE_FORMAT) {
return null;
}
if (mask) {
return this.normalizeDashes(mask);
}
if (range) {
return `9999/99/99 ${INPUT_DELIMITER} 9999/99/99`;
}
return '9999/99/99';
});
_defineProperty(this, "handleInputChange", (event, inputRole) => {
const inputValue = this.props.range ? combineSeparatedInputs(event.currentTarget.value, this.state.inputValue, inputRole) : event.currentTarget.value;
const mask = this.getMask();
// @ts-ignore
const formatString = this.normalizeDashes(this.props.formatString);
if (typeof mask === 'string' && inputValue === mask.replace(/9/g, ' ') || inputValue.length === 0) {
if (this.props.range) {
this.handleChange([]);
} else {
this.handleChange(null);
}
}
this.setState({
inputValue
});
const parseDateString = dateString => {
if (formatString === DEFAULT_DATE_FORMAT) {
return this.dateHelpers.parse(dateString, 'slashDate', this.props.locale);
}
return this.dateHelpers.parseString(dateString, formatString, this.props.locale);
};
if (this.props.range && typeof this.props.displayValueAtRangeIndex !== 'number') {
const [left, right] = this.normalizeDashes(inputValue).split(` ${INPUT_DELIMITER} `);
let startDate = this.dateHelpers.date(left);
let endDate = this.dateHelpers.date(right);
if (formatString) {
startDate = parseDateString(left);
endDate = parseDateString(right);
}
const datesValid = this.dateHelpers.isValid(startDate) && this.dateHelpers.isValid(endDate);
// added equal case so that times within the same day can be expressed
const rangeValid = this.dateHelpers.isAfter(endDate, startDate) || this.dateHelpers.isEqual(startDate, endDate);
if (datesValid && rangeValid) {
this.handleChange([startDate, endDate]);
}
} else {
const dateString = this.normalizeDashes(inputValue);
let date = this.dateHelpers.date(dateString);
const formatString = this.props.formatString;
// Prevent early parsing of value.
// Eg 25.12.2 will be transformed to 25.12.0002 formatted from date to string
// @ts-ignore
if (dateString.replace(/(\s)*/g, '').length < formatString.replace(/(\s)*/g, '').length) {
// @ts-ignore
date = null;
} else {
date = parseDateString(dateString);
}
const {
displayValueAtRangeIndex,
range,
value
} = this.props;
if (date && this.dateHelpers.isValid(date)) {
if (range && Array.isArray(value) && typeof displayValueAtRangeIndex === 'number') {
let [left, right] = value;
if (displayValueAtRangeIndex === 0) {
left = date;
if (!right) {
this.handleChange([left]);
} else {
if (this.dateHelpers.isAfter(right, left) || this.dateHelpers.isEqual(left, right)) {
this.handleChange([left, right]);
} else {
// Is resetting back to previous value appropriate? Invalid range is not
// communicated to the user, but if it was not reset the text value would
// show one value and date value another. This seems a bit better but clearly
// has a downside.
this.handleChange([...value]);
}
}
} else if (displayValueAtRangeIndex === 1) {
right = date;
if (!left) {
// If start value is not defined, set start/end to the same day.
this.handleChange([right, right]);
} else {
if (this.dateHelpers.isAfter(right, left) || this.dateHelpers.isEqual(left, right)) {
this.handleChange([left, right]);
} else {
// See comment above about resetting dates on invalid range
this.handleChange([...value]);
}
}
}
} else {
this.handleChange(date);
}
}
}
});
_defineProperty(this, "handleKeyDown", (event, inputRole) => {
const keyEvents = new Set();
keyEvents.add('ArrowDown');
keyEvents.add('Space');
keyEvents.add('Enter');
if (!this.state.isOpen && keyEvents.has(event.code)) {
this.open(inputRole);
} else if (this.state.isOpen && keyEvents.has(event.code)) {
// next line prevents the page jump on the initial arrowDown
event.preventDefault();
this.focusCalendar();
} else if (this.state.isOpen && event.code === 'Tab') {
this.close();
}
});
_defineProperty(this, "focusCalendar", () => {
if (typeof document !== 'undefined') {
const lastActiveElm = document.activeElement;
this.setState({
calendarFocused: true,
lastActiveElm
});
}
});
_defineProperty(this, "normalizeDashes", inputValue => {
// replacing both hyphens and em-dashes with en-dashes
return inputValue.replace(/-/g, INPUT_DELIMITER).replace(/—/g, INPUT_DELIMITER);
});
_defineProperty(this, "generateAriaDescribedByIds", () => {
let idList = this.state.ariaDescribedby;
if (this.props['aria-describedby']) {
idList = `${this.props['aria-describedby']} ${idList}`;
}
return idList;
});
_defineProperty(this, "hasLockedBehavior", () => {
return this.props.rangedCalendarBehavior === _constants.RANGED_CALENDAR_BEHAVIOR.locked && this.props.range;
});
_defineProperty(this, "getPlaceholder", () => this.props.placeholder || this.props.placeholder === '' ? this.props.placeholder : this.props.range ? `YYYY/MM/DD ${INPUT_DELIMITER} YYYY/MM/DD` : 'YYYY/MM/DD');
this.dateHelpers = new _dateHelpers.default(props.adapter);
this.state = {
calendarFocused: false,
isOpen: false,
selectedInput: null,
isPseudoFocused: false,
lastActiveElm: null,
inputValue: this.formatDisplayValue(props.value) || '',
ariaDescribedby: null // we initialize this post-mount to prevent SSR hydration issue
};
this.calendarID = `${(0, _reactUid.uid)(this)}-calendar`;
}
componentDidMount() {
this.setState({
ariaDescribedby: (0, _reactUid.uid)(this)
});
}
getNullDatePlaceholder(formatString) {
return (this.getMask() || formatString).split(INPUT_DELIMITER)[0].replace(/[0-9]|[a-z]/g, ' ');
}
formatDate(date, formatString) {
const format = date => {
if (formatString === DEFAULT_DATE_FORMAT) {
return this.dateHelpers.format(date, 'slashDate', this.props.locale);
}
return this.dateHelpers.formatDate(date, formatString, this.props.locale);
};
if (!date) {
return '';
} else if (Array.isArray(date) && !date[0] && !date[1]) {
return '';
} else if (Array.isArray(date) && !date[0] && date[1]) {
const endDate = format(date[1]);
const startDate = this.getNullDatePlaceholder(formatString);
return [startDate, endDate].join(` ${INPUT_DELIMITER} `);
} else if (Array.isArray(date)) {
return date.map(day => day ? format(day) : '').join(` ${INPUT_DELIMITER} `);
} else {
return format(date);
}
}
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
this.setState({
inputValue: this.formatDisplayValue(this.props.value)
});
}
}
renderInputComponent(locale, inputRole) {
const {
overrides = {}
} = this.props;
const [InputContainer, inputContainerProps] = (0, _overrides.getOverrides)(overrides.InputContainer, _styledComponents.StyledInputContainer);
const [InputComponent, inputProps] = (0, _overrides.getOverrides)(overrides.Input, _input.MaskedInput);
const inputLabel = this.props['aria-label'] || `${this.props.range ? locale.datepicker.ariaLabelRange : locale.datepicker.ariaLabel}`;
const [startDate = '', endDate = ''] = (this.state.inputValue || '').split(` ${INPUT_DELIMITER} `);
const value = inputRole === _constants.INPUT_ROLE.startDate ? startDate : inputRole === _constants.INPUT_ROLE.endDate ? endDate : this.state.inputValue;
return /*#__PURE__*/React.createElement(InputContainer, _extends({
role: "combobox",
"aria-expanded": this.state.isOpen,
"aria-haspopup": 'dialog',
"aria-controls": this.calendarID
}, inputContainerProps), /*#__PURE__*/React.createElement(InputComponent, _extends({
"aria-disabled": this.props.disabled,
"aria-label": inputLabel,
error: this.props.error,
positive: this.props.positive,
"aria-describedby": this.generateAriaDescribedByIds(),
"aria-labelledby": this.props['aria-labelledby'],
"aria-required": this.props.required || null,
disabled: this.props.disabled,
size: this.props.size,
value: value,
onBlur: this.handleInputBlur,
onKeyDown: event => this.handleKeyDown(event, inputRole),
onChange: event => this.handleInputChange(event, inputRole),
placeholder: this.getPlaceholder(),
mask: this.getMask(),
required: this.props.required,
clearable: this.props.clearable
}, inputProps)));
}
renderCalendarSelect() {
const {
overrides = {}
} = this.props;
const [CalendarSelectComponent, calendarSelectProps] = (0, _overrides.getOverrides)(overrides.CalendarSelect, _select.Select);
return /*#__PURE__*/React.createElement(CalendarSelectComponent, _extends({
"aria-describedby": this.props['aria-describedby'],
"aria-labelledby": this.props['aria-labelledby'],
onBlur: this.handleInputBlur,
size: this.props.size,
searchable: false,
placeholder: /*#__PURE__*/React.createElement(_icon.Calendar, null),
overrides: (0, _overrides.mergeOverrides)({
Root: {
style: {
minWidth: '75px'
}
},
ControlContainer: {
props: {
onKeyDown: this.handleKeyDown,
onClick: this.open
},
style: ({
$theme
}) => {
return {
cursor: 'pointer',
borderColor: this.state.calendarFocused || this.state.isPseudoFocused ? $theme.colors.borderSelected : $theme.colors.inputBorder
};
}
}
}, calendarSelectProps.overrides)
}, calendarSelectProps));
}
render() {
const {
overrides = {},
startDateLabel = 'Start Date',
endDateLabel = 'End Date'
} = this.props;
const [PopoverComponent, popoverProps] = (0, _overrides.getOverrides)(overrides.Popover, _popover.Popover);
const [InputWrapper, inputWrapperProps] = (0, _overrides.getOverrides)(overrides.InputWrapper, _styledComponents.StyledInputWrapper);
const [StartDate, startDateProps] = (0, _overrides.getOverrides)(overrides.StartDate, _styledComponents.StyledStartDate);
const [EndDate, endDateProps] = (0, _overrides.getOverrides)(overrides.EndDate, _styledComponents.StyledEndDate);
const [InputLabel, inputLabelProps] = (0, _overrides.getOverrides)(overrides.InputLabel, _styledComponents.StyledInputLabel);
const singleDateFormatString = this.props.formatString || DEFAULT_DATE_FORMAT;
const formatString = this.props.range ? `${singleDateFormatString} ${INPUT_DELIMITER} ${singleDateFormatString}` : singleDateFormatString;
return /*#__PURE__*/React.createElement(_locale.LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(PopoverComponent, _extends({
accessibilityType: _popover.ACCESSIBILITY_TYPE.none,
focusLock: false,
autoFocus: false,
mountNode: this.props.mountNode,
placement: _popover.PLACEMENT.bottom,
isOpen: this.state.isOpen,
onClickOutside: this.close,
onEsc: this.handleEsc,
content: /*#__PURE__*/React.createElement(_calendar.default, _extends({
adapter: this.props.adapter,
autoFocusCalendar: this.state.calendarFocused,
trapTabbing: true,
value: this.props.value,
onChange: this.onCalendarSelect,
selectedInput: this.state.selectedInput,
hasLockedBehavior: this.hasLockedBehavior(),
primaryButton: {
label: 'Done',
onClick: this.close
},
secondaryButton: {
label: 'Cancel',
onClick: this.close
},
id: this.calendarID
}, this.props))
}, popoverProps), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'row',
gap: '8px'
}
}, /*#__PURE__*/React.createElement(InputWrapper, _extends({}, inputWrapperProps, {
$separateRangeInputs: this.props.range
}), this.props.range ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(StartDate, startDateProps, /*#__PURE__*/React.createElement(InputLabel, inputLabelProps, startDateLabel), this.renderInputComponent(locale, _constants.INPUT_ROLE.startDate)), /*#__PURE__*/React.createElement(EndDate, endDateProps, /*#__PURE__*/React.createElement(InputLabel, inputLabelProps, endDateLabel), this.renderInputComponent(locale, _constants.INPUT_ROLE.endDate))) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(InputLabel, inputLabelProps, startDateLabel), this.renderInputComponent(locale))), /*#__PURE__*/React.createElement("div", {
style: {
marginTop: '28px'
}
}, this.renderCalendarSelect())))));
}
}
exports.default = Datepicker_DO_NOT_USE;
_defineProperty(Datepicker_DO_NOT_USE, "defaultProps", {
'aria-describedby': null,
// @ts-ignore
value: null,
formatString: DEFAULT_DATE_FORMAT,
adapter: _dateFnsAdapter.default
});