UNPKG

@checksub_team/react-widgets

Version:
559 lines (467 loc) 21.2 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 _reactLifecyclesCompat = require("react-lifecycles-compat"); 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 _reduceToListState = _interopRequireDefault(require("./util/reduceToListState")); var _getAccessors = _interopRequireDefault(require("./util/getAccessors")); var CustomPropTypes = _interopRequireWildcard(require("./util/PropTypes")); 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 _Icon = require("./Icon"); var _class, _class2, _descriptor, _descriptor2, _descriptor3, _class3, _temp; 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, /** Specify the element used to render the select (down arrow) icon. */ selectIcon: _propTypes.default.node, /** Specify the element used to render the busy indicator */ busySpinner: _propTypes.default.node, delay: _propTypes.default.number, dropUp: _propTypes.default.bool, popupTransition: CustomPropTypes.elementType, placeholder: _propTypes.default.string, /** Adds a css class to the input container element. */ containerClassName: _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 = (0, _reactLifecyclesCompat.polyfill)(_class = (_class2 = (_temp = _class3 = /*#__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(_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(_assertThisInitialized(_this))); _this.attachListRef = function (ref) { _this.listRef = ref; }; _this.attachInputRef = function (ref) { _this.inputRef = ref; }; _initializerDefineProperty(_this, "toggle", _descriptor3, _assertThisInitialized(_assertThisInitialized(_this))); _this.inputId = (0, _widgetHelpers.instanceId)(_assertThisInitialized(_assertThisInitialized(_this)), '_input'); _this.listId = (0, _widgetHelpers.instanceId)(_assertThisInitialized(_assertThisInitialized(_this)), '_listbox'); _this.activeId = (0, _widgetHelpers.instanceId)(_assertThisInitialized(_assertThisInitialized(_this)), '_listbox_active_option'); _this.handleScroll = (0, _scrollManager.default)(_assertThisInitialized(_assertThisInitialized(_this))); _this.focusManager = (0, _focusManager.default)(_assertThisInitialized(_assertThisInitialized(_this)), { willHandle: _this.handleFocusWillChange, didHandle: _this.handleFocusChanged }); _this.state = { isSuggesting: function isSuggesting() { return _this.inputRef && _this.inputRef.isSuggesting(); } }; return _this; } var _proto = Combobox.prototype; _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) { var isSuggesting = nextState.isSuggesting(), stateChanged = !(0, _.isShallowEqual)(nextState, this.state), valueChanged = !(0, _.isShallowEqual)(nextProps, this.props); return isSuggesting || stateChanged || valueChanged; }; Combobox.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) { var value = nextProps.value, data = nextProps.data, messages = nextProps.messages, filter = nextProps.filter, minLength = nextProps.minLength, caseSensitive = nextProps.caseSensitive; var focusedItem = prevState.focusedItem; var accessors = (0, _getAccessors.default)(nextProps); var valueChanged = value !== prevState.lastValue; var selectedIndex = accessors.indexOf(data, value); var dataItem = selectedIndex === -1 ? value : data[selectedIndex]; var searchTerm; // filter only when the value is not an item in the data list if (selectedIndex === -1 || prevState.isSuggesting()) { searchTerm = accessors.text(dataItem); } data = Filter.filter(data, { filter: filter, searchTerm: searchTerm, minLength: minLength, caseSensitive: caseSensitive, textField: accessors.text }); var list = (0, _reduceToListState.default)(data, prevState.list, { nextProps: nextProps }); // index may have changed after filtering if (selectedIndex !== -1) { selectedIndex = accessors.indexOf(data, value); } var focusedIndex = accessors.indexOf(data, focusedItem); if (focusedIndex === -1) { // value isn't a dataItem so find the close match focusedIndex = Filter.indexOf(data, { searchTerm: searchTerm, textField: accessors.text, filter: filter || true }); } var selectedItem = data[selectedIndex]; var nextFocusedItem = null; // If no item is focused, or is no longer in the dataset, default to either the selected item, or to the first item in the list if (focusedIndex === -1) { if (selectedItem !== undefined) { nextFocusedItem = selectedItem; } else { nextFocusedItem = data[0]; } } else { nextFocusedItem = data[focusedIndex]; } return { data: data, list: list, accessors: accessors, lastValue: value, messages: (0, _messages.getMessages)(messages), selectedItem: valueChanged ? list.nextEnabled(selectedItem) : prevState.selectedItem, focusedItem: valueChanged || focusedItem === undefined ? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem) : nextFocusedItem }; }; // has to be done early since `accept()` re-focuses the input _proto.renderInput = function renderInput() { var _this$props = this.props, suggest = _this$props.suggest, filter = _this$props.filter, busy = _this$props.busy, name = _this$props.name, data = _this$props.data, value = _this$props.value, autoFocus = _this$props.autoFocus, tabIndex = _this$props.tabIndex, placeholder = _this$props.placeholder, inputProps = _this$props.inputProps, disabled = _this$props.disabled, readOnly = _this$props.readOnly, open = _this$props.open; var accessors = this.state.accessors; var valueItem = 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: accessors.text(valueItem), onChange: this.handleInputChange, onKeyDown: this.handleInputKeyDown, ref: this.attachInputRef })); }; _proto.renderList = function renderList(messages) { var activeId = this.activeId, inputId = this.inputId, listId = this.listId; var _this$props2 = this.props, open = _this$props2.open, data = _this$props2.data, value = _this$props2.value, listProps = _this$props2.listProps, optionComponent = _this$props2.optionComponent, itemComponent = _this$props2.itemComponent, groupComponent = _this$props2.groupComponent; var _this$state = this.state, list = _this$state.list, accessors = _this$state.accessors, focusedItem = _this$state.focusedItem, selectedItem = _this$state.selectedItem, filteredData = _this$state.data; var List = this.props.listComponent; return _react.default.createElement(List, _extends({}, listProps, { id: listId, activeId: activeId, data: filteredData, dataState: list.dataState, isDisabled: list.isDisabled, textAccessor: accessors.text, valueAccessor: accessors.value, itemComponent: itemComponent, groupComponent: groupComponent, optionComponent: optionComponent, selectedItem: selectedItem, focusedItem: open ? focusedItem : null, searchTerm: accessors.text(value) || '', "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 } })); }; _proto.render = function render() { var _this2 = this; var _this$props3 = this.props, isRtl = _this$props3.isRtl, className = _this$props3.className, popupTransition = _this$props3.popupTransition, busy = _this$props3.busy, dropUp = _this$props3.dropUp, open = _this$props3.open, selectIcon = _this$props3.selectIcon, busySpinner = _this$props3.busySpinner, containerClassName = _this$props3.containerClassName; var _this$state2 = this.state, focused = _this$state2.focused, messages = _this$state2.messages; var disabled = this.props.disabled === true, readOnly = this.props.readOnly === true; var elementProps = Props.pickElementProps(this); var shouldRenderPopup = (0, _widgetHelpers.isFirstFocusedRender)(this); return _react.default.createElement(_Widget.default, _extends({}, elementProps, { open: open, isRtl: isRtl, 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') }), _react.default.createElement(_WidgetPicker.default, { className: containerClassName }, this.renderInput(), _react.default.createElement(_Select.default, { bordered: true, busy: busy, icon: selectIcon, spinner: busySpinner, onClick: this.toggle, disabled: disabled || readOnly, label: messages.openCombobox(this.props) })), shouldRenderPopup && _react.default.createElement(_Popup.default, { open: open, dropUp: dropUp, transition: popupTransition, onEntering: function onEntering() { return _this2.listRef.forceUpdate(); } }, _react.default.createElement("div", null, this.renderList(messages)))); }; _proto.focus = function focus() { if (this.inputRef) this.inputRef.focus(); }; _proto.change = function change(nextValue, typing, originalEvent) { var _this$props4 = this.props, onChange = _this$props4.onChange, lastValue = _this$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 _this$props5 = this.props, textField = _this$props5.textField, suggest = _this$props5.suggest, minLength = _this$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), _class3.propTypes = propTypes, _class3.defaultProps = { data: [], value: '', open: false, suggest: false, filter: false, delay: 500, selectIcon: _Icon.caretDown, listComponent: _List.default }, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.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.inputRef && _this3.inputRef.accept(true); _this3.focus(); }; } }), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "handleKeyDown", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this4 = this; return function (e) { var key = e.key, altKey = e.altKey; 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, list = _this4$state.list; (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(_class2.prototype, "toggle", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this5 = this; return function () { _this5.focus(); _this5.props.open ? _this5.close() : _this5.open(); }; } })), _class2)) || _class; var _default = (0, _uncontrollable.default)(Combobox, { open: 'onToggle', value: 'onChange' }, ['focus']); exports.default = _default; module.exports = exports["default"];