UNPKG

react-widgets

Version:

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

541 lines (458 loc) 19.4 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _classnames = _interopRequireDefault(require("classnames")); var _uncontrollable = _interopRequireDefault(require("uncontrollable")); var _Widget = _interopRequireDefault(require("./Widget")); var _WidgetPicker = _interopRequireDefault(require("./WidgetPicker")); var _List = _interopRequireDefault(require("./List")); var _Popup = _interopRequireDefault(require("./Popup")); var _Select = _interopRequireDefault(require("./Select")); var _ComboboxInput = _interopRequireDefault(require("./ComboboxInput")); var _messages = require("./messages"); var _focusManager = _interopRequireDefault(require("./util/focusManager")); var _listDataManager = _interopRequireDefault(require("./util/listDataManager")); var CustomPropTypes = _interopRequireWildcard(require("./util/PropTypes")); var _accessorManager = _interopRequireDefault(require("./util/accessorManager")); var _scrollManager = _interopRequireDefault(require("./util/scrollManager")); var _ = require("./util/_"); var Props = _interopRequireWildcard(require("./util/Props")); var Filter = _interopRequireWildcard(require("./util/Filter")); var _interaction = require("./util/interaction"); var _widgetHelpers = require("./util/widgetHelpers"); var _class, _descriptor, _descriptor2, _descriptor3, _class2, _temp, _jsxFileName = "src/Combobox.js"; function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; } function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); } function _extends() { _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; }; return _extends.apply(this, arguments); } var propTypes = _extends({}, Filter.propTypes, { value: _propTypes.default.any, onChange: _propTypes.default.func, open: _propTypes.default.bool, onToggle: _propTypes.default.func, itemComponent: CustomPropTypes.elementType, listComponent: CustomPropTypes.elementType, groupComponent: CustomPropTypes.elementType, groupBy: CustomPropTypes.accessor, data: _propTypes.default.array, valueField: CustomPropTypes.accessor, textField: CustomPropTypes.accessor, name: _propTypes.default.string, /** * * @type {(dataItem: ?any, metadata: { originalEvent: SyntheticEvent }) => void} */ onSelect: _propTypes.default.func, autoFocus: _propTypes.default.bool, disabled: CustomPropTypes.disabled.acceptsArray, readOnly: CustomPropTypes.disabled, /** * When `true` the Combobox will suggest, or fill in, values as you type. The suggestions * are always "startsWith", meaning it will search from the start of the `textField` property */ suggest: Filter.propTypes.filter, busy: _propTypes.default.bool, delay: _propTypes.default.number, dropUp: _propTypes.default.bool, popupTransition: CustomPropTypes.elementType, placeholder: _propTypes.default.string, inputProps: _propTypes.default.object, listProps: _propTypes.default.object, isRtl: _propTypes.default.bool, messages: _propTypes.default.shape({ openCombobox: CustomPropTypes.message, emptyList: CustomPropTypes.message, emptyFilter: CustomPropTypes.message }) /** * --- * shortcuts: * - { key: alt + down arrow, label: open combobox } * - { key: alt + up arrow, label: close combobox } * - { key: down arrow, label: move focus to next item } * - { key: up arrow, label: move focus to previous item } * - { key: home, label: move focus to first item } * - { key: end, label: move focus to last item } * - { key: enter, label: select focused item } * - { key: any key, label: search list for item starting with key } * --- * * Select an item from the list, or input a custom value. The Combobox can also make suggestions as you type. * @public */ }); var Combobox = (_class = (_temp = _class2 = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(Combobox, _React$Component); function Combobox(props, context) { var _this; _this = _React$Component.call(this, props, context) || this; _this.handleFocusWillChange = function (focused) { if (!focused && _this.inputRef) _this.inputRef.accept(); if (focused) _this.focus(); }; _this.handleFocusChanged = function (focused) { if (!focused) _this.close(); }; _initializerDefineProperty(_this, "handleSelect", _descriptor, _assertThisInitialized(_this)); _this.handleInputKeyDown = function (_ref) { var key = _ref.key; _this._deleting = key === 'Backspace' || key === 'Delete'; _this._isTyping = true; }; _this.handleInputChange = function (event) { var suggestion = _this.suggest(event.target.value); _this.change(suggestion, true, event); _this.open(); }; _initializerDefineProperty(_this, "handleKeyDown", _descriptor2, _assertThisInitialized(_this)); _this.attachListRef = function (ref) { _this.listRef = ref; }; _this.attachInputRef = function (ref) { _this.inputRef = ref; }; _initializerDefineProperty(_this, "toggle", _descriptor3, _assertThisInitialized(_this)); _this.messages = (0, _messages.getMessages)(props.messages); _this.inputId = (0, _widgetHelpers.instanceId)(_assertThisInitialized(_this), '_input'); _this.listId = (0, _widgetHelpers.instanceId)(_assertThisInitialized(_this), '_listbox'); _this.activeId = (0, _widgetHelpers.instanceId)(_assertThisInitialized(_this), '_listbox_active_option'); _this.list = (0, _listDataManager.default)(_assertThisInitialized(_this)); _this.accessors = (0, _accessorManager.default)(_assertThisInitialized(_this)); _this.handleScroll = (0, _scrollManager.default)(_assertThisInitialized(_this)); _this.focusManager = (0, _focusManager.default)(_assertThisInitialized(_this), { willHandle: _this.handleFocusWillChange, didHandle: _this.handleFocusChanged }); _this.state = _extends({}, _this.getStateFromProps(props), { open: false }); return _this; } var _proto = Combobox.prototype; _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) { var isSuggesting = this.inputRef && this.inputRef.isSuggesting(), stateChanged = !(0, _.isShallowEqual)(nextState, this.state), valueChanged = !(0, _.isShallowEqual)(nextProps, this.props); return isSuggesting || stateChanged || valueChanged; }; _proto.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { this.messages = (0, _messages.getMessages)(nextProps.messages); this.setState(this.getStateFromProps(nextProps)); }; _proto.getStateFromProps = function getStateFromProps(props) { var accessors = this.accessors, list = this.list; var value = props.value, data = props.data, filter = props.filter; var index = accessors.indexOf(data, value); var dataItem = index === -1 ? value : data[index]; var itemText = accessors.text(dataItem); var searchTerm; // filter only when the value is not an item in the data list if (index === -1 || this.inputRef && this.inputRef.isSuggesting()) { searchTerm = itemText; } data = Filter.filter(data, _extends({ searchTerm: searchTerm }, props)); var focusedIndex = index; // index may have changed after filtering if (index !== -1) { index = accessors.indexOf(data, value); focusedIndex = index; } else { // value isn't a dataItem so find the close match focusedIndex = Filter.indexOf(data, { searchTerm: searchTerm, textField: accessors.text, filter: filter || true }); } list.setData(data); return { data: data, selectedItem: list.nextEnabled(data[index]), focusedItem: list.nextEnabled(~focusedIndex ? data[focusedIndex] : data[0]) }; }; // has to be done early since `accept()` re-focuses the input _proto.renderInput = function renderInput() { var _props = this.props, suggest = _props.suggest, filter = _props.filter, busy = _props.busy, name = _props.name, data = _props.data, value = _props.value, autoFocus = _props.autoFocus, tabIndex = _props.tabIndex, placeholder = _props.placeholder, inputProps = _props.inputProps, disabled = _props.disabled, readOnly = _props.readOnly, open = _props.open; var valueItem = this.accessors.findOrSelf(data, value); var completeType = suggest ? filter ? 'both' : 'inline' : filter ? 'list' : ''; return _react.default.createElement(_ComboboxInput.default, _extends({}, inputProps, { role: "combobox", name: name, id: this.inputId, autoFocus: autoFocus, tabIndex: tabIndex, suggest: suggest, disabled: disabled === true, readOnly: readOnly === true, "aria-busy": !!busy, "aria-owns": this.listId, "aria-autocomplete": completeType, "aria-activedescendant": open ? this.activeId : null, "aria-expanded": open, "aria-haspopup": true, placeholder: placeholder, value: this.accessors.text(valueItem), onChange: this.handleInputChange, onKeyDown: this.handleInputKeyDown, ref: this.attachInputRef, __source: { fileName: _jsxFileName, lineNumber: 293 }, __self: this })); }; _proto.renderList = function renderList(messages) { var activeId = this.activeId, inputId = this.inputId, listId = this.listId, accessors = this.accessors; var _props2 = this.props, open = _props2.open, data = _props2.data; var _state = this.state, selectedItem = _state.selectedItem, focusedItem = _state.focusedItem; var List = this.props.listComponent; var props = this.list.defaultProps(); return _react.default.createElement(List, _extends({}, props, { id: listId, activeId: activeId, valueAccessor: accessors.value, textAccessor: accessors.text, selectedItem: selectedItem, focusedItem: open ? focusedItem : null, "aria-hidden": !open, "aria-labelledby": inputId, "aria-live": open && 'polite', onSelect: this.handleSelect, onMove: this.handleScroll, ref: this.attachListRef, messages: { emptyList: data.length ? messages.emptyFilter : messages.emptyList }, __source: { fileName: _jsxFileName, lineNumber: 328 }, __self: this })); }; _proto.render = function render() { var _this2 = this; var _props3 = this.props, className = _props3.className, popupTransition = _props3.popupTransition, busy = _props3.busy, dropUp = _props3.dropUp, open = _props3.open; var focused = this.state.focused; var disabled = this.props.disabled === true, readOnly = this.props.readOnly === true; var elementProps = Props.pickElementProps(this); var shouldRenderPopup = open || (0, _widgetHelpers.isFirstFocusedRender)(this); var messages = this.messages; return _react.default.createElement(_Widget.default, _extends({}, elementProps, { open: open, dropUp: dropUp, focused: focused, disabled: disabled, readOnly: readOnly, onBlur: this.focusManager.handleBlur, onFocus: this.focusManager.handleFocus, onKeyDown: this.handleKeyDown, className: (0, _classnames.default)(className, 'rw-combobox'), __source: { fileName: _jsxFileName, lineNumber: 363 }, __self: this }), _react.default.createElement(_WidgetPicker.default, { __source: { fileName: _jsxFileName, lineNumber: 375 }, __self: this }, this.renderInput(), _react.default.createElement(_Select.default, { bordered: true, busy: busy, icon: "caret-down", onClick: this.toggle, disabled: disabled || readOnly, label: messages.openCombobox(this.props), __source: { fileName: _jsxFileName, lineNumber: 377 }, __self: this })), shouldRenderPopup && _react.default.createElement(_Popup.default, { open: open, dropUp: dropUp, transition: popupTransition, onEntering: function onEntering() { return _this2.listRef.forceUpdate(); }, __source: { fileName: _jsxFileName, lineNumber: 388 }, __self: this }, _react.default.createElement("div", { __source: { fileName: _jsxFileName, lineNumber: 394 }, __self: this }, this.renderList(messages)))); }; _proto.focus = function focus() { if (this.inputRef) this.inputRef.focus(); }; _proto.change = function change(nextValue, typing, originalEvent) { var _props4 = this.props, onChange = _props4.onChange, lastValue = _props4.value; this._typedChange = !!typing; (0, _widgetHelpers.notify)(onChange, [nextValue, { lastValue: lastValue, originalEvent: originalEvent }]); }; _proto.open = function open() { if (!this.props.open) (0, _widgetHelpers.notify)(this.props.onToggle, true); }; _proto.close = function close() { if (this.props.open) (0, _widgetHelpers.notify)(this.props.onToggle, false); }; _proto.suggest = function suggest(searchTerm) { var _props5 = this.props, textField = _props5.textField, suggest = _props5.suggest, minLength = _props5.minLength; var data = this.state.data; if (!this._deleting) return Filter.suggest(data, { minLength: minLength, textField: textField, searchTerm: searchTerm, filter: suggest, caseSensitive: false }); return searchTerm; }; return Combobox; }(_react.default.Component), _class2.propTypes = propTypes, _class2.defaultProps = { data: [], value: '', open: false, suggest: false, filter: false, delay: 500, listComponent: _List.default }, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "handleSelect", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this3 = this; return function (data, originalEvent) { _this3.close(); (0, _widgetHelpers.notify)(_this3.props.onSelect, [data, { originalEvent: originalEvent }]); _this3.change(data, false, originalEvent); _this3.focus(); }; } }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "handleKeyDown", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this4 = this; return function (e) { var key = e.key, altKey = e.altKey; var list = _this4.list; var _this4$props = _this4.props, open = _this4$props.open, onKeyDown = _this4$props.onKeyDown; var _this4$state = _this4.state, focusedItem = _this4$state.focusedItem, selectedItem = _this4$state.selectedItem; (0, _widgetHelpers.notify)(onKeyDown, [e]); if (e.defaultPrevented) return; var select = function select(item) { return item != null && _this4.handleSelect(item, e); }; var focusItem = function focusItem(item) { return _this4.setState({ focusedItem: item }); }; if (key === 'End' && open) { e.preventDefault(); focusItem(list.last()); } else if (key === 'Home' && open) { e.preventDefault(); focusItem(list.first()); } else if (key === 'Escape' && open) { e.preventDefault(); _this4.close(); } else if (key === 'Enter' && open) { e.preventDefault(); select(_this4.state.focusedItem); } else if (key === 'Tab') { _this4.inputRef.accept(); } else if (key === 'ArrowDown') { e.preventDefault(); if (altKey) return _this4.open(); if (open) focusItem(list.next(focusedItem));else select(list.next(selectedItem)); } else if (key === 'ArrowUp') { e.preventDefault(); if (altKey) return _this4.close(); if (open) focusItem(list.prev(focusedItem));else select(list.prev(selectedItem)); } }; } }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, "toggle", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this5 = this; return function () { _this5.focus(); _this5.props.open ? _this5.close() : _this5.open(); }; } })), _class); var _default = (0, _uncontrollable.default)(Combobox, { open: 'onToggle', value: 'onChange' }, ['focus']); exports.default = _default; module.exports = exports["default"];