UNPKG

react-widgets

Version:

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

707 lines (596 loc) 24.7 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _reactDom = require("react-dom"); var _propTypes = _interopRequireDefault(require("prop-types")); var _activeElement = _interopRequireDefault(require("dom-helpers/activeElement")); var _classnames = _interopRequireDefault(require("classnames")); var _reactComponentManagers = require("react-component-managers"); var _uncontrollable = _interopRequireDefault(require("uncontrollable")); var _Widget = _interopRequireDefault(require("./Widget")); var _WidgetPicker = _interopRequireDefault(require("./WidgetPicker")); var _Select = _interopRequireDefault(require("./Select")); var _Popup = _interopRequireDefault(require("./Popup")); var _List = _interopRequireDefault(require("./List")); var _AddToListOption = _interopRequireDefault(require("./AddToListOption")); var _DropdownListInput = _interopRequireDefault(require("./DropdownListInput")); var _messages = require("./messages"); var Props = _interopRequireWildcard(require("./util/Props")); var Filter = _interopRequireWildcard(require("./util/Filter")); 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 _interaction = require("./util/interaction"); var _widgetHelpers = require("./util/widgetHelpers"); var _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _class2, _temp, _jsxFileName = "src/DropdownList.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 _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); } 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.'); } var CREATE_OPTION = {}; /** * --- * shortcuts: * - { key: alt + down arrow, label: open dropdown } * - { key: alt + up arrow, label: close dropdown } * - { 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: ctrl + enter, label: create new option from current searchTerm } * - { key: any key, label: search list for item starting with key } * --- * * A `<select>` replacement for single value lists. * @public */ var DropdownList = (_class = (_temp = _class2 = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(DropdownList, _React$Component); function DropdownList() { var _this; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this; _this.handleFocusChanged = function (focused) { if (!focused) _this.close(); }; _initializerDefineProperty(_this, "handleSelect", _descriptor, _assertThisInitialized(_this)); _initializerDefineProperty(_this, "handleCreate", _descriptor2, _assertThisInitialized(_this)); _initializerDefineProperty(_this, "handleClick", _descriptor3, _assertThisInitialized(_this)); _initializerDefineProperty(_this, "handleKeyDown", _descriptor4, _assertThisInitialized(_this)); _initializerDefineProperty(_this, "handleKeyPress", _descriptor5, _assertThisInitialized(_this)); _this.handleInputChange = function (e) { _this.search(e.target.value, e, 'input'); }; _this.attachInputRef = function (ref) { return _this.inputRef = ref; }; _this.attachFilterRef = function (ref) { return _this.filterRef = ref; }; _this.attachListRef = function (ref) { return _this.listRef = ref; }; _this.focus = function (target) { var _this$props = _this.props, filter = _this$props.filter, open = _this$props.open; var inst = target || (filter && open ? _this.filterRef : _this.inputRef); inst = (0, _reactDom.findDOMNode)(inst); if (inst && (0, _activeElement.default)() !== inst) inst.focus(); }; (0, _reactComponentManagers.autoFocus)(_assertThisInitialized(_this)); _this.messages = (0, _messages.getMessages)(_this.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.mounted = (0, _reactComponentManagers.mountManager)(_assertThisInitialized(_this)); _this.timeouts = (0, _reactComponentManagers.timeoutManager)(_assertThisInitialized(_this)); _this.accessors = (0, _accessorManager.default)(_assertThisInitialized(_this)); _this.handleScroll = (0, _scrollManager.default)(_assertThisInitialized(_this)); _this.focusManager = (0, _focusManager.default)(_assertThisInitialized(_this), { didHandle: _this.handleFocusChanged }); _this.state = _this.getStateFromProps(_this.props); return _this; } var _proto = DropdownList.prototype; _proto.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { this.messages = (0, _messages.getMessages)(nextProps.messages); this.setState(this.getStateFromProps(nextProps)); }; _proto.getStateFromProps = function getStateFromProps(props) { var open = props.open, value = props.value, data = props.data, searchTerm = props.searchTerm, filter = props.filter, minLength = props.minLength, caseSensitive = props.caseSensitive; var accessors = this.accessors, list = this.list; var initialIdx = accessors.indexOf(data, value); if (open) data = Filter.filter(data, { filter: filter, searchTerm: searchTerm, minLength: minLength, caseSensitive: caseSensitive, textField: this.accessors.text }); list.setData(data); var selectedItem = data[initialIdx]; return { data: data, selectedItem: list.nextEnabled(selectedItem), focusedItem: list.nextEnabled(selectedItem || data[0]) }; }; _proto.change = function change(nextValue, originalEvent) { var _props = this.props, onChange = _props.onChange, searchTerm = _props.searchTerm, lastValue = _props.value; if (!this.accessors.matches(nextValue, lastValue)) { (0, _widgetHelpers.notify)(onChange, [nextValue, { originalEvent: originalEvent, lastValue: lastValue, searchTerm: searchTerm }]); this.clearSearch(originalEvent); this.close(); } }; _proto.renderList = function renderList(messages) { var _props2 = this.props, open = _props2.open, filter = _props2.filter, data = _props2.data, searchTerm = _props2.searchTerm; var _state = this.state, selectedItem = _state.selectedItem, focusedItem = _state.focusedItem; var _accessors = this.accessors, value = _accessors.value, text = _accessors.text; var List = this.props.listComponent; var props = this.list.defaultProps(); return _react.default.createElement("div", { __source: { fileName: _jsxFileName, lineNumber: 342 }, __self: this }, filter && _react.default.createElement(_WidgetPicker.default, { className: "rw-filter-input rw-input", __source: { fileName: _jsxFileName, lineNumber: 344 }, __self: this }, _react.default.createElement("input", { value: searchTerm, className: "rw-input-reset", onChange: this.handleInputChange, placeholder: messages.filterPlaceholder(this.props), ref: this.attachFilterRef, __source: { fileName: _jsxFileName, lineNumber: 347 }, __self: this }), _react.default.createElement(_Select.default, { icon: "search", role: "presentation", "aria-hidden": "true", __source: { fileName: _jsxFileName, lineNumber: 354 }, __self: this })), _react.default.createElement(List, _extends({}, props, { id: this.listId, activeId: this.activeId, valueAccessor: value, textAccessor: text, selectedItem: selectedItem, focusedItem: open ? focusedItem : null, onSelect: this.handleSelect, onMove: this.handleScroll, "aria-live": open && 'polite', "aria-labelledby": this.inputId, "aria-hidden": !this.props.open, ref: this.attachListRef, messages: { emptyList: data.length ? messages.emptyFilter : messages.emptyList }, __source: { fileName: _jsxFileName, lineNumber: 357 }, __self: this })), this.allowCreate() && _react.default.createElement(_AddToListOption.default, { id: this.createId, searchTerm: searchTerm, onSelect: this.handleCreate, focused: !focusedItem || focusedItem === CREATE_OPTION, __source: { fileName: _jsxFileName, lineNumber: 376 }, __self: this }, messages.createOption(this.props))); }; _proto.render = function render() { var _this2 = this; var _props3 = this.props, className = _props3.className, tabIndex = _props3.tabIndex, popupTransition = _props3.popupTransition, textField = _props3.textField, data = _props3.data, busy = _props3.busy, dropUp = _props3.dropUp, placeholder = _props3.placeholder, value = _props3.value, open = _props3.open, filter = _props3.filter, inputProps = _props3.inputProps, valueComponent = _props3.valueComponent; var focused = this.state.focused; var disabled = this.props.disabled === true, readOnly = this.props.readOnly === true, valueItem = this.accessors.findOrSelf(data, value); var shouldRenderPopup = open || (0, _widgetHelpers.isFirstFocusedRender)(this); var elementProps = _extends(Props.pickElementProps(this), { name: undefined, role: 'combobox', id: this.inputId, tabIndex: open && filter ? -1 : tabIndex || 0, 'aria-owns': this.listId, 'aria-activedescendant': open ? this.activeId : null, 'aria-expanded': !!open, 'aria-haspopup': true, 'aria-busy': !!busy, 'aria-live': !open && 'polite', 'aria-autocomplete': 'list', 'aria-disabled': disabled, 'aria-readonly': readOnly }); 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, onKeyPress: this.handleKeyPress, className: (0, _classnames.default)(className, 'rw-dropdown-list'), ref: this.attachInputRef, __source: { fileName: _jsxFileName, lineNumber: 433 }, __self: this }), _react.default.createElement(_WidgetPicker.default, { onClick: this.handleClick, className: "rw-widget-input", __source: { fileName: _jsxFileName, lineNumber: 447 }, __self: this }, _react.default.createElement(_DropdownListInput.default, _extends({}, inputProps, { value: valueItem, textField: textField, placeholder: placeholder, valueComponent: valueComponent, __source: { fileName: _jsxFileName, lineNumber: 448 }, __self: this })), _react.default.createElement(_Select.default, { busy: busy, icon: "caret-down", role: "presentational", "aria-hidden": "true", disabled: disabled || readOnly, label: messages.openDropdown(this.props), __source: { fileName: _jsxFileName, lineNumber: 455 }, __self: this })), shouldRenderPopup && _react.default.createElement(_Popup.default, { open: open, dropUp: dropUp, transition: popupTransition, onEntered: function onEntered() { return _this2.focus(); }, onEntering: function onEntering() { return _this2.listRef.forceUpdate(); }, __source: { fileName: _jsxFileName, lineNumber: 465 }, __self: this }, this.renderList(messages))); }; _proto.findOption = function findOption(character, cb) { var _this3 = this; var word = ((this._currentWord || '') + character).toLowerCase(); if (!character) return; this._currentWord = word; this.timeouts.set('search', function () { var list = _this3.list, key = _this3.props.open ? 'focusedItem' : 'selectedItem', item = list.next(_this3.state[key], word); if (item === _this3.state[key]) { item = list.next(null, word); } _this3._currentWord = ''; if (item) cb(item); }, this.props.delay); }; _proto.clearSearch = function clearSearch(originalEvent) { this.search('', originalEvent, 'clear'); }; _proto.search = function search(searchTerm, originalEvent, action) { if (action === void 0) { action = 'input'; } var _props4 = this.props, onSearch = _props4.onSearch, lastSearchTerm = _props4.searchTerm; if (searchTerm !== lastSearchTerm) (0, _widgetHelpers.notify)(onSearch, [searchTerm, { action: action, lastSearchTerm: lastSearchTerm, 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.toggle = function toggle() { this.props.open ? this.close() : this.open(); }; _proto.allowCreate = function allowCreate() { var _props5 = this.props, searchTerm = _props5.searchTerm, onCreate = _props5.onCreate, allowCreate = _props5.allowCreate; return !!(onCreate && (allowCreate === true || allowCreate === 'onFilter' && searchTerm) && !this.hasExtactMatch()); }; _proto.hasExtactMatch = function hasExtactMatch() { var _props6 = this.props, searchTerm = _props6.searchTerm, caseSensitive = _props6.caseSensitive, filter = _props6.filter; var data = this.state.data; var text = this.accessors.text; var lower = function lower(text) { return caseSensitive ? text : text.toLowerCase(); }; // if there is an exact match on textFields: return filter && data.some(function (v) { return lower(text(v)) === lower(searchTerm); }); }; return DropdownList; }(_react.default.Component), _class2.propTypes = _extends({}, Filter.propTypes, { value: _propTypes.default.any, /** * @type {function ( * dataItems: ?any, * metadata: { * lastValue: ?any, * searchTerm: ?string * originalEvent: SyntheticEvent, * } * ): void} */ onChange: _propTypes.default.func, open: _propTypes.default.bool, onToggle: _propTypes.default.func, data: _propTypes.default.array, valueField: CustomPropTypes.accessor, textField: CustomPropTypes.accessor, allowCreate: _propTypes.default.oneOf([true, false, 'onFilter']), /** * A React component for customizing the rendering of the DropdownList * value */ valueComponent: CustomPropTypes.elementType, itemComponent: CustomPropTypes.elementType, listComponent: CustomPropTypes.elementType, groupComponent: CustomPropTypes.elementType, groupBy: CustomPropTypes.accessor, /** * * @type {(dataItem: ?any, metadata: { originalEvent: SyntheticEvent }) => void} */ onSelect: _propTypes.default.func, onCreate: _propTypes.default.func, /** * @type function(searchTerm: string, metadata: { action, lastSearchTerm, originalEvent? }) */ onSearch: _propTypes.default.func, searchTerm: _propTypes.default.string, busy: _propTypes.default.bool, placeholder: _propTypes.default.string, dropUp: _propTypes.default.bool, popupTransition: CustomPropTypes.elementType, disabled: CustomPropTypes.disabled.acceptsArray, readOnly: CustomPropTypes.disabled, inputProps: _propTypes.default.object, listProps: _propTypes.default.object, isRtl: _propTypes.default.bool, messages: _propTypes.default.shape({ open: _propTypes.default.string, emptyList: CustomPropTypes.message, emptyFilter: CustomPropTypes.message, filterPlaceholder: _propTypes.default.string, createOption: CustomPropTypes.message }) }), _class2.defaultProps = { data: [], delay: 500, searchTerm: '', allowCreate: false, listComponent: _List.default }, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "handleSelect", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this4 = this; return function (dataItem, originalEvent) { if (dataItem === undefined || dataItem === CREATE_OPTION) { _this4.handleCreate(_this4.props.searchTerm); return; } (0, _widgetHelpers.notify)(_this4.props.onSelect, [dataItem, { originalEvent: originalEvent }]); _this4.change(dataItem, originalEvent); _this4.close(); _this4.focus(_this4); }; } }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "handleCreate", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this5 = this; return function (searchTerm, event) { if (searchTerm === void 0) { searchTerm = ''; } (0, _widgetHelpers.notify)(_this5.props.onCreate, searchTerm); _this5.clearSearch(event); _this5.close(); _this5.focus(_this5); }; } }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, "handleClick", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this6 = this; return function (e) { _this6.focus(); _this6.toggle(); (0, _widgetHelpers.notify)(_this6.props.onClick, e); }; } }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, "handleKeyDown", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this7 = this; return function (e) { var key = e.key, altKey = e.altKey, ctrlKey = e.ctrlKey; var list = _this7.list; var _this7$props = _this7.props, open = _this7$props.open, onKeyDown = _this7$props.onKeyDown, filter = _this7$props.filter, searchTerm = _this7$props.searchTerm; var _this7$state = _this7.state, focusedItem = _this7$state.focusedItem, selectedItem = _this7$state.selectedItem; var createIsFocused = focusedItem === CREATE_OPTION; var canCreate = _this7.allowCreate(); (0, _widgetHelpers.notify)(onKeyDown, [e]); var closeWithFocus = function closeWithFocus() { _this7.close(); (0, _reactDom.findDOMNode)(_this7).focus(); }; var change = function change(item) { return item != null && _this7.change(item, e); }; var focusItem = function focusItem(item) { return _this7.setState({ focusedItem: item }); }; if (e.defaultPrevented) return; if (key === 'End') { e.preventDefault(); if (open) focusItem(list.last());else change(list.last()); } else if (key === 'Home') { e.preventDefault(); if (open) focusItem(list.first());else change(list.first()); } else if (key === 'Escape' && open) { e.preventDefault(); closeWithFocus(); } else if (key === 'Enter' && open && ctrlKey && canCreate) { e.preventDefault(); _this7.handleCreate(searchTerm, e); } else if ((key === 'Enter' || key === ' ' && !filter) && open) { e.preventDefault(); _this7.handleSelect(focusedItem, e); } else if (key === ' ' && !open) { e.preventDefault(); _this7.open(); } else if (key === 'ArrowDown') { e.preventDefault(); if (altKey) return _this7.open(); if (!open) change(list.next(selectedItem)); var next = list.next(focusedItem); var creating = createIsFocused || canCreate && focusedItem === next; focusItem(creating ? CREATE_OPTION : next); } else if (key === 'ArrowUp') { e.preventDefault(); if (altKey) return closeWithFocus(); if (!open) return change(list.prev(selectedItem)); focusItem(createIsFocused ? list.last() : list.prev(focusedItem)); } }; } }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, "handleKeyPress", [_interaction.widgetEditable], { enumerable: true, initializer: function initializer() { var _this8 = this; return function (e) { (0, _widgetHelpers.notify)(_this8.props.onKeyPress, [e]); if (e.defaultPrevented) return; if (!(_this8.props.filter && _this8.props.open)) _this8.findOption(String.fromCharCode(e.which), function (item) { _this8.mounted() && _this8.props.open ? _this8.setState({ focusedItem: item }) : item && _this8.change(item, e); }); }; } })), _class); var _default = (0, _uncontrollable.default)(DropdownList, { open: 'onToggle', value: 'onChange', searchTerm: 'onSearch' }, ['focus']); exports.default = _default; module.exports = exports["default"];