wix-style-react
Version:
532 lines (439 loc) • 21.6 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
import _inherits from "@babel/runtime/helpers/inherits";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
var _excluded = ["onFocus"];
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
import React from 'react';
import PropTypes from 'prop-types';
import isSameDay from 'date-fns/isSameDay';
import setYear from 'date-fns/setYear';
import setMonth from 'date-fns/setMonth';
import setDate from 'date-fns/setDate';
import Popover from '../Popover';
import Calendar from '../Calendar';
import DateInput from './DateInput';
import { PopoverCommonProps } from '../common/PropTypes/PopoverCommon';
import deprecationLog from '../utils/deprecationLog';
import { st, classes } from './DatePicker.st.css';
import { dataHooks } from './constants';
import { WixStyleReactEnvironmentContext } from '../WixStyleReactEnvironmentProvider/context';
import { supportedWixlocales } from 'wix-design-systems-locale-utils';
/**
* DatePicker component
*
* ### Keyboard support
* * `Left`: Move to the previous day.
* * `Right`: Move to the next day.
* * `Up`: Move to the previous week.
* * `Down`: Move to the next week.
* * `PgUp`: Move to the previous month.
* * `PgDn`: Move to the next month.
* * `Home`: Move to the previous year.
* * `End`: Move to the next year.
* * `Enter`/`Esc`/`Tab`: close the calendar. (`Enter` & `Esc` calls `preventDefault`)
*
*/
var DatePicker = /*#__PURE__*/function (_React$PureComponent) {
_inherits(DatePicker, _React$PureComponent);
var _super = _createSuper(DatePicker);
function DatePicker(props) {
var _this;
_classCallCheck(this, DatePicker);
_this = _super.call(this, props);
_defineProperty(_assertThisInitialized(_this), "openCalendar", function () {
if (!_this.state.isOpen && !_this.props.readOnly) {
_this.setState({
isOpen: true,
isDateInputFocusable: false,
value: _this.props.value || new Date()
});
}
});
_defineProperty(_assertThisInitialized(_this), "closeCalendar", function () {
_this.setState({
isOpen: false
}, function () {
if (_this.props.onClose) {
_this.props.onClose();
}
});
/*
to fix case when user press tab in opened Calendar and:
1. Calendar become closed
2. Focus triggered
3. openCalendar triggered by focus
4. Calendar become visible
5. Looks like nothing happen
We need to do such steps:
1. Close calendar(with isDateInputFocusable: false)
2. After calendar is closed, on next event loop(after focus is fired) make isDateInputFocusable: focusable
to allow user to press tab in future and open Calendar
*/
setTimeout(function () {
return _this.makeInputFocusable();
});
});
_defineProperty(_assertThisInitialized(_this), "makeInputFocusable", function () {
return _this.setState({
isDateInputFocusable: true
});
});
_defineProperty(_assertThisInitialized(_this), "_handleInputChange", function (_ref) {
var dateVal = _ref.dateVal,
textVal = _ref.textVal;
_this._saveNewValue(dateVal);
_this.setState({
inputValue: textVal
});
});
_defineProperty(_assertThisInitialized(_this), "componentDidUpdate", function (prevProps) {
var prevValue = prevProps.value;
var newValue = _this.props.value;
if (newValue !== prevValue) {
if (newValue) {
_this._saveNewValue(newValue);
return _this.setState({
inputValue: newValue
});
}
var value = _this._transformDate(new Date(), prevValue);
return _this.setState({
inputValue: '',
value: value
});
}
});
_defineProperty(_assertThisInitialized(_this), "_transformDate", function (value, oldValue) {
return [[value.getFullYear(), setYear], [value.getMonth(), setMonth], [value.getDate(), setDate]].reduce(function (_value, _ref2) {
var _ref3 = _slicedToArray(_ref2, 2),
datePart = _ref3[0],
setter = _ref3[1];
return setter(_value, datePart);
}, oldValue);
});
_defineProperty(_assertThisInitialized(_this), "_saveNewValue", function (value) {
var modifiers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (modifiers.disabled) {
return;
}
var isChanged = !isSameDay(value, _this.props.value);
if (isChanged) {
var oldValue = _this.props.value || new Date(new Date().setHours(0, 0, 0, 0));
var newValue = _this._transformDate(value, oldValue);
_this.setState({
value: newValue
}, function () {
return _this.props.onChange(newValue);
});
}
});
_defineProperty(_assertThisInitialized(_this), "_handleKeyDown", function (event) {
// TODO: dirty for now
if (!_this.state.isOpen) {
_this.openCalendar();
}
/* keyHandler(this.state.value);*/
});
_defineProperty(_assertThisInitialized(_this), "_renderInput", function () {
var _this$props = _this.props,
inputDataHook = _this$props.inputDataHook,
disabled = _this$props.disabled,
placeholderText = _this$props.placeholderText,
readOnly = _this$props.readOnly,
status = _this$props.status,
statusMessage = _this$props.statusMessage,
customInput = _this$props.customInput,
dateFormatV2 = _this$props.dateFormatV2,
dateFormat = _this$props.dateFormat,
_this$props$inputProp = _this$props.inputProps,
inputProps = _this$props$inputProp === void 0 ? {} : _this$props$inputProp,
size = _this$props.size,
clearButton = _this$props.clearButton,
onClear = _this$props.onClear,
disableKeyboardType = _this$props.disableKeyboardType,
dateStyle = _this$props.dateStyle;
var _onFocus = inputProps.onFocus,
inputPropsRest = _objectWithoutProperties(inputProps, _excluded);
return /*#__PURE__*/React.createElement(DateInput, _extends({
dataHook: inputDataHook,
className: classes.input,
value: _this.state.inputValue
/* This line normally does nothing, because once clicked, component is already focused, hence onFocus
kicks in and open the calendar.
Why do we still keep this line? Backward compatibility for clients that test the component and simulate click
without simulating focus first. */
,
onInputClicked: _this.openCalendar,
disabled: disabled,
readOnly: readOnly,
placeholder: placeholderText,
onFocus: function onFocus(e) {
_onFocus && _onFocus(e);
_this.openCalendar(e);
},
onKeyDown: _this._handleKeyDown,
tabIndex: _this.state.isDateInputFocusable ? 0 : -1,
status: status,
statusMessage: statusMessage,
autoSelect: false,
dateFormat: dateFormat,
dateFormatV2: dateFormatV2,
customInput: customInput,
locale: _this._getLocale(),
dateStyle: dateStyle,
size: size,
clearButton: clearButton,
onClear: onClear,
onChange: _this._handleInputChange,
disableEditing: disableKeyboardType
}, customInput ? customInput.props : {}, inputPropsRest));
});
var initialOpen = props.initialOpen && !props.disabled;
_this.state = {
value: props.value || new Date(),
isOpen: initialOpen,
isDateInputFocusable: !props.initialOpen,
inputValue: props.value || ''
};
if (_this.props.dateFormat || _this.props.dateFormatV2) {
deprecationLog('dateFormat and dateFormatV2 props are deprecated and will be removed in the next major release. Date format is determined for you automatically.');
}
if (_this.props.disableKeyboardType) {
deprecationLog('disableKeyboardType is set to true by default, but in next major version this will be false. Make sure to handle keyboarboard input with onChange or sets disableKeyboardType prop to false.');
}
return _this;
}
_createClass(DatePicker, [{
key: "_getLocale",
value: function _getLocale() {
if (typeof this.props.locale !== 'string') {
deprecationLog('<DatePicker/> prop "locale" with value `date-fns locale object` is deprecated and will be removed in next major release, please pass a string instead');
}
return this.props.locale || this.context.locale || 'en';
}
}, {
key: "render",
value: function render() {
var _this$props2 = this.props,
className = _this$props2.className,
showMonthDropdown = _this$props2.showMonthDropdown,
showYearDropdown = _this$props2.showYearDropdown,
filterDate = _this$props2.filterDate,
excludePastDates = _this$props2.excludePastDates,
rtl = _this$props2.rtl,
shouldCloseOnSelect = _this$props2.shouldCloseOnSelect,
width = _this$props2.width,
calendarDataHook = _this$props2.calendarDataHook,
twoMonths = _this$props2.twoMonths,
zIndex = _this$props2.zIndex,
dataHook = _this$props2.dataHook,
popoverProps = _this$props2.popoverProps,
firstDayOfWeek = _this$props2.firstDayOfWeek,
leftArrowAriaLabel = _this$props2.leftArrowAriaLabel,
leftArrowAriaLabelledBy = _this$props2.leftArrowAriaLabelledBy,
rightArrowAriaLabel = _this$props2.rightArrowAriaLabel,
rightArrowAriaLabelledBy = _this$props2.rightArrowAriaLabelledBy,
monthDropdownAriaLabel = _this$props2.monthDropdownAriaLabel,
monthDropdownAriaLabelledBy = _this$props2.monthDropdownAriaLabelledBy,
yearDropdownAriaLabel = _this$props2.yearDropdownAriaLabel,
yearDropdownAriaLabelledBy = _this$props2.yearDropdownAriaLabelledBy,
disableKeyboardType = _this$props2.disableKeyboardType;
var _this$state = this.state,
isOpen = _this$state.isOpen,
value = _this$state.value;
var popoverUpdatedProps = _objectSpread({
placement: rtl ? 'top-end' : 'top-start'
}, popoverProps);
var calendarProps = {
dataHook: dataHooks.datePickerCalendar,
className: classes.calendar,
locale: this._getLocale(),
showMonthDropdown: showMonthDropdown,
showYearDropdown: showYearDropdown,
filterDate: filterDate,
excludePastDates: excludePastDates,
rtl: rtl,
onChange: this._saveNewValue,
onClose: this.closeCalendar,
value: value,
shouldCloseOnSelect: shouldCloseOnSelect,
numOfMonths: twoMonths ? 2 : 1,
firstDayOfWeek: firstDayOfWeek,
leftArrowAriaLabel: leftArrowAriaLabel,
leftArrowAriaLabelledBy: leftArrowAriaLabelledBy,
rightArrowAriaLabel: rightArrowAriaLabel,
rightArrowAriaLabelledBy: rightArrowAriaLabelledBy,
monthDropdownAriaLabel: monthDropdownAriaLabel,
monthDropdownAriaLabelledBy: monthDropdownAriaLabelledBy,
yearDropdownAriaLabel: yearDropdownAriaLabel,
yearDropdownAriaLabelledBy: yearDropdownAriaLabelledBy
};
return /*#__PURE__*/React.createElement("div", {
className: st(classes.root, {
disableKeyboardType: disableKeyboardType
}, className),
"data-hook": dataHook,
style: {
width: width
}
}, /*#__PURE__*/React.createElement(Popover, _extends({
className: classes.popover,
dataHook: dataHooks.datePickerPopover,
onClickOutside: this.closeCalendar,
appendTo: "parent",
shown: isOpen,
zIndex: zIndex
}, popoverUpdatedProps), /*#__PURE__*/React.createElement(Popover.Element, null, /*#__PURE__*/React.createElement("div", {
className: classes.inputContainer,
"data-hook": dataHooks.datePickerInputContainer
}, this._renderInput())), /*#__PURE__*/React.createElement(Popover.Content, null, /*#__PURE__*/React.createElement("div", {
"data-hook": calendarDataHook
}, /*#__PURE__*/React.createElement(Calendar, calendarProps)))));
}
}]);
return DatePicker;
}(React.PureComponent);
_defineProperty(DatePicker, "displayName", 'DatePicker');
_defineProperty(DatePicker, "defaultProps", {
filterDate: function filterDate() {
return true;
},
rtl: false,
width: '150px',
zIndex: 1,
disabled: false,
inputDataHook: dataHooks.datePickerInput,
popoverProps: {
zIndex: 1
},
disableKeyboardType: true,
onChange: function onChange() {},
dateStyle: 'short'
});
export { DatePicker as default };
DatePicker.contextType = WixStyleReactEnvironmentContext;
DatePicker.propTypes = {
/** Specify a single CSS class name to be appended to the root element */
className: PropTypes.string,
/** Applies as data-hook HTML attribute that can be used in the tests */
dataHook: PropTypes.string,
/** Focus selected day automatically when component mounts or updates */
autoFocus: PropTypes.bool,
/** Override a field with a custom input element. If you only need to pass custom props to the `<Input/>`, then use `inputProps` instead. */
customInput: PropTypes.node,
/** Allows you to pass default Input component properties */
inputProps: PropTypes.object,
/** this prop is deprecated and should not be used
* @deprecated
*/
dateFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** this prop is deprecated and should not be used
* @deprecated
*/
dateFormatV2: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
##### Add an indication under a specific date.
Function returns the indication node of a specific date or null if this day doesn't have an indication.
* - `param` {date: Date, isSelected: boolean }
* - `date` - a date
* - `isSelected` - whether this date is the selected date
* - `return` {React.node} - the indication node of a specific date or null if this day doesn't have an indication.
*/
dateIndication: PropTypes.func,
/** Specify date picker instance locale */
locale: PropTypes.oneOfType([PropTypes.oneOf(supportedWixlocales), PropTypes.shape({
code: PropTypes.string,
formatDistance: PropTypes.func,
formatRelative: PropTypes.func,
localize: PropTypes.object,
formatLong: PropTypes.object,
match: PropTypes.object,
options: PropTypes.object
})]),
/** Sets date format of locale */
dateStyle: PropTypes.oneOf(['short', 'medium']),
/** Specify whether a field should be disabled or not */
disabled: PropTypes.bool,
/** Specify whether past dates should be selectable or not */
excludePastDates: PropTypes.bool,
/**
* ##### Specify selectable dates:
* * `param` {Date} `date` - a date to check
* * `return` {boolean} - true if `date` should be selectable, false otherwise
*/
filterDate: PropTypes.func,
/** Applies a data-hook HTML attribute for date picker input */
inputDataHook: PropTypes.string,
/** Applies a data-hook HTML attribute for date picker calendar view */
calendarDataHook: PropTypes.string,
/** Defines a placeholder of the field */
placeholderText: PropTypes.string,
/** Specify whether RTL mode is enabled or not. When true, the keyboard navigation will be changed, meaning pressing on the right arrow will navigate to the previous day, and pressing on the left arrow will navigate to the next day. */
rtl: PropTypes.bool,
/** Defines the selected date */
value: PropTypes.object,
/** Specify whether the calendar will be initially visible or not */
initialOpen: PropTypes.bool,
/** Controls the status of a field */
status: PropTypes.oneOf(['error', 'warning', 'loading']),
/** Defines the status message to be displayed on status icon hover. If not given or empty, the tooltip won’t be shown. */
statusMessage: PropTypes.node,
/** Sets the width of picker input in pixels or percentage */
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** Set a desired z-index for date picker popover */
zIndex: PropTypes.number,
/** Allows you to pass popover properties. The default placement value depends on the rtl prop - would be 'top-start' when rtl=false and 'top-end' in case of rtl=ture. */
popoverProps: PropTypes.shape(PopoverCommonProps),
/** Specify the starting day of a week, allowing only from 0 to 6 (Sunday to Saturday). The default value is 1 which means Monday. */
firstDayOfWeek: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),
/** Specifies the size of the input */
size: PropTypes.oneOf(['small', 'medium', 'large']),
/** Specify whether date picker input is readOnly or not */
readOnly: PropTypes.bool,
/** Sets today's date. The today indication is added automatically according to the user timezone but in some cases, we need the ability to add the today indication based on the business timezone. */
today: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
/** Display a clear button (x) on a non-empty field */
clearButton: PropTypes.bool,
/** Provides a callback function when day in selected in the calendar */
onChange: PropTypes.func.isRequired,
/** Displays clear button (X) on a non-empty input and calls callback with no arguments */
onClear: PropTypes.func,
/** Defines a callback function that is called whenever a user presses escape, clicks outside of the element or a date is selected and `shouldCloseOnSelect` is set. Receives an event as a first argument. */
onClose: PropTypes.func,
/** Defines a callback function that is called with the date of the first day of the month whenever the user selects a month in the calendar */
onMonthChange: PropTypes.func,
/** Disable typing the in the input. When true, choosing a date is possible only by picking from the calendar. Default: true. */
disableKeyboardType: PropTypes.bool,
/** Displays a selectable yearDropdown */
showYearDropdown: PropTypes.bool,
/** Displays a selectable monthDropdown */
showMonthDropdown: PropTypes.bool,
/** Specify whether the calendar closes on day selection */
shouldCloseOnSelect: PropTypes.bool,
/** Defines a string value that labels the months dropdown in calendar header */
monthDropdownAriaLabel: PropTypes.string,
/** Identifies the element that labels the months dropdown in calendar header */
monthDropdownAriaLabelledBy: PropTypes.string,
/** Defines a string value that labels the years dropdown in calendar header */
yearDropdownAriaLabel: PropTypes.string,
/** Identifies the element that labels the years dropdown in calendar header */
yearDropdownAriaLabelledBy: PropTypes.string,
/** Defines a string value that labels the left arrow in calendar header */
leftArrowAriaLabel: PropTypes.string,
/** Identifies the element that labels the left arrow in calendar header */
leftArrowAriaLabelledBy: PropTypes.string,
/** Defines a string value that labels the right arrow in calendar header */
rightArrowAriaLabel: PropTypes.string,
/** Identifies the element that labels the right arrow in calendar header */
rightArrowAriaLabelledBy: PropTypes.string
};