UNPKG

react-widgets

Version:

An à la carte set of polished, extensible, and accessible inputs built for React

551 lines (454 loc) 17.6 kB
'use strict'; var babelHelpers = require('./util/babelHelpers.js'); exports.__esModule = true; var _react = require('react'); var _react2 = babelHelpers.interopRequireDefault(_react); var _invariant = require('invariant'); var _invariant2 = babelHelpers.interopRequireDefault(_invariant); var _domHelpersActiveElement = require('dom-helpers/activeElement'); var _domHelpersActiveElement2 = babelHelpers.interopRequireDefault(_domHelpersActiveElement); var _classnames = require('classnames'); var _classnames2 = babelHelpers.interopRequireDefault(_classnames); var _utilCompat = require('./util/compat'); var _utilCompat2 = babelHelpers.interopRequireDefault(_utilCompat); var _util_ = require('./util/_'); var _util_2 = babelHelpers.interopRequireDefault(_util_); //pick, omit, has var _utilDates = require('./util/dates'); var _utilDates2 = babelHelpers.interopRequireDefault(_utilDates); var _utilLocalizers = require('./util/localizers'); var _utilConstants = require('./util/constants'); var _utilConstants2 = babelHelpers.interopRequireDefault(_utilConstants); var _Popup = require('./Popup'); var _Popup2 = babelHelpers.interopRequireDefault(_Popup); var _Calendar2 = require('./Calendar'); var _Calendar3 = babelHelpers.interopRequireDefault(_Calendar2); var _TimeList = require('./TimeList'); var _TimeList2 = babelHelpers.interopRequireDefault(_TimeList); var _DateInput = require('./DateInput'); var _DateInput2 = babelHelpers.interopRequireDefault(_DateInput); var _WidgetButton = require('./WidgetButton'); var _WidgetButton2 = babelHelpers.interopRequireDefault(_WidgetButton); var _utilPropTypes = require('./util/propTypes'); var _utilPropTypes2 = babelHelpers.interopRequireDefault(_utilPropTypes); var _uncontrollable = require('uncontrollable'); var _uncontrollable2 = babelHelpers.interopRequireDefault(_uncontrollable); var _utilInteraction = require('./util/interaction'); var _utilWidgetHelpers = require('./util/widgetHelpers'); var views = _utilConstants2['default'].calendarViews; var popups = _utilConstants2['default'].datePopups; var Calendar = _Calendar3['default'].ControlledComponent; var viewEnum = Object.keys(views).map(function (k) { return views[k]; }); var omit = _util_2['default'].omit; var pick = _util_2['default'].pick; var propTypes = babelHelpers._extends({}, Calendar.propTypes, { //-- controlled props ----------- value: _react2['default'].PropTypes.instanceOf(Date), onChange: _react2['default'].PropTypes.func, open: _react2['default'].PropTypes.oneOf([false, popups.TIME, popups.CALENDAR]), onToggle: _react2['default'].PropTypes.func, //------------------------------------ onSelect: _react2['default'].PropTypes.func, min: _react2['default'].PropTypes.instanceOf(Date), max: _react2['default'].PropTypes.instanceOf(Date), culture: _react2['default'].PropTypes.string, format: _utilPropTypes2['default'].dateFormat, timeFormat: _utilPropTypes2['default'].dateFormat, editFormat: _utilPropTypes2['default'].dateFormat, calendar: _react2['default'].PropTypes.bool, time: _react2['default'].PropTypes.bool, timeComponent: _utilPropTypes2['default'].elementType, //popup dropUp: _react2['default'].PropTypes.bool, duration: _react2['default'].PropTypes.number, placeholder: _react2['default'].PropTypes.string, name: _react2['default'].PropTypes.string, initialView: _react2['default'].PropTypes.oneOf(viewEnum), finalView: _react2['default'].PropTypes.oneOf(viewEnum), autoFocus: _react2['default'].PropTypes.bool, disabled: _utilPropTypes2['default'].disabled, readOnly: _utilPropTypes2['default'].readOnly, parse: _react2['default'].PropTypes.oneOfType([_react2['default'].PropTypes.arrayOf(_react2['default'].PropTypes.string), _react2['default'].PropTypes.string, _react2['default'].PropTypes.func]), 'aria-labelledby': _react2['default'].PropTypes.string, messages: _react2['default'].PropTypes.shape({ calendarButton: _react2['default'].PropTypes.string, timeButton: _react2['default'].PropTypes.string }) }); var DateTimePicker = _react2['default'].createClass(babelHelpers.createDecoratedObject([{ key: 'displayName', initializer: function initializer() { return 'DateTimePicker'; } }, { key: 'mixins', initializer: function initializer() { return [require('./mixins/TimeoutMixin'), require('./mixins/PureRenderMixin'), require('./mixins/PopupScrollToMixin'), require('./mixins/RtlParentContextMixin'), require('./mixins/AriaDescendantMixin')('valueInput', function (key, id) { var open = this.props.open; var current = this.ariaActiveDescendant(); var calIsActive = open === popups.CALENDAR && key === 'calendar'; var timeIsActive = open === popups.TIME && key === 'timelist'; if (!current || (timeIsActive || calIsActive)) return id; })]; } }, { key: 'propTypes', initializer: function initializer() { return propTypes; } }, { key: 'getInitialState', value: function getInitialState() { return { focused: false }; } }, { key: 'getDefaultProps', value: function getDefaultProps() { return { value: null, min: new Date(1900, 0, 1), max: new Date(2099, 11, 31), calendar: true, time: true, open: false, //calendar override footer: true, messages: { calendarButton: 'Select Date', timeButton: 'Select Time' }, ariaActiveDescendantKey: 'dropdownlist' }; } }, { key: 'render', value: function render() { var _cx, _this = this; var _props = this.props; var className = _props.className; var calendar = _props.calendar; var time = _props.time; var open = _props.open; var tabIndex = _props.tabIndex; var value = _props.value; var editFormat = _props.editFormat; var timeFormat = _props.timeFormat; var culture = _props.culture; var duration = _props.duration; var step = _props.step; var messages = _props.messages; var min = _props.min; var max = _props.max; var busy = _props.busy; var placeholder = _props.placeholder; var disabled = _props.disabled; var readOnly = _props.readOnly; var name = _props.name; var dropUp = _props.dropUp; var timeComponent = _props.timeComponent; var autoFocus = _props.autoFocus; var ariaLabelledby = _props['aria-labelledby']; var ariaDescribedby = _props['aria-describedby']; var focused = this.state.focused; var inputID = _utilWidgetHelpers.instanceId(this, '_input'), timeListID = _utilWidgetHelpers.instanceId(this, '_time_listbox'), dateListID = _utilWidgetHelpers.instanceId(this, '_cal'), owns = ''; var elementProps = omit(this.props, Object.keys(propTypes)), calProps = pick(this.props, Object.keys(Calendar.propTypes)); var shouldRenderList = _utilWidgetHelpers.isFirstFocusedRender(this) || open, disabledOrReadonly = disabled || readOnly, calendarIsOpen = open === popups.CALENDAR, timeIsOpen = open === popups.TIME; if (calendar) owns += dateListID; if (time) owns += ' ' + timeListID; value = dateOrNull(value); return _react2['default'].createElement( 'div', babelHelpers._extends({}, elementProps, { ref: 'element', tabIndex: '-1', onKeyDown: this._keyDown, onKeyPress: this._keyPress, onFocus: this._focus.bind(null, true), onBlur: this._focus.bind(null, false), className: _classnames2['default'](className, 'rw-datetimepicker', 'rw-widget', (_cx = { 'rw-state-focus': focused, 'rw-state-disabled': disabled, 'rw-state-readonly': readOnly, 'rw-has-both': calendar && time, 'rw-has-neither': !calendar && !time, 'rw-rtl': this.isRtl() }, _cx['rw-open' + (dropUp ? '-up' : '')] = open, _cx)) }), _react2['default'].createElement(_DateInput2['default'], { ref: 'valueInput', id: inputID, autoFocus: autoFocus, tabIndex: tabIndex || 0, role: 'combobox', 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-expanded': !!open, 'aria-busy': !!busy, 'aria-owns': owns.trim(), 'aria-haspopup': true, placeholder: placeholder, name: name, disabled: disabled, readOnly: readOnly, value: value, format: getFormat(this.props), editFormat: editFormat, editing: focused, culture: culture, parse: this._parse, onChange: this._change }), (calendar || time) && _react2['default'].createElement( 'span', { className: 'rw-select' }, calendar && _react2['default'].createElement( _WidgetButton2['default'], { tabIndex: '-1', className: 'rw-btn-calendar', disabled: disabledOrReadonly, 'aria-disabled': disabledOrReadonly, 'aria-label': messages.calendarButton, onClick: this._click.bind(null, popups.CALENDAR) }, _react2['default'].createElement('i', { className: 'rw-i rw-i-calendar', 'aria-hidden': 'true' }) ), time && _react2['default'].createElement( _WidgetButton2['default'], { tabIndex: '-1', className: 'rw-btn-time', disabled: disabledOrReadonly, 'aria-disabled': disabledOrReadonly, 'aria-label': messages.timeButton, onClick: this._click.bind(null, popups.TIME) }, _react2['default'].createElement('i', { className: 'rw-i rw-i-clock-o', 'aria-hidden': 'true' }) ) ), _react2['default'].createElement( _Popup2['default'], { dropUp: dropUp, open: timeIsOpen, duration: duration, onOpening: function () { return _this.refs.timePopup.forceUpdate(); } }, _react2['default'].createElement( 'div', null, shouldRenderList && _react2['default'].createElement(_TimeList2['default'], { ref: 'timePopup', id: timeListID, ariaActiveDescendantKey: 'timelist', 'aria-labelledby': inputID, 'aria-live': open && 'polite', 'aria-hidden': !open, value: value, format: timeFormat, step: step, min: min, max: max, culture: culture, onMove: this._scrollTo, preserveDate: !!calendar, itemComponent: timeComponent, onSelect: this._selectTime }) ) ), _react2['default'].createElement( _Popup2['default'], { className: 'rw-calendar-popup', dropUp: dropUp, open: calendarIsOpen, duration: duration }, shouldRenderList && _react2['default'].createElement(Calendar, babelHelpers._extends({}, calProps, { ref: 'calPopup', tabIndex: '-1', id: dateListID, value: value, 'aria-hidden': !open, 'aria-live': 'polite', ariaActiveDescendantKey: 'calendar', onChange: this._selectDate, // #75: need to aggressively reclaim focus from the calendar otherwise // disabled header/footer buttons will drop focus completely from the widget onNavigate: function () { return _this.focus(); } })) ) ); } }, { key: '_change', decorators: [_utilInteraction.widgetEditable], value: function _change(date, str, constrain) { var _props2 = this.props; var onChange = _props2.onChange; var value = _props2.value; if (constrain) date = this.inRangeValue(date); if (onChange) { if (date == null || value == null) { if (date != value) //eslint-disable-line eqeqeq onChange(date, str); } else if (!_utilDates2['default'].eq(date, value)) onChange(date, str); } } }, { key: '_keyDown', decorators: [_utilInteraction.widgetEditable], value: function _keyDown(e) { var _props3 = this.props; var open = _props3.open; var calendar = _props3.calendar; var time = _props3.time; _utilWidgetHelpers.notify(this.props.onKeyDown, [e]); if (e.defaultPrevented) return; if (e.key === 'Escape' && open) this.close();else if (e.altKey) { e.preventDefault(); if (e.key === 'ArrowDown') { if (calendar && time) this.open(open === popups.CALENDAR ? popups.TIME : popups.CALENDAR);else if (time) this.open(popups.TIME);else if (calendar) this.open(popups.CALENDAR); } else if (e.key === 'ArrowUp') this.close(); } else if (open) { if (open === popups.CALENDAR) this.refs.calPopup._keyDown(e); if (open === popups.TIME) this.refs.timePopup._keyDown(e); } } }, { key: '_keyPress', decorators: [_utilInteraction.widgetEditable], value: function _keyPress(e) { _utilWidgetHelpers.notify(this.props.onKeyPress, [e]); if (e.defaultPrevented) return; if (this.props.open === popups.TIME) this.refs.timePopup._keyPress(e); } }, { key: '_focus', decorators: [_utilInteraction.widgetEnabled], value: function _focus(focused, e) { var _this2 = this; this.setTimeout('focus', function () { if (!focused) _this2.close(); if (focused !== _this2.state.focused) { _utilWidgetHelpers.notify(_this2.props[focused ? 'onFocus' : 'onBlur'], e); _this2.setState({ focused: focused }); } }); } }, { key: 'focus', value: function focus() { if (_domHelpersActiveElement2['default']() !== _utilCompat2['default'].findDOMNode(this.refs.valueInput)) this.refs.valueInput.focus(); } }, { key: '_selectDate', decorators: [_utilInteraction.widgetEditable], value: function _selectDate(date) { var format = getFormat(this.props), dateTime = _utilDates2['default'].merge(date, this.props.value), dateStr = formatDate(date, format, this.props.culture); this.close(); _utilWidgetHelpers.notify(this.props.onSelect, [dateTime, dateStr]); this._change(dateTime, dateStr, true); this.focus(); } }, { key: '_selectTime', decorators: [_utilInteraction.widgetEditable], value: function _selectTime(datum) { var format = getFormat(this.props), dateTime = _utilDates2['default'].merge(this.props.value, datum.date), dateStr = formatDate(datum.date, format, this.props.culture); this.close(); _utilWidgetHelpers.notify(this.props.onSelect, [dateTime, dateStr]); this._change(dateTime, dateStr, true); this.focus(); } }, { key: '_click', decorators: [_utilInteraction.widgetEditable], value: function _click(view, e) { this.focus(); this.toggle(view, e); } }, { key: '_parse', value: function _parse(string) { var format = getFormat(this.props, true), editFormat = this.props.editFormat, parse = this.props.parse, formats = []; if (typeof parse === 'function') return parse(string, this.props.culture); if (typeof format === 'string') formats.push(format); if (typeof editFormat === 'string') formats.push(editFormat); if (parse) formats = formats.concat(this.props.parse); _invariant2['default'](formats.length, 'React Widgets: there are no specified `parse` formats provided and the `format` prop is a function. ' + 'the DateTimePicker is unable to parse `%s` into a dateTime, ' + 'please provide either a parse function or Globalize.js compatible string for `format`', string); return formatsParser(formats, this.props.culture, string); } }, { key: 'toggle', value: function toggle(view) { this.props.open ? this.props.open !== view ? this.open(view) : this.close(view) : this.open(view); } }, { key: 'open', value: function open(view) { if (this.props.open !== view && this.props[view] === true) _utilWidgetHelpers.notify(this.props.onToggle, view); } }, { key: 'close', value: function close() { if (this.props.open) _utilWidgetHelpers.notify(this.props.onToggle, false); } }, { key: 'inRangeValue', value: function inRangeValue(value) { if (value == null) return value; return _utilDates2['default'].max(_utilDates2['default'].min(value, this.props.max), this.props.min); } }])); exports['default'] = _uncontrollable2['default'](DateTimePicker, { open: 'onToggle', value: 'onChange' }); function getFormat(props) { var cal = props[popups.CALENDAR] != null ? props.calendar : true, time = props[popups.TIME] != null ? props.time : true; return props.format ? props.format : cal && time || !cal && !time ? _utilLocalizers.date.getFormat('default') : _utilLocalizers.date.getFormat(cal ? 'date' : 'time'); } function formatDate(date, format, culture) { var val = ''; if (date instanceof Date && !isNaN(date.getTime())) val = _utilLocalizers.date.format(date, format, culture); return val; } function formatsParser(formats, culture, str) { var date; for (var i = 0; i < formats.length; i++) { date = _utilLocalizers.date.parse(str, formats[i], culture); if (date) return date; } return null; } function dateOrNull(dt) { if (dt && !isNaN(dt.getTime())) return dt; return null; } module.exports = exports['default'];