react-time-picker
Advanced tools
Comparing version 3.0.0 to 3.1.0
@@ -6,3 +6,3 @@ 'use strict'; | ||
}); | ||
exports.formatTime = undefined; | ||
exports.getFormatter = undefined; | ||
@@ -17,2 +17,4 @@ var _getUserLocale = require('get-user-locale'); | ||
/* eslint-disable import/prefer-default-export */ | ||
/** | ||
@@ -22,3 +24,3 @@ * Gets Intl-based date formatter from formatter cache. If it doesn't exist in cache | ||
*/ | ||
var getFormatter = function getFormatter(options, locale) { | ||
var getFormatter = exports.getFormatter = function getFormatter(options, locale) { | ||
if (!locale) { | ||
@@ -41,7 +43,2 @@ // Default parameter is not enough as it does not protect us from null values | ||
return formatterCache[locale][stringifiedOptions]; | ||
}; | ||
// eslint-disable-next-line import/prefer-default-export | ||
var formatTime = exports.formatTime = function formatTime(date, locale) { | ||
return getFormatter({ hour: 'numeric', minute: 'numeric', second: 'numeric' }, locale)(date); | ||
}; |
@@ -6,3 +6,8 @@ 'use strict'; | ||
}); | ||
exports.getAmPmLabels = exports.updateInputWidth = exports.max = exports.min = undefined; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
var _dateFormatter = require('./dateFormatter'); | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
@@ -13,2 +18,3 @@ | ||
}; | ||
var min = exports.min = function min() { | ||
@@ -40,5 +46,36 @@ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
element.style.width = width + 'px'; | ||
element.style.width = Math.ceil(width) + 'px'; | ||
container.removeChild(span); | ||
}; | ||
var getAmPmLabels = exports.getAmPmLabels = function getAmPmLabels(locale) { | ||
var amPmFormatter = (0, _dateFormatter.getFormatter)({ hour: 'numeric' }, locale); | ||
var amString = amPmFormatter(new Date(2017, 0, 1, 9)); | ||
var pmString = amPmFormatter(new Date(2017, 0, 1, 21)); | ||
var _amString$split = amString.split('9'), | ||
_amString$split2 = _slicedToArray(_amString$split, 2), | ||
am1 = _amString$split2[0], | ||
am2 = _amString$split2[1]; | ||
var _pmString$split = pmString.split('9'), | ||
_pmString$split2 = _slicedToArray(_pmString$split, 2), | ||
pm1 = _pmString$split2[0], | ||
pm2 = _pmString$split2[1]; | ||
if (am1 !== pm1) { | ||
return [am1, pm1].map(function (el) { | ||
return el.trim(); | ||
}); | ||
} | ||
if (am2 !== pm2) { | ||
return [am2, pm2].map(function (el) { | ||
return el.trim(); | ||
}); | ||
} | ||
// Fallback | ||
return ['am', 'pm']; | ||
}; |
@@ -7,6 +7,6 @@ 'use strict'; | ||
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; }; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
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; }; | ||
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; }; }(); | ||
@@ -58,2 +58,4 @@ | ||
var _utils = require('./shared/utils'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -69,2 +71,4 @@ | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
var allViews = ['hour', 'minute', 'second']; | ||
@@ -97,13 +101,19 @@ | ||
var removeUnwantedCharacters = function removeUnwantedCharacters(str) { | ||
return str.split('').filter(function (a) { | ||
return ( | ||
// We don't want spaces in dates | ||
a.charCodeAt(0) !== 32 | ||
// Internet Explorer specific | ||
&& a.charCodeAt(0) !== 8206 | ||
// Remove non-ASCII characters | ||
&& /^[\x20-\x7F]*$/.test(a) | ||
var _renderCustomInputs = function _renderCustomInputs(placeholder, elementFunctions) { | ||
var pattern = new RegExp(Object.keys(elementFunctions).join('|'), 'gi'); | ||
var matches = placeholder.match(pattern); | ||
return placeholder.split(pattern).reduce(function (arr, element, index) { | ||
var divider = element && | ||
// eslint-disable-next-line react/no-array-index-key | ||
_react2.default.createElement( | ||
_Divider2.default, | ||
{ key: 'separator_' + index }, | ||
element | ||
); | ||
}).join(''); | ||
var res = [].concat(_toConsumableArray(arr), [divider]); | ||
if (matches[index]) { | ||
res.push(elementFunctions[matches[index]]()); | ||
} | ||
return res; | ||
}, []); | ||
}; | ||
@@ -130,2 +140,10 @@ | ||
second: null | ||
}, _this.onClick = function (event) { | ||
if (event.target === event.currentTarget) { | ||
// Wrapper was directly clicked | ||
var _event$target$childre = _slicedToArray(event.target.children, 2), | ||
firstInput = _event$target$childre[1]; | ||
focus(firstInput); | ||
} | ||
}, _this.onKeyDown = function (event) { | ||
@@ -197,3 +215,3 @@ switch (event.key) { | ||
onChange(processedValue); | ||
onChange(processedValue, false); | ||
}, _this.onChangeAmPm = function (event) { | ||
@@ -224,3 +242,3 @@ var value = event.target.value; | ||
})) { | ||
onChange(null); | ||
onChange(null, false); | ||
} else if (formElements.every(function (formElement) { | ||
@@ -234,41 +252,15 @@ return formElement.value && formElement.checkValidity(); | ||
var processedValue = _this.getProcessedValue(timeString); | ||
onChange(processedValue); | ||
onChange(processedValue, false); | ||
} | ||
}, _temp), _possibleConstructorReturn(_this, _ret); | ||
} | ||
}, _this.renderHour12 = function () { | ||
var hour = _this.state.hour; | ||
_createClass(TimeInput, [{ | ||
key: 'getProcessedValue', | ||
/** | ||
* Gets current value in a desired format. | ||
*/ | ||
value: function getProcessedValue(value) { | ||
var nativeValueParser = this.nativeValueParser; | ||
return nativeValueParser(value); | ||
} | ||
/** | ||
* Returns value type that can be returned with currently applied settings. | ||
*/ | ||
}, { | ||
key: 'renderHour12', | ||
value: function renderHour12() { | ||
var hour = this.state.hour; | ||
return _react2.default.createElement(_Hour12Input2.default, _extends({ | ||
key: 'hour12' | ||
}, this.commonInputProps, { | ||
}, _this.commonInputProps, { | ||
value: hour | ||
})); | ||
} | ||
}, { | ||
key: 'renderHour24', | ||
value: function renderHour24() { | ||
var hour = this.state.hour; | ||
}, _this.renderHour24 = function () { | ||
var hour = _this.state.hour; | ||
@@ -278,25 +270,15 @@ | ||
key: 'hour24' | ||
}, this.commonInputProps, { | ||
}, _this.commonInputProps, { | ||
value: hour | ||
})); | ||
} | ||
}, { | ||
key: 'renderMinute', | ||
value: function renderMinute() { | ||
var maxDetail = this.props.maxDetail; | ||
}, _this.renderMinute = function () { | ||
var maxDetail = _this.props.maxDetail; | ||
var _this$state = _this.state, | ||
hour = _this$state.hour, | ||
minute = _this$state.minute; | ||
// Do not display if maxDetail is "hour" or less | ||
if (allViews.indexOf(maxDetail) < 1) { | ||
return null; | ||
} | ||
var _state = this.state, | ||
hour = _state.hour, | ||
minute = _state.minute; | ||
return _react2.default.createElement(_MinuteInput2.default, _extends({ | ||
key: 'minute' | ||
}, this.commonInputProps, { | ||
}, _this.commonInputProps, { | ||
hour: hour, | ||
@@ -306,23 +288,13 @@ maxDetail: maxDetail, | ||
})); | ||
} | ||
}, { | ||
key: 'renderSecond', | ||
value: function renderSecond() { | ||
var maxDetail = this.props.maxDetail; | ||
}, _this.renderSecond = function () { | ||
var maxDetail = _this.props.maxDetail; | ||
var _this$state2 = _this.state, | ||
hour = _this$state2.hour, | ||
minute = _this$state2.minute, | ||
second = _this$state2.second; | ||
// Do not display if maxDetail is "minute" or less | ||
if (allViews.indexOf(maxDetail) < 2) { | ||
return null; | ||
} | ||
var _state2 = this.state, | ||
hour = _state2.hour, | ||
minute = _state2.minute, | ||
second = _state2.second; | ||
return _react2.default.createElement(_SecondInput2.default, _extends({ | ||
key: 'second' | ||
}, this.commonInputProps, { | ||
}, _this.commonInputProps, { | ||
hour: hour, | ||
@@ -333,7 +305,5 @@ maxDetail: maxDetail, | ||
})); | ||
} | ||
}, { | ||
key: 'renderAmPm', | ||
value: function renderAmPm() { | ||
var amPm = this.state.amPm; | ||
}, _this.renderAmPm = function () { | ||
var amPm = _this.state.amPm; | ||
var locale = _this.props.locale; | ||
@@ -343,46 +313,42 @@ | ||
key: 'ampm' | ||
}, this.commonInputProps, { | ||
value: amPm, | ||
onChange: this.onChangeAmPm | ||
}, _this.commonInputProps, { | ||
locale: locale, | ||
onChange: _this.onChangeAmPm, | ||
value: amPm | ||
})); | ||
}, _temp), _possibleConstructorReturn(_this, _ret); | ||
} | ||
_createClass(TimeInput, [{ | ||
key: 'getProcessedValue', | ||
/** | ||
* Gets current value in a desired format. | ||
*/ | ||
value: function getProcessedValue(value) { | ||
var nativeValueParser = this.nativeValueParser; | ||
return nativeValueParser(value); | ||
} | ||
/** | ||
* Returns value type that can be returned with currently applied settings. | ||
*/ | ||
}, { | ||
key: 'renderCustomInputs', | ||
value: function renderCustomInputs() { | ||
var _this2 = this; | ||
var placeholder = this.placeholder; | ||
var divider = this.divider, | ||
placeholder = this.placeholder; | ||
var elementFunctions = { | ||
'hour-12': this.renderHour12, | ||
'hour-24': this.renderHour24, | ||
minute: this.renderMinute, | ||
second: this.renderSecond, | ||
ampm: this.renderAmPm | ||
}; | ||
return placeholder.split(divider).map(function (part) { | ||
switch (part) { | ||
case 'hour-12': | ||
return _this2.renderHour12(); | ||
case 'hour-24': | ||
return _this2.renderHour24(); | ||
case 'minute': | ||
return _this2.renderMinute(); | ||
case 'second': | ||
return _this2.renderSecond(); | ||
case 'ampm': | ||
return _this2.renderAmPm(); | ||
default: | ||
return null; | ||
} | ||
}).filter(Boolean).reduce(function (result, element, index) { | ||
if (index && element.key !== 'ampm') { | ||
result.push( | ||
// eslint-disable-next-line react/no-array-index-key | ||
_react2.default.createElement( | ||
_Divider2.default, | ||
{ key: 'separator_' + index }, | ||
divider | ||
)); | ||
} | ||
result.push(element); | ||
return result; | ||
}, []); | ||
return _renderCustomInputs(placeholder, elementFunctions); | ||
} | ||
@@ -421,3 +387,7 @@ }, { | ||
'div', | ||
{ className: className }, | ||
{ | ||
className: className, | ||
onClick: this.onClick, | ||
role: 'presentation' | ||
}, | ||
this.renderNativeInput(), | ||
@@ -428,2 +398,21 @@ this.renderCustomInputs() | ||
}, { | ||
key: 'formatTime', | ||
get: function get() { | ||
var _props2 = this.props, | ||
locale = _props2.locale, | ||
maxDetail = _props2.maxDetail; | ||
var options = { hour: 'numeric' }; | ||
var level = allViews.indexOf(maxDetail); | ||
if (level >= 1) { | ||
options.minute = 'numeric'; | ||
} | ||
if (level >= 2) { | ||
options.second = 'numeric'; | ||
} | ||
return (0, _dateFormatter.getFormatter)(options, locale); | ||
} | ||
}, { | ||
key: 'valueType', | ||
@@ -449,17 +438,9 @@ get: function get() { | ||
} | ||
// eslint-disable-next-line class-methods-use-this | ||
}, { | ||
key: 'divider', | ||
get: function get() { | ||
var locale = this.props.locale; | ||
var date = new Date(2017, 0, 1, 21, 12, 13); | ||
return removeUnwantedCharacters((0, _dateFormatter.formatTime)(date, locale)).match(/[^0-9]/)[0]; | ||
return this.formatTime(date).match(/[^0-9a-z]/i)[0]; | ||
} | ||
// eslint-disable-next-line class-methods-use-this | ||
}, { | ||
@@ -472,3 +453,3 @@ key: 'placeholder', | ||
return removeUnwantedCharacters((0, _dateFormatter.formatTime)(date, locale)).replace('21', 'hour-24').replace('9', 'hour-12').replace('13', 'minute').replace('14', 'second').replace(/AM|PM/i, this.divider + 'ampm'); | ||
return this.formatTime(date).replace('21', 'hour-24').replace('9', 'hour-12').replace('13', 'minute').replace('14', 'second').replace(new RegExp((0, _utils.getAmPmLabels)(locale).join('|')), 'ampm'); | ||
} | ||
@@ -478,11 +459,11 @@ }, { | ||
get: function get() { | ||
var _this3 = this; | ||
var _this2 = this; | ||
var _props2 = this.props, | ||
className = _props2.className, | ||
disabled = _props2.disabled, | ||
isClockOpen = _props2.isClockOpen, | ||
maxTime = _props2.maxTime, | ||
minTime = _props2.minTime, | ||
required = _props2.required; | ||
var _props3 = this.props, | ||
className = _props3.className, | ||
disabled = _props3.disabled, | ||
isClockOpen = _props3.isClockOpen, | ||
maxTime = _props3.maxTime, | ||
minTime = _props3.minTime, | ||
required = _props3.required; | ||
@@ -502,3 +483,3 @@ | ||
// Save a reference to each input field | ||
_this3[name + 'Input'] = ref; | ||
_this2[name + 'Input'] = ref; | ||
} | ||
@@ -505,0 +486,0 @@ }; |
@@ -7,2 +7,4 @@ 'use strict'; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
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; }; }(); | ||
@@ -26,2 +28,4 @@ | ||
var _utils = require('../shared/utils'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -51,2 +55,3 @@ | ||
itemRef = _props.itemRef, | ||
locale = _props.locale, | ||
onChange = _props.onChange, | ||
@@ -59,2 +64,7 @@ required = _props.required, | ||
var _getAmPmLabels = (0, _utils.getAmPmLabels)(locale), | ||
_getAmPmLabels2 = _slicedToArray(_getAmPmLabels, 2), | ||
amLabel = _getAmPmLabels2[0], | ||
pmLabel = _getAmPmLabels2[1]; | ||
return _react2.default.createElement( | ||
@@ -83,3 +93,3 @@ 'select', | ||
{ disabled: this.amDisabled, value: 'am' }, | ||
'am' | ||
amLabel | ||
), | ||
@@ -89,3 +99,3 @@ _react2.default.createElement( | ||
{ disabled: this.pmDisabled, value: 'pm' }, | ||
'pm' | ||
pmLabel | ||
) | ||
@@ -119,2 +129,3 @@ ); | ||
itemRef: _propTypes2.default.func, | ||
locale: _propTypes2.default.string, | ||
maxTime: _propTypes3.isTime, | ||
@@ -121,0 +132,0 @@ minTime: _propTypes3.isTime, |
@@ -70,7 +70,3 @@ 'use strict'; | ||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = TimePicker.__proto__ || Object.getPrototypeOf(TimePicker)).call.apply(_ref, [this].concat(args))), _this), _this.state = {}, _this.onClick = function (event) { | ||
if (_this.wrapper && !_this.wrapper.contains(event.target)) { | ||
_this.closeClock(); | ||
} | ||
}, _this.openClock = function () { | ||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = TimePicker.__proto__ || Object.getPrototypeOf(TimePicker)).call.apply(_ref, [this].concat(args))), _this), _this.state = {}, _this.openClock = function () { | ||
_this.setState({ isOpen: true }); | ||
@@ -117,2 +113,11 @@ }, _this.closeClock = function () { | ||
_this.openClock(); | ||
}, _this.onBlur = function () { | ||
var onBlur = _this.props.onBlur; | ||
if (onBlur) { | ||
onBlur(event); | ||
} | ||
_this.closeClock(); | ||
}, _this.stopPropagation = function (event) { | ||
@@ -126,12 +131,2 @@ return event.stopPropagation(); | ||
_createClass(TimePicker, [{ | ||
key: 'componentDidMount', | ||
value: function componentDidMount() { | ||
document.addEventListener('mousedown', this.onClick); | ||
} | ||
}, { | ||
key: 'componentWillUnmount', | ||
value: function componentWillUnmount() { | ||
document.removeEventListener('mousedown', this.onClick); | ||
} | ||
}, { | ||
key: 'renderInputs', | ||
@@ -199,2 +194,4 @@ value: function renderInputs() { | ||
value: function renderClock() { | ||
var _this2 = this; | ||
var disableClock = this.props.disableClock; | ||
@@ -224,3 +221,3 @@ var isOpen = this.state.isOpen; | ||
ref: function ref(_ref2) { | ||
if (!_ref2) { | ||
if (!_ref2 || !isOpen) { | ||
return; | ||
@@ -234,3 +231,8 @@ } | ||
if (collisions.collidedBottom) { | ||
_ref2.classList.add(className + '--above-label'); | ||
var overflowTopAfterChange = collisions.overflowTop + _ref2.clientHeight + _this2.wrapper.clientHeight; | ||
// If it's going to make situation any better, display the calendar above the input | ||
if (overflowTopAfterChange < collisions.overflowBottom) { | ||
_ref2.classList.add(className + '--above-label'); | ||
} | ||
} | ||
@@ -249,3 +251,3 @@ } | ||
value: function render() { | ||
var _this2 = this; | ||
var _this3 = this; | ||
@@ -264,4 +266,9 @@ var _props3 = this.props, | ||
onFocus: this.onFocus, | ||
onBlur: this.onBlur, | ||
ref: function ref(_ref3) { | ||
_this2.wrapper = _ref3; | ||
if (!_ref3) { | ||
return; | ||
} | ||
_this3.wrapper = _ref3; | ||
} | ||
@@ -268,0 +275,0 @@ }), |
{ | ||
"name": "react-time-picker", | ||
"version": "3.0.0", | ||
"version": "3.1.0", | ||
"description": "A time picker for your React app.", | ||
@@ -5,0 +5,0 @@ "main": "dist/entry.js", |
@@ -213,4 +213,6 @@ import React from 'react'; | ||
expect(separators).toHaveLength(2); | ||
expect(separators).toHaveLength(3); | ||
expect(separators.at(0).text()).toBe(':'); | ||
expect(separators.at(1).text()).toBe(':'); | ||
expect(separators.at(2).text()).toBe(' '); | ||
}); | ||
@@ -225,4 +227,5 @@ | ||
const customInputs = component.find('input[type="number"]'); | ||
const ampm = component.find('select'); | ||
expect(separators).toHaveLength(customInputs.length - 1); | ||
expect(separators).toHaveLength(customInputs.length + ampm.length - 1); | ||
}); | ||
@@ -282,12 +285,11 @@ | ||
const customInputs = component.find('input[type="number"]'); | ||
const secondInput = customInputs.at(2); | ||
const select = component.find('select'); | ||
secondInput.getDOMNode().focus(); | ||
select.getDOMNode().focus(); | ||
expect(document.activeElement).toBe(secondInput.getDOMNode()); | ||
expect(document.activeElement).toBe(select.getDOMNode()); | ||
secondInput.simulate('keydown', getKey('ArrowRight')); | ||
select.simulate('keydown', getKey('ArrowRight')); | ||
expect(document.activeElement).toBe(secondInput.getDOMNode()); | ||
expect(document.activeElement).toBe(select.getDOMNode()); | ||
}); | ||
@@ -316,3 +318,3 @@ | ||
it('does not jump to the next field when right arrow is pressed when the last input is focused', () => { | ||
it('does not jump to the previous field when left arrow is pressed when the first input is focused', () => { | ||
const component = mount( | ||
@@ -356,3 +358,3 @@ <TimeInput | ||
expect(onChange).toHaveBeenCalled(); | ||
expect(onChange).toHaveBeenCalledWith('20:17:00'); | ||
expect(onChange).toHaveBeenCalledWith('20:17:00', false); | ||
}); | ||
@@ -381,3 +383,3 @@ | ||
expect(onChange).toHaveBeenCalledTimes(1); | ||
expect(onChange).toHaveBeenCalledWith(null); | ||
expect(onChange).toHaveBeenCalledWith(null, false); | ||
}); | ||
@@ -404,3 +406,3 @@ | ||
expect(onChange).toHaveBeenCalled(); | ||
expect(onChange).toHaveBeenCalledWith('20:17:00'); | ||
expect(onChange).toHaveBeenCalledWith('20:17:00', false); | ||
}); | ||
@@ -427,4 +429,4 @@ | ||
expect(onChange).toHaveBeenCalled(); | ||
expect(onChange).toHaveBeenCalledWith(null); | ||
expect(onChange).toHaveBeenCalledWith(null, false); | ||
}); | ||
}); |
@@ -8,18 +8,2 @@ import React from 'react'; | ||
const mockDocumentListeners = () => { | ||
const eventMap = {}; | ||
document.addEventListener = jest.fn((method, cb) => { | ||
if (!eventMap[method]) { | ||
eventMap[method] = []; | ||
} | ||
eventMap[method].push(cb); | ||
}); | ||
return { | ||
simulate: (method, args) => { | ||
eventMap[method].forEach(cb => cb(args)); | ||
}, | ||
}; | ||
}; | ||
describe('TimePicker', () => { | ||
@@ -183,5 +167,3 @@ it('passes default name to TimeInput', () => { | ||
it('closes Calendar component when clicked outside', () => { | ||
const { simulate } = mockDocumentListeners(); | ||
it('closes Calendar component when focused outside', () => { | ||
const component = mount( | ||
@@ -191,5 +173,6 @@ <TimePicker isOpen /> | ||
simulate('mousedown', { | ||
target: document, | ||
}); | ||
const customInputs = component.find('input[type="number"]'); | ||
const hourInput = customInputs.at(0); | ||
hourInput.simulate('blur'); | ||
component.update(); | ||
@@ -200,5 +183,3 @@ | ||
it('does not close Calendar component when clicked inside', () => { | ||
const { simulate } = mockDocumentListeners(); | ||
it('does not close Calendar component when focused inside', () => { | ||
const component = mount( | ||
@@ -208,5 +189,8 @@ <TimePicker isOpen /> | ||
simulate('mousedown', { | ||
target: component.getDOMNode(), | ||
}); | ||
const customInputs = component.find('input[type="number"]'); | ||
const hourInput = customInputs.at(0); | ||
const minuteInput = customInputs.at(1); | ||
hourInput.simulate('blur'); | ||
minuteInput.simulate('focus'); | ||
component.update(); | ||
@@ -213,0 +197,0 @@ |
@@ -5,2 +5,4 @@ import getUserLocale from 'get-user-locale'; | ||
/* eslint-disable import/prefer-default-export */ | ||
/** | ||
@@ -10,3 +12,3 @@ * Gets Intl-based date formatter from formatter cache. If it doesn't exist in cache | ||
*/ | ||
const getFormatter = (options, locale) => { | ||
export const getFormatter = (options, locale) => { | ||
if (!locale) { | ||
@@ -30,7 +32,1 @@ // Default parameter is not enough as it does not protect us from null values | ||
}; | ||
// eslint-disable-next-line import/prefer-default-export | ||
export const formatTime = (date, locale) => getFormatter( | ||
{ hour: 'numeric', minute: 'numeric', second: 'numeric' }, | ||
locale, | ||
)(date); |
@@ -0,2 +1,5 @@ | ||
import { getFormatter } from './dateFormatter'; | ||
const isValidNumber = a => typeof a === 'number' && !isNaN(a); | ||
export const min = (...args) => Math.min(...args.filter(isValidNumber)); | ||
@@ -14,5 +17,25 @@ export const max = (...args) => Math.max(...args.filter(isValidNumber)); | ||
const { width } = span.getBoundingClientRect(); | ||
element.style.width = `${width}px`; | ||
element.style.width = `${Math.ceil(width)}px`; | ||
container.removeChild(span); | ||
}; | ||
export const getAmPmLabels = (locale) => { | ||
const amPmFormatter = getFormatter({ hour: 'numeric' }, locale); | ||
const amString = amPmFormatter(new Date(2017, 0, 1, 9)); | ||
const pmString = amPmFormatter(new Date(2017, 0, 1, 21)); | ||
const [am1, am2] = amString.split('9'); | ||
const [pm1, pm2] = pmString.split('9'); | ||
if (am1 !== pm1) { | ||
return [am1, pm1].map(el => el.trim()); | ||
} | ||
if (am2 !== pm2) { | ||
return [am2, pm2].map(el => el.trim()); | ||
} | ||
// Fallback | ||
return ['am', 'pm']; | ||
}; |
@@ -13,3 +13,3 @@ import React, { PureComponent } from 'react'; | ||
import { formatTime } from './shared/dateFormatter'; | ||
import { getFormatter } from './shared/dateFormatter'; | ||
import { | ||
@@ -25,2 +25,3 @@ getHours, | ||
import { isTime } from './shared/propTypes'; | ||
import { getAmPmLabels } from './shared/utils'; | ||
@@ -53,13 +54,20 @@ const allViews = ['hour', 'minute', 'second']; | ||
const removeUnwantedCharacters = str => str | ||
.split('') | ||
.filter(a => ( | ||
// We don't want spaces in dates | ||
a.charCodeAt(0) !== 32 | ||
// Internet Explorer specific | ||
&& a.charCodeAt(0) !== 8206 | ||
// Remove non-ASCII characters | ||
&& /^[\x20-\x7F]*$/.test(a) | ||
)) | ||
.join(''); | ||
const renderCustomInputs = (placeholder, elementFunctions) => { | ||
const pattern = new RegExp(Object.keys(elementFunctions).join('|'), 'gi'); | ||
const matches = placeholder.match(pattern); | ||
return placeholder.split(pattern) | ||
.reduce((arr, element, index) => { | ||
const divider = element && ( | ||
// eslint-disable-next-line react/no-array-index-key | ||
<Divider key={`separator_${index}`}> | ||
{element} | ||
</Divider> | ||
); | ||
const res = [...arr, divider]; | ||
if (matches[index]) { | ||
res.push(elementFunctions[matches[index]]()); | ||
} | ||
return res; | ||
}, []); | ||
}; | ||
@@ -113,2 +121,17 @@ export default class TimeInput extends PureComponent { | ||
get formatTime() { | ||
const { locale, maxDetail } = this.props; | ||
const options = { hour: 'numeric' }; | ||
const level = allViews.indexOf(maxDetail); | ||
if (level >= 1) { | ||
options.minute = 'numeric'; | ||
} | ||
if (level >= 2) { | ||
options.second = 'numeric'; | ||
} | ||
return getFormatter(options, locale); | ||
} | ||
/** | ||
@@ -144,14 +167,8 @@ * Gets current value in a desired format. | ||
// eslint-disable-next-line class-methods-use-this | ||
get divider() { | ||
const { locale } = this.props; | ||
const date = new Date(2017, 0, 1, 21, 12, 13); | ||
return ( | ||
removeUnwantedCharacters(formatTime(date, locale)) | ||
.match(/[^0-9]/)[0] | ||
); | ||
return this.formatTime(date).match(/[^0-9a-z]/i)[0]; | ||
} | ||
// eslint-disable-next-line class-methods-use-this | ||
get placeholder() { | ||
@@ -162,3 +179,3 @@ const { locale } = this.props; | ||
return ( | ||
removeUnwantedCharacters(formatTime(date, locale)) | ||
this.formatTime(date) | ||
.replace('21', 'hour-24') | ||
@@ -168,3 +185,3 @@ .replace('9', 'hour-12') | ||
.replace('14', 'second') | ||
.replace(/AM|PM/i, `${this.divider}ampm`) | ||
.replace(new RegExp(getAmPmLabels(locale).join('|')), 'ampm') | ||
); | ||
@@ -200,2 +217,10 @@ } | ||
onClick = (event) => { | ||
if (event.target === event.currentTarget) { | ||
// Wrapper was directly clicked | ||
const [/* nativeInput */, firstInput] = event.target.children; | ||
focus(firstInput); | ||
} | ||
} | ||
onKeyDown = (event) => { | ||
@@ -275,3 +300,3 @@ switch (event.key) { | ||
onChange(processedValue); | ||
onChange(processedValue, false); | ||
} | ||
@@ -315,3 +340,3 @@ | ||
if (formElementsWithoutSelect.every(formElement => !formElement.value)) { | ||
onChange(null); | ||
onChange(null, false); | ||
} else if ( | ||
@@ -325,7 +350,7 @@ formElements.every(formElement => formElement.value && formElement.checkValidity()) | ||
const processedValue = this.getProcessedValue(timeString); | ||
onChange(processedValue); | ||
onChange(processedValue, false); | ||
} | ||
} | ||
renderHour12() { | ||
renderHour12 = () => { | ||
const { hour } = this.state; | ||
@@ -342,3 +367,3 @@ | ||
renderHour24() { | ||
renderHour24 = () => { | ||
const { hour } = this.state; | ||
@@ -355,10 +380,4 @@ | ||
renderMinute() { | ||
renderMinute = () => { | ||
const { maxDetail } = this.props; | ||
// Do not display if maxDetail is "hour" or less | ||
if (allViews.indexOf(maxDetail) < 1) { | ||
return null; | ||
} | ||
const { hour, minute } = this.state; | ||
@@ -377,10 +396,4 @@ | ||
renderSecond() { | ||
renderSecond = () => { | ||
const { maxDetail } = this.props; | ||
// Do not display if maxDetail is "minute" or less | ||
if (allViews.indexOf(maxDetail) < 2) { | ||
return null; | ||
} | ||
const { hour, minute, second } = this.state; | ||
@@ -400,4 +413,5 @@ | ||
renderAmPm() { | ||
renderAmPm = () => { | ||
const { amPm } = this.state; | ||
const { locale } = this.props; | ||
@@ -408,4 +422,5 @@ return ( | ||
{...this.commonInputProps} | ||
locale={locale} | ||
onChange={this.onChangeAmPm} | ||
value={amPm} | ||
onChange={this.onChangeAmPm} | ||
/> | ||
@@ -416,33 +431,12 @@ ); | ||
renderCustomInputs() { | ||
const { divider, placeholder } = this; | ||
const { placeholder } = this; | ||
const elementFunctions = { | ||
'hour-12': this.renderHour12, | ||
'hour-24': this.renderHour24, | ||
minute: this.renderMinute, | ||
second: this.renderSecond, | ||
ampm: this.renderAmPm, | ||
}; | ||
return ( | ||
placeholder | ||
.split(divider) | ||
.map((part) => { | ||
switch (part) { | ||
case 'hour-12': return this.renderHour12(); | ||
case 'hour-24': return this.renderHour24(); | ||
case 'minute': return this.renderMinute(); | ||
case 'second': return this.renderSecond(); | ||
case 'ampm': return this.renderAmPm(); | ||
default: return null; | ||
} | ||
}) | ||
.filter(Boolean) | ||
.reduce((result, element, index) => { | ||
if (index && element.key !== 'ampm') { | ||
result.push( | ||
// eslint-disable-next-line react/no-array-index-key | ||
<Divider key={`separator_${index}`}> | ||
{divider} | ||
</Divider>, | ||
); | ||
} | ||
result.push(element); | ||
return result; | ||
}, []) | ||
); | ||
return renderCustomInputs(placeholder, elementFunctions); | ||
} | ||
@@ -479,3 +473,7 @@ | ||
return ( | ||
<div className={className}> | ||
<div | ||
className={className} | ||
onClick={this.onClick} | ||
role="presentation" | ||
> | ||
{this.renderNativeInput()} | ||
@@ -482,0 +480,0 @@ {this.renderCustomInputs()} |
@@ -10,2 +10,3 @@ import React, { PureComponent } from 'react'; | ||
import { isTime } from '../shared/propTypes'; | ||
import { getAmPmLabels } from '../shared/utils'; | ||
@@ -27,6 +28,7 @@ class AmPm extends PureComponent { | ||
const { | ||
className, disabled, itemRef, onChange, required, value, | ||
className, disabled, itemRef, locale, onChange, required, value, | ||
} = this.props; | ||
const name = 'amPm'; | ||
const [amLabel, pmLabel] = getAmPmLabels(locale); | ||
@@ -56,6 +58,6 @@ return ( | ||
<option disabled={this.amDisabled} value="am"> | ||
am | ||
{amLabel} | ||
</option> | ||
<option disabled={this.pmDisabled} value="pm"> | ||
pm | ||
{pmLabel} | ||
</option> | ||
@@ -71,2 +73,3 @@ </select> | ||
itemRef: PropTypes.func, | ||
locale: PropTypes.string, | ||
maxTime: isTime, | ||
@@ -73,0 +76,0 @@ minTime: isTime, |
@@ -35,16 +35,2 @@ import React, { PureComponent } from 'react'; | ||
componentDidMount() { | ||
document.addEventListener('mousedown', this.onClick); | ||
} | ||
componentWillUnmount() { | ||
document.removeEventListener('mousedown', this.onClick); | ||
} | ||
onClick = (event) => { | ||
if (this.wrapper && !this.wrapper.contains(event.target)) { | ||
this.closeClock(); | ||
} | ||
} | ||
openClock = () => { | ||
@@ -94,2 +80,12 @@ this.setState({ isOpen: true }); | ||
onBlur = () => { | ||
const { onBlur } = this.props; | ||
if (onBlur) { | ||
onBlur(event); | ||
} | ||
this.closeClock(); | ||
} | ||
stopPropagation = event => event.stopPropagation(); | ||
@@ -185,3 +181,3 @@ | ||
ref={(ref) => { | ||
if (!ref) { | ||
if (!ref || !isOpen) { | ||
return; | ||
@@ -195,3 +191,10 @@ } | ||
if (collisions.collidedBottom) { | ||
ref.classList.add(`${className}--above-label`); | ||
const overflowTopAfterChange = ( | ||
collisions.overflowTop + ref.clientHeight + this.wrapper.clientHeight | ||
); | ||
// If it's going to make situation any better, display the calendar above the input | ||
if (overflowTopAfterChange < collisions.overflowBottom) { | ||
ref.classList.add(`${className}--above-label`); | ||
} | ||
} | ||
@@ -224,3 +227,10 @@ }} | ||
onFocus={this.onFocus} | ||
ref={(ref) => { this.wrapper = ref; }} | ||
onBlur={this.onBlur} | ||
ref={(ref) => { | ||
if (!ref) { | ||
return; | ||
} | ||
this.wrapper = ref; | ||
}} | ||
> | ||
@@ -227,0 +237,0 @@ {this.renderInputs()} |
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
160026
4217
45