react-datetime-picker
Advanced tools
Comparing version 2.3.0 to 2.4.0
@@ -92,22 +92,70 @@ "use strict"; | ||
var findPreviousInput = function findPreviousInput(element) { | ||
var previousElement = element.previousElementSibling; // Divider between inputs | ||
var isSameDate = function isSameDate(date, year, month, day) { | ||
return (0, _dates.getYear)(date) === year && (0, _dates.getMonth)(date) === month && (0, _dates.getDay)(date) === day; | ||
}; | ||
if (!previousElement) { | ||
var getValueFromRange = function getValueFromRange(valueOrArrayOfValues, index) { | ||
if (Array.isArray(valueOrArrayOfValues)) { | ||
return valueOrArrayOfValues[index]; | ||
} | ||
return valueOrArrayOfValues; | ||
}; | ||
var parseAndValidateDate = function parseAndValidateDate(rawValue) { | ||
if (!rawValue) { | ||
return null; | ||
} | ||
return previousElement.previousElementSibling; // Actual input | ||
var valueDate = new Date(rawValue); | ||
if (isNaN(valueDate.getTime())) { | ||
throw new Error("Invalid date: ".concat(rawValue)); | ||
} | ||
return valueDate; | ||
}; | ||
var findNextInput = function findNextInput(element) { | ||
var nextElement = element.nextElementSibling; // Divider between inputs | ||
if (!nextElement) { | ||
var getDetailValue = function getDetailValue(value, minDate, maxDate) { | ||
if (!value) { | ||
return null; | ||
} | ||
return nextElement.nextElementSibling; // Actual input | ||
return (0, _utils.between)(value, minDate, maxDate); | ||
}; | ||
var getValueFrom = function getValueFrom(value) { | ||
var valueFrom = getValueFromRange(value, 0); | ||
return parseAndValidateDate(valueFrom); | ||
}; | ||
var getDetailValueFrom = function getDetailValueFrom(value, minDate, maxDate) { | ||
var valueFrom = getValueFrom(value); | ||
return getDetailValue(valueFrom, minDate, maxDate); | ||
}; | ||
var getValueTo = function getValueTo(value) { | ||
var valueTo = getValueFromRange(value, 1); | ||
return parseAndValidateDate(valueTo); | ||
}; | ||
var getDetailValueTo = function getDetailValueTo(value, minDate, maxDate) { | ||
var valueTo = getValueTo(value); | ||
return getDetailValue(valueTo, minDate, maxDate); | ||
}; | ||
var isValidInput = function isValidInput(element) { | ||
return element.tagName === 'INPUT' && element.type === 'number'; | ||
}; | ||
var findInput = function findInput(element, property) { | ||
var nextElement = element; | ||
do { | ||
nextElement = nextElement[property]; | ||
} while (nextElement && !isValidInput(nextElement)); | ||
return nextElement; | ||
}; | ||
var focus = function focus(element) { | ||
@@ -117,4 +165,6 @@ return element && element.focus(); | ||
var renderCustomInputs = function renderCustomInputs(placeholder, elementFunctions) { | ||
var pattern = new RegExp(Object.keys(elementFunctions).join('|'), 'gi'); | ||
var _renderCustomInputs = function renderCustomInputs(placeholder, elementFunctions) { | ||
var pattern = new RegExp(Object.keys(elementFunctions).map(function (el) { | ||
return "".concat(el, "+"); | ||
}).join('|'), 'g'); | ||
var matches = placeholder.match(pattern); | ||
@@ -128,5 +178,9 @@ return placeholder.split(pattern).reduce(function (arr, element, index) { | ||
var res = [].concat(_toConsumableArray(arr), [divider]); | ||
var currentMatch = matches && matches[index]; | ||
if (matches && matches[index]) { | ||
res.push(elementFunctions[matches[index]]()); | ||
if (currentMatch) { | ||
var renderFunction = elementFunctions[currentMatch] || elementFunctions[Object.keys(elementFunctions).find(function (elementFunction) { | ||
return currentMatch.match(elementFunction); | ||
})]; | ||
res.push(renderFunction(currentMatch)); | ||
} | ||
@@ -169,7 +223,3 @@ | ||
// Wrapper was directly clicked | ||
var _event$target$childre = _slicedToArray(event.target.children, 2), | ||
/* nativeInput */ | ||
firstInput = _event$target$childre[1]; | ||
var firstInput = event.target.children[1]; | ||
focus(firstInput); | ||
@@ -182,10 +232,2 @@ } | ||
case 'ArrowLeft': | ||
{ | ||
event.preventDefault(); | ||
var input = event.target; | ||
var previousInput = findPreviousInput(input); | ||
focus(previousInput); | ||
break; | ||
} | ||
case 'ArrowRight': | ||
@@ -196,4 +238,5 @@ case _this.dateDivider: | ||
event.preventDefault(); | ||
var _input = event.target; | ||
var nextInput = findNextInput(_input); | ||
var input = event.target; | ||
var property = event.key === 'ArrowLeft' ? 'previousElementSibling' : 'nextElementSibling'; | ||
var nextInput = findInput(input, property); | ||
focus(nextInput); | ||
@@ -319,6 +362,4 @@ break; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderDay", function () { | ||
var _this$props = _this.props, | ||
maxDetail = _this$props.maxDetail, | ||
showLeadingZeros = _this$props.showLeadingZeros; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderDay", function (currentMatch) { | ||
var showLeadingZeros = _this.props.showLeadingZeros; | ||
var _this$state = _this.state, | ||
@@ -328,23 +369,31 @@ day = _this$state.day, | ||
year = _this$state.year; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error("Unsupported token: ".concat(currentMatch)); | ||
} | ||
var showLeadingZerosFromFormat = currentMatch && currentMatch.length === 2; | ||
return _react.default.createElement(_DayInput.default, _extends({ | ||
key: "day" | ||
}, _this.commonInputProps, { | ||
maxDetail: maxDetail, | ||
month: month, | ||
showLeadingZeros: showLeadingZeros, | ||
year: year, | ||
value: day | ||
showLeadingZeros: showLeadingZerosFromFormat || showLeadingZeros, | ||
value: day, | ||
year: year | ||
})); | ||
}); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderMonth", function () { | ||
var _this$props2 = _this.props, | ||
maxDetail = _this$props2.maxDetail, | ||
showLeadingZeros = _this$props2.showLeadingZeros; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderMonth", function (currentMatch) { | ||
var showLeadingZeros = _this.props.showLeadingZeros; | ||
var month = _this.state.month; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error("Unsupported token: ".concat(currentMatch)); | ||
} | ||
var showLeadingZerosFromFormat = currentMatch && currentMatch.length === 2; | ||
return _react.default.createElement(_MonthInput.default, _extends({ | ||
key: "month" | ||
}, _this.commonInputProps, { | ||
maxDetail: maxDetail, | ||
showLeadingZeros: showLeadingZeros, | ||
showLeadingZeros: showLeadingZerosFromFormat || showLeadingZeros, | ||
value: month | ||
@@ -364,7 +413,17 @@ })); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderHour12", function () { | ||
var hour = _this.state.hour; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderHour12", function (currentMatch) { | ||
var _this$state2 = _this.state, | ||
amPm = _this$state2.amPm, | ||
hour = _this$state2.hour; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error("Unsupported token: ".concat(currentMatch)); | ||
} | ||
var showLeadingZeros = currentMatch && currentMatch.length === 2; | ||
return _react.default.createElement(_Hour12Input.default, _extends({ | ||
key: "hour12" | ||
}, _this.commonInputProps, { | ||
amPm: amPm, | ||
showLeadingZeros: showLeadingZeros, | ||
value: hour | ||
@@ -374,7 +433,14 @@ })); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderHour24", function () { | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderHour24", function (currentMatch) { | ||
var hour = _this.state.hour; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error("Unsupported token: ".concat(currentMatch)); | ||
} | ||
var showLeadingZeros = currentMatch && currentMatch.length === 2; | ||
return _react.default.createElement(_Hour24Input.default, _extends({ | ||
key: "hour24" | ||
}, _this.commonInputProps, { | ||
showLeadingZeros: showLeadingZeros, | ||
value: hour | ||
@@ -384,7 +450,12 @@ })); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderMinute", function () { | ||
var maxDetail = _this.props.maxDetail; | ||
var _this$state2 = _this.state, | ||
hour = _this$state2.hour, | ||
minute = _this$state2.minute; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderMinute", function (currentMatch) { | ||
var _this$state3 = _this.state, | ||
hour = _this$state3.hour, | ||
minute = _this$state3.minute; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error("Unsupported token: ".concat(currentMatch)); | ||
} | ||
var showLeadingZeros = currentMatch && currentMatch.length === 2; | ||
return _react.default.createElement(_MinuteInput.default, _extends({ | ||
@@ -394,3 +465,3 @@ key: "minute" | ||
hour: hour, | ||
maxDetail: maxDetail, | ||
showLeadingZeros: showLeadingZeros, | ||
value: minute | ||
@@ -400,8 +471,13 @@ })); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderSecond", function () { | ||
var maxDetail = _this.props.maxDetail; | ||
var _this$state3 = _this.state, | ||
hour = _this$state3.hour, | ||
minute = _this$state3.minute, | ||
second = _this$state3.second; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "renderSecond", function (currentMatch) { | ||
var _this$state4 = _this.state, | ||
hour = _this$state4.hour, | ||
minute = _this$state4.minute, | ||
second = _this$state4.second; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error("Unsupported token: ".concat(currentMatch)); | ||
} | ||
var showLeadingZeros = currentMatch ? currentMatch.length === 2 : true; | ||
return _react.default.createElement(_SecondInput.default, _extends({ | ||
@@ -411,4 +487,4 @@ key: "second" | ||
hour: hour, | ||
maxDetail: maxDetail, | ||
minute: minute, | ||
showLeadingZeros: showLeadingZeros, | ||
value: second | ||
@@ -434,35 +510,27 @@ })); | ||
_createClass(DateTimeInput, [{ | ||
key: "renderCustomDateInputs", | ||
value: function renderCustomDateInputs() { | ||
var datePlaceholder = this.datePlaceholder; | ||
key: "renderCustomInputs", | ||
value: function renderCustomInputs() { | ||
var placeholder = this.placeholder; | ||
var elementFunctions = { | ||
day: this.renderDay, | ||
month: this.renderMonth, | ||
year: this.renderYear | ||
d: this.renderDay, | ||
M: this.renderMonth, | ||
y: this.renderYear, | ||
h: this.renderHour12, | ||
H: this.renderHour24, | ||
m: this.renderMinute, | ||
s: this.renderSecond, | ||
a: this.renderAmPm | ||
}; | ||
return renderCustomInputs(datePlaceholder, elementFunctions); | ||
return _renderCustomInputs(placeholder, elementFunctions); | ||
} | ||
}, { | ||
key: "renderCustomTimeInputs", | ||
value: function renderCustomTimeInputs() { | ||
var timePlaceholder = this.timePlaceholder; | ||
var elementFunctions = { | ||
'hour-12': this.renderHour12, | ||
'hour-24': this.renderHour24, | ||
minute: this.renderMinute, | ||
second: this.renderSecond, | ||
ampm: this.renderAmPm | ||
}; | ||
return renderCustomInputs(timePlaceholder, elementFunctions); | ||
} | ||
}, { | ||
key: "renderNativeInput", | ||
value: function renderNativeInput() { | ||
var _this$props3 = this.props, | ||
disabled = _this$props3.disabled, | ||
maxDate = _this$props3.maxDate, | ||
minDate = _this$props3.minDate, | ||
name = _this$props3.name, | ||
required = _this$props3.required, | ||
value = _this$props3.value; | ||
var _this$props = this.props, | ||
disabled = _this$props.disabled, | ||
maxDate = _this$props.maxDate, | ||
minDate = _this$props.minDate, | ||
name = _this$props.name, | ||
required = _this$props.required; | ||
var value = this.state.value; | ||
return _react.default.createElement(_NativeInput.default, { | ||
@@ -488,3 +556,3 @@ key: "time", | ||
role: "presentation" | ||
}, this.renderNativeInput(), this.renderCustomDateInputs(), _react.default.createElement(_Divider.default, null, "\xA0"), this.renderCustomTimeInputs()); | ||
}, this.renderNativeInput(), this.renderCustomInputs()); | ||
} | ||
@@ -494,5 +562,3 @@ }, { | ||
get: function get() { | ||
var _this$props4 = this.props, | ||
locale = _this$props4.locale, | ||
maxDetail = _this$props4.maxDetail; | ||
var maxDetail = this.props.maxDetail; | ||
var options = { | ||
@@ -511,12 +577,12 @@ hour: 'numeric' | ||
return (0, _dateFormatter.getFormatter)(locale, options); | ||
} | ||
return (0, _dateFormatter.getFormatter)(options); | ||
} // eslint-disable-next-line class-methods-use-this | ||
}, { | ||
key: "formatNumber", | ||
get: function get() { | ||
var locale = this.props.locale; | ||
var options = { | ||
useGrouping: false | ||
}; | ||
return (0, _dateFormatter.getFormatter)(locale, options); | ||
return (0, _dateFormatter.getFormatter)(options); | ||
} | ||
@@ -526,5 +592,3 @@ }, { | ||
get: function get() { | ||
var locale = this.props.locale; | ||
var date = new Date(2017, 11, 11); | ||
return (0, _dateFormatter.formatDate)(locale, date).match(/[^0-9a-z]/i)[0]; | ||
return this.datePlaceholder.match(/[^0-9a-z]/i)[0]; | ||
} | ||
@@ -534,4 +598,3 @@ }, { | ||
get: function get() { | ||
var date = new Date(2017, 0, 1, 21, 12, 13); | ||
return this.formatTime(date).match(/[^0-9a-z]/i)[0]; | ||
return this.timePlaceholder.match(/[^0-9a-z]/i)[0]; | ||
} | ||
@@ -546,3 +609,3 @@ }, { | ||
var date = new Date(year, monthIndex, day); | ||
return (0, _dateFormatter.formatDate)(locale, date).replace(this.formatNumber(year), 'year').replace(this.formatNumber(monthIndex + 1), 'month').replace(this.formatNumber(day), 'day'); | ||
return (0, _dateFormatter.formatDate)(locale, date).replace(this.formatNumber(locale, year), 'y').replace(this.formatNumber(locale, monthIndex + 1), 'M').replace(this.formatNumber(locale, day), 'd'); | ||
} | ||
@@ -558,5 +621,16 @@ }, { | ||
var date = new Date(2017, 0, 1, hour24, minute, second); | ||
return this.formatTime(date).replace(this.formatNumber(hour24), 'hour-24').replace(this.formatNumber(hour12), 'hour-12').replace(this.formatNumber(minute), 'minute').replace(this.formatNumber(second), 'second').replace(new RegExp((0, _utils.getAmPmLabels)(locale).join('|')), 'ampm'); | ||
return this.formatTime(locale, date).replace(this.formatNumber(locale, hour12), 'h').replace(this.formatNumber(locale, hour24), 'H').replace(this.formatNumber(locale, minute), 'mm').replace(this.formatNumber(locale, second), 'ss').replace(new RegExp((0, _utils.getAmPmLabels)(locale).join('|')), 'a'); | ||
} | ||
}, { | ||
key: "placeholder", | ||
get: function get() { | ||
var format = this.props.format; | ||
if (format) { | ||
return format; | ||
} | ||
return "".concat(this.datePlaceholder, "\xA0").concat(this.timePlaceholder); | ||
} | ||
}, { | ||
key: "maxTime", | ||
@@ -570,8 +644,8 @@ get: function get() { | ||
var _this$state4 = this.state, | ||
year = _this$state4.year, | ||
month = _this$state4.month, | ||
day = _this$state4.day; | ||
var _this$state5 = this.state, | ||
year = _this$state5.year, | ||
month = _this$state5.month, | ||
day = _this$state5.day; | ||
if ((0, _dates.getYear)(maxDate) !== year || (0, _dates.getMonth)(maxDate) !== month || (0, _dates.getDay)(maxDate) !== day) { | ||
if (!isSameDate(maxDate, year, month, day)) { | ||
return null; | ||
@@ -591,8 +665,8 @@ } | ||
var _this$state5 = this.state, | ||
year = _this$state5.year, | ||
month = _this$state5.month, | ||
day = _this$state5.day; | ||
var _this$state6 = this.state, | ||
year = _this$state6.year, | ||
month = _this$state6.month, | ||
day = _this$state6.day; | ||
if ((0, _dates.getYear)(minDate) !== year || (0, _dates.getMonth)(minDate) !== month || (0, _dates.getDay)(minDate) !== day) { | ||
if (!isSameDate(minDate, year, month, day)) { | ||
return null; | ||
@@ -610,9 +684,9 @@ } | ||
minTime = this.minTime; | ||
var _this$props5 = this.props, | ||
className = _this$props5.className, | ||
disabled = _this$props5.disabled, | ||
isWidgetOpen = _this$props5.isWidgetOpen, | ||
maxDate = _this$props5.maxDate, | ||
minDate = _this$props5.minDate, | ||
required = _this$props5.required; | ||
var _this$props2 = this.props, | ||
className = _this$props2.className, | ||
disabled = _this$props2.disabled, | ||
isWidgetOpen = _this$props2.isWidgetOpen, | ||
maxDate = _this$props2.maxDate, | ||
minDate = _this$props2.minDate, | ||
required = _this$props2.required; | ||
return { | ||
@@ -649,2 +723,4 @@ className: className, | ||
value: function getDerivedStateFromProps(nextProps, prevState) { | ||
var minDate = nextProps.minDate, | ||
maxDate = nextProps.maxDate; | ||
var nextState = {}; | ||
@@ -666,7 +742,12 @@ /** | ||
var nextValue = nextProps.value; | ||
var nextValue = getDetailValueFrom(nextProps.value, minDate, maxDate); | ||
var values = [nextValue, prevState.value]; | ||
if ( // Toggling calendar visibility resets values | ||
nextState.isCalendarOpen // Flag was toggled | ||
|| datesAreDifferent(nextValue, prevState.value)) { | ||
|| datesAreDifferent.apply(void 0, _toConsumableArray(values.map(function (value) { | ||
return getDetailValueFrom(value, minDate, maxDate); | ||
}))) || datesAreDifferent.apply(void 0, _toConsumableArray(values.map(function (value) { | ||
return getDetailValueTo(value, minDate, maxDate); | ||
})))) { | ||
if (nextValue) { | ||
@@ -712,2 +793,3 @@ var _convert24to = (0, _dates.convert24to12)((0, _dates.getHours)(nextValue)); | ||
disabled: _propTypes.default.bool, | ||
format: _propTypes.default.string, | ||
isWidgetOpen: _propTypes.default.bool, | ||
@@ -722,4 +804,4 @@ locale: _propTypes.default.string, | ||
showLeadingZeros: _propTypes.default.bool, | ||
value: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.instanceOf(Date)]) | ||
value: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.instanceOf(Date), _propTypes.default.arrayOf(_propTypes.default.instanceOf(Date))]) | ||
}; | ||
(0, _reactLifecyclesCompat.polyfill)(DateTimeInput); |
@@ -105,8 +105,2 @@ "use strict"; | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onTimeChange", function (value) { | ||
var closeWidgets = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
_this.onChange(value, closeWidgets); | ||
}); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onChange", function (value) { | ||
@@ -249,2 +243,3 @@ var closeWidgets = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
disabled = _this$props3.disabled, | ||
format = _this$props3.format, | ||
locale = _this$props3.locale, | ||
@@ -266,2 +261,3 @@ maxDetail = _this$props3.maxDetail, | ||
disabled: disabled, | ||
format: format, | ||
locale: locale, | ||
@@ -273,3 +269,3 @@ isWidgetOpen: isCalendarOpen || isClockOpen, | ||
name: name, | ||
onChange: this.onTimeChange, | ||
onChange: this.onChange, | ||
placeholder: this.placeholder, | ||
@@ -459,4 +455,5 @@ required: required, | ||
clockClassName: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.arrayOf(_propTypes.default.string)]), | ||
disableClock: _propTypes.default.bool, | ||
disabled: _propTypes.default.bool, | ||
disableClock: _propTypes.default.bool, | ||
format: _propTypes.default.string, | ||
isCalendarOpen: _propTypes.default.bool, | ||
@@ -468,3 +465,7 @@ isClockOpen: _propTypes.default.bool, | ||
name: _propTypes.default.string, | ||
onCalendarClose: _propTypes.default.func, | ||
onCalendarOpen: _propTypes.default.func, | ||
onChange: _propTypes.default.func, | ||
onClockClose: _propTypes.default.func, | ||
onClockOpen: _propTypes.default.func, | ||
required: _propTypes.default.bool, | ||
@@ -471,0 +472,0 @@ showLeadingZeros: _propTypes.default.bool, |
@@ -12,40 +12,16 @@ "use strict"; | ||
var formatterCache = {}; | ||
/** | ||
* Gets Intl-based date formatter from formatter cache. If it doesn't exist in cache | ||
* just yet, it will be created on the fly. | ||
*/ | ||
var getFormatter = function getFormatter(locale, options) { | ||
if (!locale) { | ||
// Default parameter is not enough as it does not protect us from null values | ||
// eslint-disable-next-line no-param-reassign | ||
locale = (0, _getUserLocale.default)(); | ||
} | ||
var stringifiedOptions = JSON.stringify(options); | ||
if (!formatterCache[locale]) { | ||
formatterCache[locale] = {}; | ||
} | ||
if (!formatterCache[locale][stringifiedOptions]) { | ||
formatterCache[locale][stringifiedOptions] = function (n) { | ||
return n.toLocaleString(locale, options); | ||
}; | ||
} | ||
return formatterCache[locale][stringifiedOptions]; | ||
/* eslint-disable import/prefer-default-export */ | ||
var getFormatter = function getFormatter(options) { | ||
return function (locale, date) { | ||
return date.toLocaleString(locale || (0, _getUserLocale.default)(), options); | ||
}; | ||
}; | ||
exports.getFormatter = getFormatter; | ||
var formatDate = function formatDate(locale, date) { | ||
return getFormatter(locale, { | ||
day: 'numeric', | ||
month: 'numeric', | ||
year: 'numeric' | ||
})(date); | ||
var formatDateOptions = { | ||
day: 'numeric', | ||
month: 'numeric', | ||
year: 'numeric' | ||
}; | ||
var formatDate = getFormatter(formatDateOptions); | ||
exports.formatDate = formatDate; |
@@ -6,2 +6,8 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "between", { | ||
enumerable: true, | ||
get: function get() { | ||
return _utils.between; | ||
} | ||
}); | ||
Object.defineProperty(exports, "callIfDefined", { | ||
@@ -8,0 +14,0 @@ enumerable: true, |
{ | ||
"name": "react-datetime-picker", | ||
"version": "2.3.0", | ||
"version": "2.4.0", | ||
"description": "A date range picker for your React app.", | ||
@@ -27,2 +27,5 @@ "main": "dist/entry.js", | ||
"!**/src/entry.nostyle.js" | ||
], | ||
"testPathIgnorePatterns": [ | ||
"utils.js" | ||
] | ||
@@ -52,6 +55,6 @@ }, | ||
"react-clock": "^2.3.0", | ||
"react-date-picker": "^7.4.0", | ||
"react-date-picker": "^7.5.1", | ||
"react-fit": "^1.0.3", | ||
"react-lifecycles-compat": "^3.0.4", | ||
"react-time-picker": "^3.4.0" | ||
"react-time-picker": "^3.5.2" | ||
}, | ||
@@ -58,0 +61,0 @@ "devDependencies": { |
@@ -97,2 +97,3 @@ ![downloads](https://img.shields.io/npm/dt/react-datetime-picker.svg) ![build](https://img.shields.io/travis/wojtekmaj/react-datetime-picker/master.svg) ![dependencies](https://img.shields.io/david/wojtekmaj/react-datetime-picker.svg | ||
|disableClock|Defines whether the clock should be disabled. Defaults to false.|`true`| | ||
|format|Defines input format based on [Unicode Technical Standard #35](https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table). Supported values are: `y`, `M`, `MM`, `d`, `dd`, `H`, `HH`, `h`, `hh`, `m`, `mm`, `s`, `ss`, `a`.|`"y-MM-dd h:mm:ss a"`| | ||
|isCalendarOpen|Defines whether the calendar should be opened. Defaults to false.|`true`| | ||
@@ -106,5 +107,5 @@ |isClockOpen|Defines whether the clock should be opened. Defaults to false.|`true`| | ||
|name|Defines input name. Defaults to "datetime".|`"myCustomName"`| | ||
|onChange|Function called when the user clicks an item on the most detailed view available.|`(value) => alert('New date is: ', value)`| | ||
|onCalendarClose|Function called when the calendar closes.|`() => alert('Calendar closed')`| | ||
|onCalendarOpen|Function called when the calendar opens.|`() => alert('Calendar opened')`| | ||
|onChange|Function called when the user clicks an item on the most detailed view available.|`(value) => alert('New date is: ', value)`| | ||
|onClockClose|Function called when the clock closes.|`() => alert('Clock closed')`| | ||
@@ -111,0 +112,0 @@ |onClockOpen|Function called when the clock opens.|`() => alert('Clock opened')`| |
@@ -6,2 +6,4 @@ import React from 'react'; | ||
import { muteConsole, restoreConsole } from './utils'; | ||
/* eslint-disable comma-dangle */ | ||
@@ -106,3 +108,3 @@ | ||
it('shows a given date in all inputs correctly (12-hour format)', () => { | ||
it('shows a given date in all inputs correctly given Date (12-hour format)', () => { | ||
const date = new Date(2017, 8, 30, 22, 17, 0); | ||
@@ -121,12 +123,58 @@ | ||
expect(nativeInput.getDOMNode().value).toBe('2017-09-30T22:17'); | ||
expect(customInputs.at(0).getDOMNode().value).toBe('9'); | ||
expect(customInputs.at(1).getDOMNode().value).toBe('30'); | ||
expect(customInputs.at(2).getDOMNode().value).toBe('2017'); | ||
expect(customInputs.at(3).getDOMNode().value).toBe('10'); | ||
expect(customInputs.at(4).getDOMNode().value).toBe('17'); | ||
expect(customInputs.at(5).getDOMNode().value).toBe('0'); | ||
expect(nativeInput.prop('value')).toBe('2017-09-30T22:17:00'); | ||
expect(customInputs.at(0).prop('value')).toBe(9); | ||
expect(customInputs.at(1).prop('value')).toBe(30); | ||
expect(customInputs.at(2).prop('value')).toBe(2017); | ||
expect(customInputs.at(3).prop('value')).toBe(10); | ||
expect(customInputs.at(4).prop('value')).toBe(17); | ||
expect(customInputs.at(5).prop('value')).toBe(0); | ||
}); | ||
itIfFullICU('shows a given date in all inputs correctly (24-hour format)', () => { | ||
it('shows a given date in all inputs correctly given array of Date objects (12-hour format)', () => { | ||
const date = [new Date(2017, 8, 30, 22, 17, 0), new Date(2017, 8, 30, 0, 0, 0, -1)]; | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
maxDetail="second" | ||
value={date} | ||
/> | ||
); | ||
const nativeInput = component.find('input[type="datetime-local"]'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(nativeInput.prop('value')).toBe('2017-09-30T22:17:00'); | ||
expect(customInputs.at(0).prop('value')).toBe(9); | ||
expect(customInputs.at(1).prop('value')).toBe(30); | ||
expect(customInputs.at(2).prop('value')).toBe(2017); | ||
expect(customInputs.at(3).prop('value')).toBe(10); | ||
expect(customInputs.at(4).prop('value')).toBe(17); | ||
expect(customInputs.at(5).prop('value')).toBe(0); | ||
}); | ||
it('shows a given date in all inputs correctly given ISO string (12-hour format)', () => { | ||
const date = '2017-09-30T22:17:00.000'; | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
maxDetail="second" | ||
value={date} | ||
/> | ||
); | ||
const nativeInput = component.find('input[type="datetime-local"]'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(nativeInput.prop('value')).toBe('2017-09-30T22:17:00'); | ||
expect(customInputs.at(0).prop('value')).toBe(9); | ||
expect(customInputs.at(1).prop('value')).toBe(30); | ||
expect(customInputs.at(2).prop('value')).toBe(2017); | ||
expect(customInputs.at(3).prop('value')).toBe(10); | ||
expect(customInputs.at(4).prop('value')).toBe(17); | ||
expect(customInputs.at(5).prop('value')).toBe(0); | ||
}); | ||
itIfFullICU('shows a given date in all inputs correctly given Date (24-hour format)', () => { | ||
const date = new Date(2017, 8, 30, 22, 17, 0); | ||
@@ -146,11 +194,101 @@ | ||
expect(nativeInput.getDOMNode().value).toBe('2017-09-30T22:17'); | ||
expect(customInputs.at(0).getDOMNode().value).toBe('2017'); | ||
expect(customInputs.at(1).getDOMNode().value).toBe('9'); | ||
expect(customInputs.at(2).getDOMNode().value).toBe('30'); | ||
expect(customInputs.at(3).getDOMNode().value).toBe('22'); | ||
expect(customInputs.at(4).getDOMNode().value).toBe('17'); | ||
expect(customInputs.at(5).getDOMNode().value).toBe('0'); | ||
expect(nativeInput.prop('value')).toBe('2017-09-30T22:17:00'); | ||
expect(customInputs.at(0).prop('value')).toBe(2017); | ||
expect(customInputs.at(1).prop('value')).toBe(9); | ||
expect(customInputs.at(2).prop('value')).toBe(30); | ||
expect(customInputs.at(3).prop('value')).toBe(22); | ||
expect(customInputs.at(4).prop('value')).toBe(17); | ||
expect(customInputs.at(5).prop('value')).toBe(0); | ||
}); | ||
itIfFullICU('shows a given date in all inputs correctly given array of Date objects (24-hour format)', () => { | ||
const date = [new Date(2017, 8, 30, 22, 17, 0), new Date(2017, 8, 30, 0, 0, 0, -1)]; | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
locale="de-DE" | ||
maxDetail="second" | ||
value={date} | ||
/> | ||
); | ||
const nativeInput = component.find('input[type="datetime-local"]'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(nativeInput.prop('value')).toBe('2017-09-30T22:17:00'); | ||
expect(customInputs.at(0).prop('value')).toBe(2017); | ||
expect(customInputs.at(1).prop('value')).toBe(9); | ||
expect(customInputs.at(2).prop('value')).toBe(30); | ||
expect(customInputs.at(3).prop('value')).toBe(22); | ||
expect(customInputs.at(4).prop('value')).toBe(17); | ||
expect(customInputs.at(5).prop('value')).toBe(0); | ||
}); | ||
itIfFullICU('shows a given date in all inputs correctly given ISO string (24-hour format)', () => { | ||
const date = '2017-09-30T22:17:00.000'; | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
locale="de-DE" | ||
maxDetail="second" | ||
value={date} | ||
/> | ||
); | ||
const nativeInput = component.find('input[type="datetime-local"]'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(nativeInput.prop('value')).toBe('2017-09-30T22:17:00'); | ||
expect(customInputs.at(0).prop('value')).toBe(2017); | ||
expect(customInputs.at(1).prop('value')).toBe(9); | ||
expect(customInputs.at(2).prop('value')).toBe(30); | ||
expect(customInputs.at(3).prop('value')).toBe(22); | ||
expect(customInputs.at(4).prop('value')).toBe(17); | ||
expect(customInputs.at(5).prop('value')).toBe(0); | ||
}); | ||
it('shows empty value in all inputs correctly given null', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
maxDetail="second" | ||
value={null} | ||
/> | ||
); | ||
const nativeInput = component.find('input[type="datetime-local"]'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(nativeInput.prop('value')).toBeFalsy(); | ||
expect(customInputs.at(0).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(1).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(2).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(3).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(4).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(5).prop('value')).toBeFalsy(); | ||
}); | ||
it('shows empty value in all inputs correctly given an array of nulls', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
maxDetail="second" | ||
value={[null, null]} | ||
/> | ||
); | ||
const nativeInput = component.find('input[type="datetime-local"]'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(nativeInput.prop('value')).toBeFalsy(); | ||
expect(customInputs.at(0).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(1).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(2).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(3).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(4).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(5).prop('value')).toBeFalsy(); | ||
}); | ||
it('clears the value correctly', () => { | ||
@@ -172,9 +310,9 @@ const date = new Date(2017, 8, 30, 22, 17, 0); | ||
expect(nativeInput.getDOMNode().value).toBe(''); | ||
expect(customInputs.at(0).getDOMNode().value).toBe(''); | ||
expect(customInputs.at(1).getDOMNode().value).toBe(''); | ||
expect(customInputs.at(2).getDOMNode().value).toBe(''); | ||
expect(customInputs.at(3).getDOMNode().value).toBe(''); | ||
expect(customInputs.at(4).getDOMNode().value).toBe(''); | ||
expect(customInputs.at(5).getDOMNode().value).toBe(''); | ||
expect(nativeInput.prop('value')).toBeFalsy(); | ||
expect(customInputs.at(0).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(1).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(2).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(3).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(4).prop('value')).toBeFalsy(); | ||
expect(customInputs.at(5).prop('value')).toBeFalsy(); | ||
}); | ||
@@ -219,2 +357,347 @@ | ||
describe('renders custom inputs in a proper order given format', () => { | ||
it('renders "y" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="y" | ||
/> | ||
); | ||
const componentInput = component.find('YearInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "yyyy" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="yyyy" | ||
/> | ||
); | ||
const componentInput = component.find('YearInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "M" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="M" | ||
/> | ||
); | ||
const componentInput = component.find('MonthInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "MM" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="MM" | ||
/> | ||
); | ||
const componentInput = component.find('MonthInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
expect(componentInput.prop('showLeadingZeros')).toBeTruthy(); | ||
}); | ||
it('throws error for "MMM"', () => { | ||
muteConsole(); | ||
const renderComponent = () => mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="MMM" | ||
/> | ||
); | ||
expect(renderComponent).toThrow('Unsupported token: MMM'); | ||
restoreConsole(); | ||
}); | ||
it('renders "d" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="d" | ||
/> | ||
); | ||
const componentInput = component.find('DayInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "dd" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="dd" | ||
/> | ||
); | ||
const componentInput = component.find('DayInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
expect(componentInput.prop('showLeadingZeros')).toBeTruthy(); | ||
}); | ||
it('throws error for "ddd"', () => { | ||
muteConsole(); | ||
const renderComponent = () => mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="ddd" | ||
/> | ||
); | ||
expect(renderComponent).toThrow('Unsupported token: ddd'); | ||
restoreConsole(); | ||
}); | ||
it('renders "yyyy-MM-dd" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="yyyy-MM-d" | ||
/> | ||
); | ||
const monthInput = component.find('MonthInput'); | ||
const dayInput = component.find('DayInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(monthInput).toHaveLength(1); | ||
expect(dayInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(3); | ||
expect(customInputs.at(0).prop('name')).toBe('year'); | ||
expect(customInputs.at(1).prop('name')).toBe('month'); | ||
expect(customInputs.at(2).prop('name')).toBe('day'); | ||
expect(monthInput.prop('showLeadingZeros')).toBeTruthy(); | ||
expect(dayInput.prop('showLeadingZeros')).toBeFalsy(); | ||
}); | ||
it('renders "h" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="h" | ||
/> | ||
); | ||
const componentInput = component.find('Hour12Input'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "hh" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="hh" | ||
/> | ||
); | ||
const componentInput = component.find('Hour12Input'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
expect(componentInput.prop('showLeadingZeros')).toBeTruthy(); | ||
}); | ||
it('throws error for "hhh"', () => { | ||
muteConsole(); | ||
const renderComponent = () => mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="hhh" | ||
/> | ||
); | ||
expect(renderComponent).toThrow('Unsupported token: hhh'); | ||
restoreConsole(); | ||
}); | ||
it('renders "H" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="H" | ||
/> | ||
); | ||
const componentInput = component.find('Hour24Input'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "HH" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="HH" | ||
/> | ||
); | ||
const componentInput = component.find('Hour24Input'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
expect(componentInput.prop('showLeadingZeros')).toBeTruthy(); | ||
}); | ||
it('throws error for "HHH"', () => { | ||
muteConsole(); | ||
const renderComponent = () => mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="HHH" | ||
/> | ||
); | ||
expect(renderComponent).toThrow('Unsupported token: HHH'); | ||
restoreConsole(); | ||
}); | ||
it('renders "m" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="m" | ||
/> | ||
); | ||
const componentInput = component.find('MinuteInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "mm" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="mm" | ||
/> | ||
); | ||
const componentInput = component.find('MinuteInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
expect(componentInput.prop('showLeadingZeros')).toBeTruthy(); | ||
}); | ||
it('throws error for "mmm"', () => { | ||
muteConsole(); | ||
const renderComponent = () => mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="mmm" | ||
/> | ||
); | ||
expect(renderComponent).toThrow('Unsupported token: mmm'); | ||
restoreConsole(); | ||
}); | ||
it('renders "s" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="s" | ||
/> | ||
); | ||
const componentInput = component.find('SecondInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
}); | ||
it('renders "ss" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="ss" | ||
/> | ||
); | ||
const componentInput = component.find('SecondInput'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(1); | ||
expect(componentInput.prop('showLeadingZeros')).toBeTruthy(); | ||
}); | ||
it('throws error for "sss"', () => { | ||
muteConsole(); | ||
const renderComponent = () => mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="sss" | ||
/> | ||
); | ||
expect(renderComponent).toThrow('Unsupported token: sss'); | ||
restoreConsole(); | ||
}); | ||
it('renders "a" properly', () => { | ||
const component = mount( | ||
<DateTimeInput | ||
{...defaultProps} | ||
format="a" | ||
/> | ||
); | ||
const componentInput = component.find('AmPm'); | ||
const customInputs = component.find('input[type="number"]'); | ||
expect(componentInput).toHaveLength(1); | ||
expect(customInputs).toHaveLength(0); | ||
}); | ||
}); | ||
it('renders proper input separators', () => { | ||
@@ -268,3 +751,3 @@ const component = mount( | ||
it('jumps to the next field when separator key is pressed', () => { | ||
it('jumps to the next field when date separator key is pressed', () => { | ||
const component = mount( | ||
@@ -283,3 +766,4 @@ <DateTimeInput {...defaultProps} /> | ||
const separators = component.find('.react-datetime-picker__inputGroup__divider'); | ||
const separatorKey = separators.at(0).text(); | ||
const separatorsTexts = separators.map(el => el.text()).filter(el => el.trim()); | ||
const separatorKey = separatorsTexts[0]; | ||
dayInput.simulate('keydown', getKey(separatorKey)); | ||
@@ -290,2 +774,23 @@ | ||
it('jumps to the next field when time separator key is pressed', () => { | ||
const component = mount( | ||
<DateTimeInput {...defaultProps} /> | ||
); | ||
const customInputs = component.find('input[type="number"]'); | ||
const dayInput = customInputs.at(0); | ||
const monthInput = customInputs.at(1); | ||
dayInput.getDOMNode().focus(); | ||
expect(document.activeElement).toBe(dayInput.getDOMNode()); | ||
const separators = component.find('.react-datetime-picker__inputGroup__divider'); | ||
const separatorsTexts = separators.map(el => el.text()).filter(el => el.trim()); | ||
const separatorKey = separatorsTexts[separatorsTexts.length - 1]; | ||
dayInput.simulate('keydown', getKey(separatorKey)); | ||
expect(document.activeElement).toBe(monthInput.getDOMNode()); | ||
}); | ||
it('does not jump to the next field when right arrow is pressed when the last input is focused', () => { | ||
@@ -292,0 +797,0 @@ const component = mount( |
@@ -348,2 +348,109 @@ import React from 'react'; | ||
}); | ||
it('closes Calendar when calling internal onChange', () => { | ||
const component = mount( | ||
<DateTimePicker isCalendarOpen /> | ||
); | ||
const { onChange } = component.instance(); | ||
onChange(new Date()); | ||
expect(component.state('isCalendarOpen')).toBe(false); | ||
}); | ||
it('does not close Calendar when calling internal onChange with closeWidgets = false', () => { | ||
const component = mount( | ||
<DateTimePicker isCalendarOpen /> | ||
); | ||
const { onChange } = component.instance(); | ||
onChange(new Date(), false); | ||
expect(component.state('isCalendarOpen')).toBe(true); | ||
}); | ||
it('closes Clock when calling internal onChange', () => { | ||
const component = mount( | ||
<DateTimePicker isClockOpen /> | ||
); | ||
const { onChange } = component.instance(); | ||
onChange(new Date()); | ||
expect(component.state('isClockOpen')).toBe(false); | ||
}); | ||
it('does not close Clock when calling internal onChange with closeWidgets = false', () => { | ||
const component = mount( | ||
<DateTimePicker isClockOpen /> | ||
); | ||
const { onChange } = component.instance(); | ||
onChange(new Date(), false); | ||
expect(component.state('isClockOpen')).toBe(true); | ||
}); | ||
it('calls onChange callback when calling internal internal onChange', () => { | ||
const nextValue = new Date(2019, 0, 1, 21, 40, 11, 458); | ||
const onChange = jest.fn(); | ||
const component = mount( | ||
<DateTimePicker | ||
onChange={onChange} | ||
value={new Date(2018, 6, 17)} | ||
/> | ||
); | ||
const { onChange: onChangeInternal } = component.instance(); | ||
onChangeInternal(nextValue); | ||
expect(onChange).toHaveBeenCalledWith(nextValue); | ||
}); | ||
it('calls onChange callback with merged new date & old time when calling internal onDateChange', () => { | ||
const hours = 21; | ||
const minutes = 40; | ||
const seconds = 11; | ||
const ms = 458; | ||
const nextValue = new Date(2019, 0, 1); | ||
const onChange = jest.fn(); | ||
const component = mount( | ||
<DateTimePicker | ||
onChange={onChange} | ||
value={new Date(2018, 6, 17, hours, minutes, seconds, ms)} | ||
/> | ||
); | ||
const { onDateChange } = component.instance(); | ||
onDateChange(nextValue); | ||
expect(onChange).toHaveBeenCalledWith(new Date(2019, 0, 1, hours, minutes, seconds, ms)); | ||
}); | ||
it('clears the value when clicking on a button', () => { | ||
const onChange = jest.fn(); | ||
const component = mount( | ||
<DateTimePicker onChange={onChange} /> | ||
); | ||
const calendar = component.find('Calendar'); | ||
const button = component.find('button.react-datetime-picker__clear-button'); | ||
expect(calendar).toHaveLength(0); | ||
button.simulate('click'); | ||
component.update(); | ||
expect(onChange).toHaveBeenCalledWith(null); | ||
}); | ||
}); |
@@ -29,3 +29,3 @@ import React, { PureComponent } from 'react'; | ||
import { isMaxDate, isMinDate } from './shared/propTypes'; | ||
import { getAmPmLabels } from './shared/utils'; | ||
import { between, getAmPmLabels } from './shared/utils'; | ||
@@ -42,23 +42,80 @@ const defaultMinDate = new Date(-8.64e15); | ||
const findPreviousInput = (element) => { | ||
const previousElement = element.previousElementSibling; // Divider between inputs | ||
if (!previousElement) { | ||
const isSameDate = (date, year, month, day) => ( | ||
getYear(date) === year | ||
&& getMonth(date) === month | ||
&& getDay(date) === day | ||
); | ||
const getValueFromRange = (valueOrArrayOfValues, index) => { | ||
if (Array.isArray(valueOrArrayOfValues)) { | ||
return valueOrArrayOfValues[index]; | ||
} | ||
return valueOrArrayOfValues; | ||
}; | ||
const parseAndValidateDate = (rawValue) => { | ||
if (!rawValue) { | ||
return null; | ||
} | ||
return previousElement.previousElementSibling; // Actual input | ||
const valueDate = new Date(rawValue); | ||
if (isNaN(valueDate.getTime())) { | ||
throw new Error(`Invalid date: ${rawValue}`); | ||
} | ||
return valueDate; | ||
}; | ||
const findNextInput = (element) => { | ||
const nextElement = element.nextElementSibling; // Divider between inputs | ||
if (!nextElement) { | ||
const getDetailValue = (value, minDate, maxDate) => { | ||
if (!value) { | ||
return null; | ||
} | ||
return nextElement.nextElementSibling; // Actual input | ||
return between(value, minDate, maxDate); | ||
}; | ||
const getValueFrom = (value) => { | ||
const valueFrom = getValueFromRange(value, 0); | ||
return parseAndValidateDate(valueFrom); | ||
}; | ||
const getDetailValueFrom = (value, minDate, maxDate) => { | ||
const valueFrom = getValueFrom(value); | ||
return getDetailValue(valueFrom, minDate, maxDate); | ||
}; | ||
const getValueTo = (value) => { | ||
const valueTo = getValueFromRange(value, 1); | ||
return parseAndValidateDate(valueTo); | ||
}; | ||
const getDetailValueTo = (value, minDate, maxDate) => { | ||
const valueTo = getValueTo(value); | ||
return getDetailValue(valueTo, minDate, maxDate); | ||
}; | ||
const isValidInput = element => element.tagName === 'INPUT' && element.type === 'number'; | ||
const findInput = (element, property) => { | ||
let nextElement = element; | ||
do { | ||
nextElement = nextElement[property]; | ||
} while (nextElement && !isValidInput(nextElement)); | ||
return nextElement; | ||
}; | ||
const focus = element => element && element.focus(); | ||
const renderCustomInputs = (placeholder, elementFunctions) => { | ||
const pattern = new RegExp(Object.keys(elementFunctions).join('|'), 'gi'); | ||
const pattern = new RegExp( | ||
Object.keys(elementFunctions).map(el => `${el}+`).join('|'), 'g', | ||
); | ||
const matches = placeholder.match(pattern); | ||
return placeholder.split(pattern) | ||
@@ -73,4 +130,12 @@ .reduce((arr, element, index) => { | ||
const res = [...arr, divider]; | ||
if (matches && matches[index]) { | ||
res.push(elementFunctions[matches[index]]()); | ||
const currentMatch = matches && matches[index]; | ||
if (currentMatch) { | ||
const renderFunction = ( | ||
elementFunctions[currentMatch] | ||
|| elementFunctions[ | ||
Object.keys(elementFunctions) | ||
.find(elementFunction => currentMatch.match(elementFunction)) | ||
] | ||
); | ||
res.push(renderFunction(currentMatch)); | ||
} | ||
@@ -83,2 +148,4 @@ return res; | ||
static getDerivedStateFromProps(nextProps, prevState) { | ||
const { minDate, maxDate } = nextProps; | ||
const nextState = {}; | ||
@@ -99,7 +166,13 @@ | ||
*/ | ||
const nextValue = nextProps.value; | ||
const nextValue = getDetailValueFrom(nextProps.value, minDate, maxDate); | ||
const values = [nextValue, prevState.value]; | ||
if ( | ||
// Toggling calendar visibility resets values | ||
nextState.isCalendarOpen // Flag was toggled | ||
|| datesAreDifferent(nextValue, prevState.value) | ||
|| datesAreDifferent( | ||
...values.map(value => getDetailValueFrom(value, minDate, maxDate)), | ||
) | ||
|| datesAreDifferent( | ||
...values.map(value => getDetailValueTo(value, minDate, maxDate)), | ||
) | ||
) { | ||
@@ -140,3 +213,3 @@ if (nextValue) { | ||
get formatTime() { | ||
const { locale, maxDetail } = this.props; | ||
const { maxDetail } = this.props; | ||
@@ -152,24 +225,18 @@ const options = { hour: 'numeric' }; | ||
return getFormatter(locale, options); | ||
return getFormatter(options); | ||
} | ||
// eslint-disable-next-line class-methods-use-this | ||
get formatNumber() { | ||
const { locale } = this.props; | ||
const options = { useGrouping: false }; | ||
return getFormatter(locale, options); | ||
return getFormatter(options); | ||
} | ||
get dateDivider() { | ||
const { locale } = this.props; | ||
const date = new Date(2017, 11, 11); | ||
return formatDate(locale, date).match(/[^0-9a-z]/i)[0]; | ||
return this.datePlaceholder.match(/[^0-9a-z]/i)[0]; | ||
} | ||
get timeDivider() { | ||
const date = new Date(2017, 0, 1, 21, 12, 13); | ||
return this.formatTime(date).match(/[^0-9a-z]/i)[0]; | ||
return this.timePlaceholder.match(/[^0-9a-z]/i)[0]; | ||
} | ||
@@ -188,5 +255,5 @@ | ||
formatDate(locale, date) | ||
.replace(this.formatNumber(year), 'year') | ||
.replace(this.formatNumber(monthIndex + 1), 'month') | ||
.replace(this.formatNumber(day), 'day') | ||
.replace(this.formatNumber(locale, year), 'y') | ||
.replace(this.formatNumber(locale, monthIndex + 1), 'M') | ||
.replace(this.formatNumber(locale, day), 'd') | ||
); | ||
@@ -205,11 +272,21 @@ } | ||
return ( | ||
this.formatTime(date) | ||
.replace(this.formatNumber(hour24), 'hour-24') | ||
.replace(this.formatNumber(hour12), 'hour-12') | ||
.replace(this.formatNumber(minute), 'minute') | ||
.replace(this.formatNumber(second), 'second') | ||
.replace(new RegExp(getAmPmLabels(locale).join('|')), 'ampm') | ||
this.formatTime(locale, date) | ||
.replace(this.formatNumber(locale, hour12), 'h') | ||
.replace(this.formatNumber(locale, hour24), 'H') | ||
.replace(this.formatNumber(locale, minute), 'mm') | ||
.replace(this.formatNumber(locale, second), 'ss') | ||
.replace(new RegExp(getAmPmLabels(locale).join('|')), 'a') | ||
); | ||
} | ||
get placeholder() { | ||
const { format } = this.props; | ||
if (format) { | ||
return format; | ||
} | ||
return `${this.datePlaceholder}\u00a0${this.timePlaceholder}`; | ||
} | ||
get maxTime() { | ||
@@ -224,7 +301,3 @@ const { maxDate } = this.props; | ||
if ( | ||
getYear(maxDate) !== year | ||
|| getMonth(maxDate) !== month | ||
|| getDay(maxDate) !== day | ||
) { | ||
if (!isSameDate(maxDate, year, month, day)) { | ||
return null; | ||
@@ -245,7 +318,3 @@ } | ||
if ( | ||
getYear(minDate) !== year | ||
|| getMonth(minDate) !== month | ||
|| getDay(minDate) !== day | ||
) { | ||
if (!isSameDate(minDate, year, month, day)) { | ||
return null; | ||
@@ -299,3 +368,3 @@ } | ||
// Wrapper was directly clicked | ||
const [/* nativeInput */, firstInput] = event.target.children; | ||
const firstInput = event.target.children[1]; | ||
focus(firstInput); | ||
@@ -307,10 +376,3 @@ } | ||
switch (event.key) { | ||
case 'ArrowLeft': { | ||
event.preventDefault(); | ||
const input = event.target; | ||
const previousInput = findPreviousInput(input); | ||
focus(previousInput); | ||
break; | ||
} | ||
case 'ArrowLeft': | ||
case 'ArrowRight': | ||
@@ -322,3 +384,4 @@ case this.dateDivider: | ||
const input = event.target; | ||
const nextInput = findNextInput(input); | ||
const property = event.key === 'ArrowLeft' ? 'previousElementSibling' : 'nextElementSibling'; | ||
const nextInput = findInput(input, property); | ||
focus(nextInput); | ||
@@ -454,6 +517,12 @@ break; | ||
renderDay = () => { | ||
const { maxDetail, showLeadingZeros } = this.props; | ||
renderDay = (currentMatch) => { | ||
const { showLeadingZeros } = this.props; | ||
const { day, month, year } = this.state; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error(`Unsupported token: ${currentMatch}`); | ||
} | ||
const showLeadingZerosFromFormat = currentMatch && currentMatch.length === 2; | ||
return ( | ||
@@ -463,7 +532,6 @@ <DayInput | ||
{...this.commonInputProps} | ||
maxDetail={maxDetail} | ||
month={month} | ||
showLeadingZeros={showLeadingZeros} | ||
showLeadingZeros={showLeadingZerosFromFormat || showLeadingZeros} | ||
value={day} | ||
year={year} | ||
value={day} | ||
/> | ||
@@ -473,6 +541,12 @@ ); | ||
renderMonth = () => { | ||
const { maxDetail, showLeadingZeros } = this.props; | ||
renderMonth = (currentMatch) => { | ||
const { showLeadingZeros } = this.props; | ||
const { month } = this.state; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error(`Unsupported token: ${currentMatch}`); | ||
} | ||
const showLeadingZerosFromFormat = currentMatch && currentMatch.length === 2; | ||
return ( | ||
@@ -482,4 +556,3 @@ <MonthInput | ||
{...this.commonInputProps} | ||
maxDetail={maxDetail} | ||
showLeadingZeros={showLeadingZeros} | ||
showLeadingZeros={showLeadingZerosFromFormat || showLeadingZeros} | ||
value={month} | ||
@@ -503,5 +576,11 @@ /> | ||
renderHour12 = () => { | ||
const { hour } = this.state; | ||
renderHour12 = (currentMatch) => { | ||
const { amPm, hour } = this.state; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error(`Unsupported token: ${currentMatch}`); | ||
} | ||
const showLeadingZeros = currentMatch && currentMatch.length === 2; | ||
return ( | ||
@@ -511,2 +590,4 @@ <Hour12Input | ||
{...this.commonInputProps} | ||
amPm={amPm} | ||
showLeadingZeros={showLeadingZeros} | ||
value={hour} | ||
@@ -517,5 +598,11 @@ /> | ||
renderHour24 = () => { | ||
renderHour24 = (currentMatch) => { | ||
const { hour } = this.state; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error(`Unsupported token: ${currentMatch}`); | ||
} | ||
const showLeadingZeros = currentMatch && currentMatch.length === 2; | ||
return ( | ||
@@ -525,2 +612,3 @@ <Hour24Input | ||
{...this.commonInputProps} | ||
showLeadingZeros={showLeadingZeros} | ||
value={hour} | ||
@@ -531,6 +619,11 @@ /> | ||
renderMinute = () => { | ||
const { maxDetail } = this.props; | ||
renderMinute = (currentMatch) => { | ||
const { hour, minute } = this.state; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error(`Unsupported token: ${currentMatch}`); | ||
} | ||
const showLeadingZeros = currentMatch && currentMatch.length === 2; | ||
return ( | ||
@@ -541,3 +634,3 @@ <MinuteInput | ||
hour={hour} | ||
maxDetail={maxDetail} | ||
showLeadingZeros={showLeadingZeros} | ||
value={minute} | ||
@@ -548,6 +641,11 @@ /> | ||
renderSecond = () => { | ||
const { maxDetail } = this.props; | ||
renderSecond = (currentMatch) => { | ||
const { hour, minute, second } = this.state; | ||
if (currentMatch && currentMatch.length > 2) { | ||
throw new Error(`Unsupported token: ${currentMatch}`); | ||
} | ||
const showLeadingZeros = currentMatch ? currentMatch.length === 2 : true; | ||
return ( | ||
@@ -558,4 +656,4 @@ <SecondInput | ||
hour={hour} | ||
maxDetail={maxDetail} | ||
minute={minute} | ||
showLeadingZeros={showLeadingZeros} | ||
value={second} | ||
@@ -581,26 +679,18 @@ /> | ||
renderCustomDateInputs() { | ||
const { datePlaceholder } = this; | ||
renderCustomInputs() { | ||
const { placeholder } = this; | ||
const elementFunctions = { | ||
day: this.renderDay, | ||
month: this.renderMonth, | ||
year: this.renderYear, | ||
d: this.renderDay, | ||
M: this.renderMonth, | ||
y: this.renderYear, | ||
h: this.renderHour12, | ||
H: this.renderHour24, | ||
m: this.renderMinute, | ||
s: this.renderSecond, | ||
a: this.renderAmPm, | ||
}; | ||
return renderCustomInputs(datePlaceholder, elementFunctions); | ||
return renderCustomInputs(placeholder, elementFunctions); | ||
} | ||
renderCustomTimeInputs() { | ||
const { timePlaceholder } = this; | ||
const elementFunctions = { | ||
'hour-12': this.renderHour12, | ||
'hour-24': this.renderHour24, | ||
minute: this.renderMinute, | ||
second: this.renderSecond, | ||
ampm: this.renderAmPm, | ||
}; | ||
return renderCustomInputs(timePlaceholder, elementFunctions); | ||
} | ||
renderNativeInput() { | ||
@@ -613,4 +703,4 @@ const { | ||
required, | ||
value, | ||
} = this.props; | ||
const { value } = this.state; | ||
@@ -642,7 +732,3 @@ return ( | ||
{this.renderNativeInput()} | ||
{this.renderCustomDateInputs()} | ||
<Divider> | ||
{'\u00a0'} | ||
</Divider> | ||
{this.renderCustomTimeInputs()} | ||
{this.renderCustomInputs()} | ||
</div> | ||
@@ -661,2 +747,3 @@ ); | ||
disabled: PropTypes.bool, | ||
format: PropTypes.string, | ||
isWidgetOpen: PropTypes.bool, | ||
@@ -674,2 +761,3 @@ locale: PropTypes.string, | ||
PropTypes.instanceOf(Date), | ||
PropTypes.arrayOf(PropTypes.instanceOf(Date)), | ||
]), | ||
@@ -676,0 +764,0 @@ }; |
@@ -94,6 +94,2 @@ import React, { PureComponent } from 'react'; | ||
onTimeChange = (value, closeWidgets = true) => { | ||
this.onChange(value, closeWidgets); | ||
} | ||
onChange = (value, closeWidgets = true) => { | ||
@@ -182,2 +178,3 @@ this.setState(prevState => ({ | ||
disabled, | ||
format, | ||
locale, | ||
@@ -200,2 +197,3 @@ maxDetail, | ||
disabled={disabled} | ||
format={format} | ||
locale={locale} | ||
@@ -207,3 +205,3 @@ isWidgetOpen={isCalendarOpen || isClockOpen} | ||
name={name} | ||
onChange={this.onTimeChange} | ||
onChange={this.onChange} | ||
placeholder={this.placeholder} | ||
@@ -381,4 +379,5 @@ required={required} | ||
]), | ||
disableClock: PropTypes.bool, | ||
disabled: PropTypes.bool, | ||
disableClock: PropTypes.bool, | ||
format: PropTypes.string, | ||
isCalendarOpen: PropTypes.bool, | ||
@@ -390,3 +389,7 @@ isClockOpen: PropTypes.bool, | ||
name: PropTypes.string, | ||
onCalendarClose: PropTypes.func, | ||
onCalendarOpen: PropTypes.func, | ||
onChange: PropTypes.func, | ||
onClockClose: PropTypes.func, | ||
onClockOpen: PropTypes.func, | ||
required: PropTypes.bool, | ||
@@ -393,0 +396,0 @@ showLeadingZeros: PropTypes.bool, |
import getUserLocale from 'get-user-locale'; | ||
const formatterCache = {}; | ||
/* eslint-disable import/prefer-default-export */ | ||
/** | ||
* Gets Intl-based date formatter from formatter cache. If it doesn't exist in cache | ||
* just yet, it will be created on the fly. | ||
*/ | ||
export const getFormatter = (locale, options) => { | ||
if (!locale) { | ||
// Default parameter is not enough as it does not protect us from null values | ||
// eslint-disable-next-line no-param-reassign | ||
locale = getUserLocale(); | ||
} | ||
export const getFormatter = options => (locale, date) => ( | ||
date.toLocaleString(locale || getUserLocale(), options) | ||
); | ||
const stringifiedOptions = JSON.stringify(options); | ||
if (!formatterCache[locale]) { | ||
formatterCache[locale] = {}; | ||
} | ||
if (!formatterCache[locale][stringifiedOptions]) { | ||
formatterCache[locale][stringifiedOptions] = n => n.toLocaleString(locale, options); | ||
} | ||
return formatterCache[locale][stringifiedOptions]; | ||
}; | ||
export const formatDate = (locale, date) => getFormatter( | ||
locale, | ||
{ day: 'numeric', month: 'numeric', year: 'numeric' }, | ||
)(date); | ||
const formatDateOptions = { day: 'numeric', month: 'numeric', year: 'numeric' }; | ||
export const formatDate = getFormatter(formatDateOptions); |
export { | ||
between, | ||
callIfDefined, | ||
@@ -3,0 +4,0 @@ min, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
156943
32
3831
142
Updatedreact-date-picker@^7.5.1
Updatedreact-time-picker@^3.5.2