react-widgets
Version:
An à la carte set of polished, extensible, and accessible inputs built for React
541 lines (458 loc) • 19.4 kB
JavaScript
"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"];