react-day-picker
Version:
Flexible date picker component for React
683 lines (601 loc) • 22.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HIDE_TIMEOUT = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _extends = Object.assign || 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; };
exports.OverlayComponent = OverlayComponent;
exports.defaultFormat = defaultFormat;
exports.defaultParse = defaultParse;
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _DayPicker = require('./DayPicker');
var _DayPicker2 = _interopRequireDefault(_DayPicker);
var _DateUtils = require('./DateUtils');
var _ModifiersUtils = require('./ModifiersUtils');
var _keys = require('./keys');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
// When clicking on a day cell, overlay will be hidden after this timeout
var HIDE_TIMEOUT = exports.HIDE_TIMEOUT = 100;
/**
* The default component used as Overlay.
*
* @param {Object} props
*/
function OverlayComponent(_ref) {
var input = _ref.input,
selectedDay = _ref.selectedDay,
month = _ref.month,
children = _ref.children,
classNames = _ref.classNames,
props = _objectWithoutProperties(_ref, ['input', 'selectedDay', 'month', 'children', 'classNames']);
return _react2.default.createElement(
'div',
_extends({ className: classNames.overlayWrapper }, props),
_react2.default.createElement(
'div',
{ className: classNames.overlay },
children
)
);
}
OverlayComponent.propTypes = {
input: _propTypes2.default.any,
selectedDay: _propTypes2.default.any,
month: _propTypes2.default.instanceOf(Date),
children: _propTypes2.default.node,
classNames: _propTypes2.default.object
};
/**
* The default function used to format a Date to String, passed to the `format`
* prop.
* @param {Date} d
* @return {String}
*/
function defaultFormat(d) {
if ((0, _DateUtils.isDate)(d)) {
var year = d.getFullYear();
var month = '' + (d.getMonth() + 1);
var day = '' + d.getDate();
return year + '-' + month + '-' + day;
}
return '';
}
/**
* The default function used to parse a String as Date, passed to the `parse`
* prop.
* @param {String} str
* @return {Date}
*/
function defaultParse(str) {
if (typeof str !== 'string') {
return undefined;
}
var split = str.split('-');
if (split.length !== 3) {
return undefined;
}
var year = parseInt(split[0], 10);
var month = parseInt(split[1], 10) - 1;
var day = parseInt(split[2], 10);
if (isNaN(year) || String(year).length > 4 || isNaN(month) || isNaN(day) || day <= 0 || day > 31 || month < 0 || month >= 12) {
return undefined;
}
return new Date(year, month, day, 12, 0, 0, 0); // always set noon to avoid time zone issues
}
var DayPickerInput = function (_React$Component) {
_inherits(DayPickerInput, _React$Component);
function DayPickerInput(props) {
_classCallCheck(this, DayPickerInput);
var _this = _possibleConstructorReturn(this, (DayPickerInput.__proto__ || Object.getPrototypeOf(DayPickerInput)).call(this, props));
_this.input = null;
_this.daypicker = null;
_this.clickTimeout = null;
_this.hideTimeout = null;
_this.inputBlurTimeout = null;
_this.inputFocusTimeout = null;
_this.state = _this.getInitialStateFromProps(props);
_this.state.showOverlay = props.showOverlay;
_this.hideAfterDayClick = _this.hideAfterDayClick.bind(_this);
_this.handleInputClick = _this.handleInputClick.bind(_this);
_this.handleInputFocus = _this.handleInputFocus.bind(_this);
_this.handleInputBlur = _this.handleInputBlur.bind(_this);
_this.handleInputChange = _this.handleInputChange.bind(_this);
_this.handleInputKeyDown = _this.handleInputKeyDown.bind(_this);
_this.handleInputKeyUp = _this.handleInputKeyUp.bind(_this);
_this.handleDayClick = _this.handleDayClick.bind(_this);
_this.handleMonthChange = _this.handleMonthChange.bind(_this);
_this.handleOverlayFocus = _this.handleOverlayFocus.bind(_this);
_this.handleOverlayBlur = _this.handleOverlayBlur.bind(_this);
return _this;
}
_createClass(DayPickerInput, [{
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps) {
var newState = {};
// Current props
var _props = this.props,
value = _props.value,
formatDate = _props.formatDate,
format = _props.format,
dayPickerProps = _props.dayPickerProps;
// Update the input value if `value`, `dayPickerProps.locale` or `format`
// props have changed
if (value !== prevProps.value || dayPickerProps.locale !== prevProps.dayPickerProps.locale || format !== prevProps.format) {
if ((0, _DateUtils.isDate)(value)) {
newState.value = formatDate(value, format, dayPickerProps.locale);
} else {
newState.value = value;
}
}
// Update the month if the months from props changed
var prevMonth = prevProps.dayPickerProps.month;
if (dayPickerProps.month && dayPickerProps.month !== prevMonth && !(0, _DateUtils.isSameMonth)(dayPickerProps.month, prevMonth)) {
newState.month = dayPickerProps.month;
}
// Updated the selected days from props if they changed
if (prevProps.dayPickerProps.selectedDays !== dayPickerProps.selectedDays) {
newState.selectedDays = dayPickerProps.selectedDays;
}
if (Object.keys(newState).length > 0) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState(newState);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
clearTimeout(this.clickTimeout);
clearTimeout(this.hideTimeout);
clearTimeout(this.inputFocusTimeout);
clearTimeout(this.inputBlurTimeout);
clearTimeout(this.overlayBlurTimeout);
}
}, {
key: 'getInitialMonthFromProps',
value: function getInitialMonthFromProps(props) {
var dayPickerProps = props.dayPickerProps,
format = props.format;
var day = void 0;
if (props.value) {
if ((0, _DateUtils.isDate)(props.value)) {
day = props.value;
} else {
day = props.parseDate(props.value, format, dayPickerProps.locale);
}
}
return dayPickerProps.initialMonth || dayPickerProps.month || day || new Date();
}
}, {
key: 'getInitialStateFromProps',
value: function getInitialStateFromProps(props) {
var dayPickerProps = props.dayPickerProps,
formatDate = props.formatDate,
format = props.format,
typedValue = props.typedValue;
var value = props.value;
if (props.value && (0, _DateUtils.isDate)(props.value)) {
value = formatDate(props.value, format, dayPickerProps.locale);
}
return {
value: value,
typedValue: typedValue,
month: this.getInitialMonthFromProps(props),
selectedDays: dayPickerProps.selectedDays
};
}
}, {
key: 'getInput',
value: function getInput() {
return this.input;
}
}, {
key: 'getDayPicker',
value: function getDayPicker() {
return this.daypicker;
}
/**
* Update the component's state and fire the `onDayChange` event passing the
* day's modifiers to it.
*
* @param {Date} day - Will be used for changing the month
* @param {String} value - Input field value
* @private
*/
}, {
key: 'updateState',
value: function updateState(day, value, callback) {
var _this2 = this;
var _props2 = this.props,
dayPickerProps = _props2.dayPickerProps,
onDayChange = _props2.onDayChange;
this.setState({ month: day, value: value, typedValue: '' }, function () {
if (callback) {
callback();
}
if (!onDayChange) {
return;
}
var modifiersObj = _extends({
disabled: dayPickerProps.disabledDays,
selected: dayPickerProps.selectedDays
}, dayPickerProps.modifiers);
var modifiers = (0, _ModifiersUtils.getModifiersForDay)(day, modifiersObj).reduce(function (obj, modifier) {
return _extends({}, obj, _defineProperty({}, modifier, true));
}, {});
onDayChange(day, modifiers, _this2);
});
}
/**
* Show the Day Picker overlay.
*
* @memberof DayPickerInput
*/
}, {
key: 'showDayPicker',
value: function showDayPicker() {
var _this3 = this;
var _props3 = this.props,
parseDate = _props3.parseDate,
format = _props3.format,
dayPickerProps = _props3.dayPickerProps;
var _state = this.state,
value = _state.value,
showOverlay = _state.showOverlay;
if (showOverlay) {
return;
}
// Reset the current displayed month when showing the overlay
var month = value ? parseDate(value, format, dayPickerProps.locale) // Use the month in the input field
: this.getInitialMonthFromProps(this.props); // Restore the month from the props
this.setState(function (state) {
return {
showOverlay: true,
month: month || state.month
};
}, function () {
if (_this3.props.onDayPickerShow) _this3.props.onDayPickerShow();
});
}
/**
* Hide the Day Picker overlay
*
* @memberof DayPickerInput
*/
}, {
key: 'hideDayPicker',
value: function hideDayPicker() {
var _this4 = this;
if (this.state.showOverlay === false) {
return;
}
this.setState({ showOverlay: false }, function () {
if (_this4.props.onDayPickerHide) _this4.props.onDayPickerHide();
});
}
}, {
key: 'hideAfterDayClick',
value: function hideAfterDayClick() {
var _this5 = this;
if (!this.props.hideOnDayClick) {
return;
}
this.hideTimeout = setTimeout(function () {
_this5.overlayHasFocus = false;
_this5.hideDayPicker();
}, HIDE_TIMEOUT);
}
}, {
key: 'handleInputClick',
value: function handleInputClick(e) {
this.showDayPicker();
if (this.props.inputProps.onClick) {
e.persist();
this.props.inputProps.onClick(e);
}
}
}, {
key: 'handleInputFocus',
value: function handleInputFocus(e) {
var _this6 = this;
this.showDayPicker();
// Set `overlayHasFocus` after a timeout so the overlay can be hidden when
// the input is blurred
this.inputFocusTimeout = setTimeout(function () {
_this6.overlayHasFocus = false;
}, 2);
if (this.props.inputProps.onFocus) {
e.persist();
this.props.inputProps.onFocus(e);
}
}
// When the input is blurred, the overlay should disappear. However the input
// is blurred also when the user interacts with the overlay (e.g. the overlay
// get the focus by clicking it). In these cases, the overlay should not be
// hidden. There are different approaches to avoid hiding the overlay when
// this happens, but the only cross-browser hack we’ve found is to set all
// these timeouts in code before changing `overlayHasFocus`.
}, {
key: 'handleInputBlur',
value: function handleInputBlur(e) {
var _this7 = this;
this.inputBlurTimeout = setTimeout(function () {
if (!_this7.overlayHasFocus) {
_this7.hideDayPicker();
}
}, 1);
if (this.props.inputProps.onBlur) {
e.persist();
this.props.inputProps.onBlur(e);
}
}
}, {
key: 'handleOverlayFocus',
value: function handleOverlayFocus(e) {
e.preventDefault();
this.overlayHasFocus = true;
if (!this.props.keepFocus || !this.input || typeof this.input.focus !== 'function') {
return;
}
this.input.focus();
}
}, {
key: 'handleOverlayBlur',
value: function handleOverlayBlur() {
var _this8 = this;
// We need to set a timeout otherwise IE11 will hide the overlay when
// focusing it
this.overlayBlurTimeout = setTimeout(function () {
_this8.overlayHasFocus = false;
}, 3);
}
}, {
key: 'handleInputChange',
value: function handleInputChange(e) {
var _props4 = this.props,
dayPickerProps = _props4.dayPickerProps,
format = _props4.format,
inputProps = _props4.inputProps,
onDayChange = _props4.onDayChange,
parseDate = _props4.parseDate;
if (inputProps.onChange) {
e.persist();
inputProps.onChange(e);
}
var value = e.target.value;
if (value.trim() === '') {
this.setState({ value: value, typedValue: '' });
if (onDayChange) onDayChange(undefined, {}, this);
return;
}
var day = parseDate(value, format, dayPickerProps.locale);
if (!day) {
// Day is invalid: we save the value in the typedValue state
this.setState({ value: value, typedValue: value });
if (onDayChange) onDayChange(undefined, {}, this);
return;
}
this.updateState(day, value);
}
}, {
key: 'handleInputKeyDown',
value: function handleInputKeyDown(e) {
if (e.keyCode === _keys.TAB) {
this.hideDayPicker();
} else {
this.showDayPicker();
}
if (this.props.inputProps.onKeyDown) {
e.persist();
this.props.inputProps.onKeyDown(e);
}
}
}, {
key: 'handleInputKeyUp',
value: function handleInputKeyUp(e) {
if (e.keyCode === _keys.ESC) {
this.hideDayPicker();
} else {
this.showDayPicker();
}
if (this.props.inputProps.onKeyUp) {
e.persist();
this.props.inputProps.onKeyUp(e);
}
}
}, {
key: 'handleMonthChange',
value: function handleMonthChange(month) {
var _this9 = this;
this.setState({ month: month }, function () {
if (_this9.props.dayPickerProps && _this9.props.dayPickerProps.onMonthChange) {
_this9.props.dayPickerProps.onMonthChange(month);
}
});
}
}, {
key: 'handleDayClick',
value: function handleDayClick(day, modifiers, e) {
var _this10 = this;
var _props5 = this.props,
clickUnselectsDay = _props5.clickUnselectsDay,
dayPickerProps = _props5.dayPickerProps,
onDayChange = _props5.onDayChange,
formatDate = _props5.formatDate,
format = _props5.format;
if (dayPickerProps.onDayClick) {
dayPickerProps.onDayClick(day, modifiers, e);
}
// Do nothing if the day is disabled
if (modifiers.disabled || dayPickerProps && dayPickerProps.classNames && modifiers[dayPickerProps.classNames.disabled]) {
return;
}
// If the clicked day is already selected, remove the clicked day
// from the selected days and empty the field value
if (modifiers.selected && clickUnselectsDay) {
var selectedDays = this.state.selectedDays;
if (Array.isArray(selectedDays)) {
selectedDays = selectedDays.slice(0);
var selectedDayIdx = selectedDays.indexOf(day);
selectedDays.splice(selectedDayIdx, 1);
} else if (selectedDays) {
selectedDays = null;
}
this.setState({ value: '', typedValue: '', selectedDays: selectedDays }, this.hideAfterDayClick);
if (onDayChange) {
onDayChange(undefined, modifiers, this);
}
return;
}
var value = formatDate(day, format, dayPickerProps.locale);
this.setState({ value: value, typedValue: '', month: day }, function () {
if (onDayChange) {
onDayChange(day, modifiers, _this10);
}
_this10.hideAfterDayClick();
});
}
}, {
key: 'renderOverlay',
value: function renderOverlay() {
var _this11 = this;
var _props6 = this.props,
classNames = _props6.classNames,
dayPickerProps = _props6.dayPickerProps,
parseDate = _props6.parseDate,
formatDate = _props6.formatDate,
format = _props6.format;
var _state2 = this.state,
selectedDays = _state2.selectedDays,
value = _state2.value;
var selectedDay = void 0;
if (!selectedDays && value) {
var day = parseDate(value, format, dayPickerProps.locale);
if (day) {
selectedDay = day;
}
} else if (selectedDays) {
selectedDay = selectedDays;
}
var onTodayButtonClick = void 0;
if (dayPickerProps.todayButton) {
// Set the current day when clicking the today button
onTodayButtonClick = function onTodayButtonClick() {
return _this11.updateState(new Date(), formatDate(new Date(), format, dayPickerProps.locale), _this11.hideAfterDayClick);
};
}
var Overlay = this.props.overlayComponent;
return _react2.default.createElement(
Overlay,
{
classNames: classNames,
month: this.state.month,
selectedDay: selectedDay,
input: this.input,
tabIndex: 0 // tabIndex is necessary to catch focus/blur events on Safari
, onFocus: this.handleOverlayFocus,
onBlur: this.handleOverlayBlur
},
_react2.default.createElement(_DayPicker2.default, _extends({
ref: function ref(el) {
return _this11.daypicker = el;
},
onTodayButtonClick: onTodayButtonClick
}, dayPickerProps, {
month: this.state.month,
selectedDays: selectedDay,
onDayClick: this.handleDayClick,
onMonthChange: this.handleMonthChange
}))
);
}
}, {
key: 'render',
value: function render() {
var _this12 = this;
var Input = this.props.component;
var inputProps = this.props.inputProps;
return _react2.default.createElement(
'div',
{ className: this.props.classNames.container, style: this.props.style },
_react2.default.createElement(Input, _extends({
ref: function ref(el) {
return _this12.input = el;
},
placeholder: this.props.placeholder
}, inputProps, {
value: this.state.value || this.state.typedValue,
onChange: this.handleInputChange,
onFocus: this.handleInputFocus,
onBlur: this.handleInputBlur,
onKeyDown: this.handleInputKeyDown,
onKeyUp: this.handleInputKeyUp,
onClick: !inputProps.disabled ? this.handleInputClick : undefined
})),
this.state.showOverlay && this.renderOverlay()
);
}
}]);
return DayPickerInput;
}(_react2.default.Component);
DayPickerInput.propTypes = {
value: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.instanceOf(Date)]),
inputProps: _propTypes2.default.object,
placeholder: _propTypes2.default.string,
format: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.arrayOf(_propTypes2.default.string)]),
formatDate: _propTypes2.default.func,
parseDate: _propTypes2.default.func,
typedValue: _propTypes2.default.string,
showOverlay: _propTypes2.default.bool,
dayPickerProps: _propTypes2.default.object,
hideOnDayClick: _propTypes2.default.bool,
clickUnselectsDay: _propTypes2.default.bool,
keepFocus: _propTypes2.default.bool,
component: _propTypes2.default.any,
overlayComponent: _propTypes2.default.any,
style: _propTypes2.default.object,
classNames: _propTypes2.default.shape({
container: _propTypes2.default.string,
overlayWrapper: _propTypes2.default.string,
overlay: _propTypes2.default.string.isRequired
}),
onDayChange: _propTypes2.default.func,
onDayPickerHide: _propTypes2.default.func,
onDayPickerShow: _propTypes2.default.func,
onChange: _propTypes2.default.func,
onClick: _propTypes2.default.func,
onFocus: _propTypes2.default.func,
onBlur: _propTypes2.default.func,
onKeyUp: _propTypes2.default.func
};
DayPickerInput.defaultProps = {
dayPickerProps: {},
value: '',
typedValue: '',
placeholder: 'YYYY-M-D',
format: 'L',
formatDate: defaultFormat,
parseDate: defaultParse,
showOverlay: false,
hideOnDayClick: true,
clickUnselectsDay: false,
keepFocus: true,
component: 'input',
inputProps: {},
overlayComponent: OverlayComponent,
classNames: {
container: 'DayPickerInput',
overlayWrapper: 'DayPickerInput-OverlayWrapper',
overlay: 'DayPickerInput-Overlay'
}
};
exports.default = DayPickerInput;
//# sourceMappingURL=DayPickerInput.js.map