react-widgets
Version:
An à la carte set of polished, extensible, and accessible inputs built for React
488 lines (412 loc) • 15.8 kB
JavaScript
'use strict';
var babelHelpers = require('./util/babelHelpers.js');
exports.__esModule = true;
var _react = require('react');
var _react2 = babelHelpers.interopRequireDefault(_react);
var _domHelpersActiveElement = require('dom-helpers/activeElement');
var _domHelpersActiveElement2 = babelHelpers.interopRequireDefault(_domHelpersActiveElement);
var _domHelpersQueryContains = require('dom-helpers/query/contains');
var _domHelpersQueryContains2 = babelHelpers.interopRequireDefault(_domHelpersQueryContains);
var _classnames = require('classnames');
var _classnames2 = babelHelpers.interopRequireDefault(_classnames);
var _util_ = require('./util/_');
var _util_2 = babelHelpers.interopRequireDefault(_util_);
var _Popup = require('./Popup');
var _Popup2 = babelHelpers.interopRequireDefault(_Popup);
var _utilCompat = require('./util/compat');
var _utilCompat2 = babelHelpers.interopRequireDefault(_utilCompat);
var _utilPropTypes = require('./util/propTypes');
var _utilPropTypes2 = babelHelpers.interopRequireDefault(_utilPropTypes);
var _List = require('./List');
var _List2 = babelHelpers.interopRequireDefault(_List);
var _ListGroupable = require('./ListGroupable');
var _ListGroupable2 = babelHelpers.interopRequireDefault(_ListGroupable);
var _utilValidateListInterface = require('./util/validateListInterface');
var _utilValidateListInterface2 = babelHelpers.interopRequireDefault(_utilValidateListInterface);
var _uncontrollable = require('uncontrollable');
var _uncontrollable2 = babelHelpers.interopRequireDefault(_uncontrollable);
var _utilDataHelpers = require('./util/dataHelpers');
var _utilInteraction = require('./util/interaction');
var _utilWidgetHelpers = require('./util/widgetHelpers');
var omit = _util_2['default'].omit;
var pick = _util_2['default'].pick;
var result = _util_2['default'].result;
var propTypes = {
//-- controlled props -----------
value: _react2['default'].PropTypes.any,
onChange: _react2['default'].PropTypes.func,
open: _react2['default'].PropTypes.bool,
onToggle: _react2['default'].PropTypes.func,
//------------------------------------
data: _react2['default'].PropTypes.array,
valueField: _react2['default'].PropTypes.string,
textField: _utilPropTypes2['default'].accessor,
valueComponent: _utilPropTypes2['default'].elementType,
itemComponent: _utilPropTypes2['default'].elementType,
listComponent: _utilPropTypes2['default'].elementType,
groupComponent: _utilPropTypes2['default'].elementType,
groupBy: _utilPropTypes2['default'].accessor,
onSelect: _react2['default'].PropTypes.func,
searchTerm: _react2['default'].PropTypes.string,
onSearch: _react2['default'].PropTypes.func,
busy: _react2['default'].PropTypes.bool,
delay: _react2['default'].PropTypes.number,
dropUp: _react2['default'].PropTypes.bool,
duration: _react2['default'].PropTypes.number, //popup
disabled: _utilPropTypes2['default'].disabled.acceptsArray,
readOnly: _utilPropTypes2['default'].readOnly.acceptsArray,
messages: _react2['default'].PropTypes.shape({
open: _utilPropTypes2['default'].message,
emptyList: _utilPropTypes2['default'].message,
emptyFilter: _utilPropTypes2['default'].message,
filterPlaceholder: _utilPropTypes2['default'].message
})
};
var DropdownList = _react2['default'].createClass(babelHelpers.createDecoratedObject([{
key: 'displayName',
initializer: function initializer() {
return 'DropdownList';
}
}, {
key: 'mixins',
initializer: function initializer() {
return [require('./mixins/TimeoutMixin'), require('./mixins/PureRenderMixin'), require('./mixins/DataFilterMixin'), require('./mixins/PopupScrollToMixin'), require('./mixins/RtlParentContextMixin'), require('./mixins/AriaDescendantMixin')()];
}
}, {
key: 'propTypes',
initializer: function initializer() {
return propTypes;
}
}, {
key: 'getDefaultProps',
value: function getDefaultProps() {
return {
delay: 500,
value: '',
open: false,
data: [],
searchTerm: '',
messages: msgs(),
ariaActiveDescendantKey: 'dropdownlist'
};
}
}, {
key: 'getInitialState',
value: function getInitialState() {
var _props = this.props;
var open = _props.open;
var filter = _props.filter;
var value = _props.value;
var data = _props.data;
var searchTerm = _props.searchTerm;
var valueField = _props.valueField;
var processed = filter ? this.filter(data, searchTerm) : data,
initialIdx = _utilDataHelpers.dataIndexOf(data, value, valueField);
return {
filteredData: open && filter ? processed : null,
selectedItem: processed[initialIdx],
focusedItem: processed[initialIdx] || data[0]
};
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this.refs.list && _utilValidateListInterface2['default'](this.refs.list);
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(props) {
var open = props.open;
var filter = props.filter;
var value = props.value;
var data = props.data;
var searchTerm = props.searchTerm;
var valueField = props.valueField;
var processed = filter ? this.filter(data, searchTerm) : data,
idx = _utilDataHelpers.dataIndexOf(data, value, valueField);
this.setState({
filteredData: open && filter ? processed : null,
selectedItem: processed[idx],
focusedItem: processed[! ~idx ? 0 : idx]
});
}
}, {
key: 'render',
value: function render() {
var _cx,
_this = this;
var _props2 = this.props;
var className = _props2.className;
var tabIndex = _props2.tabIndex;
var filter = _props2.filter;
var valueField = _props2.valueField;
var textField = _props2.textField;
var groupBy = _props2.groupBy;
var messages = _props2.messages;
var data = _props2.data;
var busy = _props2.busy;
var dropUp = _props2.dropUp;
var placeholder = _props2.placeholder;
var value = _props2.value;
var open = _props2.open;
var ValueComponent = _props2.valueComponent;
var List = _props2.listComponent;
List = List || groupBy && _ListGroupable2['default'] || _List2['default'];
var elementProps = omit(this.props, Object.keys(propTypes));
var listProps = pick(this.props, Object.keys(List.propTypes));
var popupProps = pick(this.props, Object.keys(_Popup2['default'].propTypes));
var _state = this.state;
var focusedItem = _state.focusedItem;
var selectedItem = _state.selectedItem;
var focused = _state.focused;
var items = this._data(),
disabled = _utilInteraction.isDisabled(this.props),
readOnly = _utilInteraction.isReadOnly(this.props),
valueItem = _utilDataHelpers.dataItem(data, value, valueField),
// take value from the raw data
listID = _utilWidgetHelpers.instanceId(this, '__listbox');
var shouldRenderList = _utilWidgetHelpers.isFirstFocusedRender(this) || open;
messages = msgs(messages);
return _react2['default'].createElement(
'div',
babelHelpers._extends({}, elementProps, {
ref: 'input',
role: 'combobox',
tabIndex: tabIndex || '0',
'aria-expanded': open,
'aria-haspopup': true,
'aria-owns': listID,
'aria-busy': !!busy,
'aria-live': !open && 'polite',
'aria-autocomplete': 'list',
'aria-disabled': disabled,
'aria-readonly': readOnly,
onKeyDown: this._keyDown,
onKeyPress: this._keyPress,
onClick: this._click,
onFocus: this._focus.bind(null, true),
onBlur: this._focus.bind(null, false),
className: _classnames2['default'](className, 'rw-dropdownlist', 'rw-widget', (_cx = {
'rw-state-disabled': disabled,
'rw-state-readonly': readOnly,
'rw-state-focus': focused,
'rw-rtl': this.isRtl()
}, _cx['rw-open' + (dropUp ? '-up' : '')] = open, _cx)) }),
_react2['default'].createElement(
'span',
{ className: 'rw-dropdownlist-picker rw-select rw-btn' },
_react2['default'].createElement(
'i',
{ className: 'rw-i rw-i-caret-down' + (busy ? ' rw-loading' : '') },
_react2['default'].createElement(
'span',
{ className: 'rw-sr' },
result(messages.open, this.props)
)
)
),
_react2['default'].createElement(
'div',
{
className: 'rw-input'
},
!valueItem && placeholder ? _react2['default'].createElement(
'span',
{ className: 'rw-placeholder' },
placeholder
) : this.props.valueComponent ? _react2['default'].createElement(ValueComponent, { item: valueItem }) : _utilDataHelpers.dataText(valueItem, textField)
),
_react2['default'].createElement(
_Popup2['default'],
babelHelpers._extends({}, popupProps, {
onOpen: function () {
return _this.focus();
},
onOpening: function () {
return _this.refs.list.forceUpdate();
}
}),
_react2['default'].createElement(
'div',
null,
filter && this._renderFilter(messages),
shouldRenderList && _react2['default'].createElement(List, babelHelpers._extends({ ref: 'list'
}, listProps, {
data: items,
id: listID,
'aria-live': open && 'polite',
'aria-labelledby': _utilWidgetHelpers.instanceId(this),
'aria-hidden': !this.props.open,
selected: selectedItem,
focused: open ? focusedItem : null,
onSelect: this._onSelect,
onMove: this._scrollTo,
messages: {
emptyList: data.length ? messages.emptyFilter : messages.emptyList
} }))
)
)
);
}
}, {
key: '_renderFilter',
value: function _renderFilter(messages) {
var _this2 = this;
return _react2['default'].createElement(
'div',
{ ref: 'filterWrapper', className: 'rw-filter-input' },
_react2['default'].createElement(
'span',
{ className: 'rw-select rw-btn' },
_react2['default'].createElement('i', { className: 'rw-i rw-i-search' })
),
_react2['default'].createElement('input', { ref: 'filter', className: 'rw-input',
placeholder: _util_2['default'].result(messages.filterPlaceholder, this.props),
value: this.props.searchTerm,
onChange: function (e) {
return _utilWidgetHelpers.notify(_this2.props.onSearch, e.target.value);
} })
);
}
}, {
key: '_focus',
decorators: [_utilInteraction.widgetEnabled],
value: function _focus(focused, e) {
var _this3 = this;
this.setTimeout('focus', function () {
if (!focused) _this3.close();
if (focused !== _this3.state.focused) {
_utilWidgetHelpers.notify(_this3.props[focused ? 'onFocus' : 'onBlur'], e);
_this3.setState({ focused: focused });
}
});
}
}, {
key: '_onSelect',
decorators: [_utilInteraction.widgetEditable],
value: function _onSelect(data) {
this.close();
_utilWidgetHelpers.notify(this.props.onSelect, data);
this.change(data);
this.focus(this);
}
}, {
key: '_click',
decorators: [_utilInteraction.widgetEditable],
value: function _click(e) {
var wrapper = this.refs.filterWrapper;
if (!this.props.filter || !this.props.open) this.toggle();else if (!_domHelpersQueryContains2['default'](_utilCompat2['default'].findDOMNode(wrapper), e.target)) this.close();
_utilWidgetHelpers.notify(this.props.onClick, e);
}
}, {
key: '_keyDown',
decorators: [_utilInteraction.widgetEditable],
value: function _keyDown(e) {
var _this4 = this;
var self = this,
key = e.key,
alt = e.altKey,
list = this.refs.list,
filtering = this.props.filter,
focusedItem = this.state.focusedItem,
selectedItem = this.state.selectedItem,
isOpen = this.props.open,
closeWithFocus = function closeWithFocus() {
_this4.close(), _utilCompat2['default'].findDOMNode(_this4).focus();
};
_utilWidgetHelpers.notify(this.props.onKeyDown, [e]);
if (e.defaultPrevented) return;
if (key === 'End') {
if (isOpen) this.setState({ focusedItem: list.last() });else change(list.last());
e.preventDefault();
} else if (key === 'Home') {
if (isOpen) this.setState({ focusedItem: list.first() });else change(list.first());
e.preventDefault();
} else if (key === 'Escape' && isOpen) {
closeWithFocus();
} else if ((key === 'Enter' || key === ' ' && !filtering) && isOpen) {
change(this.state.focusedItem, true);
} else if (key === 'ArrowDown') {
if (alt) this.open();else if (isOpen) this.setState({ focusedItem: list.next(focusedItem) });else change(list.next(selectedItem));
e.preventDefault();
} else if (key === 'ArrowUp') {
if (alt) closeWithFocus();else if (isOpen) this.setState({ focusedItem: list.prev(focusedItem) });else change(list.prev(selectedItem));
e.preventDefault();
}
function change(item, fromList) {
if (!item) return;
fromList ? self._onSelect(item) : self.change(item);
}
}
}, {
key: '_keyPress',
decorators: [_utilInteraction.widgetEditable],
value: function _keyPress(e) {
var _this5 = this;
_utilWidgetHelpers.notify(this.props.onKeyPress, [e]);
if (e.defaultPrevented) return;
if (!(this.props.filter && this.props.open)) this.search(String.fromCharCode(e.which), function (item) {
_this5.isMounted() && _this5.props.open ? _this5.setState({ focusedItem: item }) : item && _this5.change(item);
});
}
}, {
key: 'change',
value: function change(data) {
if (!_utilDataHelpers.valueMatcher(data, this.props.value, this.props.valueField)) {
_utilWidgetHelpers.notify(this.props.onChange, data);
_utilWidgetHelpers.notify(this.props.onSearch, '');
this.close();
}
}
}, {
key: 'focus',
value: function focus(target) {
var inst = target || (this.props.filter && this.props.open ? this.refs.filter : this.refs.input);
if (_domHelpersActiveElement2['default']() !== _utilCompat2['default'].findDOMNode(inst)) _utilCompat2['default'].findDOMNode(inst).focus();
}
}, {
key: '_data',
value: function _data() {
return this.state.filteredData || this.props.data.concat();
}
}, {
key: 'search',
value: function search(character, cb) {
var _this6 = this;
var word = ((this._searchTerm || '') + character).toLowerCase();
if (!character) return;
this._searchTerm = word;
this.setTimeout('search', function () {
var list = _this6.refs.list,
key = _this6.props.open ? 'focusedItem' : 'selectedItem',
item = list.next(_this6.state[key], word);
_this6._searchTerm = '';
if (item) cb(item);
}, this.props.delay);
}
}, {
key: 'open',
value: function open() {
_utilWidgetHelpers.notify(this.props.onToggle, true);
}
}, {
key: 'close',
value: function close() {
_utilWidgetHelpers.notify(this.props.onToggle, false);
}
}, {
key: 'toggle',
value: function toggle() {
this.props.open ? this.close() : this.open();
}
}]));
function msgs(msgs) {
return babelHelpers._extends({
open: 'open dropdown',
filterPlaceholder: '',
emptyList: 'There are no items in this list',
emptyFilter: 'The filter returned no results'
}, msgs);
}
exports['default'] = _uncontrollable2['default'](DropdownList, { open: 'onToggle', value: 'onChange', searchTerm: 'onSearch' });
module.exports = exports['default'];